Skip to content

Commit

Permalink
Add rotation methods
Browse files Browse the repository at this point in the history
  • Loading branch information
kylecorry31 committed Mar 3, 2023
1 parent 0e32864 commit 47a683e
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 1 deletion.
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
ext.groupId = 'com.kylecorry.sol'
ext.versionName = '6.4.1'
ext.versionName = '6.4.2'
}

allprojects {
Expand Down
18 changes: 18 additions & 0 deletions math/src/main/java/com/kylecorry/sol/math/Vector2.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.kylecorry.sol.math

import com.kylecorry.sol.math.SolMath.cosDegrees
import com.kylecorry.sol.math.SolMath.sinDegrees
import kotlin.math.sqrt

data class Vector2(val x: Float, val y: Float) {
Expand Down Expand Up @@ -29,4 +31,20 @@ data class Vector2(val x: Float, val y: Float) {
return (other - this).magnitude()
}

fun rotate(angle: Float, origin: Vector2 = zero): Vector2 {
if (angle % 360f == 0f) return this
val x = this.x - origin.x
val y = this.y - origin.y
val cos = cosDegrees(angle)
val sin = sinDegrees(angle)
return Vector2(
x * cos - y * sin + origin.x,
x * sin + y * cos + origin.y
)
}

companion object {
val zero = Vector2(0f, 0f)
}

}
58 changes: 58 additions & 0 deletions math/src/main/java/com/kylecorry/sol/math/geometry/Rectangle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ import com.kylecorry.sol.math.Vector2

data class Rectangle(val left: Float, val top: Float, val right: Float, val bottom: Float) {

val center = Vector2((left + right) / 2, (top + bottom) / 2)
val topLeft = Vector2(left, top)
val topRight = Vector2(right, top)
val bottomLeft = Vector2(left, bottom)
val bottomRight = Vector2(right, bottom)

val corners = listOf(topLeft, topRight, bottomLeft, bottomRight)

fun width(): Float {
return right - left
}
Expand All @@ -19,4 +27,54 @@ data class Rectangle(val left: Float, val top: Float, val right: Float, val bott
fun contains(point: Vector2): Boolean {
return point.x in left..right && point.y in bottom..top
}

fun contains(rectangle: Rectangle): Boolean {
return contains(rectangle.topLeft) &&
contains(rectangle.topRight) &&
contains(rectangle.bottomLeft) &&
contains(rectangle.bottomRight)
}

fun intersects(rectangle: Rectangle): Boolean {
return contains(rectangle.topLeft) ||
contains(rectangle.topRight) ||
contains(rectangle.bottomLeft) ||
contains(rectangle.bottomRight)
}

fun rotate(angle: Float, center: Vector2 = this.center): Rectangle {
val rotated = corners.map { it.rotate(angle, center) }
return boundingBox(rotated)
}

companion object {
fun fromCenter(center: Vector2, width: Float, height: Float): Rectangle {
val halfWidth = width / 2
val halfHeight = height / 2
return Rectangle(
center.x - halfWidth,
center.y + halfHeight,
center.x + halfWidth,
center.y - halfHeight
)
}

fun fromCorners(topLeft: Vector2, bottomRight: Vector2): Rectangle {
return Rectangle(topLeft.x, topLeft.y, bottomRight.x, bottomRight.y)
}

fun fromCorners(topLeft: Vector2, width: Float, height: Float): Rectangle {
return Rectangle(topLeft.x, topLeft.y, topLeft.x + width, topLeft.y - height)
}

fun boundingBox(points: List<Vector2>): Rectangle {
if (points.isEmpty()) {
return Rectangle(0f, 0f, 0f, 0f)
}

val x = points.map { it.x }
val y = points.map { it.y }
return Rectangle(x.min(), y.max(), x.max(), y.min())
}
}
}
49 changes: 49 additions & 0 deletions math/src/test/java/com/kylecorry/sol/math/Vector2Test.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.kylecorry.sol.math

import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import java.util.stream.Stream

internal class Vector2Test {

@ParameterizedTest
@MethodSource("provideRotation")
fun testRotation(vector: Vector2, angle: Float, origin: Vector2, expected: Vector2) {
val rotated = vector.rotate(angle, origin)

assertEquals(expected.x, rotated.x, 0.0001f)
assertEquals(expected.y, rotated.y, 0.0001f)
}

companion object {
@JvmStatic
fun provideRotation(): Stream<Arguments> {
return Stream.of(
// CW
Arguments.of(Vector2(1f, 0f), 90f, Vector2.zero, Vector2(0f, 1f)),
Arguments.of(Vector2(1f, 0f), 180f, Vector2.zero, Vector2(-1f, 0f)),
Arguments.of(Vector2(1f, 0f), 270f, Vector2.zero, Vector2(0f, -1f)),
Arguments.of(Vector2(1f, 0f), 360f, Vector2.zero, Vector2(1f, 0f)),
Arguments.of(Vector2(2f, 0f), 90f, Vector2(1f, 2f), Vector2(3f, 3f)),
Arguments.of(Vector2(2f, 0f), 180f, Vector2(1f, 2f), Vector2(0f, 4f)),
Arguments.of(Vector2(2f, 0f), 270f, Vector2(1f, 2f), Vector2(-1f, 1f)),
Arguments.of(Vector2(2f, 0f), 360f, Vector2(1f, 2f), Vector2(2f, 0f)),
// CCW
Arguments.of(Vector2(1f, 0f), -90f, Vector2.zero, Vector2(0f, -1f)),
Arguments.of(Vector2(1f, 0f), -180f, Vector2.zero, Vector2(-1f, 0f)),
Arguments.of(Vector2(1f, 0f), -270f, Vector2.zero, Vector2(0f, 1f)),
Arguments.of(Vector2(1f, 0f), -360f, Vector2.zero, Vector2(1f, 0f)),
Arguments.of(Vector2(2f, 0f), -90f, Vector2(1f, 2f), Vector2(-1f, 1f)),
Arguments.of(Vector2(2f, 0f), -180f, Vector2(1f, 2f), Vector2(0f, 4f)),
Arguments.of(Vector2(2f, 0f), -270f, Vector2(1f, 2f), Vector2(3f, 3f)),
Arguments.of(Vector2(2f, 0f), -360f, Vector2(1f, 2f), Vector2(2f, 0f)),
// No rotation
Arguments.of(Vector2(1f, 0f), 0f, Vector2.zero, Vector2(1f, 0f)),
Arguments.of(Vector2(1f, 0f), 0f, Vector2(1f, 0f), Vector2(1f, 0f)),
)
}
}

}

0 comments on commit 47a683e

Please sign in to comment.