Update type-safe angles

This commit is contained in:
Alexander Nozik 2023-02-05 20:05:53 +03:00
parent 0366a69123
commit 2c6d1e89c5
9 changed files with 104 additions and 85 deletions

View File

@ -10,6 +10,7 @@
- Algebra now has an obligatory `bufferFactory` (#477). - Algebra now has an obligatory `bufferFactory` (#477).
### Changed ### Changed
- Trajectory use type-safe angles
- Tensor operations switched to prefix notation - Tensor operations switched to prefix notation
- Row-wise and column-wise ND shapes in the core - Row-wise and column-wise ND shapes in the core
- Shape is read-only - Shape is read-only

View File

@ -7,8 +7,9 @@ package space.kscience.kmath.geometry
import kotlin.jvm.JvmInline import kotlin.jvm.JvmInline
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.floor
public sealed interface Angle { public sealed interface Angle : Comparable<Angle> {
public fun toRadians(): Radians public fun toRadians(): Radians
public fun toDegrees(): Degrees public fun toDegrees(): Degrees
@ -17,7 +18,15 @@ public sealed interface Angle {
public operator fun times(other: Number): Angle public operator fun times(other: Number): Angle
public operator fun div(other: Number): Angle public operator fun div(other: Number): Angle
public operator fun div(other: Angle): Double
public operator fun unaryMinus(): Angle public operator fun unaryMinus(): Angle
public companion object {
public val zero: Radians = Radians(0.0)
public val pi: Radians = Radians(PI)
public val piTimes2: Radians = Radians(PI * 2)
public val piDiv2: Radians = Radians(PI / 2)
}
} }
/** /**
@ -28,12 +37,16 @@ public value class Radians(public val value: Double) : Angle {
override fun toRadians(): Radians = this override fun toRadians(): Radians = this
override fun toDegrees(): Degrees = Degrees(value * 180 / PI) override fun toDegrees(): Degrees = Degrees(value * 180 / PI)
public override fun plus(other: Angle): Radians = Radians(value + other.toRadians().value) public override fun plus(other: Angle): Radians = Radians(value + other.radians)
public override fun minus(other: Angle): Radians = Radians(value - other.toRadians().value) public override fun minus(other: Angle): Radians = Radians(value - other.radians)
public override fun times(other: Number): Radians = Radians(value + other.toDouble()) public override fun times(other: Number): Radians = Radians(value * other.toDouble())
public override fun div(other: Number): Radians = Radians(value / other.toDouble()) public override fun div(other: Number): Radians = Radians(value / other.toDouble())
override fun div(other: Angle): Double = value / other.radians
public override fun unaryMinus(): Radians = Radians(-value) public override fun unaryMinus(): Radians = Radians(-value)
override fun compareTo(other: Angle): Int = value.compareTo(other.radians)
} }
public fun sin(angle: Angle): Double = kotlin.math.sin(angle.toRadians().value) public fun sin(angle: Angle): Double = kotlin.math.sin(angle.toRadians().value)
@ -42,6 +55,8 @@ public fun tan(angle: Angle): Double = kotlin.math.tan(angle.toRadians().value)
public val Number.radians: Radians get() = Radians(toDouble()) public val Number.radians: Radians get() = Radians(toDouble())
public val Angle.radians: Double get() = toRadians().value
/** /**
* Type safe degrees * Type safe degrees
*/ */
@ -50,30 +65,26 @@ public value class Degrees(public val value: Double) : Angle {
override fun toRadians(): Radians = Radians(value * PI / 180) override fun toRadians(): Radians = Radians(value * PI / 180)
override fun toDegrees(): Degrees = this override fun toDegrees(): Degrees = this
public override fun plus(other: Angle): Degrees = Degrees(value + other.toDegrees().value) public override fun plus(other: Angle): Degrees = Degrees(value + other.degrees)
public override fun minus(other: Angle): Degrees = Degrees(value - other.toDegrees().value) public override fun minus(other: Angle): Degrees = Degrees(value - other.degrees)
public override fun times(other: Number): Degrees = Degrees(value + other.toDouble()) public override fun times(other: Number): Degrees = Degrees(value * other.toDouble())
public override fun div(other: Number): Degrees = Degrees(value / other.toDouble()) public override fun div(other: Number): Degrees = Degrees(value / other.toDouble())
override fun div(other: Angle): Double = value / other.degrees
public override fun unaryMinus(): Degrees = Degrees(-value) public override fun unaryMinus(): Degrees = Degrees(-value)
override fun compareTo(other: Angle): Int = value.compareTo(other.degrees)
} }
public val Number.degrees: Degrees get() = Degrees(toDouble()) public val Number.degrees: Degrees get() = Degrees(toDouble())
public val Angle.degrees: Double get() = toDegrees().value
/** /**
* A holder class for Pi representation in radians and degrees * Normalized angle 2 PI range symmetric around [center]. By default, uses (0, 2PI) range.
*/ */
public object Pi { public fun Angle.normalized(center: Angle = Angle.pi): Angle =
public val radians: Radians = Radians(PI) this - Angle.piTimes2 * floor((radians + PI - center.radians) / PI / 2)
public val degrees: Degrees = radians.toDegrees()
}
public object PiTimes2 { public fun abs(angle: Angle): Angle = if (angle < Angle.zero) -angle else angle
public val radians: Radians = Radians(2 * PI)
public val degrees: Degrees = radians.toDegrees()
}
public object PiDiv2 {
public val radians: Radians = Radians(PI / 2)
public val degrees: Degrees = radians.toDegrees()
}

View File

@ -0,0 +1,16 @@
package space.kscience.kmath.geometry
import kotlin.test.Test
import kotlin.test.assertEquals
class AngleTest {
@Test
fun normalization() {
assertEquals(30.degrees, 390.degrees.normalized())
assertEquals(30.degrees, (-330).degrees.normalized())
assertEquals(200.degrees, 200.degrees.normalized())
assertEquals(30.degrees, 390.degrees.normalized(Angle.zero))
assertEquals(30.degrees, (-330).degrees.normalized(Angle.zero))
assertEquals((-160).degrees, 200.degrees.normalized(Angle.zero))
}
}

View File

@ -5,13 +5,9 @@
package space.kscience.kmath.trajectory package space.kscience.kmath.trajectory
import space.kscience.kmath.geometry.Circle2D import space.kscience.kmath.geometry.*
import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
import kotlin.math.PI
import kotlin.math.acos import kotlin.math.acos
import kotlin.math.cos
import kotlin.math.sin
internal fun DubinsPose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first internal fun DubinsPose2D.getLeftCircle(radius: Double): Circle2D = getTangentCircles(radius).first
@ -65,12 +61,11 @@ private fun innerTangent(
with(Euclidean2DSpace) { with(Euclidean2DSpace) {
val centers = StraightTrajectory2D(base.center, direction.center) val centers = StraightTrajectory2D(base.center, direction.center)
if (centers.length < base.radius * 2) return null if (centers.length < base.radius * 2) return null
val angle = theta( val angle = when (side) {
when (side) { CircleTrajectory2D.Direction.LEFT -> centers.bearing + acos(base.radius * 2 / centers.length).radians
CircleTrajectory2D.Direction.LEFT -> centers.bearing + acos(base.radius * 2 / centers.length) CircleTrajectory2D.Direction.RIGHT -> centers.bearing - acos(base.radius * 2 / centers.length).radians
CircleTrajectory2D.Direction.RIGHT -> centers.bearing - acos(base.radius * 2 / centers.length) }.normalized()
}
)
val dX = base.radius * sin(angle) val dX = base.radius * sin(angle)
val dY = base.radius * cos(angle) val dY = base.radius * cos(angle)
val p1 = vector(base.center.x + dX, base.center.y + dY) val p1 = vector(base.center.x + dX, base.center.y + dY)
@ -78,8 +73,6 @@ private fun innerTangent(
return StraightTrajectory2D(p1, p2) return StraightTrajectory2D(p1, p2)
} }
internal fun theta(theta: Double): Double = (theta + (2 * PI)) % (2 * PI)
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
public object DubinsPath { public object DubinsPath {
@ -91,8 +84,8 @@ public object DubinsPath {
/** /**
* Return Dubins trajectory type or null if trajectory is not a Dubins path * Return Dubins trajectory type or null if trajectory is not a Dubins path
*/ */
public fun trajectoryTypeOf(trajectory2D: CompositeTrajectory2D): Type?{ public fun trajectoryTypeOf(trajectory2D: CompositeTrajectory2D): Type? {
if(trajectory2D.segments.size != 3) return null if (trajectory2D.segments.size != 3) return null
val a = trajectory2D.segments.first() as? CircleTrajectory2D ?: return null val a = trajectory2D.segments.first() as? CircleTrajectory2D ?: return null
val b = trajectory2D.segments[1] val b = trajectory2D.segments[1]
val c = trajectory2D.segments.last() as? CircleTrajectory2D ?: return null val c = trajectory2D.segments.last() as? CircleTrajectory2D ?: return null
@ -129,13 +122,13 @@ public object DubinsPath {
if (centers.length > turningRadius * 4) return null if (centers.length > turningRadius * 4) return null
val firstVariant = run { val firstVariant = run {
var theta = theta(centers.bearing - acos(centers.length / (turningRadius * 4))) var theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized()
var dX = turningRadius * sin(theta) var dX = turningRadius * sin(theta)
var dY = turningRadius * cos(theta) var dY = turningRadius * cos(theta)
val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2)
val e = Circle2D(p, turningRadius) val e = Circle2D(p, turningRadius)
val p1 = vector(c1.center.x + dX, c1.center.y + dY) val p1 = vector(c1.center.x + dX, c1.center.y + dY)
theta = theta(centers.bearing + acos(centers.length / (turningRadius * 4))) theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized()
dX = turningRadius * sin(theta) dX = turningRadius * sin(theta)
dY = turningRadius * cos(theta) dY = turningRadius * cos(theta)
val p2 = vector(e.center.x + dX, e.center.y + dY) val p2 = vector(e.center.x + dX, e.center.y + dY)
@ -146,13 +139,13 @@ public object DubinsPath {
} }
val secondVariant = run { val secondVariant = run {
var theta = theta(centers.bearing + acos(centers.length / (turningRadius * 4))) var theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized()
var dX = turningRadius * sin(theta) var dX = turningRadius * sin(theta)
var dY = turningRadius * cos(theta) var dY = turningRadius * cos(theta)
val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2)
val e = Circle2D(p, turningRadius) val e = Circle2D(p, turningRadius)
val p1 = vector(c1.center.x + dX, c1.center.y + dY) val p1 = vector(c1.center.x + dX, c1.center.y + dY)
theta = theta(centers.bearing - acos(centers.length / (turningRadius * 4))) theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized()
dX = turningRadius * sin(theta) dX = turningRadius * sin(theta)
dY = turningRadius * cos(theta) dY = turningRadius * cos(theta)
val p2 = vector(e.center.x + dX, e.center.y + dY) val p2 = vector(e.center.x + dX, e.center.y + dY)
@ -173,13 +166,13 @@ public object DubinsPath {
if (centers.length > turningRadius * 4) return null if (centers.length > turningRadius * 4) return null
val firstVariant = run { val firstVariant = run {
var theta = theta(centers.bearing + acos(centers.length / (turningRadius * 4))) var theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized()
var dX = turningRadius * sin(theta) var dX = turningRadius * sin(theta)
var dY = turningRadius * cos(theta) var dY = turningRadius * cos(theta)
val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2)
val e = Circle2D(p, turningRadius) val e = Circle2D(p, turningRadius)
val p1 = vector(c1.center.x + dX, c1.center.y + dY) val p1 = vector(c1.center.x + dX, c1.center.y + dY)
theta = theta(centers.bearing - acos(centers.length / (turningRadius * 4))) theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized()
dX = turningRadius * sin(theta) dX = turningRadius * sin(theta)
dY = turningRadius * cos(theta) dY = turningRadius * cos(theta)
val p2 = vector(e.center.x + dX, e.center.y + dY) val p2 = vector(e.center.x + dX, e.center.y + dY)
@ -190,13 +183,13 @@ public object DubinsPath {
} }
val secondVariant = run { val secondVariant = run {
var theta = theta(centers.bearing - acos(centers.length / (turningRadius * 4))) var theta = (centers.bearing - acos(centers.length / (turningRadius * 4)).radians).normalized()
var dX = turningRadius * sin(theta) var dX = turningRadius * sin(theta)
var dY = turningRadius * cos(theta) var dY = turningRadius * cos(theta)
val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2) val p = vector(c1.center.x + dX * 2, c1.center.y + dY * 2)
val e = Circle2D(p, turningRadius) val e = Circle2D(p, turningRadius)
val p1 = vector(c1.center.x + dX, c1.center.y + dY) val p1 = vector(c1.center.x + dX, c1.center.y + dY)
theta = theta(centers.bearing + acos(centers.length / (turningRadius * 4))) theta = (centers.bearing + acos(centers.length / (turningRadius * 4)).radians).normalized()
dX = turningRadius * sin(theta) dX = turningRadius * sin(theta)
dY = turningRadius * cos(theta) dY = turningRadius * cos(theta)
val p2 = vector(e.center.x + dX, e.center.y + dY) val p2 = vector(e.center.x + dX, e.center.y + dY)

View File

@ -12,9 +12,7 @@ import kotlinx.serialization.UseSerializers
import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.DoubleVector2D import space.kscience.kmath.geometry.*
import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.Vector
import kotlin.math.atan2 import kotlin.math.atan2
/** /**
@ -23,7 +21,7 @@ import kotlin.math.atan2
@Serializable(DubinsPose2DSerializer::class) @Serializable(DubinsPose2DSerializer::class)
public interface DubinsPose2D : DoubleVector2D { public interface DubinsPose2D : DoubleVector2D {
public val coordinates: DoubleVector2D public val coordinates: DoubleVector2D
public val bearing: Double public val bearing: Angle
} }
@Serializable @Serializable
@ -31,14 +29,14 @@ public class PhaseVector2D(
override val coordinates: DoubleVector2D, override val coordinates: DoubleVector2D,
public val velocity: DoubleVector2D, public val velocity: DoubleVector2D,
) : DubinsPose2D, DoubleVector2D by coordinates { ) : DubinsPose2D, DoubleVector2D by coordinates {
override val bearing: Double get() = atan2(velocity.x, velocity.y) override val bearing: Angle get() = atan2(velocity.x, velocity.y).radians
} }
@Serializable @Serializable
@SerialName("DubinsPose2D") @SerialName("DubinsPose2D")
private class DubinsPose2DImpl( private class DubinsPose2DImpl(
override val coordinates: DoubleVector2D, override val coordinates: DoubleVector2D,
override val bearing: Double, override val bearing: Angle,
) : DubinsPose2D, DoubleVector2D by coordinates{ ) : DubinsPose2D, DoubleVector2D by coordinates{
override fun toString(): String = "DubinsPose2D(x=$x, y=$y, bearing=$bearing)" override fun toString(): String = "DubinsPose2D(x=$x, y=$y, bearing=$bearing)"
@ -60,4 +58,4 @@ public object DubinsPose2DSerializer: KSerializer<DubinsPose2D>{
} }
} }
public fun DubinsPose2D(coordinate: DoubleVector2D, theta: Double): DubinsPose2D = DubinsPose2DImpl(coordinate, theta) public fun DubinsPose2D(coordinate: DoubleVector2D, theta: Angle): DubinsPose2D = DubinsPose2DImpl(coordinate, theta)

View File

@ -3,15 +3,13 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/ */
@file:UseSerializers(Euclidean2DSpace.VectorSerializer::class) @file:UseSerializers(Euclidean2DSpace.VectorSerializer::class)
package space.kscience.kmath.trajectory package space.kscience.kmath.trajectory
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import space.kscience.kmath.geometry.Circle2D import space.kscience.kmath.geometry.*
import space.kscience.kmath.geometry.DoubleVector2D
import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
import kotlin.math.PI
import kotlin.math.atan2 import kotlin.math.atan2
@Serializable @Serializable
@ -29,7 +27,7 @@ public data class StraightTrajectory2D(
) : Trajectory2D { ) : Trajectory2D {
override val length: Double get() = start.distanceTo(end) override val length: Double get() = start.distanceTo(end)
public val bearing: Double get() = theta(atan2(end.x - start.x, end.y - start.y)) public val bearing: Angle get() = (atan2(end.x - start.x, end.y - start.y).radians).normalized()
} }
/** /**
@ -49,26 +47,25 @@ public data class CircleTrajectory2D(
/** /**
* Arc length in radians * Arc length in radians
*/ */
val arcLength: Double val arcLength: Angle
get() = theta( get() = if (direction == Direction.LEFT) {
if (direction == Direction.LEFT) {
start.bearing - end.bearing start.bearing - end.bearing
} else { } else {
end.bearing - start.bearing end.bearing - start.bearing
} }.normalized()
)
override val length: Double by lazy { override val length: Double by lazy {
circle.radius * arcLength circle.radius * arcLength.radians
} }
public val direction: Direction by lazy { public val direction: Direction by lazy {
if (start.y < circle.center.y) { if (start.y < circle.center.y) {
if (start.bearing > PI) Direction.RIGHT else Direction.LEFT if (start.bearing > Angle.pi) Direction.RIGHT else Direction.LEFT
} else if (start.y > circle.center.y) { } else if (start.y > circle.center.y) {
if (start.bearing < PI) Direction.RIGHT else Direction.LEFT if (start.bearing < Angle.pi) Direction.RIGHT else Direction.LEFT
} else { } else {
if (start.bearing == 0.0) { if (start.bearing == Angle.zero) {
if (start.x < circle.center.x) Direction.RIGHT else Direction.LEFT if (start.x < circle.center.x) Direction.RIGHT else Direction.LEFT
} else { } else {
if (start.x > circle.center.x) Direction.RIGHT else Direction.LEFT if (start.x > circle.center.x) Direction.RIGHT else Direction.LEFT
@ -85,13 +82,13 @@ public data class CircleTrajectory2D(
): CircleTrajectory2D { ): CircleTrajectory2D {
fun calculatePose( fun calculatePose(
vector: DoubleVector2D, vector: DoubleVector2D,
theta: Double, theta: Angle,
direction: Direction, direction: Direction,
): DubinsPose2D = DubinsPose2D( ): DubinsPose2D = DubinsPose2D(
vector, vector,
when (direction) { when (direction) {
Direction.LEFT -> theta(theta - PI / 2) Direction.LEFT -> (theta - Angle.piDiv2).normalized()
Direction.RIGHT -> theta(theta + PI / 2) Direction.RIGHT -> (theta + Angle.piDiv2).normalized()
} }
) )
@ -100,7 +97,7 @@ public data class CircleTrajectory2D(
val pose1 = calculatePose(start, s1.bearing, direction) val pose1 = calculatePose(start, s1.bearing, direction)
val pose2 = calculatePose(end, s2.bearing, direction) val pose2 = calculatePose(end, s2.bearing, direction)
val trajectory = CircleTrajectory2D(Circle2D(center, s1.length), pose1, pose2) val trajectory = CircleTrajectory2D(Circle2D(center, s1.length), pose1, pose2)
if(trajectory.direction != direction){ if (trajectory.direction != direction) {
error("Trajectory direction mismatch") error("Trajectory direction mismatch")
} }
return trajectory return trajectory
@ -113,5 +110,6 @@ public class CompositeTrajectory2D(public val segments: List<Trajectory2D>) : Tr
override val length: Double get() = segments.sumOf { it.length } override val length: Double get() = segments.sumOf { it.length }
} }
public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D = CompositeTrajectory2D(segments.toList()) public fun CompositeTrajectory2D(vararg segments: Trajectory2D): CompositeTrajectory2D =
CompositeTrajectory2D(segments.toList())

View File

@ -6,19 +6,21 @@
package space.kscience.kmath.trajectory package space.kscience.kmath.trajectory
import space.kscience.kmath.geometry.Euclidean2DSpace import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.radians
import space.kscience.kmath.geometry.sin
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.sin
const val maxFloatDelta = 0.000001 const val maxFloatDelta = 0.000001
fun Double.radiansToDegrees() = this * 180 / PI fun Double.radiansToDegrees() = this * 180 / PI
fun Double.equalFloat(other: Double) = abs(this - other) < maxFloatDelta fun Double.equalFloat(other: Double) = abs(this - other) < maxFloatDelta
fun DubinsPose2D.equalsFloat(other: DubinsPose2D) = x.equalFloat(other.x) && y.equalFloat(other.y) && bearing.equalFloat(other.bearing) fun DubinsPose2D.equalsFloat(other: DubinsPose2D) =
x.equalFloat(other.x) && y.equalFloat(other.y) && bearing.radians.equalFloat(other.bearing.radians)
fun StraightTrajectory2D.inverse() = StraightTrajectory2D(end, start) fun StraightTrajectory2D.inverse() = StraightTrajectory2D(end, start)
fun StraightTrajectory2D.shift(shift: Int, width: Double): StraightTrajectory2D = with(Euclidean2DSpace){ fun StraightTrajectory2D.shift(shift: Int, width: Double): StraightTrajectory2D = with(Euclidean2DSpace) {
val dX = width * sin(inverse().bearing) val dX = width * sin(inverse().bearing)
val dY = width * sin(bearing) val dY = width * sin(bearing)

View File

@ -8,8 +8,8 @@ package space.kscience.kmath.trajectory.segments
import space.kscience.kmath.geometry.Circle2D import space.kscience.kmath.geometry.Circle2D
import space.kscience.kmath.geometry.Euclidean2DSpace import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.circumference import space.kscience.kmath.geometry.circumference
import space.kscience.kmath.geometry.degrees
import space.kscience.kmath.trajectory.CircleTrajectory2D import space.kscience.kmath.trajectory.CircleTrajectory2D
import space.kscience.kmath.trajectory.radiansToDegrees
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -20,7 +20,7 @@ class ArcTests {
val circle = Circle2D(vector(0.0, 0.0), 2.0) val circle = Circle2D(vector(0.0, 0.0), 2.0)
val arc = CircleTrajectory2D.of(circle.center, vector(-2.0, 0.0), vector(0.0, 2.0), CircleTrajectory2D.Direction.RIGHT) val arc = CircleTrajectory2D.of(circle.center, vector(-2.0, 0.0), vector(0.0, 2.0), CircleTrajectory2D.Direction.RIGHT)
assertEquals(circle.circumference / 4, arc.length, 1.0) assertEquals(circle.circumference / 4, arc.length, 1.0)
assertEquals(0.0, arc.start.bearing.radiansToDegrees()) assertEquals(0.0, arc.start.bearing.degrees)
assertEquals(90.0, arc.end.bearing.radiansToDegrees()) assertEquals(90.0, arc.end.bearing.degrees)
} }
} }

View File

@ -6,8 +6,8 @@
package space.kscience.kmath.trajectory.segments package space.kscience.kmath.trajectory.segments
import space.kscience.kmath.geometry.Euclidean2DSpace import space.kscience.kmath.geometry.Euclidean2DSpace
import space.kscience.kmath.geometry.degrees
import space.kscience.kmath.trajectory.StraightTrajectory2D import space.kscience.kmath.trajectory.StraightTrajectory2D
import space.kscience.kmath.trajectory.radiansToDegrees
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
import kotlin.test.Test import kotlin.test.Test
@ -19,19 +19,19 @@ class LineTests {
fun lineTest() = with(Euclidean2DSpace){ fun lineTest() = with(Euclidean2DSpace){
val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0)) val straight = StraightTrajectory2D(vector(0.0, 0.0), vector(100.0, 100.0))
assertEquals(sqrt(100.0.pow(2) + 100.0.pow(2)), straight.length) assertEquals(sqrt(100.0.pow(2) + 100.0.pow(2)), straight.length)
assertEquals(45.0, straight.bearing.radiansToDegrees()) assertEquals(45.0, straight.bearing.degrees)
} }
@Test @Test
fun lineAngleTest() = with(Euclidean2DSpace){ fun lineAngleTest() = with(Euclidean2DSpace){
//val zero = Vector2D(0.0, 0.0) //val zero = Vector2D(0.0, 0.0)
val north = StraightTrajectory2D(zero, vector(0.0, 2.0)) val north = StraightTrajectory2D(zero, vector(0.0, 2.0))
assertEquals(0.0, north.bearing.radiansToDegrees()) assertEquals(0.0, north.bearing.degrees)
val east = StraightTrajectory2D(zero, vector(2.0, 0.0)) val east = StraightTrajectory2D(zero, vector(2.0, 0.0))
assertEquals(90.0, east.bearing.radiansToDegrees()) assertEquals(90.0, east.bearing.degrees)
val south = StraightTrajectory2D(zero, vector(0.0, -2.0)) val south = StraightTrajectory2D(zero, vector(0.0, -2.0))
assertEquals(180.0, south.bearing.radiansToDegrees()) assertEquals(180.0, south.bearing.degrees)
val west = StraightTrajectory2D(zero, vector(-2.0, 0.0)) val west = StraightTrajectory2D(zero, vector(-2.0, 0.0))
assertEquals(270.0, west.bearing.radiansToDegrees()) assertEquals(270.0, west.bearing.degrees)
} }
} }