Skip to content

Commit 6fc9f00

Browse files
committed
Make Vector2 a value class
Signed-off-by: Kyle Corry <[email protected]>
1 parent 8ae1e2f commit 6fc9f00

File tree

5 files changed

+88
-10
lines changed

5 files changed

+88
-10
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,5 @@ temp/
8181

8282
# Custom
8383
.idea/markdown.xml
84-
.idea/AndroidProjectSystem.xml
84+
.idea/AndroidProjectSystem.xml
85+
/.vscode/tasks.json

src/main/kotlin/com/kylecorry/sol/math/Vector2.kt

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,16 @@ import com.kylecorry.sol.math.SolMath.toDegrees
77
import kotlin.math.atan2
88
import kotlin.math.sqrt
99

10-
data class Vector2(val x: Float, val y: Float) {
10+
@JvmInline
11+
value class Vector2 internal constructor(internal val packed: Long) {
12+
13+
constructor(x: Float, y: Float) : this(packXY(x, y))
14+
15+
val x: Float
16+
get() = Float.fromBits(((packed ushr 32) and 0xFFFFFFFFL).toInt())
17+
18+
val y: Float
19+
get() = Float.fromBits((packed and 0xFFFFFFFFL).toInt())
1120

1221
operator fun minus(other: Vector2): Vector2 {
1322
return Vector2(x - other.x, y - other.y)
@@ -66,8 +75,25 @@ data class Vector2(val x: Float, val y: Float) {
6675
)
6776
}
6877

78+
fun copy(x: Float = this.x, y: Float = this.y): Vector2 {
79+
return Vector2(x, y)
80+
}
81+
82+
override fun toString(): String {
83+
return "Vector2(x=$x, y=$y)"
84+
}
85+
6986
companion object {
70-
val zero = Vector2(0f, 0f)
87+
val zero = from(0f, 0f)
88+
89+
fun from(x: Float, y: Float): Vector2 {
90+
return Vector2(packXY(x, y))
91+
}
7192
}
93+
}
7294

73-
}
95+
private fun packXY(x: Float, y: Float): Long {
96+
val xb = x.toBits()
97+
val yb = y.toBits()
98+
return ((xb.toLong() and 0xFFFFFFFFL) shl 32) or (yb.toLong() and 0xFFFFFFFFL)
99+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.kylecorry.sol.math
2+
3+
import org.junit.jupiter.params.converter.ArgumentConversionException
4+
import org.junit.jupiter.params.converter.SimpleArgumentConverter
5+
6+
/**
7+
* Converts between Vector2 and Long (packed) for parameterized tests.
8+
* - If target type is Vector2, accepts Long (packed) and constructs Vector2.
9+
* - If target type is Long, accepts Vector2 and returns its packed value.
10+
*/
11+
class Vector2LongConverter : SimpleArgumentConverter() {
12+
override fun convert(source: Any?, targetType: Class<*>): Any {
13+
if (source == null) {
14+
throw ArgumentConversionException("Source is null")
15+
}
16+
17+
return when (targetType) {
18+
List::class.java -> {
19+
if (source is java.util.List<*>) {
20+
source.map { item ->
21+
when (item) {
22+
is Long -> Vector2(item)
23+
is Number -> Vector2(item.toLong())
24+
is Vector2 -> item
25+
else -> throw ArgumentConversionException("Cannot convert list element ${item?.let { it::class.java }} to Vector2")
26+
}
27+
}
28+
} else {
29+
throw ArgumentConversionException("Cannot convert ${source::class.java} to List<Vector2>. Expected List<Long|Number|Vector2>.")
30+
}
31+
}
32+
33+
Long::class.java -> {
34+
if (source is Vector2) {
35+
source.packed
36+
} else {
37+
throw ArgumentConversionException("Cannot convert ${source::class.java} to Long. Expected Vector2 -> Long.")
38+
}
39+
}
40+
41+
else -> throw ArgumentConversionException("Unsupported target type: ${targetType.name}")
42+
}
43+
}
44+
}

src/test/kotlin/com/kylecorry/sol/math/Vector2Test.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.kylecorry.sol.math
22

3-
import org.junit.jupiter.api.Assertions.*
4-
import org.junit.jupiter.api.Test
3+
import org.junit.jupiter.api.Assertions.assertEquals
54
import org.junit.jupiter.params.ParameterizedTest
5+
import org.junit.jupiter.params.converter.ConvertWith
66
import org.junit.jupiter.params.provider.Arguments
77
import org.junit.jupiter.params.provider.CsvSource
88
import org.junit.jupiter.params.provider.MethodSource
@@ -12,7 +12,12 @@ internal class Vector2Test {
1212

1313
@ParameterizedTest
1414
@MethodSource("provideRotation")
15-
fun rotate(vector: Vector2, angle: Float, origin: Vector2, expected: Vector2) {
15+
fun rotate(
16+
@ConvertWith(Vector2LongConverter::class) vector: Vector2,
17+
angle: Float,
18+
@ConvertWith(Vector2LongConverter::class) origin: Vector2,
19+
@ConvertWith(Vector2LongConverter::class) expected: Vector2
20+
) {
1621
val rotated = vector.rotate(angle, origin)
1722

1823
assertEquals(expected.x, rotated.x, 0.0001f)

src/test/kotlin/com/kylecorry/sol/math/geometry/IntersectionMathTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.kylecorry.sol.math.geometry
22

33
import com.kylecorry.sol.math.Vector2
4+
import com.kylecorry.sol.math.Vector2LongConverter
45
import org.junit.jupiter.api.Assertions.*
56
import org.junit.jupiter.params.ParameterizedTest
7+
import org.junit.jupiter.params.converter.ConvertWith
68
import org.junit.jupiter.params.provider.Arguments
79
import org.junit.jupiter.params.provider.MethodSource
810
import java.util.stream.Stream
@@ -22,10 +24,10 @@ internal class IntersectionMathTest {
2224
@ParameterizedTest
2325
@MethodSource("providePointsRectIntersection")
2426
fun getIntersectionPointsRect(
25-
a: Vector2,
26-
b: Vector2,
27+
@ConvertWith(Vector2LongConverter::class) a: Vector2,
28+
@ConvertWith(Vector2LongConverter::class) b: Vector2,
2729
rect: Rectangle,
28-
expected: List<Vector2>
30+
@ConvertWith(Vector2LongConverter::class) expected: List<Vector2>
2931
) {
3032
val intersection = IntersectionMath.getIntersection(a, b, rect)
3133
assertEquals(expected.size, intersection.size)

0 commit comments

Comments
 (0)