Refactor rotations. Add Rotation matrix to Euler conversion

This commit is contained in:
Alexander Nozik 2023-08-13 14:51:50 +03:00
parent 67994d35d9
commit eff70eb690
11 changed files with 228 additions and 111 deletions

View File

@ -7,7 +7,7 @@ package space.kscience.kmath.geometry
import space.kscience.kmath.linear.Point import space.kscience.kmath.linear.Point
public interface Vector2D<T> : Point<T>, Vector { public interface Vector2D<T> : Point<T> {
public val x: T public val x: T
public val y: T public val y: T
override val size: Int get() = 2 override val size: Int get() = 2

View File

@ -8,7 +8,7 @@ package space.kscience.kmath.geometry
import space.kscience.kmath.linear.Point import space.kscience.kmath.linear.Point
import space.kscience.kmath.structures.Buffer import space.kscience.kmath.structures.Buffer
public interface Vector3D<T> : Point<T>, Vector { public interface Vector3D<T> : Point<T> {
public val x: T public val x: T
public val y: T public val y: T
public val z: T public val z: T

View File

@ -6,8 +6,6 @@
package space.kscience.kmath.geometry.euclidean2d package space.kscience.kmath.geometry.euclidean2d
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.kmath.geometry.Euclidean2DSpace.distanceTo
import kotlin.math.*
import kotlin.math.PI import kotlin.math.PI
/** /**
@ -16,7 +14,7 @@ import kotlin.math.PI
@Serializable @Serializable
public data class Circle2D( public data class Circle2D(
@Serializable(Float64Space2D.VectorSerializer::class) public val center: DoubleVector2D, @Serializable(Float64Space2D.VectorSerializer::class) public val center: DoubleVector2D,
public val radius: Double public val radius: Double,
) )

View File

@ -14,17 +14,15 @@ import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector2D import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.operations.Float32Field import space.kscience.kmath.operations.Float32Field
import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.structures.Float32
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
@Serializable(Float32Space2D.VectorSerializer::class) @Serializable(Float32Space2D.VectorSerializer::class)
public interface Float32Vector2D: Vector2D<Float> public interface Float32Vector2D : Vector2D<Float>
public object Float32Space2D : public object Float32Space2D : GeometrySpace<Float32Vector2D, Float32> {
GeometrySpace<Float32Vector2D>,
ScaleOperations<Float32Vector2D> {
@Serializable @Serializable
@SerialName("Float32Vector2D") @SerialName("Float32Vector2D")
@ -53,13 +51,13 @@ public object Float32Space2D :
override val zero: Float32Vector2D by lazy { vector(0f, 0f) } override val zero: Float32Vector2D by lazy { vector(0f, 0f) }
override fun norm(arg: Float32Vector2D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2)).toDouble() override fun norm(arg: Float32Vector2D): Float32 = sqrt(arg.x.pow(2) + arg.y.pow(2))
public fun Float32Vector2D.norm(): Double = norm(this) public fun Float32Vector2D.norm(): Float32 = norm(this)
override fun Float32Vector2D.unaryMinus(): Float32Vector2D = vector(-x, -y) override fun Float32Vector2D.unaryMinus(): Float32Vector2D = vector(-x, -y)
override fun Float32Vector2D.distanceTo(other: Float32Vector2D): Double = (this - other).norm() override fun Float32Vector2D.distanceTo(other: Float32Vector2D): Float32 = (this - other).norm()
override fun add(left: Float32Vector2D, right: Float32Vector2D): Float32Vector2D = override fun add(left: Float32Vector2D, right: Float32Vector2D): Float32Vector2D =
vector(left.x + right.x, left.y + right.y) vector(left.x + right.x, left.y + right.y)
@ -72,6 +70,8 @@ public object Float32Space2D :
public val xAxis: Float32Vector2D = vector(1.0, 0.0) public val xAxis: Float32Vector2D = vector(1.0, 0.0)
public val yAxis: Float32Vector2D = vector(0.0, 1.0) public val yAxis: Float32Vector2D = vector(0.0, 1.0)
override val defaultPrecision: Float32 = 1e-3f
} }
public fun Float32Vector2D(x: Number, y: Number): Float32Vector2D = Float32Space2D.vector(x, y) public fun Float32Vector2D(x: Number, y: Number): Float32Vector2D = Float32Space2D.vector(x, y)

View File

@ -13,14 +13,12 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector2D import space.kscience.kmath.geometry.Vector2D
import space.kscience.kmath.linear.Float64LinearSpace
import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.operations.Norm
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
public typealias DoubleVector2D = Vector2D<Double> public typealias DoubleVector2D = Vector2D<Double>
public typealias Float64Vector2D = Vector2D<Double> public typealias Float64Vector2D = Vector2D<Double>
@ -30,10 +28,9 @@ public val Vector2D<Double>.r: Double get() = Float64Space2D.norm(this)
/** /**
* 2D Euclidean space * 2D Euclidean space
*/ */
public object Float64Space2D : public object Float64Space2D : GeometrySpace<DoubleVector2D, Double> {
GeometrySpace<DoubleVector2D, Double>,
ScaleOperations<DoubleVector2D>, public val linearSpace: Float64LinearSpace = Float64LinearSpace
Norm<DoubleVector2D, Double> {
@Serializable @Serializable
@SerialName("Float64Vector2D") @SerialName("Float64Vector2D")

View File

@ -14,17 +14,15 @@ import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector3D import space.kscience.kmath.geometry.Vector3D
import space.kscience.kmath.operations.Float32Field import space.kscience.kmath.operations.Float32Field
import space.kscience.kmath.operations.ScaleOperations import space.kscience.kmath.structures.Float32
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
@Serializable(Float32Space3D.VectorSerializer::class) @Serializable(Float32Space3D.VectorSerializer::class)
public interface Float32Vector3D: Vector3D<Float> public interface Float32Vector3D : Vector3D<Float>
public object Float32Space3D : public object Float32Space3D : GeometrySpace<Float32Vector3D, Float32> {
GeometrySpace<Float32Vector3D>,
ScaleOperations<Float32Vector3D>{
@Serializable @Serializable
@SerialName("Float32Vector3D") @SerialName("Float32Vector3D")
@ -54,13 +52,13 @@ public object Float32Space3D :
override val zero: Float32Vector3D by lazy { vector(0.0, 0.0, 0.0) } override val zero: Float32Vector3D by lazy { vector(0.0, 0.0, 0.0) }
override fun norm(arg: Float32Vector3D): Double = sqrt(arg.x.pow(2) + arg.y.pow(2) + arg.z.pow(2)).toDouble() override fun norm(arg: Float32Vector3D): Float32 = sqrt(arg.x.pow(2) + arg.y.pow(2) + arg.z.pow(2))
public fun Float32Vector3D.norm(): Double = norm(this) public fun Float32Vector3D.norm(): Float32 = norm(this)
override fun Float32Vector3D.unaryMinus(): Float32Vector3D = vector(-x, -y, -z) override fun Float32Vector3D.unaryMinus(): Float32Vector3D = vector(-x, -y, -z)
override fun Float32Vector3D.distanceTo(other: Float32Vector3D): Double = (this - other).norm() override fun Float32Vector3D.distanceTo(other: Float32Vector3D): Float32 = (this - other).norm()
override fun add(left: Float32Vector3D, right: Float32Vector3D): Float32Vector3D = override fun add(left: Float32Vector3D, right: Float32Vector3D): Float32Vector3D =
vector(left.x + right.x, left.y + right.y, left.z + right.z) vector(left.x + right.x, left.y + right.y, left.z + right.z)
@ -101,6 +99,8 @@ public object Float32Space3D :
public val xAxis: Float32Vector3D = vector(1.0, 0.0, 0.0) public val xAxis: Float32Vector3D = vector(1.0, 0.0, 0.0)
public val yAxis: Float32Vector3D = vector(0.0, 1.0, 0.0) public val yAxis: Float32Vector3D = vector(0.0, 1.0, 0.0)
public val zAxis: Float32Vector3D = vector(0.0, 0.0, 1.0) public val zAxis: Float32Vector3D = vector(0.0, 0.0, 1.0)
override val defaultPrecision: Float32 = 1e-3f
} }
public fun Float32Vector3D(x: Number, y: Number, z: Number): Float32Vector3D = Float32Space3D.vector(x, y, z) public fun Float32Vector3D(x: Number, y: Number, z: Number): Float32Vector3D = Float32Space3D.vector(x, y, z)

View File

@ -13,9 +13,8 @@ import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.Encoder
import space.kscience.kmath.geometry.GeometrySpace import space.kscience.kmath.geometry.GeometrySpace
import space.kscience.kmath.geometry.Vector3D import space.kscience.kmath.geometry.Vector3D
import space.kscience.kmath.linear.Float64LinearSpace
import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.operations.Float64Field
import space.kscience.kmath.operations.Norm
import space.kscience.kmath.operations.ScaleOperations
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
@ -37,8 +36,9 @@ public typealias Float64Vector3D = Vector3D<Double>
public val DoubleVector3D.r: Double get() = Float64Space3D.norm(this) public val DoubleVector3D.r: Double get() = Float64Space3D.norm(this)
public object Float64Space3D : GeometrySpace<DoubleVector3D, Double>, ScaleOperations<DoubleVector3D>, public object Float64Space3D : GeometrySpace<DoubleVector3D, Double>{
Norm<DoubleVector3D, Double> {
public val linearSpace: Float64LinearSpace = Float64LinearSpace
@Serializable @Serializable
@SerialName("Float64Vector3D") @SerialName("Float64Vector3D")

View File

@ -6,20 +6,36 @@
package space.kscience.kmath.geometry.euclidean3d package space.kscience.kmath.geometry.euclidean3d
import space.kscience.kmath.UnstableKMathAPI import space.kscience.kmath.UnstableKMathAPI
import space.kscience.kmath.complex.Quaternion import space.kscience.kmath.complex.*
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.complex.normalized
import space.kscience.kmath.complex.reciprocal
import space.kscience.kmath.geometry.* import space.kscience.kmath.geometry.*
import space.kscience.kmath.linear.LinearSpace import space.kscience.kmath.linear.LinearSpace
import space.kscience.kmath.linear.Matrix import space.kscience.kmath.linear.Matrix
import space.kscience.kmath.linear.linearSpace import space.kscience.kmath.linear.linearSpace
import space.kscience.kmath.linear.matrix import space.kscience.kmath.linear.matrix
import space.kscience.kmath.operations.Float64Field import space.kscience.kmath.operations.Float64Field
import kotlin.math.pow import kotlin.math.*
import kotlin.math.sqrt
internal fun DoubleVector3D.toQuaternion(): Quaternion = Quaternion(0.0, x, y, z) public operator fun Quaternion.times(other: Quaternion): Quaternion = QuaternionAlgebra.multiply(this, other)
public operator fun Quaternion.div(other: Quaternion): Quaternion = QuaternionAlgebra.divide(this, other)
public fun Quaternion.power(number: Number): Quaternion = QuaternionAlgebra.power(this, number)
/**
* Linear interpolation between [from] and [to] in spherical space
*/
public fun QuaternionAlgebra.slerp(from: Quaternion, to: Quaternion, fraction: Double): Quaternion =
(to / from).pow(fraction) * from
public fun QuaternionAlgebra.angleBetween(q1: Quaternion, q2: Quaternion): Angle = (q1.conjugate * q2).theta
public infix fun Quaternion.dot(other: Quaternion): Double = w * other.w + x * other.x + y * other.y + z * other.z
/**
* Represent a vector as quaternion with zero a rotation angle.
*/
internal fun DoubleVector3D.asQuaternion(): Quaternion = Quaternion(0.0, x, y, z)
/** /**
* Angle in radians denoted by this quaternion rotation * Angle in radians denoted by this quaternion rotation
@ -51,23 +67,30 @@ public val Quaternion.vector: DoubleVector3D
} }
/** /**
* Rotate a vector in a [Float64Space3D] * Rotate a vector in a [Float64Space3D] with [quaternion]
*/ */
public fun Float64Space3D.rotate(vector: DoubleVector3D, q: Quaternion): DoubleVector3D = with(QuaternionAlgebra) { public fun Float64Space3D.rotate(vector: DoubleVector3D, quaternion: Quaternion): DoubleVector3D =
val p = vector.toQuaternion() with(QuaternionAlgebra) {
(q * p * q.reciprocal).vector val p = vector.asQuaternion()
} (quaternion * p * quaternion.reciprocal).vector
}
/** /**
* Use a composition of quaternions to create a rotation * Use a composition of quaternions to create a rotation
*/ */
@UnstableKMathAPI @UnstableKMathAPI
public fun Float64Space3D.rotate(vector: DoubleVector3D, composition: QuaternionAlgebra.() -> Quaternion): DoubleVector3D = public fun Float64Space3D.rotate(
vector: DoubleVector3D,
composition: QuaternionAlgebra.() -> Quaternion,
): DoubleVector3D =
rotate(vector, QuaternionAlgebra.composition()) rotate(vector, QuaternionAlgebra.composition())
/**
* Rotate a [Float64] vector in 3D space with a rotation matrix
*/
public fun Float64Space3D.rotate(vector: DoubleVector3D, matrix: Matrix<Double>): DoubleVector3D { public fun Float64Space3D.rotate(vector: DoubleVector3D, matrix: Matrix<Double>): DoubleVector3D {
require(matrix.colNum == 3 && matrix.rowNum == 3) { "Square 3x3 rotation matrix is required" } require(matrix.colNum == 3 && matrix.rowNum == 3) { "Square 3x3 rotation matrix is required" }
return with(Float64Field.linearSpace) { matrix.dot(vector).asVector3D() } return with(linearSpace) { (matrix dot vector).asVector3D() }
} }
/** /**
@ -86,6 +109,8 @@ public fun Quaternion.toRotationMatrix(
} }
/** /**
* Convert a quaternion to a rotation matrix
*
* taken from https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ * taken from https://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/
*/ */
public fun Quaternion.Companion.fromRotationMatrix(matrix: Matrix<Double>): Quaternion { public fun Quaternion.Companion.fromRotationMatrix(matrix: Matrix<Double>): Quaternion {
@ -146,6 +171,8 @@ public enum class RotationOrder {
} }
/** /**
* Create a quaternion from Euler angles
*
* Based on https://github.com/mrdoob/three.js/blob/master/src/math/Quaternion.js * Based on https://github.com/mrdoob/three.js/blob/master/src/math/Quaternion.js
*/ */
public fun Quaternion.Companion.fromEuler( public fun Quaternion.Companion.fromEuler(
@ -154,13 +181,13 @@ public fun Quaternion.Companion.fromEuler(
c: Angle, c: Angle,
rotationOrder: RotationOrder, rotationOrder: RotationOrder,
): Quaternion { ): Quaternion {
val c1 = cos (a / 2) val c1 = cos(a / 2)
val c2 = cos (b / 2) val c2 = cos(b / 2)
val c3 = cos (c / 2) val c3 = cos(c / 2)
val s1 = sin (a / 2) val s1 = sin(a / 2)
val s2 = sin (b / 2) val s2 = sin(b / 2)
val s3 = sin (c / 2) val s3 = sin(c / 2)
return when (rotationOrder) { return when (rotationOrder) {
@ -206,6 +233,130 @@ public fun Quaternion.Companion.fromEuler(
c1 * s2 * c3 - s1 * c2 * s3, c1 * s2 * c3 - s1 * c2 * s3,
c1 * c2 * s3 + s1 * s2 * c3 c1 * c2 * s3 + s1 * s2 * c3
) )
else -> TODO("Proper Euler rotation orders are not supported yet")
else -> TODO("Proper Euler rotation orders are not supported yet")
} }
} }
/**
* A vector consisting of angles
*/
public data class AngleVector(override val x: Angle, override val y: Angle, override val z: Angle) : Vector3D<Angle> {
public companion object
}
public fun Quaternion.Companion.fromEuler(
angles: AngleVector,
rotationOrder: RotationOrder,
): Quaternion = fromEuler(angles.x, angles.y, angles.z, rotationOrder)
/**
* Based on https://github.com/mrdoob/three.js/blob/master/src/math/Euler.js
*/
public fun AngleVector.Companion.fromRotationMatrix(
matrix: Matrix<Double>,
rotationOrder: RotationOrder,
gimbaldLockThreshold: Double = 0.9999999,
): AngleVector = when (rotationOrder) {
RotationOrder.XYZ -> {
if (abs(matrix[0, 2]) < gimbaldLockThreshold) {
AngleVector(
atan2(-matrix[1, 2], matrix[2, 2]).radians,
asin(matrix[0, 2].coerceIn(-1.0, 1.0)).radians,
atan2(-matrix[0, 1], matrix[0, 0]).radians
)
} else {
AngleVector(
atan2(matrix[2, 1], matrix[1, 1]).radians,
asin(matrix[0, 2].coerceIn(-1.0, 1.0)).radians,
Angle.zero
)
}
}
RotationOrder.YXZ -> {
if (abs(matrix[1, 2]) < gimbaldLockThreshold) {
AngleVector(
x = asin(-matrix[1, 2].coerceIn(-1.0, 1.0)).radians,
y = atan2(matrix[0, 2], matrix[2, 2]).radians,
z = atan2(matrix[1, 0], matrix[1, 1]).radians,
)
} else {
AngleVector(
x = asin(-matrix[1, 2].coerceIn(-1.0, 1.0)).radians,
y = atan2(-matrix[2, 0], matrix[0, 0]).radians,
z = Angle.zero,
)
}
}
RotationOrder.ZXY -> {
if (abs(matrix[2, 1]) < gimbaldLockThreshold) {
AngleVector(
x = asin(matrix[2, 1].coerceIn(-1.0, 1.0)).radians,
y = atan2(-matrix[2, 0], matrix[2, 2]).radians,
z = atan2(-matrix[0, 1], matrix[1, 1]).radians,
)
} else {
AngleVector(
x = asin(matrix[2, 1].coerceIn(-1.0, 1.0)).radians,
y = Angle.zero,
z = atan2(matrix[1, 0], matrix[0, 0]).radians,
)
}
}
RotationOrder.ZYX -> {
if (abs(matrix[2, 0]) < gimbaldLockThreshold) {
AngleVector(
x = atan2(matrix[2, 1], matrix[2, 2]).radians,
y = asin(-matrix[2, 0].coerceIn(-1.0, 1.0)).radians,
z = atan2(matrix[1, 0], matrix[0, 0]).radians,
)
} else {
AngleVector(
x = Angle.zero,
y = asin(-matrix[2, 0].coerceIn(-1.0, 1.0)).radians,
z = atan2(-matrix[0, 1], matrix[1, 1]).radians,
)
}
}
RotationOrder.YZX -> {
if (abs(matrix[1, 0]) < gimbaldLockThreshold) {
AngleVector(
x = atan2(-matrix[1, 2], matrix[1, 1]).radians,
y = atan2(-matrix[2, 0], matrix[0, 0]).radians,
z = asin(matrix[1, 0].coerceIn(-1.0, 1.0)).radians,
)
} else {
AngleVector(
x = Angle.zero,
y = atan2(matrix[0, 2], matrix[2, 2]).radians,
z = asin(matrix[1, 0].coerceIn(-1.0, 1.0)).radians,
)
}
}
RotationOrder.XZY -> {
if (abs(matrix[0, 1]) < gimbaldLockThreshold) {
AngleVector(
x = atan2(matrix[2, 1], matrix[1, 1]).radians,
y = atan2(matrix[0, 2], matrix[0, 0]).radians,
z = asin(-matrix[0, 1].coerceIn(-1.0, 1.0)).radians,
)
} else {
AngleVector(
x = atan2(-matrix[1, 2], matrix[2, 2]).radians,
y = Angle.zero,
z = asin(-matrix[0, 1].coerceIn(-1.0, 1.0)).radians,
)
}
}
else -> TODO("Proper Euler rotation orders are not supported yet")
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2018-2023 KMath contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package space.kscience.kmath.geometry
import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.complex.conjugate
import space.kscience.kmath.complex.normalized
import space.kscience.kmath.geometry.euclidean3d.Float32Space3D
import space.kscience.kmath.geometry.euclidean3d.Float32Vector3D
import space.kscience.kmath.geometry.euclidean3d.theta
import kotlin.math.asin
import kotlin.math.atan2
import kotlin.math.pow
public operator fun Quaternion.times(other: Quaternion): Quaternion = QuaternionAlgebra.multiply(this, other)
public operator fun Quaternion.div(other: Quaternion): Quaternion = QuaternionAlgebra.divide(this, other)
public fun Quaternion.power(number: Number): Quaternion = QuaternionAlgebra.power(this, number)
/**
* Linear interpolation between [from] and [to] in spherical space
*/
public fun QuaternionAlgebra.slerp(from: Quaternion, to: Quaternion, fraction: Double): Quaternion =
(to / from).pow(fraction) * from
public fun QuaternionAlgebra.angleBetween(q1: Quaternion, q2: Quaternion): Angle = (q1.conjugate * q2).theta
public val Quaternion.inclination: Radians get() = asin(2 * (w * y - x * z)).radians
public val Quaternion.azimuth: Angle get() = atan2(2 * (w * z + x * y), 1 - 2 * (y.pow(2) + z.pow(2))).radians.normalized()
public infix fun Quaternion.dot(other: Quaternion): Double = w * other.w + x * other.x + y * other.y + z * other.z
private fun Quaternion.normalizedToEuler(): Float32Vector3D {
val roll = atan2(2 * y * w + 2 * x * z, 1 - 2 * y * y - 2 * z * z);
val pitch = atan2(2 * x * w - 2 * y * z, 1 - 2 * x * x - 2 * z * z);
val yaw = asin(2 * x * y + 2 * z * w);
return Float32Vector3D(roll, pitch, yaw)
}
/**
* Quaternion to XYZ Cardan angles
*/
public fun Quaternion.toEuler(): Float32Vector3D = if (QuaternionAlgebra.norm(this) == 0.0) {
Float32Space3D.zero
} else {
normalized().normalizedToEuler()
}

View File

@ -5,6 +5,11 @@
package space.kscience.kmath.geometry package space.kscience.kmath.geometry
import space.kscience.kmath.geometry.euclidean2d.Float64Space2D
import space.kscience.kmath.geometry.euclidean2d.Float64Vector2D
import space.kscience.kmath.geometry.euclidean3d.Float64Space3D
import space.kscience.kmath.geometry.euclidean3d.Float64Vector3D
/** /**
* Vector equality within given [precision] (using [GeometrySpace.norm] provided by the space * Vector equality within given [precision] (using [GeometrySpace.norm] provided by the space
@ -22,7 +27,7 @@ public fun <V : Any, D : Comparable<D>> V.equalsVector(
*/ */
public fun Float64Vector2D.equalsVector( public fun Float64Vector2D.equalsVector(
other: Float64Vector2D, other: Float64Vector2D,
precision: Double = Euclidean3DSpace.defaultPrecision, precision: Double = Float64Space2D.defaultPrecision,
): Boolean = equalsVector(Float64Space2D, other, precision) ): Boolean = equalsVector(Float64Space2D, other, precision)
/** /**
@ -30,7 +35,7 @@ public fun Float64Vector2D.equalsVector(
*/ */
public fun Float64Vector3D.equalsVector( public fun Float64Vector3D.equalsVector(
other: Float64Vector3D, other: Float64Vector3D,
precision: Double = Euclidean3DSpace.defaultPrecision, precision: Double = Float64Space3D.defaultPrecision,
): Boolean = equalsVector(Float64Space3D, other, precision) ): Boolean = equalsVector(Float64Space3D, other, precision)
/** /**

View File

@ -6,6 +6,7 @@
package space.kscience.kmath.geometry package space.kscience.kmath.geometry
import space.kscience.kmath.complex.Quaternion import space.kscience.kmath.complex.Quaternion
import space.kscience.kmath.complex.QuaternionAlgebra
import space.kscience.kmath.complex.normalized import space.kscience.kmath.complex.normalized
import space.kscience.kmath.geometry.euclidean3d.* import space.kscience.kmath.geometry.euclidean3d.*
import space.kscience.kmath.structures.Float64Buffer import space.kscience.kmath.structures.Float64Buffer
@ -26,12 +27,31 @@ class RotationTest {
} }
@Test @Test
fun matrixConversion() { fun matrixConversion() = with(QuaternionAlgebra){
val q = Quaternion(1.0, 2.0, -3.0, 4.0).normalized() val q = Quaternion(1.0, 2.0, -3.0, 4.0).normalized()
val matrix = q.toRotationMatrix() val matrix = q.toRotationMatrix()
for (ro in listOf(
RotationOrder.XYZ,
RotationOrder.YXZ,
RotationOrder.ZXY,
RotationOrder.ZYX,
RotationOrder.YZX,
RotationOrder.XZY
)) {
val angles = AngleVector.fromRotationMatrix(matrix, ro)
val reconstructed = Quaternion.fromEuler(angles, ro)
if( reconstructed.w>0) {
assertBufferEquals(q, reconstructed)
} else{
assertBufferEquals(q, -reconstructed)
}
}
assertBufferEquals(q, Quaternion.fromRotationMatrix(matrix)) assertBufferEquals(q, Quaternion.fromRotationMatrix(matrix))
} }
@ -50,4 +70,5 @@ class RotationTest {
val q1 = Quaternion.fromEuler(0.1.radians, 0.2.radians, 0.3.radians, RotationOrder.XYZ) val q1 = Quaternion.fromEuler(0.1.radians, 0.2.radians, 0.3.radians, RotationOrder.XYZ)
assertBufferEquals(Float64Buffer(0.9818562, 0.0640713, 0.0911575, 0.1534393), q1) assertBufferEquals(Float64Buffer(0.9818562, 0.0640713, 0.0911575, 0.1534393), q1)
} }
} }