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 2 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
2 changes: 2 additions & 0 deletions math/build.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dependencies {
provided "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"

testCompile "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-desktop"
}
119 changes: 119 additions & 0 deletions math/src/main/kotlin/ktx/math/KVector.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
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
*/
interface KVector<T : KVector<T>> {

/**
* 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.
*
* @return The squared euclidean length
*/
val len2: Float

/** @return Whether the length of this vector is smaller than the given margin */
fun isZero(margin: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean

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

/** @return Result of subtracting the given vector from this vector */
operator fun minus(v: T): T

/** @return Result of adding the given vector from this vector */
operator fun plus(v: T): T

/** @return This vector scaled by the given [scalar] */
operator fun times(scalar: Float): T

/** @return This vector scaled by the given [vector] */
operator fun times(vector: T): T

/** @return The dot product of this vector by the given [vector] */
fun dot(vector: T): Float

/**
* 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.
* @return The squared distance between this and the other vector
*/
fun dst2(vector: T): Float

fun lerp(target: T, alpha: Float): T

fun withRandomDirection(): T

fun isOnLine(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean

fun epsilonEquals(other: T, epsilon: Float): Boolean
}

/**
* @return The euclidean length
*/
val KVector<*>.len: Float get() = sqrt(len2)

/**
* @return the unit vector of same direction or [this] if it is zero.
*/
val <T : KVector<T>> T.nor: T
get() {
val l2 = len2

if (l2 == 1f || l2 == 0f) return this

return withLength2(1f)
}

/** @return Whether this vector is a unit length vector within the given margin. (no margin by default) */
fun <T : KVector<T>> T.isUnit(margin: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean = abs(1f - len2) < margin

fun <T : KVector<T>> T.dst(v: T): Float = sqrt(dst2(v))
fun <T : KVector<T>> T.limit(limit: Float): T = limit2(limit * limit)

/** @return [this] is the [len2] is <= [limit2] and A vector with the same direction and length [limit2] otherwise */
fun <T : KVector<T>> T.limit2(limit2: Float): T =
if (len2 <= limit2) this else withLength2(limit2)

fun <T : KVector<T>> T.clamp(min: Float, max: Float): T = clamp2(min * min, max * max)

/** @return Clamps this vector's length to given [min] and [max] values*/
fun <T : KVector<T>> T.clamp2(min2: Float, max2: Float): T {
val l2 = len2
return when {
l2 < min2 -> withLength2(min2)
l2 > max2 -> withLength2(max2)
else -> this
}
}

fun <T : KVector<T>> T.withLength(len: Float): T = withLength2(len * len)

/** @return The opposite vector of same length */
operator fun <T : KVector<T>> T.unaryMinus(): T = times(-1f)

fun <T : KVector<T>> T.interpolate(target: T, alpha: Float, interpolator: Interpolation): T =
lerp(target, interpolator.apply(alpha))
czyzby marked this conversation as resolved.
Show resolved Hide resolved

fun <T : KVector<T>> T.isCollinear(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
isOnLine(other, epsilon) && dot(other) > 0f

fun <T : KVector<T>> T.isCollinearOpposite(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
isOnLine(other, epsilon) && dot(other) < 0f

fun <T : KVector<T>> T.isPerpendicular(other: T, epsilon: Float = MathUtils.FLOAT_ROUNDING_ERROR): Boolean =
MathUtils.isZero(dot(other), epsilon)

fun <T : KVector<T>> T.hasSameDirection(other: T): Boolean =
dot(other) > 0f

fun <T : KVector<T>> T.hasOppositeDirection(other: T): Boolean =
dot(other) < 0f
144 changes: 144 additions & 0 deletions math/src/main/kotlin/ktx/math/KVector2.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package ktx.math

import com.badlogic.gdx.math.MathUtils
import com.badlogic.gdx.math.Matrix3
import com.badlogic.gdx.math.Vector2
import kotlin.math.*

/**
* Represent an **immutable** vector
*
* @property x the x-component of this vector
* @property y the y-component of this vector
*/
data class KVector2(val x: Float, val y: Float) : KVector<KVector2> {

override val len2: Float = Vector2.len2(x, y)

override fun isZero(margin: Float): Boolean = (x == 0f && y == 0f) || len2 < margin

override operator fun minus(v: KVector2): KVector2 = minus(v.x, v.y)

/** @return Result of subtracting the given vector from this vector */
fun minus(deltaX: Float = 0f, deltaY: Float = 0f): KVector2 = KVector2(x - deltaX, y - deltaY)

override operator fun plus(v: KVector2): KVector2 = plus(v.x, v.y)

/** @return Result of adding the given vector from this vector */
fun plus(deltaX: Float = 0f, deltaY: Float = 0f): KVector2 = KVector2(x + deltaX, y + deltaY)

override operator fun times(scalar: Float): KVector2 = times(scalar, scalar)
override operator fun times(vector: KVector2): KVector2 = times(vector.x, vector.y)

/** @return This vector scaled by the given [xf] and [yf] factors */
fun times(xf: Float, yf: Float): KVector2 = KVector2(x * xf, y * yf)

override fun dot(vector: KVector2): Float = dot(vector.x, vector.y)

/** @return The dot product of this vector by the given vector */
fun dot(ox: Float, oy: Float): Float = Vector2.dot(x, y, ox, oy)

override fun dst2(vector: KVector2): Float = dst2(vector.x, vector.y)

/**
* 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.
* @return The squared distance between this and the other vector
*/
fun dst2(ox: Float, oy: Float): Float = Vector2.dst2(x, y, ox, oy)

/** @return the distance between this and the other vector */
fun dst(ox: Float, oy: Float): Float = Vector2.dst(x, y, ox, oy)

override fun withLength2(len2: Float): KVector2 {
val oldLen2 = this.len2

if (oldLen2 == 0f || oldLen2 == len2) return this

return times(sqrt(len2 / oldLen2))
}

operator fun times(matrix: Matrix3): KVector2 = KVector2(
x = x * matrix.`val`[0] + y * matrix.`val`[3] + matrix.`val`[6],
y = x * matrix.`val`[1] + y * matrix.`val`[4] + matrix.`val`[7]
)

fun crs(vector: KVector2): Float = crs(vector.x, vector.y)
fun crs(x: Float, y: Float): Float = this.x * y - this.y * x

fun withAngleRad(radians: Float): KVector2 = KVector2(len, 0f).rotateRad(radians)

fun angleRad(reference: KVector2 = KVector2.X): Float = angleRad(reference.x, reference.y)
fun angleRad(x: Float, y: Float): Float {
val result = atan2(this.y, this.x) - atan2(y, x)
return when {
result > MathUtils.PI -> result - MathUtils.PI2
result < -MathUtils.PI -> result + MathUtils.PI2
else -> result
}
}

fun rotateRad(radians: Float): KVector2 {
val cos = cos(radians.toDouble()).toFloat()
val sin = sin(radians.toDouble()).toFloat()

return KVector2(
x = this.x * cos - this.y * sin,
y = this.x * sin + this.y * cos
)
}

fun angle(reference: KVector2 = KVector2.X): Float = angle(reference.x, reference.y)
fun angle(x: Float, y: Float): Float = angleRad(x, y) * MathUtils.radiansToDegrees

fun rotate(degrees: Float): KVector2 = rotateRad(degrees * MathUtils.degreesToRadians)
fun withAngle(degrees: Float): KVector2 = withAngleRad(degrees * MathUtils.degreesToRadians)

fun rotate90(dir: Int): KVector2 =
if (dir >= 0) KVector2(x = -y, y = x) else KVector2(x = y, y = -x)

override fun lerp(target: KVector2, alpha: Float): KVector2 {
val invAlpha = 1.0f - alpha
return KVector2(
x = x * invAlpha + target.x * alpha,
y = y * invAlpha + target.y * alpha
)
}

override fun withRandomDirection(): KVector2 = withAngleRad(MathUtils.random(0f, MathUtils.PI2))

override fun epsilonEquals(other: KVector2, epsilon: Float): Boolean =
epsilonEquals(other.x, other.y, epsilon)

fun epsilonEquals(x: Float, y: Float, epsilon: Float = Float.MIN_VALUE): Boolean =
abs(x - this.x) <= epsilon && abs(y - this.y) <= epsilon

override fun isOnLine(other: KVector2, epsilon: Float): Boolean =
MathUtils.isZero(x * other.y - y * other.x, epsilon)

override fun toString(): String = "($x,$y)"

companion object {

/** Vector zero */
val ZERO = KVector2(0f, 0f)

/** unit vector of positive x axis */
val X = KVector2(1f, 0f)

/** unit vector of positive y axis */
val Y = KVector2(0f, 1f)

/**
* @return [KVector2] represented by the specified string according to the format of [KVector2::toString]
*/
fun fromString(string: String): KVector2 =
Vector2().fromString(string).toKVector2()
}
}

/** @return an instance of [KVector2] with the same x and y values */
fun KVector2.toVector2(): Vector2 = Vector2(x, y)

/** @return an instance of [Vector2] with the same x and y values */
fun Vector2.toKVector2(): KVector2 = KVector2(x, y)