Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Immutable Vector 2D #161

Merged
merged 40 commits into from
Dec 11, 2018
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
08b121c
Add KVector and KVector2
jcornaz Aug 13, 2018
05b49d9
Merge branch 'master' into feature/immutable-vectors
jcornaz Sep 30, 2018
0746b62
Rename KVector to ImmutableVector
jcornaz Oct 1, 2018
2d4eb3c
Test with small vectors
jcornaz Oct 1, 2018
98823c4
inline alias functions and solve few warnings
jcornaz Oct 1, 2018
7403c9d
Provide angleDeg() and deprecate angle()
jcornaz Oct 2, 2018
429e094
Improve documentation
jcornaz Oct 2, 2018
1f66974
Update changelog and start to write the readme
jcornaz Oct 2, 2018
58c4cbb
Merge branch 'develop' into feature/immutable-vectors
jcornaz Oct 2, 2018
7d9f0c4
Port syntax sugars provided by ktx for `Vector2`
jcornaz Oct 2, 2018
9ef346d
Fix some typos
jcornaz Oct 2, 2018
446afb2
Add module name in changelog
jcornaz Oct 3, 2018
008693f
Minor clean up
jcornaz Oct 3, 2018
c388137
Explicitly test equals and hashCode
jcornaz Oct 3, 2018
f1d51eb
Test destructuring immutable vector
jcornaz Oct 3, 2018
ca6364b
Make setLength2 and extension function
jcornaz Oct 3, 2018
71875f7
Address some minor issues
jcornaz Oct 4, 2018
d4b51a7
Rename conversion functions (`toMutable` and `toImmutable`)
jcornaz Oct 4, 2018
9de52b1
Fix typos in tests
jcornaz Oct 4, 2018
ac08485
Add mutation method deprecated to allow smoother migration
jcornaz Oct 4, 2018
9a19a62
Explicitly test the usage of method `copy` and update readme.
jcornaz Oct 4, 2018
c4d8a2e
Increase deprecation level to ERROR for mutation methods
jcornaz Oct 4, 2018
9279fee
Change deprecation reason and level of `rotate`
jcornaz Oct 4, 2018
1c0070e
Update new names of functions in tests names
jcornaz Oct 4, 2018
063a017
Fix deprecation message and level of `cpy`
jcornaz Oct 4, 2018
455ce76
Add examples in readme
jcornaz Oct 7, 2018
4d29ad3
Fix typo in readme
jcornaz Oct 7, 2018
dbdf93b
Improve documentation
jcornaz Oct 27, 2018
b95c6ad
Fix misleading documentation of result vectors
jcornaz Oct 27, 2018
cdc72c9
Add some tests
jcornaz Oct 27, 2018
06909b8
test withClamp and withClamp2
jcornaz Oct 27, 2018
90f7186
Add few tests for angles
jcornaz Nov 17, 2018
c1bb643
Add direction comparison tests
jcornaz Nov 18, 2018
99b02c8
Add tests for commutativity when relevant
jcornaz Nov 18, 2018
18d87eb
Add more tests for interpolations
jcornaz Nov 18, 2018
3cb53ad
Merge branch 'develop' into feature/immutable-vectors
jcornaz Dec 5, 2018
a9456b7
Change signature and update documentation of ImmutableVector2.time(Af…
jcornaz Dec 11, 2018
43c9e58
Address small typo issues
jcornaz Dec 11, 2018
6997b62
Use fixed seed when testing withRandomDirection
jcornaz Dec 11, 2018
8a58587
Add reference to arguments in documentations
jcornaz Dec 11, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,4 +1,5 @@
#### 1.9.8-SNAPSHOT
- **[FEATURE]** (`ktx-math`) Added `ImmutableVector2`, an immutable equivalent to `Vector2`.

#### 1.9.8-b5

Expand Down
66 changes: 66 additions & 0 deletions math/README.md
Expand Up @@ -39,6 +39,72 @@ Note that since `Shape2D` has `contains(Vector2)` method, `in` operator can be u
(like `Rectangle`, `Ellipse` or `Circle`). For example, given `vec: Vector2` and `rect: Rectangle` variables, you can
call `vec in rect` (or `vec !in rect`) to check if the rectangle contains (or doesn't) the point stored by the vector.

#### `ImmutableVector2`
- `ImmutableVector2` is an immutable equivalent to `Vector2`. It provides most of the functionality of `Vector2`, but
mutation methods return new vectors instead of mutate the reference.
- Note that one may want to create type aliases to makes the usage more concise: `typealias Vect2 = ImmutableVector2`
- `ImmutableVector` is comparable (`>`, `>=`, `<`, `<=` are available). Comparison is evaluated by length
- instances can be destructed: `val (x, y) = vector2`
- `Vector2.toImmutableVector2()` Returns an immutable vector with same `x` and `y` attributes than this `Vector2`
- `ImmutableVector2.toVector2()` Returns an mutable vector with same `x` and `y` attributes than this `ImmutableVector2`
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
- Most of the functions of `Vector2` which mutate the vector are provided but deprecated. This allow smooth migration from
`Vector2`.
- Notable difference with `Vector2`
- `+`, `-`, `*`, `/` are available and replace `add`, `sub` and `scl`.
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
- `withLength()` and `withLength2()` replace `setLength()` and `setLength2()` and return a new vector of same direction
with the specified length
- `withRandomRotation` replace `setToRandomRotation` and return a new vector of same length and a random rotation
- `withAngleDeg()` and `withAngleRad` replace `setAngle` and `setAngleRad` and return a new vector of same length and
the given angle to x-axis
- `cpy` is deprecated and is not necessary. Immutable vectors can be safely shared. However since `ImmutableVector` is
a `data class`, there is a `copy(x, y)` method available allowing to easily create new vectors based on existing ones.
- `set(x, y)` and `setZero()` are not provided.
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
- Functions dealing with angles in degree are suffixed with `Deg` and all returns values between `-180` and `+180`.
- All angle functions return the angle toward positive y-axis.
- `dot` is an infix function
- `x` and `crs` infix functions replace `crs` (cross product)

##### Usage examples
Creating new `ImmutableVector2`
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
```kotlin
import ktx.math.*

val v0 = ImmutableVector2.ZERO // pre-defined vector
val v1 = ImmutableVector2(1f, 2f) // arbitrary vector
val v2 = ImmutableVector2.X.withRotationDeg(30f) // unit vector of given angle
val v3 = -ImmutableVector2.X // inverse of a vector
```

Converting from LibGDX `Vector2` to `ImmutableVector2` (and vice versa)
```kotlin
import ktx.math.*
jcornaz marked this conversation as resolved.
Show resolved Hide resolved

val mutable1 = Vector2()
val immutable = mutable.toImmutable()
val mutable2 = immutable.toMutable()
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
```

Working with immutable vector
```kotlin
var vector1 = ImmutableVector2.X

// this is only allowed with variables (declared with `var`).
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
vector1 += ImmutableVector2.Y
vector1 *= 3f

val vector2 = vector.withClamp(0f, 1f) * 5f // vector1 is not modified
```

Creating typealias
```kotlin
import ktx.math.*

jcornaz marked this conversation as resolved.
Show resolved Hide resolved
typealias Vec2 = ImmutableVector2

var v1 = (Vec2.X + Vect2.Y).nor
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
var v2 = Vec2(1f, 2f).withLength(3f)
```
jcornaz marked this conversation as resolved.
Show resolved Hide resolved

#### `Vector3`

- `vec3` is a global factory function that can create `Vector3` instances with named parameters for extra readability.
Expand Down
2 changes: 2 additions & 0 deletions math/build.gradle
@@ -1,3 +1,5 @@
dependencies {
provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

testCompile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
}
181 changes: 181 additions & 0 deletions math/src/main/kotlin/ktx/math/ImmutableVector.kt
@@ -0,0 +1,181 @@
@file:Suppress("NOTHING_TO_INLINE")

package ktx.math

import com.badlogic.gdx.math.Interpolation
import com.badlogic.gdx.math.MathUtils
import com.badlogic.gdx.math.Vector
import kotlin.math.abs
import kotlin.math.sqrt

/**
* Represent an immutable vector.
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
*/
interface ImmutableVector<T : ImmutableVector<T>> : Comparable<T> {

/**
* Returns the squared euclidean length
*
* This method is faster than [Vector.len] because it avoids calculating a square root. It is useful for comparisons,
* but not for getting exact lengths, as the return value is the square of the actual length.
*/
val len2: Float

/** Returns the euclidean length */
val len: Float get() = sqrt(len2)

/**
* Returns the unit vector of same direction or this vector if it is zero.
*/
val nor: T

/** Returns whether the length of this vector is smaller than the given [margin] */
fun isZero(margin: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean

/** Returns whether this vector is a unit length vector within the given [margin]. (no margin by default) */
fun isUnit(margin: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean = abs(1f - len2) < margin

/** Returns the opposite vector of same length */
operator fun unaryMinus(): T

/** Returns the result of subtracting the [other] vector from this vector */
operator fun minus(other: T): T

/** Returns the result of adding the [other] vector to this vector */
operator fun plus(other: T): T

/** Returns the same vector with all members incremented by 1 */
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
operator fun inc(): T

/** Returns the same vector with all members decremented by 1 */
operator fun dec(): T

/** Returns this vector scaled by the given [scalar] */
operator fun times(scalar: Float): T

/** Returns his vector scaled by the given [vector] */
operator fun times(vector: T): T

/** Returns a vector of the same direction and a squared length of [length2] */
fun withLength2(length2: Float): T

/** Returns he dot product of this vector by the given [vector] */
infix fun dot(vector: T): Float

/**
* Returns the squared distance between this and the other vector
*
* This method is faster than [dst] because it avoids calculating a square root. It is useful for comparisons,
* but not for getting exact distance, as the return value is the square of the actual distance.
*/
infix fun dst2(vector: T): Float

/** Linearly interpolates between this vector and the target vector by alpha */
jcornaz marked this conversation as resolved.
Show resolved Hide resolved
fun withLerp(target: T, alpha: Float): T

/** Returns a vector of same length and a random direction */
fun withRandomDirection(): T

/** Returns a vector of same direction and the given [length] */
fun withLength(length: Float): T = withLength2(length * length)

/** Returns this vector if the [ImmutableVector.len] is <= [limit] or a vector with the same direction and length [limit] otherwise */
fun withLimit(limit: Float): T = withLimit2(limit * limit)

/** Returns this vector if the [ImmutableVector.len2] is <= [limit2] or a vector with the same direction and length [limit2] otherwise */
fun withLimit2(limit2: Float): T

/** Clamps this vector's length to given [min] and [max] values*/
fun withClamp(min: Float, max: Float): T = withClamp2(min * min, max * max)

/** Clamps this vector's squared length to given [min2] and [max2] values*/
fun withClamp2(min2: Float, max2: Float): T

/** Interpolates between this vector and the given [target] vector by [alpha] (within range [0,1]) using the given [interpolation] method. */
fun withInterpolation(target: T, alpha: Float, interpolation: Interpolation): T =
withLerp(target, interpolation.apply(alpha))

/** Returns true if this vector is in line with the other vector (either in the same or the opposite direction) */
fun isOnLine(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean

/** Compares this vector with the other vector, using the supplied epsilon for fuzzy equality testing */
fun epsilonEquals(other: T, epsilon: Float): Boolean

override fun compareTo(other: T): Int = len2.compareTo(other.len2)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withLerp(target, alpha)"), DeprecationLevel.ERROR)
fun lerp(target: T, alpha: Float): T = withLerp(target, alpha)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withLength2(len2)"), DeprecationLevel.ERROR)
fun setLength2(len2: Float): T = withLength2(len2)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this * scalar"), DeprecationLevel.ERROR)
fun scl(scalar: Float): T = this * scalar

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this * v"), DeprecationLevel.ERROR)
fun scl(v: T): T = this * v

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this + v"), DeprecationLevel.ERROR)
fun add(v: T): T = this + v

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withRandomDirection()"), DeprecationLevel.ERROR)
fun setToRandomDirection(): T = withRandomDirection()

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this + (v * scalar)"), DeprecationLevel.ERROR)
fun mulAdd(v: T, scalar: Float): T = this + (v * scalar)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this + (v * mulVec)"), DeprecationLevel.ERROR)
fun mulAdd(v: T, mulVec: T): T = this + (v * mulVec)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withLimit(limit)"), DeprecationLevel.ERROR)
fun limit(limit: Float): T = withLimit(limit)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withClamp(min, max)"), DeprecationLevel.ERROR)
fun clamp(min: Float, max: Float): T = withClamp(min, max)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("v"), DeprecationLevel.ERROR)
fun set(v: T): T = v

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withInterpolation(target, alpha, interpolator)"), DeprecationLevel.ERROR)
fun interpolate(target: T, alpha: Float, interpolator: Interpolation): T = withInterpolation(target, alpha, interpolator)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withLength(len)"), DeprecationLevel.ERROR)
fun setLength(len: Float): T = withLength(len)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("withLimit2(limit2)"), DeprecationLevel.ERROR)
fun limit2(limit2: Float): T = withLimit2(limit2)

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("this - v"), DeprecationLevel.ERROR)
fun sub(v: T): T = this - v

@Deprecated(MUTABLE_METHOD_DEPRECATION_MESSAGE, ReplaceWith("nor"), DeprecationLevel.ERROR)
fun nor(): T = nor
}

/** Returns the distance between this and the [other] vector */
inline infix fun <T : ImmutableVector<T>> T.dst(other: T): Float = sqrt(dst2(other))

/** Returns this vector scaled by (1 / [scalar]) */
inline operator fun <T : ImmutableVector<T>> T.div(scalar: Float): T = times(1 / scalar)

/** Returns true if this vector is collinear with the [other] vector */
fun <T : ImmutableVector<T>> T.isCollinear(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
isOnLine(other, epsilon) && hasSameDirection(other)

/** Returns true if this vector is opposite collinear with the [other] vector */
fun <T : ImmutableVector<T>> T.isCollinearOpposite(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
isOnLine(other, epsilon) && hasOppositeDirection(other)

/** Returns true if this vector is opposite perpendicular with the [other] vector */
fun <T : ImmutableVector<T>> T.isPerpendicular(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
MathUtils.isZero(dot(other), epsilon)

/** Returns whether this vector has similar direction compared to the other vector. */
fun <T : ImmutableVector<T>> T.hasSameDirection(other: T): Boolean =
dot(other) > 0f

/** Returns whether this vector has opposite direction compared to the other vector. */
fun <T : ImmutableVector<T>> T.hasOppositeDirection(other: T): Boolean =
dot(other) < 0f

internal const val MUTABLE_METHOD_DEPRECATION_MESSAGE = "Unlike its equivalent in LibGDX, this function doesn't mutate the vector"
jcornaz marked this conversation as resolved.
Show resolved Hide resolved