From 1863fa973640cb6e2972f6786dbafdd680481a46 Mon Sep 17 00:00:00 2001
From: Sylvain
@@ -1556,11 +1555,9 @@ public static void randomizeAxisAngle(Random random, AxisAngleBasics axisAngleTo
*/
public static void randomizeAxisAngle(Random random, double minMaxAngle, AxisAngleBasics axisAngleToRandomize)
{
- // Generate uniformly random point on unit sphere (based on http://mathworld.wolfram.com/SpherePointPicking.html )
- double height = 2.0 * random.nextDouble() - 1.0;
+ // Generate uniformly random point on the unit-sphere (based on http://mathworld.wolfram.com/SpherePointPicking.html )
double angle = nextDouble(random, minMaxAngle);
- double radius = EuclidCoreTools.squareRoot(1.0 - height * height);
- axisAngleToRandomize.set(radius * EuclidCoreTools.cos(angle), radius * EuclidCoreTools.sin(angle), height, angle);
+ axisAngleToRandomize.set(EuclidCoreRandomTools.nextVector3D(random), angle);
}
/**
diff --git a/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java b/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java
index 02c9f7121..a80d83348 100644
--- a/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java
+++ b/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java
@@ -1,20 +1,25 @@
package us.ihmc.euclid.tools;
-import java.util.Collections;
-import java.util.List;
-
import org.ejml.MatrixDimensionException;
import org.ejml.data.Matrix;
-
+import us.ihmc.euclid.axisAngle.interfaces.AxisAngleReadOnly;
import us.ihmc.euclid.interfaces.EuclidGeometry;
import us.ihmc.euclid.matrix.interfaces.Matrix3DReadOnly;
+import us.ihmc.euclid.matrix.interfaces.RotationMatrixReadOnly;
+import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly;
import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly;
import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly;
import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly;
import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly;
import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics;
import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly;
import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
+import us.ihmc.euclid.yawPitchRoll.interfaces.YawPitchRollReadOnly;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
/**
* This class provides a variety of generic tools such as fast square-root algorithm
@@ -467,7 +472,7 @@ public static double fastSquareRoot(double squaredValueClosedToOne)
* @param a the first element.
* @param b the second element.
* @return {@code true} if at least one element is equal to {@link Double#NaN}, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean containsNaN(double a, double b)
{
@@ -481,7 +486,7 @@ public static boolean containsNaN(double a, double b)
* @param b the second element.
* @param c the third element.
* @return {@code true} if at least one element is equal to {@link Double#NaN}, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean containsNaN(double a, double b, double c)
{
@@ -496,7 +501,7 @@ public static boolean containsNaN(double a, double b, double c)
* @param c the third element.
* @param d the fourth element.
* @return {@code true} if at least one element is equal to {@link Double#NaN}, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean containsNaN(double a, double b, double c, double d)
{
@@ -516,7 +521,7 @@ public static boolean containsNaN(double a, double b, double c, double d)
* @param a7 the eighth element.
* @param a8 the ninth element.
* @return {@code true} if at least one element is equal to {@link Double#NaN}, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean containsNaN(double a0, double a1, double a2, double a3, double a4, double a5, double a6, double a7, double a8)
{
@@ -524,9 +529,7 @@ public static boolean containsNaN(double a0, double a1, double a2, double a3, do
return true;
if (Double.isNaN(a3) || Double.isNaN(a4) || Double.isNaN(a5))
return true;
- if (Double.isNaN(a6) || Double.isNaN(a7) || Double.isNaN(a8))
- return true;
- return false;
+ return Double.isNaN(a6) || Double.isNaN(a7) || Double.isNaN(a8);
}
/**
@@ -534,7 +537,7 @@ public static boolean containsNaN(double a0, double a1, double a2, double a3, do
*
* @param array the array containing the elements to test for {@link Double#NaN}. Not modified.
* @return {@code true} if at least one element is equal to {@link Double#NaN}, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean containsNaN(double[] array)
{
@@ -806,7 +809,7 @@ public static final double max(double a, double b, double c)
* @param d the fourth argument to compare.
* @return the maximum value of the four arguments.
*/
- public static final double max(double a, double b, double c, double d)
+ public static double max(double a, double b, double c, double d)
{
if (a > b)
{
@@ -849,7 +852,7 @@ public static final double min(double a, double b, double c)
* @param d the fourth argument to compare.
* @return the minimum value of the four arguments.
*/
- public static final double min(double a, double b, double c, double d)
+ public static double min(double a, double b, double c, double d)
{
if (a < b)
{
@@ -942,7 +945,7 @@ public static boolean epsilonEquals(double expectedValue, double actualValue, do
*/
public static boolean equals(EuclidGeometry a, EuclidGeometry b)
{
- return (a == b) || (a != null && a.equals(b));
+ return Objects.equals(a, b);
}
/**
@@ -1051,7 +1054,7 @@ public static boolean areAllZero(double x, double y, double z, double s, double
* @param actualAngle the second angle in the comparison.
* @param epsilon the tolerance to use for the test.
* @return {@code true} if the two angles are considered to be geometrically equal, {@code false}
- * otherwise.
+ * otherwise.
*/
public static boolean angleGeometricallyEquals(double expectedAngle, double actualAngle, double epsilon)
{
@@ -1081,11 +1084,10 @@ public static boolean isAngleZero(double angle, double epsilon)
*
* @param value value
* @param minMax inclusive absolute boundary
- * @return
- *
@@ -429,7 +429,6 @@ public static void convertYawPitchRollToMatrix(double yaw, double pitch, double
((RotationMatrixBasics) matrixToPack).setUnsafe(m00, m01, m02, m10, m11, m12, m20, m21, m22);
else
matrixToPack.set(m00, m01, m02, m10, m11, m12, m20, m21, m22);
-
}
/**
diff --git a/src/main/java/us/ihmc/euclid/rotationConversion/RotationVectorConversion.java b/src/main/java/us/ihmc/euclid/rotationConversion/RotationVectorConversion.java
index 2340160d1..c09463480 100644
--- a/src/main/java/us/ihmc/euclid/rotationConversion/RotationVectorConversion.java
+++ b/src/main/java/us/ihmc/euclid/rotationConversion/RotationVectorConversion.java
@@ -179,7 +179,7 @@ public static void convertMatrixToRotationVector(RotationMatrixReadOnly rotation
double m21 = rotationMatrix.getM21();
double m22 = rotationMatrix.getM22();
- convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, rotationVectorToPack);
+ convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, rotationVectorToPack);
}
/**
@@ -220,16 +220,16 @@ public static void convertMatrixToRotationVector(RotationMatrixReadOnly rotation
* conversion.
* @param rotationVectorToPack the vector in which the result is stored. Modified.
*/
- static void convertMatrixToRotationVectorImpl(double m00,
- double m01,
- double m02,
- double m10,
- double m11,
- double m12,
- double m20,
- double m21,
- double m22,
- Vector3DBasics rotationVectorToPack)
+ public static void convertMatrixToRotationVector(double m00,
+ double m01,
+ double m02,
+ double m10,
+ double m11,
+ double m12,
+ double m20,
+ double m21,
+ double m22,
+ Vector3DBasics rotationVectorToPack)
{
if (EuclidCoreTools.containsNaN(m00, m01, m02, m10, m11, m12, m20, m21, m22))
{
diff --git a/src/main/java/us/ihmc/euclid/tools/AxisAngleTools.java b/src/main/java/us/ihmc/euclid/tools/AxisAngleTools.java
index 952be8a43..48cc8a630 100644
--- a/src/main/java/us/ihmc/euclid/tools/AxisAngleTools.java
+++ b/src/main/java/us/ihmc/euclid/tools/AxisAngleTools.java
@@ -13,6 +13,7 @@
import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly;
import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics;
import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly;
+import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics;
import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics;
import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly;
import us.ihmc.euclid.tuple4D.interfaces.Vector4DBasics;
@@ -156,7 +157,7 @@ public static void addTransform(AxisAngleReadOnly axisAngle, Tuple3DReadOnly tup
* @param checkIfTransformInXYPlane whether this method should assert that the axis-angle represents
* a transformation in the XY plane.
* @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the axis-angle
- * does not represent a transformation in the XY plane.
+ * does not represent a transformation in the XY plane.
*/
public static void transform(AxisAngleReadOnly axisAngle, Tuple2DReadOnly tupleOriginal, Tuple2DBasics tupleTransformed, boolean checkIfTransformInXYPlane)
{
@@ -181,7 +182,7 @@ public static void transform(AxisAngleReadOnly axisAngle, Tuple2DReadOnly tupleO
* @param checkIfTransformInXYPlane whether this method should assert that the axis-angle represents
* a transformation in the XY plane.
* @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the axis-angle
- * does not represent a transformation in the XY plane.
+ * does not represent a transformation in the XY plane.
*/
public static void inverseTransform(AxisAngleReadOnly axisAngle,
Tuple2DReadOnly tupleOriginal,
@@ -548,9 +549,8 @@ public static void multiply(Orientation3DReadOnly orientation1,
}
double beta, u2x, u2y, u2z;
- if (orientation2 instanceof AxisAngleReadOnly)
+ if (orientation2 instanceof AxisAngleReadOnly aa2)
{ // In this case orientation2 might be the same object as axisAngleToPack, so let's save its components first.
- AxisAngleReadOnly aa2 = (AxisAngleReadOnly) orientation2;
beta = aa2.getAngle();
u2x = aa2.getX();
u2y = aa2.getY();
@@ -1118,7 +1118,7 @@ public static void appendRollRotation(AxisAngleReadOnly axisAngleOriginal, doubl
* @param orientation3D the orientation3D to be used for comparison. Not modified
* @param limitToPi limits the result to [0 , pi].
* @return angular distance between the two orientations in range: [0, 2pi] when limitToPi =
- * false.
+ * false.
*/
public static double distance(AxisAngleReadOnly axisAngle, Orientation3DReadOnly orientation3D, boolean limitToPi)
{
@@ -1151,7 +1151,7 @@ public static double distance(AxisAngleReadOnly axisAngle, Orientation3DReadOnly
* @param quaternion the quaternion to be used for comparison. Not modified
* @param limitToPi limits the result to [0 , pi] if set true.
* @return angular distance between the two orientations in range: [0, 2pi] when limitToPi =
- * false.
+ * false.
*/
public static double distance(AxisAngleReadOnly axisAngle, QuaternionReadOnly quaternion, boolean limitToPi)
{
@@ -1242,7 +1242,7 @@ public static double distance(AxisAngleReadOnly axisAngle, RotationMatrixReadOnl
* @param yawPitchRoll the yawPitchRoll to be used for comparison. Not modified
* @param limitToPi Limits the result to [0, pi].
* @return angular distance between the two orientations in range: [0, 2pi] when limitToPi =
- * false.
+ * false.
*/
public static double distance(AxisAngleReadOnly axisAngle, YawPitchRollReadOnly yawPitchRoll, boolean limitToPi)
{
@@ -1305,7 +1305,7 @@ public static double distance(AxisAngleReadOnly axisAngle, YawPitchRollReadOnly
* @param aa2 the second axis-angle to measure the distance. Not modified.
* @param limitToPi Limits the result to [0, pi].
* @return the angle representing the distance between the two axis-angles. It is contained in [0,
- * 2pi] when limitToPi = false.
+ * 2pi] when limitToPi = false.
*/
public static double distance(AxisAngleReadOnly aa1, AxisAngleReadOnly aa2, boolean limitToPi)
{
@@ -1352,4 +1352,35 @@ static double distance(AxisAngleReadOnly aa1, double u2x, double u2y, double u2z
}
return Math.abs(gamma);
}
+
+ /**
+ * Computes the angular velocity from the finite difference of two orientations.
+ *
+ * @param previousOrientation the orientation at the previous time step. Not modified.
+ * @param currentOrientation the orientation at the current time step. Not modified.
+ * @param dt the time step.
+ * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified.
+ * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics)
+ */
+ public static void finiteDifference(AxisAngleReadOnly previousOrientation,
+ AxisAngleReadOnly currentOrientation,
+ double dt,
+ Vector3DBasics angularVelocityToPack)
+ {
+ double halfAnglePrev = 0.5 * previousOrientation.getAngle();
+ double sinHalfAnglePrev = Math.sin(halfAnglePrev);
+ double xPrev = previousOrientation.getX() * sinHalfAnglePrev;
+ double yPrev = previousOrientation.getY() * sinHalfAnglePrev;
+ double zPrev = previousOrientation.getZ() * sinHalfAnglePrev;
+ double sPrev = Math.cos(halfAnglePrev);
+
+ double halfAngleCurr = 0.5 * currentOrientation.getAngle();
+ double sinHalfAngleCurr = Math.sin(halfAngleCurr);
+ double xCurr = currentOrientation.getX() * sinHalfAngleCurr;
+ double yCurr = currentOrientation.getY() * sinHalfAngleCurr;
+ double zCurr = currentOrientation.getZ() * sinHalfAngleCurr;
+ double sCurr = Math.cos(halfAngleCurr);
+
+ QuaternionTools.finiteDifference(xPrev, yPrev, zPrev, sPrev, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack);
+ }
}
diff --git a/src/main/java/us/ihmc/euclid/tools/EuclidCoreRandomTools.java b/src/main/java/us/ihmc/euclid/tools/EuclidCoreRandomTools.java
index 86f5073ea..0b828addd 100644
--- a/src/main/java/us/ihmc/euclid/tools/EuclidCoreRandomTools.java
+++ b/src/main/java/us/ihmc/euclid/tools/EuclidCoreRandomTools.java
@@ -1,9 +1,6 @@
package us.ihmc.euclid.tools;
-import java.util.Random;
-
import org.ejml.data.DMatrixRMaj;
-
import us.ihmc.euclid.Axis2D;
import us.ihmc.euclid.Axis3D;
import us.ihmc.euclid.axisAngle.AxisAngle;
@@ -40,6 +37,8 @@
import us.ihmc.euclid.tuple4D.Vector4D32;
import us.ihmc.euclid.yawPitchRoll.YawPitchRoll;
+import java.util.Random;
+
/**
* This class provides random generators to generate random geometry objects.
*
+ * The resulting angular velocity is expressed in the local coordinates of the orientation. Note that the angular velocity can be considered to be expressed + * the local frame described by either the previous or current orientation, i.e. ω = Rcurrentprevious ω. + *
+ *+ * The method identifies the type of orientation used to perform the finite difference and calls the appropriate method. Note though that to get best + * performance, it is recommended to compute the finite difference using two quaternions. + *
+ * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + */ + public static void finiteDifference(Orientation3DReadOnly previousOrientation, + Orientation3DReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + if (previousOrientation instanceof QuaternionReadOnly qPrev) + { + if (currentOrientation instanceof QuaternionReadOnly qCurr) + QuaternionTools.finiteDifference(qPrev, qCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof RotationMatrixReadOnly rCurr) + RotationMatrixTools.finiteDifference(qPrev, rCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof AxisAngleReadOnly aaCurr) + QuaternionTools.finiteDifference(qPrev, aaCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof YawPitchRollReadOnly yprCurr) + QuaternionTools.finiteDifference(qPrev, yprCurr, dt, angularVelocityToPack); + else + throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); + } + else if (previousOrientation instanceof RotationMatrixReadOnly rPrev) + { + if (currentOrientation instanceof RotationMatrixReadOnly rCurr) + RotationMatrixTools.finiteDifference(rPrev, rCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof QuaternionReadOnly qCurr) + RotationMatrixTools.finiteDifference(rPrev, qCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof AxisAngleReadOnly aaCurr) + RotationMatrixTools.finiteDifference(rPrev, aaCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof YawPitchRollReadOnly yprCurr) + RotationMatrixTools.finiteDifference(rPrev, yprCurr, dt, angularVelocityToPack); + else + throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); + } + else if (previousOrientation instanceof AxisAngleReadOnly aaPrev) + { + if (currentOrientation instanceof AxisAngleReadOnly aaCurr) + AxisAngleTools.finiteDifference(aaPrev, aaCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof QuaternionReadOnly qCurr) + QuaternionTools.finiteDifference(aaPrev, qCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof RotationMatrixReadOnly rCurr) + RotationMatrixTools.finiteDifference(aaPrev, rCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof YawPitchRollReadOnly yprCurr) + YawPitchRollTools.finiteDifference(aaPrev, yprCurr, dt, angularVelocityToPack); + else + throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); + } + else if (previousOrientation instanceof YawPitchRollReadOnly yprPrev) + { + if (currentOrientation instanceof YawPitchRollReadOnly yprCurr) + YawPitchRollTools.finiteDifference(yprPrev, yprCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof RotationMatrixReadOnly rCurr) + RotationMatrixTools.finiteDifference(yprPrev, rCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof QuaternionReadOnly qCurr) + QuaternionTools.finiteDifference(yprPrev, qCurr, dt, angularVelocityToPack); + else if (currentOrientation instanceof AxisAngleReadOnly aaCurr) + YawPitchRollTools.finiteDifference(yprPrev, aaCurr, dt, angularVelocityToPack); + else + throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); + } + else + throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); + } + + private static UnsupportedOperationException newUnsupportedFiniteDifferenceException(Orientation3DReadOnly previousOrientation, + Orientation3DReadOnly currentOrientation) + { + return new UnsupportedOperationException("Unsupported orientation type: [currentOrientation = %s, previousOrientation = %s].".formatted(currentOrientation.getClass() + .getSimpleName(), + previousOrientation.getClass() + .getSimpleName())); + } } diff --git a/src/main/java/us/ihmc/euclid/tools/QuaternionTools.java b/src/main/java/us/ihmc/euclid/tools/QuaternionTools.java index 7a654c175..cf0887dd0 100644 --- a/src/main/java/us/ihmc/euclid/tools/QuaternionTools.java +++ b/src/main/java/us/ihmc/euclid/tools/QuaternionTools.java @@ -12,6 +12,7 @@ import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; import us.ihmc.euclid.tuple4D.interfaces.Tuple4DReadOnly; @@ -207,9 +208,8 @@ public static void multiply(Orientation3DReadOnly orientation1, } double q2s, q2x, q2y, q2z; - if (orientation2 instanceof QuaternionReadOnly) + if (orientation2 instanceof QuaternionReadOnly q2) { // In this case orientation2 might be the same object as quaternionToPack, so let's save its components first. - QuaternionReadOnly q2 = (QuaternionReadOnly) orientation2; q2x = q2.getX(); q2y = q2.getY(); q2z = q2.getZ(); @@ -790,7 +790,7 @@ private static void addTransform(boolean subtract, * @param checkIfTransformInXYPlane whether this method should assert that the quaternion represents * a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the quaternion - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void transform(QuaternionReadOnly quaternion, Tuple2DReadOnly tupleOriginal, Tuple2DBasics tupleTransformed, boolean checkIfTransformInXYPlane) { @@ -818,7 +818,7 @@ public static void transform(QuaternionReadOnly quaternion, Tuple2DReadOnly tupl * @param checkIfTransformInXYPlane whether this method should assert that the quaternion represents * a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the quaternion - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void inverseTransform(QuaternionReadOnly quaternion, Tuple2DReadOnly tupleOriginal, @@ -848,7 +848,7 @@ public static void inverseTransform(QuaternionReadOnly quaternion, * @param checkIfTransformInXYPlane whether this method should assert that the quaternion represents * a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the quaternion - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ private static void transformImpl(QuaternionReadOnly quaternion, boolean conjugateQuaternion, @@ -1541,7 +1541,7 @@ public static double distance(QuaternionReadOnly quaternion, Orientation3DReadOn * @param q2 the quaternion to be used in the comparison. Not modified. * @param limitToPi Limits the result to [0,pi]. * @return the angle representing the distance between the two quaternions. It is contained in [0, - * 2pi] + * 2pi] */ public static double distance(QuaternionReadOnly q1, QuaternionReadOnly q2, boolean limitToPi) { @@ -1567,7 +1567,7 @@ static double distance(double q1x, double q1y, double q1z, double q1s, double q2 * @param quaternion the quaternion to be used in the comparison. Not modified. * @param rotationMatrix the rotationMatrix to be used in the comparison. Not modified. * @return the angle representing the distance between the two quaternions. It is contained in [0, - * pi] + * pi] */ public static double distance(QuaternionReadOnly quaternion, RotationMatrixReadOnly rotationMatrix) { @@ -1619,7 +1619,7 @@ public static double distance(QuaternionReadOnly quaternion, RotationMatrixReadO * @param yawPitchRoll the yawPitchRollto be used in the comparison. Not modified. * @param limitToPi Limits the result to [0,pi]. * @return the angle representing the distance between the two quaternions. It is contained in [0, - * 2pi] + * 2pi] */ public static double distance(QuaternionReadOnly quaternion, YawPitchRollReadOnly yawPitchRoll, boolean limitToPi) { @@ -1663,7 +1663,7 @@ public static double distance(QuaternionReadOnly quaternion, YawPitchRollReadOnl * @param axisAngle the axisAngle to be used in the comparison. Not modified. * @param limitToPi Limits the result to [0,pi]. * @return the angle representing the distance between the two quaternions. It is contained in [0, - * pi] + * pi] */ public static double distance(QuaternionReadOnly quaternion, AxisAngleReadOnly axisAngle, boolean limitToPi) { @@ -1676,7 +1676,7 @@ public static double distance(QuaternionReadOnly quaternion, AxisAngleReadOnly a * @param quaternion the quaternion to be used in the comparison. Not modified. * @param limitToPi limits the resulting distance to [0,pi] * @return the angle representing the distance between the two quaternions. It is contained in [0, - * 2pi] unless limitToPi is set to True. + * 2pi] unless limitToPi is set to True. */ public static double angle(QuaternionReadOnly quaternion, boolean limitToPi) { @@ -1688,7 +1688,7 @@ public static double angle(QuaternionReadOnly quaternion, boolean limitToPi) * * @param quaternion the quaternion to be used in the comparison. Not modified. * @return the angle representing the distance between the two quaternions. It is contained in [0, - * 2pi]. + * 2pi]. */ public static double angle(QuaternionReadOnly quaternion) { @@ -1795,4 +1795,230 @@ private static void interpolate(double q0x, double qs = alpha0 * q0s + alphaf * qfs; interpolationToPack.set(qx, qy, qz, qs); } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(QuaternionReadOnly previousOrientation, + QuaternionReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + finiteDifference(previousOrientation.getX(), + previousOrientation.getY(), + previousOrientation.getZ(), + previousOrientation.getS(), + currentOrientation.getX(), + currentOrientation.getY(), + currentOrientation.getZ(), + currentOrientation.getS(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(AxisAngleReadOnly previousOrientation, + QuaternionReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAngle = 0.5 * previousOrientation.getAngle(); + double sinHalfAngle = Math.sin(halfAngle); + double xPrev = previousOrientation.getX() * sinHalfAngle; + double yPrev = previousOrientation.getY() * sinHalfAngle; + double zPrev = previousOrientation.getZ() * sinHalfAngle; + double sPrev = Math.cos(halfAngle); + finiteDifference(xPrev, + yPrev, + zPrev, + sPrev, + currentOrientation.getX(), + currentOrientation.getY(), + currentOrientation.getZ(), + currentOrientation.getS(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(QuaternionReadOnly previousOrientation, + AxisAngleReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAngleCurr = 0.5 * currentOrientation.getAngle(); + double sinHalfAngleCurr = Math.sin(halfAngleCurr); + double xCurr = currentOrientation.getX() * sinHalfAngleCurr; + double yCurr = currentOrientation.getY() * sinHalfAngleCurr; + double zCurr = currentOrientation.getZ() * sinHalfAngleCurr; + double sCurr = Math.cos(halfAngleCurr); + finiteDifference(previousOrientation.getX(), + previousOrientation.getY(), + previousOrientation.getZ(), + previousOrientation.getS(), + xCurr, + yCurr, + zCurr, + sCurr, + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(YawPitchRollReadOnly previousOrientation, + QuaternionReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double xCurr = currentOrientation.getX(); + double yCurr = currentOrientation.getY(); + double zCurr = currentOrientation.getZ(); + double sCurr = currentOrientation.getS(); + + finiteDifferenceImpl(previousOrientation, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + static void finiteDifferenceImpl(YawPitchRollReadOnly previousOrientation, + double xCurr, + double yCurr, + double zCurr, + double sCurr, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfYawPrev = 0.5 * previousOrientation.getYaw(); + double cYawPrev = EuclidCoreTools.cos(halfYawPrev); + double sYawPrev = EuclidCoreTools.sin(halfYawPrev); + + double halfPitchPrev = 0.5 * previousOrientation.getPitch(); + double cPitchPrev = EuclidCoreTools.cos(halfPitchPrev); + double sPitchPrev = EuclidCoreTools.sin(halfPitchPrev); + + double halfRollPrev = 0.5 * previousOrientation.getRoll(); + double cRollPrev = EuclidCoreTools.cos(halfRollPrev); + double sRollPrev = EuclidCoreTools.sin(halfRollPrev); + + double xPrev = cYawPrev * cPitchPrev * sRollPrev - sYawPrev * sPitchPrev * cRollPrev; + double yPrev = sYawPrev * cPitchPrev * sRollPrev + cYawPrev * sPitchPrev * cRollPrev; + double zPrev = sYawPrev * cPitchPrev * cRollPrev - cYawPrev * sPitchPrev * sRollPrev; + double sPrev = cYawPrev * cPitchPrev * cRollPrev + sYawPrev * sPitchPrev * sRollPrev; + + finiteDifference(xPrev, yPrev, zPrev, sPrev, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(QuaternionReadOnly previousOrientation, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + finiteDifferenceImpl(previousOrientation.getX(), + previousOrientation.getY(), + previousOrientation.getZ(), + previousOrientation.getS(), + currentOrientation, + dt, + angularVelocityToPack); + } + + static void finiteDifferenceImpl(double xPrev, + double yPrev, + double zPrev, + double sPrev, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfYawCurr = 0.5 * currentOrientation.getYaw(); + double cYawCurr = EuclidCoreTools.cos(halfYawCurr); + double sYawCurr = EuclidCoreTools.sin(halfYawCurr); + + double halfPitchCurr = 0.5 * currentOrientation.getPitch(); + double cPitchCurr = EuclidCoreTools.cos(halfPitchCurr); + double sPitchCurr = EuclidCoreTools.sin(halfPitchCurr); + + double halfRollCurr = 0.5 * currentOrientation.getRoll(); + double cRollCurr = EuclidCoreTools.cos(halfRollCurr); + double sRollCurr = EuclidCoreTools.sin(halfRollCurr); + + double xCurr = cYawCurr * cPitchCurr * sRollCurr - sYawCurr * sPitchCurr * cRollCurr; + double yCurr = sYawCurr * cPitchCurr * sRollCurr + cYawCurr * sPitchCurr * cRollCurr; + double zCurr = sYawCurr * cPitchCurr * cRollCurr - cYawCurr * sPitchCurr * sRollCurr; + double sCurr = cYawCurr * cPitchCurr * cRollCurr + sYawCurr * sPitchCurr * sRollCurr; + + finiteDifference(xPrev, yPrev, zPrev, sPrev, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + static void finiteDifference(double xPrev, + double yPrev, + double zPrev, + double sPrev, + double xCurr, + double yCurr, + double zCurr, + double sCurr, + double dt, + Vector3DBasics angularVelocityToPack) + { + double xDiff = sPrev * xCurr - xPrev * sCurr - yPrev * zCurr + zPrev * yCurr; + double yDiff = sPrev * yCurr + xPrev * zCurr - yPrev * sCurr - zPrev * xCurr; + double zDiff = sPrev * zCurr - xPrev * yCurr + yPrev * xCurr - zPrev * sCurr; + double sDiff = sPrev * sCurr + xPrev * xCurr + yPrev * yCurr + zPrev * zCurr; + + double uDiffNormSquared = EuclidCoreTools.normSquared(xDiff, yDiff, zDiff); + + angularVelocityToPack.set(xDiff, yDiff, zDiff); + + // See EuclidCoreToolsTest.testFiniteDifference() for the tolerance value. + if (uDiffNormSquared >= 1.0e-11) + { + double uDiffNorm = EuclidCoreTools.squareRoot(uDiffNormSquared); + double w = EuclidCoreTools.atan2(uDiffNorm, sDiff) / uDiffNorm; + angularVelocityToPack.scale(w); + } + else + { + angularVelocityToPack.scale(Math.signum(sDiff)); + } + + angularVelocityToPack.scale(2.0 / dt); + } } diff --git a/src/main/java/us/ihmc/euclid/tools/RotationMatrixTools.java b/src/main/java/us/ihmc/euclid/tools/RotationMatrixTools.java index 864493404..7cab1de95 100644 --- a/src/main/java/us/ihmc/euclid/tools/RotationMatrixTools.java +++ b/src/main/java/us/ihmc/euclid/tools/RotationMatrixTools.java @@ -8,10 +8,12 @@ import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; import us.ihmc.euclid.rotationConversion.AxisAngleConversion; import us.ihmc.euclid.rotationConversion.RotationMatrixConversion; +import us.ihmc.euclid.rotationConversion.RotationVectorConversion; import us.ihmc.euclid.tuple2D.interfaces.Tuple2DBasics; import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; import us.ihmc.euclid.yawPitchRoll.interfaces.YawPitchRollReadOnly; @@ -158,9 +160,8 @@ else if (orientation2.isZeroOrientation()) } double b00, b01, b02, b10, b11, b12, b20, b21, b22; - if (orientation2 instanceof RotationMatrixReadOnly) + if (orientation2 instanceof RotationMatrixReadOnly b) { // In this case orientation2 might be the same object as matrixToPack, so let's save its components first. - RotationMatrixReadOnly b = (RotationMatrixReadOnly) orientation2; b00 = b.getM00(); b01 = b.getM01(); b02 = b.getM02(); @@ -1033,7 +1034,7 @@ public static double distance(RotationMatrixReadOnly rotationMatrix, Orientation * @param rotationMatrix the rotation matrix to be used for comparison. Not modified. * @param quaternion the quaternion to be used for comparison. Not modified. * @return the angle representing the distance between the two orientations. It is contained in [0, - * pi]. + * pi]. */ public static double distance(RotationMatrixReadOnly rotationMatrix, QuaternionReadOnly quaternion) { @@ -1046,7 +1047,7 @@ public static double distance(RotationMatrixReadOnly rotationMatrix, QuaternionR * @param rotationMatrix the rotation matrix to be used for comparison. Not modified. * @param axisAngle the axisAngle to be used for comparison. Not modified. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, pi]. + * [0, pi]. */ public static double distance(RotationMatrixReadOnly rotationMatrix, AxisAngleReadOnly axisAngle) { @@ -1059,7 +1060,7 @@ public static double distance(RotationMatrixReadOnly rotationMatrix, AxisAngleRe * @param rotationMatrix the rotation matrix to be used for comparison. Not modified. * @param yawPitchRoll the yawPitchRoll to be used for comparison. Not modified. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, pi]. + * [0, pi]. */ public static double distance(RotationMatrixReadOnly rotationMatrix, YawPitchRollReadOnly yawPitchRoll) { @@ -1108,7 +1109,7 @@ public static double distance(RotationMatrixReadOnly rotationMatrix, YawPitchRol * @param a the first rotation matrix. Not modified. * @param b the second rotation matrix. Not modified. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, pi]. + * [0, pi]. */ public static double distance(RotationMatrixReadOnly a, RotationMatrixReadOnly b) { @@ -1146,7 +1147,7 @@ static double distance(RotationMatrixReadOnly a, double b00, double b01, double * * @param m the rotation matrix to be used for comparison. Not modified * @return the angle representing the distance from origin (zero orientation). It is contained in - * [0, pi]. + * [0, pi]. */ public static double angle(RotationMatrixReadOnly m) { @@ -1175,7 +1176,7 @@ public static double angle(RotationMatrixReadOnly m) * @param m21 element at (2,1) * @param m22 element at (2,2) * @return the angle representing the distance from origin (zero orientation). It is contained in - * [0, pi]. + * [0, pi]. */ public static double angle(double m00, double m01, double m02, double m10, double m11, double m12, double m20, double m21, double m22) { @@ -1205,4 +1206,396 @@ else if (m00 + m11 + m22 > 3.0 - 1.0e-7) return angle; } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(RotationMatrixReadOnly previousOrientation, + RotationMatrixReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + finiteDifference(previousOrientation.getM00(), + previousOrientation.getM01(), + previousOrientation.getM02(), + previousOrientation.getM10(), + previousOrientation.getM11(), + previousOrientation.getM12(), + previousOrientation.getM20(), + previousOrientation.getM21(), + previousOrientation.getM22(), + currentOrientation.getM00(), + currentOrientation.getM01(), + currentOrientation.getM02(), + currentOrientation.getM10(), + currentOrientation.getM11(), + currentOrientation.getM12(), + currentOrientation.getM20(), + currentOrientation.getM21(), + currentOrientation.getM22(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(RotationMatrixReadOnly previousOrientation, + QuaternionReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + finiteDifferenceImpl(previousOrientation, + currentOrientation.getX(), + currentOrientation.getY(), + currentOrientation.getZ(), + currentOrientation.getS(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(RotationMatrixReadOnly previousOrientation, + AxisAngleReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAngle = 0.5 * currentOrientation.getAngle(); + double sinHalfAngle = Math.sin(halfAngle); + double xCurr = currentOrientation.getX() * sinHalfAngle; + double yCurr = currentOrientation.getY() * sinHalfAngle; + double zCurr = currentOrientation.getZ() * sinHalfAngle; + double sCurr = Math.cos(halfAngle); + finiteDifferenceImpl(previousOrientation, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + private static void finiteDifferenceImpl(RotationMatrixReadOnly previousOrientation, + double xCurr, + double yCurr, + double zCurr, + double sCurr, + double dt, + Vector3DBasics angularVelocityToPack) + { + double yy2 = 2.0 * yCurr * yCurr; + double zz2 = 2.0 * zCurr * zCurr; + double xx2 = 2.0 * xCurr * xCurr; + double xy2 = 2.0 * xCurr * yCurr; + double sz2 = 2.0 * sCurr * zCurr; + double xz2 = 2.0 * xCurr * zCurr; + double sy2 = 2.0 * sCurr * yCurr; + double yz2 = 2.0 * yCurr * zCurr; + double sx2 = 2.0 * sCurr * xCurr; + + double c00 = 1.0 - yy2 - zz2; + double c01 = xy2 - sz2; + double c02 = xz2 + sy2; + double c10 = xy2 + sz2; + double c11 = 1.0 - xx2 - zz2; + double c12 = yz2 - sx2; + double c20 = xz2 - sy2; + double c21 = yz2 + sx2; + double c22 = 1.0 - xx2 - yy2; + + finiteDifference(previousOrientation.getM00(), + previousOrientation.getM01(), + previousOrientation.getM02(), + previousOrientation.getM10(), + previousOrientation.getM11(), + previousOrientation.getM12(), + previousOrientation.getM20(), + previousOrientation.getM21(), + previousOrientation.getM22(), + c00, + c01, + c02, + c10, + c11, + c12, + c20, + c21, + c22, + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(QuaternionReadOnly previousOrientation, + RotationMatrixReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + + finiteDifferenceImpl(previousOrientation.getX(), + previousOrientation.getY(), + previousOrientation.getZ(), + previousOrientation.getS(), + currentOrientation, + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(AxisAngleReadOnly previousOrientation, + RotationMatrixReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAngle = 0.5 * previousOrientation.getAngle(); + double sinHalfAngle = Math.sin(halfAngle); + double xPrev = previousOrientation.getX() * sinHalfAngle; + double yPrev = previousOrientation.getY() * sinHalfAngle; + double zPrev = previousOrientation.getZ() * sinHalfAngle; + double sPrev = Math.cos(halfAngle); + finiteDifferenceImpl(xPrev, yPrev, zPrev, sPrev, currentOrientation, dt, angularVelocityToPack); + } + + private static void finiteDifferenceImpl(double xPrev, + double yPrev, + double zPrev, + double sPrev, + RotationMatrixReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double yy2 = 2.0 * yPrev * yPrev; + double zz2 = 2.0 * zPrev * zPrev; + double xx2 = 2.0 * xPrev * xPrev; + double xy2 = 2.0 * xPrev * yPrev; + double sz2 = 2.0 * sPrev * zPrev; + double xz2 = 2.0 * xPrev * zPrev; + double sy2 = 2.0 * sPrev * yPrev; + double yz2 = 2.0 * yPrev * zPrev; + double sx2 = 2.0 * sPrev * xPrev; + + double c00 = 1.0 - yy2 - zz2; + double c01 = xy2 - sz2; + double c02 = xz2 + sy2; + double c10 = xy2 + sz2; + double c11 = 1.0 - xx2 - zz2; + double c12 = yz2 - sx2; + double c20 = xz2 - sy2; + double c21 = yz2 + sx2; + double c22 = 1.0 - xx2 - yy2; + + finiteDifference(c00, + c01, + c02, + c10, + c11, + c12, + c20, + c21, + c22, + currentOrientation.getM00(), + currentOrientation.getM01(), + currentOrientation.getM02(), + currentOrientation.getM10(), + currentOrientation.getM11(), + currentOrientation.getM12(), + currentOrientation.getM20(), + currentOrientation.getM21(), + currentOrientation.getM22(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(YawPitchRollReadOnly previousOrientation, + RotationMatrixReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double yawPrev = previousOrientation.getYaw(); + double pitchPrev = previousOrientation.getPitch(); + double rollPrev = previousOrientation.getRoll(); + + double coscPrev = EuclidCoreTools.cos(yawPrev); + double sincPrev = EuclidCoreTools.sin(yawPrev); + + double cosbPrev = EuclidCoreTools.cos(pitchPrev); + double sinbPrev = EuclidCoreTools.sin(pitchPrev); + + double cosaPrev = EuclidCoreTools.cos(rollPrev); + double sinaPrev = EuclidCoreTools.sin(rollPrev); + + // Introduction to Robotics, 2.64 + double p00 = coscPrev * cosbPrev; + double p01 = coscPrev * sinbPrev * sinaPrev - sincPrev * cosaPrev; + double p02 = coscPrev * sinbPrev * cosaPrev + sincPrev * sinaPrev; + double p10 = sincPrev * cosbPrev; + double p11 = sincPrev * sinbPrev * sinaPrev + coscPrev * cosaPrev; + double p12 = sincPrev * sinbPrev * cosaPrev - coscPrev * sinaPrev; + double p20 = -sinbPrev; + double p21 = cosbPrev * sinaPrev; + double p22 = cosbPrev * cosaPrev; + + finiteDifference(p00, + p01, + p02, + p10, + p11, + p12, + p20, + p21, + p22, + currentOrientation.getM00(), + currentOrientation.getM01(), + currentOrientation.getM02(), + currentOrientation.getM10(), + currentOrientation.getM11(), + currentOrientation.getM12(), + currentOrientation.getM20(), + currentOrientation.getM21(), + currentOrientation.getM22(), + dt, + angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(RotationMatrixReadOnly previousOrientation, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double p00 = previousOrientation.getM00(); + double p01 = previousOrientation.getM01(); + double p02 = previousOrientation.getM02(); + double p10 = previousOrientation.getM10(); + double p11 = previousOrientation.getM11(); + double p12 = previousOrientation.getM12(); + double p20 = previousOrientation.getM20(); + double p21 = previousOrientation.getM21(); + double p22 = previousOrientation.getM22(); + + finiteDifferenceImpl(p00, p01, p02, p10, p11, p12, p20, p21, p22, currentOrientation, dt, angularVelocityToPack); + } + + static void finiteDifferenceImpl(double p00, + double p01, + double p02, + double p10, + double p11, + double p12, + double p20, + double p21, + double p22, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double yawCurr = currentOrientation.getYaw(); + double pitchCurr = currentOrientation.getPitch(); + double rollCurr = currentOrientation.getRoll(); + + double coscCurr = EuclidCoreTools.cos(yawCurr); + double sincCurr = EuclidCoreTools.sin(yawCurr); + + double cosbCurr = EuclidCoreTools.cos(pitchCurr); + double sinbCurr = EuclidCoreTools.sin(pitchCurr); + + double cosaCurr = EuclidCoreTools.cos(rollCurr); + double sinaCurr = EuclidCoreTools.sin(rollCurr); + + // Introduction to Robotics, 2.64 + double c00 = coscCurr * cosbCurr; + double c01 = coscCurr * sinbCurr * sinaCurr - sincCurr * cosaCurr; + double c02 = coscCurr * sinbCurr * cosaCurr + sincCurr * sinaCurr; + double c10 = sincCurr * cosbCurr; + double c11 = sincCurr * sinbCurr * sinaCurr + coscCurr * cosaCurr; + double c12 = sincCurr * sinbCurr * cosaCurr - coscCurr * sinaCurr; + double c20 = -sinbCurr; + double c21 = cosbCurr * sinaCurr; + double c22 = cosbCurr * cosaCurr; + + finiteDifference(p00, p01, p02, p10, p11, p12, p20, p21, p22, c00, c01, c02, c10, c11, c12, c20, c21, c22, dt, angularVelocityToPack); + } + + static void finiteDifference(double p00, + double p01, + double p02, + double p10, + double p11, + double p12, + double p20, + double p21, + double p22, + double c00, + double c01, + double c02, + double c10, + double c11, + double c12, + double c20, + double c21, + double c22, + double dt, + Vector3DBasics angularVelocityToPack) + { + // Multiply the transpose of the previous orientation by the current orientation. + double m00 = p00 * c00 + p10 * c10 + p20 * c20; + double m01 = p00 * c01 + p10 * c11 + p20 * c21; + double m02 = p00 * c02 + p10 * c12 + p20 * c22; + double m10 = p01 * c00 + p11 * c10 + p21 * c20; + double m11 = p01 * c01 + p11 * c11 + p21 * c21; + double m12 = p01 * c02 + p11 * c12 + p21 * c22; + double m20 = p02 * c00 + p12 * c10 + p22 * c20; + double m21 = p02 * c01 + p12 * c11 + p22 * c21; + double m22 = p02 * c02 + p12 * c12 + p22 * c22; + + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, angularVelocityToPack); + angularVelocityToPack.scale(1.0 / dt); + } } diff --git a/src/main/java/us/ihmc/euclid/tools/YawPitchRollTools.java b/src/main/java/us/ihmc/euclid/tools/YawPitchRollTools.java index e037f76ef..f2aeeb99a 100644 --- a/src/main/java/us/ihmc/euclid/tools/YawPitchRollTools.java +++ b/src/main/java/us/ihmc/euclid/tools/YawPitchRollTools.java @@ -13,6 +13,7 @@ import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; import us.ihmc.euclid.tuple4D.interfaces.Vector4DBasics; import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; @@ -138,7 +139,7 @@ public static double angle(YawPitchRollReadOnly yawPitchRoll) * @param limitToPi converts the resulting angular distance to within [0 , pi] if set * true. * @return The angle representing the distance between the yawPitchRoll and the other orientation, - * contained in [0, 2pi]. + * contained in [0, 2pi]. */ public static double distance(YawPitchRollReadOnly yawPitchRoll, Orientation3DReadOnly orientation3D, boolean limitToPi) { @@ -171,7 +172,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll, Orientation3DRe * @param quaternion the quaternion to be used for comparison. Not modified. * @param limitToPi limits the result to [0,pi] if set True. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, 2pi]. + * [0, 2pi]. */ public static double distance(YawPitchRollReadOnly yawPitchRoll, QuaternionReadOnly quaternion, boolean limitToPi) { @@ -184,7 +185,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll, QuaternionReadO * @param yawPitchRoll the yawPitchRoll to be used for comparison. Not modified. * @param rotationMatrix the rotationMatrix to be used for comparison. Not modified. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, pi]. + * [0, pi]. */ public static double distance(YawPitchRollReadOnly yawPitchRoll, RotationMatrixReadOnly rotationMatrix) { @@ -198,7 +199,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll, RotationMatrixR * @param axisAngle the axisAngleto be used for comparison. Not modified. * @param limitToPi Limits the result to [0,pi] if set True. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, 2pi]. + * [0, 2pi]. */ public static double distance(YawPitchRollReadOnly yawPitchRoll, AxisAngleReadOnly axisAngle, boolean limitToPi) { @@ -211,7 +212,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll, AxisAngleReadOn * @param yawPitchRoll the yawPitchRoll to be used for comparison. Not modified. * @param axisAngle the axisAngleto be used for comparison. Not modified. * @return the angle representing the distance between the two rotation matrices. It is contained in - * [0, 2pi]. + * [0, 2pi]. */ public static double distance(YawPitchRollReadOnly yawPitchRoll, AxisAngleReadOnly axisAngle) { @@ -226,7 +227,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll, AxisAngleReadOn * @param yawPitchRoll2 the second yaw-pitch-roll to measure the distance. Not modified * @param limitToPi limits the result to [0,pi] * @return the angle representing the distance between the two yaw-pitch-roll. It is contained in - * [0, 2pi] + * [0, 2pi] */ public static double distance(YawPitchRollReadOnly yawPitchRoll1, YawPitchRollReadOnly yawPitchRoll2, boolean limitToPi) { @@ -246,7 +247,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll1, YawPitchRollRe * @param yawPitchRoll1 the first yaw-pitch-roll to measure the distance. Not modified. * @param yawPitchRoll2 the second yaw-pitch-roll to measure the distance. Not modified * @return the angle representing the distance between the two yaw-pitch-roll. It is contained in - * [0, 2pi] + * [0, 2pi] */ public static double distance(YawPitchRollReadOnly yawPitchRoll1, YawPitchRollReadOnly yawPitchRoll2) { @@ -271,7 +272,7 @@ public static double distance(YawPitchRollReadOnly yawPitchRoll1, YawPitchRollRe * x-axis. * @param limitToPi Limits the result to [0, pi]. * @return the angle representing the distance between the two yaw-pitch-roll. It is contained in - * [0, 2pi] + * [0, 2pi] */ public static double distance(double yaw1, double pitch1, double roll1, double yaw2, double pitch2, double roll2, boolean limitToPi) { @@ -352,7 +353,7 @@ public static double distance(double yaw1, double pitch1, double roll1, double y * @param roll2 the third angle of the second orientation representing the rotation around the * x-axis. * @return the angle representing the distance between the two yaw-pitch-roll. It is contained in - * [0, 2pi] + * [0, 2pi] */ public static double distance(double yaw1, double pitch1, double roll1, double yaw2, double pitch2, double roll2) { @@ -576,7 +577,7 @@ public static void addTransform(YawPitchRollReadOnly yawPitchRoll, Tuple3DReadOn * @param checkIfTransformInXYPlane whether this method should assert that the yaw-pitch-roll * represents a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the yaw-pitch-roll - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void transform(double yaw, double pitch, @@ -601,7 +602,7 @@ public static void transform(double yaw, * @param checkIfTransformInXYPlane whether this method should assert that the yaw-pitch-roll * represents a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the yaw-pitch-roll - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void transform(YawPitchRollReadOnly yawPitchRoll, Tuple2DReadOnly tupleOriginal, @@ -631,7 +632,7 @@ public static void transform(YawPitchRollReadOnly yawPitchRoll, * @param checkIfTransformInXYPlane whether this method should assert that the yaw-pitch-roll * represents a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the yaw-pitch-roll - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void inverseTransform(double yaw, double pitch, @@ -661,7 +662,7 @@ public static void inverseTransform(double yaw, * @param checkIfTransformInXYPlane whether this method should assert that the yaw-pitch-roll * represents a transformation in the XY plane. * @throws NotAMatrix2DException if {@code checkIfTransformInXYPlane == true} and the yaw-pitch-roll - * does not represent a transformation in the XY plane. + * does not represent a transformation in the XY plane. */ public static void inverseTransform(YawPitchRollReadOnly yawPitchRoll, Tuple2DReadOnly tupleOriginal, @@ -1247,17 +1248,15 @@ public static void multiply(Orientation3DReadOnly orientation1, { double q1s, q1x, q1y, q1z; - if (orientation1 instanceof QuaternionReadOnly) + if (orientation1 instanceof QuaternionReadOnly quaternion1) { - QuaternionReadOnly quaternion1 = (QuaternionReadOnly) orientation1; q1s = quaternion1.getS(); q1x = quaternion1.getX(); q1y = quaternion1.getY(); q1z = quaternion1.getZ(); } - else if (orientation1 instanceof AxisAngleReadOnly) + else if (orientation1 instanceof AxisAngleReadOnly axisAngle1) { - AxisAngleReadOnly axisAngle1 = (AxisAngleReadOnly) orientation1; double halfTheta = 0.5 * axisAngle1.getAngle(); double sinHalfTheta = EuclidCoreTools.sin(halfTheta) / axisAngle1.axisNorm(); q1x = axisAngle1.getX() * sinHalfTheta; @@ -1287,17 +1286,15 @@ else if (orientation1 instanceof AxisAngleReadOnly) double q2s, q2x, q2y, q2z; - if (orientation2 instanceof QuaternionReadOnly) + if (orientation2 instanceof QuaternionReadOnly quaternion2) { - QuaternionReadOnly quaternion2 = (QuaternionReadOnly) orientation2; q2s = quaternion2.getS(); q2x = quaternion2.getX(); q2y = quaternion2.getY(); q2z = quaternion2.getZ(); } - else if (orientation2 instanceof AxisAngleReadOnly) + else if (orientation2 instanceof AxisAngleReadOnly axisAngle2) { - AxisAngleReadOnly axisAngle2 = (AxisAngleReadOnly) orientation2; double halfTheta = 0.5 * axisAngle2.getAngle(); double sinHalfTheta = EuclidCoreTools.sin(halfTheta) / axisAngle2.axisNorm(); q2x = axisAngle2.getX() * sinHalfTheta; @@ -1571,4 +1568,83 @@ public static void appendRollRotation(YawPitchRollReadOnly yawPitchRollOriginal, roll = EuclidCoreTools.trimAngleMinusPiToPi(roll + yawPitchRollOriginal.getRoll()); yawPitchRollToPack.set(yaw, pitch, roll); } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(YawPitchRollReadOnly previousOrientation, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfYawCurr = 0.5 * currentOrientation.getYaw(); + double cYawCurr = EuclidCoreTools.cos(halfYawCurr); + double sYawCurr = EuclidCoreTools.sin(halfYawCurr); + + double halfPitchCurr = 0.5 * currentOrientation.getPitch(); + double cPitchCurr = EuclidCoreTools.cos(halfPitchCurr); + double sPitchCurr = EuclidCoreTools.sin(halfPitchCurr); + + double halfRollCurr = 0.5 * currentOrientation.getRoll(); + double cRollCurr = EuclidCoreTools.cos(halfRollCurr); + double sRollCurr = EuclidCoreTools.sin(halfRollCurr); + + double xCurr = cYawCurr * cPitchCurr * sRollCurr - sYawCurr * sPitchCurr * cRollCurr; + double yCurr = sYawCurr * cPitchCurr * sRollCurr + cYawCurr * sPitchCurr * cRollCurr; + double zCurr = sYawCurr * cPitchCurr * cRollCurr - cYawCurr * sPitchCurr * sRollCurr; + double sCurr = cYawCurr * cPitchCurr * cRollCurr + sYawCurr * sPitchCurr * sRollCurr; + QuaternionTools.finiteDifferenceImpl(previousOrientation, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(YawPitchRollReadOnly previousOrientation, + AxisAngleReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAngleCurr = 0.5 * currentOrientation.getAngle(); + double sinHalfAngleCurr = Math.sin(halfAngleCurr); + double xCurr = currentOrientation.getX() * sinHalfAngleCurr; + double yCurr = currentOrientation.getY() * sinHalfAngleCurr; + double zCurr = currentOrientation.getZ() * sinHalfAngleCurr; + double sCurr = Math.cos(halfAngleCurr); + QuaternionTools.finiteDifferenceImpl(previousOrientation, xCurr, yCurr, zCurr, sCurr, dt, angularVelocityToPack); + } + + /** + * Computes the angular velocity from the finite difference of two orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @param angularVelocityToPack the vector used to store the angular velocity expressed in the orientation's local coordinates. Modified. + * @see EuclidCoreTools#finiteDifference(Orientation3DReadOnly, Orientation3DReadOnly, double, Vector3DBasics) + */ + public static void finiteDifference(AxisAngleReadOnly previousOrientation, + YawPitchRollReadOnly currentOrientation, + double dt, + Vector3DBasics angularVelocityToPack) + { + double halfAnglePrev = 0.5 * previousOrientation.getAngle(); + double sinHalfAnglePrev = Math.sin(halfAnglePrev); + double xPrev = previousOrientation.getX() * sinHalfAnglePrev; + double yPrev = previousOrientation.getY() * sinHalfAnglePrev; + double zPrev = previousOrientation.getZ() * sinHalfAnglePrev; + double sPrev = Math.cos(halfAnglePrev); + QuaternionTools.finiteDifferenceImpl(xPrev, yPrev, zPrev, sPrev, currentOrientation, dt, angularVelocityToPack); + } } diff --git a/src/test/java/us/ihmc/euclid/rotationConversion/RotationVectorConversionTest.java b/src/test/java/us/ihmc/euclid/rotationConversion/RotationVectorConversionTest.java index 76c6b5593..7919d9e92 100644 --- a/src/test/java/us/ihmc/euclid/rotationConversion/RotationVectorConversionTest.java +++ b/src/test/java/us/ihmc/euclid/rotationConversion/RotationVectorConversionTest.java @@ -1,15 +1,6 @@ package us.ihmc.euclid.rotationConversion; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; -import static us.ihmc.euclid.tools.EuclidCoreTools.cos; -import static us.ihmc.euclid.tools.EuclidCoreTools.sin; - -import java.util.Random; - import org.junit.jupiter.api.Test; - import us.ihmc.euclid.axisAngle.AxisAngle; import us.ihmc.euclid.matrix.RotationMatrix; import us.ihmc.euclid.tools.EuclidCoreRandomTools; @@ -19,6 +10,13 @@ import us.ihmc.euclid.tuple4D.Quaternion; import us.ihmc.euclid.yawPitchRoll.YawPitchRoll; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; +import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; +import static us.ihmc.euclid.tools.EuclidCoreTools.cos; +import static us.ihmc.euclid.tools.EuclidCoreTools.sin; + public class RotationVectorConversionTest { private static final double EPSILON = 1.0e-12; @@ -202,7 +200,7 @@ public void testMatrixToRotationVector() throws Exception m12 = uy * uz * (1.0 - cos(angle)) - ux * sin(angle); m21 = uy * uz * (1.0 - cos(angle)) + ux * sin(angle); - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); if (actualRotationVector.getX() * expectedRotationVector.getX() < 0.0) { @@ -237,7 +235,7 @@ public void testMatrixToRotationVector() throws Exception m12 = uy * uz * (1.0 - cos(angle)) - ux * sin(angle); m21 = uy * uz * (1.0 - cos(angle)) + ux * sin(angle); - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); EuclidCoreTestTools.assertEquals(expectedRotationVector, actualRotationVector, EPSILON); } @@ -246,7 +244,7 @@ public void testMatrixToRotationVector() throws Exception m00 = m11 = m22 = 1.0; m01 = m02 = m12 = 0.0; m10 = m20 = m21 = 0.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); EuclidCoreTestTools.assertTuple3DIsSetToZero(actualRotationVector); // Pi/2 around x @@ -259,7 +257,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 1.0; m22 = 0.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(Math.PI / 2.0, actualRotationVector.getX(), EPSILON); assertEquals(0.0, actualRotationVector.getY(), EPSILON); assertEquals(0.0, actualRotationVector.getZ(), EPSILON); @@ -274,7 +272,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 0.0; m22 = -1.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(Math.PI, actualRotationVector.getX(), EPSILON); assertEquals(0.0, actualRotationVector.getY(), EPSILON); assertEquals(0.0, actualRotationVector.getZ(), EPSILON); @@ -289,7 +287,7 @@ public void testMatrixToRotationVector() throws Exception m20 = -1.0; m21 = 0.0; m22 = 0.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(0.0, actualRotationVector.getX(), EPSILON); assertEquals(Math.PI / 2.0, actualRotationVector.getY(), EPSILON); assertEquals(0.0, actualRotationVector.getZ(), EPSILON); @@ -304,7 +302,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 0.0; m22 = -1.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(0.0, actualRotationVector.getX(), EPSILON); assertEquals(Math.PI, actualRotationVector.getY(), EPSILON); assertEquals(0.0, actualRotationVector.getZ(), EPSILON); @@ -319,7 +317,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 0.0; m22 = 1.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(0.0, actualRotationVector.getX(), EPSILON); assertEquals(0.0, actualRotationVector.getY(), EPSILON); assertEquals(Math.PI / 2.0, actualRotationVector.getZ(), EPSILON); @@ -334,7 +332,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 0.0; m22 = 1.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(0.0, actualRotationVector.getX(), EPSILON); assertEquals(0.0, actualRotationVector.getY(), EPSILON); assertEquals(Math.PI, actualRotationVector.getZ(), EPSILON); @@ -350,7 +348,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 0.0; m22 = -1.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getX(), EPSILON); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getY(), EPSILON); assertEquals(0.0, actualRotationVector.getZ(), EPSILON); @@ -365,7 +363,7 @@ public void testMatrixToRotationVector() throws Exception m20 = 1.0; m21 = 0.0; m22 = 0.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getX(), EPSILON); assertEquals(0.0, actualRotationVector.getY(), EPSILON); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getZ(), EPSILON); @@ -380,36 +378,36 @@ public void testMatrixToRotationVector() throws Exception m20 = 0.0; m21 = 1.0; m22 = 0.0; - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, actualRotationVector); assertEquals(0.0, actualRotationVector.getX(), EPSILON); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getY(), EPSILON); assertEquals(Math.PI * sqrt2Over2, actualRotationVector.getZ(), EPSILON); - RotationVectorConversion.convertMatrixToRotationVectorImpl(Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, 0.0, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, actualRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, Double.NaN, actualRotationVector); EuclidCoreTestTools.assertTuple3DContainsOnlyNaN(actualRotationVector); // Test with an actual matrix @@ -427,7 +425,7 @@ public void testMatrixToRotationVector() throws Exception m21 = rotationMatrix.getM21(); m22 = rotationMatrix.getM22(); RotationVectorConversion.convertMatrixToRotationVector(rotationMatrix, actualRotationVector); - RotationVectorConversion.convertMatrixToRotationVectorImpl(m00, m01, m02, m10, m11, m12, m20, m21, m22, expectedRotationVector); + RotationVectorConversion.convertMatrixToRotationVector(m00, m01, m02, m10, m11, m12, m20, m21, m22, expectedRotationVector); EuclidCoreTestTools.assertEquals(expectedRotationVector, actualRotationVector, EPSILON); // Assert the parameter that does get modified assertTrue(rotationMatrix.equals(rotationMatrixCopy)); @@ -458,9 +456,9 @@ public void testYawPitchRollToRotationVector() throws Exception YawPitchRoll yawPitchRoll = new YawPitchRoll(yaw, pitch, roll); RotationVectorConversion.convertYawPitchRollToRotationVector(yawPitchRoll, actualRotationVector); EuclidCoreTestTools.assertEquals(expectedRotationVector, actualRotationVector, EPSILON); - assertTrue(yawPitchRoll.getYaw() == yaw); - assertTrue(yawPitchRoll.getPitch() == pitch); - assertTrue(yawPitchRoll.getRoll() == roll); + assertEquals(yawPitchRoll.getYaw(), yaw); + assertEquals(yawPitchRoll.getPitch(), pitch); + assertEquals(yawPitchRoll.getRoll(), roll); } } } diff --git a/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java b/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java index 41ed87269..e459305eb 100644 --- a/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java +++ b/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java @@ -1,33 +1,34 @@ package us.ihmc.euclid.tools; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; -import static us.ihmc.euclid.tools.EuclidCoreTools.EPS_NORM_FAST_SQRT; -import static us.ihmc.euclid.tools.EuclidCoreTools.wrap; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Random; - import org.junit.jupiter.api.Test; - +import us.ihmc.euclid.axisAngle.AxisAngle; import us.ihmc.euclid.matrix.Matrix3D; +import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; import us.ihmc.euclid.tuple2D.Point2D; import us.ihmc.euclid.tuple2D.Vector2D; import us.ihmc.euclid.tuple3D.Point3D; import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; +import us.ihmc.euclid.tuple4D.Quaternion; import us.ihmc.euclid.tuple4D.Vector4D; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; +import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; +import static us.ihmc.euclid.tools.EuclidCoreTools.EPS_NORM_FAST_SQRT; +import static us.ihmc.euclid.tools.EuclidCoreTools.wrap; public class EuclidCoreToolsTest { @Test public void testConstants() { - Point2D expectedOrigin2D = new Point2D(); assertEquals(expectedOrigin2D, EuclidCoreTools.origin2D); assertEquals(expectedOrigin2D.hashCode(), EuclidCoreTools.origin2D.hashCode()); @@ -65,7 +66,6 @@ public void testConstants() assertEquals(expectedIdentity3D, EuclidCoreTools.identityMatrix3D); assertEquals(expectedIdentity3D.toString(), EuclidCoreTools.identityMatrix3D.toString()); assertEquals(expectedIdentity3D.hashCode(), EuclidCoreTools.identityMatrix3D.hashCode()); - } @Test @@ -165,160 +165,160 @@ public void testContainsNaNWithArray() throws Exception @Test public void testNormSquaredWith2Elements() throws Exception { - assertTrue(EuclidCoreTools.normSquared(1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0) == 0.0); + assertEquals(1.0, EuclidCoreTools.normSquared(1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.normSquared(0.0, 0.0)); - assertTrue(EuclidCoreTools.normSquared(-1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -1.0) == 1.0); + assertEquals(1.0, EuclidCoreTools.normSquared(-1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, -1.0)); - assertTrue(EuclidCoreTools.normSquared(2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 2.0) == 4.0); + assertEquals(4.0, EuclidCoreTools.normSquared(2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 2.0)); - assertTrue(EuclidCoreTools.normSquared(-2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -2.0) == 4.0); + assertEquals(4.0, EuclidCoreTools.normSquared(-2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, -2.0)); } @Test public void testNormSquaredWith3Elements() throws Exception { - assertTrue(EuclidCoreTools.normSquared(1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0) == 0.0); - - assertTrue(EuclidCoreTools.normSquared(-1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, -1.0) == 1.0); - - assertTrue(EuclidCoreTools.normSquared(2.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 2.0) == 4.0); - - assertTrue(EuclidCoreTools.normSquared(-2.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, -2.0) == 4.0); + assertEquals(1.0, EuclidCoreTools.normSquared(1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0)); + + assertEquals(1.0, EuclidCoreTools.normSquared(-1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, -1.0)); + + assertEquals(4.0, EuclidCoreTools.normSquared(2.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, 2.0)); + + assertEquals(4.0, EuclidCoreTools.normSquared(-2.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, -2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, -2.0)); } @Test public void testNormSquaredWith4Elements() throws Exception { - assertTrue(EuclidCoreTools.normSquared(1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 0.0) == 0.0); - - assertTrue(EuclidCoreTools.normSquared(-1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0, -1.0) == 1.0); - - assertTrue(EuclidCoreTools.normSquared(2.0, 0.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 2.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 2.0) == 4.0); - - assertTrue(EuclidCoreTools.normSquared(-2.0, 0.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, -2.0, 0.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, -2.0, 0.0) == 4.0); - assertTrue(EuclidCoreTools.normSquared(0.0, 0.0, 0.0, -2.0) == 4.0); + assertEquals(1.0, EuclidCoreTools.normSquared(1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 0.0)); + + assertEquals(1.0, EuclidCoreTools.normSquared(-1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, -1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0, -1.0)); + + assertEquals(4.0, EuclidCoreTools.normSquared(2.0, 0.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 2.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, 2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0, 2.0)); + + assertEquals(4.0, EuclidCoreTools.normSquared(-2.0, 0.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, -2.0, 0.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, -2.0, 0.0)); + assertEquals(4.0, EuclidCoreTools.normSquared(0.0, 0.0, 0.0, -2.0)); } @Test public void testNormWith2Elements() throws Exception { - assertTrue(EuclidCoreTools.norm(1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.norm(-1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.norm(-2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, -2.0) == 2.0); - - assertTrue(EuclidCoreTools.fastNorm(1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.fastNorm(-1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(-2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -2.0) == 2.0); + assertEquals(1.0, EuclidCoreTools.norm(1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.norm(0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(-1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.norm(2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.norm(-2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, -2.0)); + + assertEquals(1.0, EuclidCoreTools.fastNorm(1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.fastNorm(0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(-1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(-2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, -2.0)); } @Test public void testNormWith3Elements() throws Exception { - assertTrue(EuclidCoreTools.norm(1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.norm(-1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.norm(-2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, -2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, -2.0) == 2.0); - - assertTrue(EuclidCoreTools.fastNorm(1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.fastNorm(-1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(-2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, -2.0) == 2.0); + assertEquals(1.0, EuclidCoreTools.norm(1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.norm(0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(-1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.norm(2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.norm(-2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, -2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, -2.0)); + + assertEquals(1.0, EuclidCoreTools.fastNorm(1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(-1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(-2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, -2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, -2.0)); } @Test public void testNormWith4Elements() throws Exception { - assertTrue(EuclidCoreTools.norm(1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.norm(-1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, -1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.norm(2.0, 0.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.norm(-2.0, 0.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, -2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, -2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.norm(0.0, 0.0, 0.0, -2.0) == 2.0); - - assertTrue(EuclidCoreTools.fastNorm(1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 0.0) == 0.0); - assertTrue(EuclidCoreTools.fastNorm(-1.0, 0.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -1.0, 0.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, -1.0, 0.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, -1.0) == 1.0); - assertTrue(EuclidCoreTools.fastNorm(2.0, 0.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 2.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(-2.0, 0.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, -2.0, 0.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, -2.0, 0.0) == 2.0); - assertTrue(EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, -2.0) == 2.0); + assertEquals(1.0, EuclidCoreTools.norm(1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.norm(0.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(-1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, -1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.norm(0.0, 0.0, 0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.norm(2.0, 0.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, 2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, 0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.norm(-2.0, 0.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, -2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, -2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.norm(0.0, 0.0, 0.0, -2.0)); + + assertEquals(1.0, EuclidCoreTools.fastNorm(1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, 1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 1.0)); + assertEquals(0.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(-1.0, 0.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, -1.0, 0.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, -1.0, 0.0)); + assertEquals(1.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, -1.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(2.0, 0.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, 2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, 2.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(-2.0, 0.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, -2.0, 0.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, -2.0, 0.0)); + assertEquals(2.0, EuclidCoreTools.fastNorm(0.0, 0.0, 0.0, -2.0)); } @Test @@ -456,7 +456,7 @@ public void testMax() throws Exception double c = EuclidCoreRandomTools.nextDouble(random, 10.0); double expected = Math.max(a, Math.max(b, c)); double actual = EuclidCoreTools.max(a, b, c); - assertTrue(expected == actual); + assertEquals(expected, actual); } for (int i = 0; i < ITERATIONS; i++) @@ -467,7 +467,7 @@ public void testMax() throws Exception double d = EuclidCoreRandomTools.nextDouble(random, 10.0); double expected = Math.max(a, Math.max(b, Math.max(c, d))); double actual = EuclidCoreTools.max(a, b, c, d); - assertTrue(expected == actual); + assertEquals(expected, actual); } } @@ -483,7 +483,7 @@ public void testMin() throws Exception double c = EuclidCoreRandomTools.nextDouble(random, 10.0); double expected = Math.min(a, Math.min(b, c)); double actual = EuclidCoreTools.min(a, b, c); - assertTrue(expected == actual); + assertEquals(expected, actual); } for (int i = 0; i < ITERATIONS; i++) @@ -494,7 +494,7 @@ public void testMin() throws Exception double d = EuclidCoreRandomTools.nextDouble(random, 10.0); double expected = Math.min(a, Math.min(b, Math.min(c, d))); double actual = EuclidCoreTools.min(a, b, c, d); - assertTrue(expected == actual); + assertEquals(expected, actual); } } @@ -514,7 +514,7 @@ public void testMed() throws Exception double actual = EuclidCoreTools.med(a, b, c); if (expected != actual) EuclidCoreTools.med(a, b, c); - assertTrue(expected == actual, "a = " + a + ", b = " + b + ", c = " + c); + assertEquals(expected, actual, "a = " + a + ", b = " + b + ", c = " + c); } } @@ -582,13 +582,13 @@ public void testInterpolate() throws Exception alpha = 0.5; result = EuclidCoreTools.interpolate(a, b, alpha); - assertTrue(result == 0.5 * a + 0.5 * b); + assertEquals(result, 0.5 * a + 0.5 * b); alpha = 0.0; result = EuclidCoreTools.interpolate(a, b, alpha); - assertTrue(result == a); + assertEquals(result, a); alpha = 1.0; result = EuclidCoreTools.interpolate(a, b, alpha); - assertTrue(result == b); + assertEquals(result, b); for (alpha = -2.0; alpha <= 2.0; alpha += 0.1) { @@ -612,9 +612,9 @@ public void testClamp() throws Exception double valueUnder = EuclidCoreRandomTools.nextDouble(random, -100.0, 0.0) - minMax; double valueOver = EuclidCoreRandomTools.nextDouble(random, 0.0, 100.0) + minMax; - assertTrue(valueInside == EuclidCoreTools.clamp(valueInside, minMax)); - assertTrue(-minMax == EuclidCoreTools.clamp(valueUnder, minMax)); - assertTrue(minMax == EuclidCoreTools.clamp(valueOver, minMax)); + assertEquals(valueInside, EuclidCoreTools.clamp(valueInside, minMax)); + assertEquals(-minMax, EuclidCoreTools.clamp(valueUnder, minMax)); + assertEquals(minMax, EuclidCoreTools.clamp(valueOver, minMax)); } EuclidCoreTestTools.assertExceptionIsThrown(() -> EuclidCoreTools.clamp(0.0, -EuclidCoreTools.CLAMP_EPS - 1.0e-12), RuntimeException.class); @@ -630,9 +630,9 @@ public void testClamp() throws Exception double valueUnder = min - EuclidCoreRandomTools.nextDouble(random, 0.0, 100.0); double valueOver = max + EuclidCoreRandomTools.nextDouble(random, 0.0, 100.0); - assertTrue(valueInside == EuclidCoreTools.clamp(valueInside, min, max)); - assertTrue(min == EuclidCoreTools.clamp(valueUnder, min, max)); - assertTrue(max == EuclidCoreTools.clamp(valueOver, min, max)); + assertEquals(valueInside, EuclidCoreTools.clamp(valueInside, min, max)); + assertEquals(min, EuclidCoreTools.clamp(valueUnder, min, max)); + assertEquals(max, EuclidCoreTools.clamp(valueOver, min, max)); } double min = EuclidCoreRandomTools.nextDouble(random, -100.0, 100.0); @@ -774,6 +774,108 @@ public void testRotate() EuclidCoreTools.rotate(list, 1, 4, +1); assertEquals(Arrays.asList(9, 2, 0, 1, 9), list); } + } + + @Test + public void testFiniteDifference() + { + Random random = new Random(234234); + + for (int i = 0; i < ITERATIONS; i++) + { // Let's first verify that the Quaternion finite difference is correct. + double dt = EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0); + + // We initialize the current orientation to a random orientation. + QuaternionReadOnly currentOrientation = EuclidCoreRandomTools.nextQuaternion(random); + Quaternion nextOrientation = new Quaternion(); // We're going to compute this one. + + // We build the next orientation from a desired angular velocity that we're going to integrate and append. + Vector3D expectedAngularVelocityWorld = EuclidCoreRandomTools.nextVector3D(random); + Vector3D expectedAngularVelocityLocalCurrent = new Vector3D(); + currentOrientation.inverseTransform(expectedAngularVelocityWorld, expectedAngularVelocityLocalCurrent); + AxisAngle diff = new AxisAngle(); + diff.setAngle(expectedAngularVelocityLocalCurrent.norm() * dt); + diff.getAxis().set(expectedAngularVelocityLocalCurrent); + nextOrientation.set(currentOrientation); + nextOrientation.append(diff); + + Vector3D expectedAngularVelocityLocalNext = new Vector3D(); + nextOrientation.inverseTransform(expectedAngularVelocityWorld, expectedAngularVelocityLocalNext); + + // Showing that the angular velocity is the same in both frames. + // This is a sanity check to make sure the test is correct. + EuclidCoreTestTools.assertEquals(expectedAngularVelocityLocalNext, expectedAngularVelocityLocalCurrent, 1.0e-12); + + Vector3DBasics actualAngularVelocity = new Vector3D(); + QuaternionTools.finiteDifference(currentOrientation, nextOrientation, dt, actualAngularVelocity); + EuclidCoreTestTools.assertEquals("Iteration: " + i + ", angular distance: " + nextOrientation.distance(currentOrientation) + ", velocity error: " + + actualAngularVelocity.differenceNorm(expectedAngularVelocityLocalNext), + expectedAngularVelocityLocalNext, + actualAngularVelocity, + 2.0e-10); + } + + for (int i = 0; i < ITERATIONS; i++) + { + double dt = EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0); + // Quaternion and others: + compareFiniteDifference(EuclidCoreRandomTools.nextQuaternion(random), EuclidCoreRandomTools.nextQuaternion(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextQuaternion(random), EuclidCoreRandomTools.nextRotationMatrix(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextQuaternion(random), EuclidCoreRandomTools.nextAxisAngle(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextQuaternion(random), EuclidCoreRandomTools.nextYawPitchRoll(random), dt, 1.0e-12, i); + + // RotationMatrix and others: + compareFiniteDifference(EuclidCoreRandomTools.nextRotationMatrix(random), EuclidCoreRandomTools.nextRotationMatrix(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextRotationMatrix(random), EuclidCoreRandomTools.nextQuaternion(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextRotationMatrix(random), EuclidCoreRandomTools.nextAxisAngle(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextRotationMatrix(random), EuclidCoreRandomTools.nextYawPitchRoll(random), dt, 1.0e-12, i); + + // AxisAngle and others: + compareFiniteDifference(EuclidCoreRandomTools.nextAxisAngle(random), EuclidCoreRandomTools.nextAxisAngle(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextAxisAngle(random), EuclidCoreRandomTools.nextQuaternion(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextAxisAngle(random), EuclidCoreRandomTools.nextRotationMatrix(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextAxisAngle(random), EuclidCoreRandomTools.nextYawPitchRoll(random), dt, 1.0e-12, i); + + // YawPitchRoll and others: + compareFiniteDifference(EuclidCoreRandomTools.nextYawPitchRoll(random), EuclidCoreRandomTools.nextYawPitchRoll(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextYawPitchRoll(random), EuclidCoreRandomTools.nextQuaternion(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextYawPitchRoll(random), EuclidCoreRandomTools.nextRotationMatrix(random), dt, 1.0e-12, i); + compareFiniteDifference(EuclidCoreRandomTools.nextYawPitchRoll(random), EuclidCoreRandomTools.nextAxisAngle(random), dt, 1.0e-12, i); + + // To make sure we're not missing anything: + compareFiniteDifference(EuclidCoreRandomTools.nextOrientation3D(random), EuclidCoreRandomTools.nextOrientation3D(random), dt, 1.0e-12, i); + } + } + private static void compareFiniteDifference(Orientation3DReadOnly prevOrientation, + Orientation3DReadOnly currOrientation, + double dt, + double epsilon, + int iteration) + { + Vector3DBasics actualAngularVelocity = new Vector3D(); + Vector3DBasics expectedAngularVelocity = new Vector3D(); + + Quaternion prevQuaternion = new Quaternion(prevOrientation); + Quaternion currQuaternion = new Quaternion(currOrientation); + if (prevQuaternion.distance(currQuaternion) > Math.PI) + { + if (prevOrientation.distance(currOrientation) < Math.PI) + { // We're dealing with an implementation of Orientation3DReadOnly that is limited to [-pi, pi], need to restrict the quaternion to the same range. + prevQuaternion.negate(); + } + } + QuaternionTools.finiteDifference(prevQuaternion, currQuaternion, dt, actualAngularVelocity); + EuclidCoreTools.finiteDifference(prevOrientation, currOrientation, dt, expectedAngularVelocity); + double toleranceScale = Math.max(1.0, expectedAngularVelocity.norm()); + EuclidCoreTestTools.assertEquals("Iteration: %d, prev type: %s, curr type: %s, error norm rel: %s".formatted(iteration, + prevOrientation.getClass().getSimpleName(), + currOrientation.getClass().getSimpleName(), + actualAngularVelocity.differenceNorm( + expectedAngularVelocity) + / toleranceScale), + expectedAngularVelocity, + actualAngularVelocity, + epsilon * toleranceScale); } } diff --git a/src/test/java/us/ihmc/euclid/tools/QuaternionToolsTest.java b/src/test/java/us/ihmc/euclid/tools/QuaternionToolsTest.java index 169bd6a27..928f5f33d 100644 --- a/src/test/java/us/ihmc/euclid/tools/QuaternionToolsTest.java +++ b/src/test/java/us/ihmc/euclid/tools/QuaternionToolsTest.java @@ -1,16 +1,7 @@ package us.ihmc.euclid.tools; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; -import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; - import us.ihmc.euclid.axisAngle.AxisAngle; import us.ihmc.euclid.exceptions.NotAnOrientation2DException; import us.ihmc.euclid.matrix.Matrix3D; @@ -24,10 +15,18 @@ import us.ihmc.euclid.tuple3D.Vector3D; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; import us.ihmc.euclid.tuple4D.Quaternion; import us.ihmc.euclid.tuple4D.Vector4D; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; import us.ihmc.euclid.yawPitchRoll.YawPitchRoll; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.jupiter.api.Assertions.*; +import static us.ihmc.euclid.EuclidTestConstants.ITERATIONS; + public class QuaternionToolsTest { private static final double EPSILON = 1.0e-12; @@ -1324,4 +1323,135 @@ public void testMultiplyMatrixQuaternionResultPutInQuaternion() throws Exception } } + @Test + @Disabled + public void testQuaternionFiniteDifferenceApprox() + { // The goal here is to evaluate the approximation vs the accurate finite difference. + Random random = new Random(2342314); + double maxUDiffNormSquared = 1.0e-11; // We'll use this threshold to trigger the approximation in the actual method. + int benchmarkIterations = 1000000; + + int samples = 0; + double maxErrorNormPrecise = 0.0; + double maxErrorNormApprox = 0.0; + + for (int i = 0; i < benchmarkIterations; i++) + { + double dt = 1.0e-5;//EuclidCoreRandomTools.nextDouble(random, 0.0, 0.1); + Quaternion prevQ = EuclidCoreRandomTools.nextQuaternion(random); + + Quaternion diffQ = new Quaternion(); + Quaternion currQ = new Quaternion(); + + AxisAngle diffAA = new AxisAngle(); + diffAA.getAxis().set(EuclidCoreRandomTools.nextVector3D(random)); + + double diffAngleUB = 0.30; + double diffAngleLB = 0.00; + + // Doing binary search to find the angle that will trigger the approximation exactly. + while (diffAngleUB - diffAngleLB > 1.0e-12) + { + double diffAngle = 0.5 * (diffAngleUB + diffAngleLB); + diffAA.setAngle(diffAngle); + diffQ.set(diffAA); + currQ.set(prevQ); + currQ.append(diffQ); + + if (computeUDiffNormSquared(prevQ, currQ) >= maxUDiffNormSquared) + diffAngleUB = diffAngle; + else + diffAngleLB = diffAngle; + } + + // We only want to test the approximation when it is triggered. + diffAA.setAngle(diffAngleLB); + diffQ.set(diffAA); + currQ.set(prevQ); + currQ.append(diffQ); + + assertTrue(computeUDiffNormSquared(prevQ, currQ) < maxUDiffNormSquared, "Iteration: " + i + ", diffAngle: " + diffAA.getAngle()); + + Vector3DBasics angularVelocityGroundTruth = new Vector3D(); + diffQ.getRotationVector(angularVelocityGroundTruth); + angularVelocityGroundTruth.scale(1.0 / dt); + + samples++; + + Vector3D angularVelocityPrecise = finiteDifferencePrecise(prevQ, currQ, dt); + Vector3D angularVelocityApprox = finiteDifferenceApprox(prevQ, currQ, dt); + + double errorNormPrecise = angularVelocityPrecise.differenceNorm(angularVelocityGroundTruth); + maxErrorNormPrecise = Math.max(maxErrorNormPrecise, errorNormPrecise / angularVelocityGroundTruth.norm()); + double errorNormApprox = angularVelocityApprox.differenceNorm(angularVelocityGroundTruth); + maxErrorNormApprox = Math.max(maxErrorNormApprox, errorNormApprox / angularVelocityGroundTruth.norm()); + } + + System.out.println("Number of samples: " + samples + "/" + benchmarkIterations + " (" + 100.0 * samples / benchmarkIterations + "%)"); + System.out.println("Max error norm precise: " + maxErrorNormPrecise); + System.out.println("Max error norm approx: " + maxErrorNormApprox); + } + + private static double computeUDiffNormSquared(QuaternionReadOnly prev, Quaternion curr) + { + double sPrev = prev.getS(); + double xPrev = prev.getX(); + double yPrev = prev.getY(); + double zPrev = prev.getZ(); + + double sCurr = curr.getS(); + double xCurr = curr.getX(); + double yCurr = curr.getY(); + double zCurr = curr.getZ(); + + double xDiff = sPrev * xCurr - xPrev * sCurr - yPrev * zCurr + zPrev * yCurr; + double yDiff = sPrev * yCurr + xPrev * zCurr - yPrev * sCurr - zPrev * xCurr; + double zDiff = sPrev * zCurr - xPrev * yCurr + yPrev * xCurr - zPrev * sCurr; + return EuclidCoreTools.normSquared(xDiff, yDiff, zDiff); + } + + private static Vector3D finiteDifferenceApprox(QuaternionReadOnly prev, Quaternion curr, double dt) + { + double sPrev = prev.getS(); + double xPrev = prev.getX(); + double yPrev = prev.getY(); + double zPrev = prev.getZ(); + + double sCurr = curr.getS(); + double xCurr = curr.getX(); + double yCurr = curr.getY(); + double zCurr = curr.getZ(); + + double xDiff = sPrev * xCurr - xPrev * sCurr - yPrev * zCurr + zPrev * yCurr; + double yDiff = sPrev * yCurr + xPrev * zCurr - yPrev * sCurr - zPrev * xCurr; + double zDiff = sPrev * zCurr - xPrev * yCurr + yPrev * xCurr - zPrev * sCurr; + double sDiff = sPrev * sCurr + xPrev * xCurr + yPrev * yCurr + zPrev * zCurr; + + double wx = 2.0 / dt * xDiff * Math.signum(sDiff); + double wy = 2.0 / dt * yDiff * Math.signum(sDiff); + double wz = 2.0 / dt * zDiff * Math.signum(sDiff); + return new Vector3D(wx, wy, wz); + } + + private static Vector3D finiteDifferencePrecise(QuaternionReadOnly prev, Quaternion curr, double dt) + { + double sPrev = prev.getS(); + double xPrev = prev.getX(); + double yPrev = prev.getY(); + double zPrev = prev.getZ(); + + double sCurr = curr.getS(); + double xCurr = curr.getX(); + double yCurr = curr.getY(); + double zCurr = curr.getZ(); + + double xDiff = sPrev * xCurr - xPrev * sCurr - yPrev * zCurr + zPrev * yCurr; + double yDiff = sPrev * yCurr + xPrev * zCurr - yPrev * sCurr - zPrev * xCurr; + double zDiff = sPrev * zCurr - xPrev * yCurr + yPrev * xCurr - zPrev * sCurr; + double sDiff = sPrev * sCurr + xPrev * xCurr + yPrev * yCurr + zPrev * zCurr; + + double uDiffNorm = EuclidCoreTools.norm(xDiff, yDiff, zDiff); + double w = 2.0 / dt * EuclidCoreTools.atan2(uDiffNorm, sDiff) / uDiffNorm; + return new Vector3D(xDiff * w, yDiff * w, zDiff * w); + } } From 460f00284e770015f9f40780ca7d9477682d5bed Mon Sep 17 00:00:00 2001 From: Sylvain+ * This method handles 2π wrap around. + *
+ * + * @param previousAngle the angle (in radians) at the previous time step. + * @param currentAngle the angle (in radians) at the current time step. + * @param dt the time step. + * @return the angular velocity in radians per second. + */ + public static double finiteDifferenceAngle(double previousAngle, double currentAngle, double dt) + { + return angleDifferenceMinusPiToPi(currentAngle, previousAngle) / dt; + } + + /** + * Computes the finite difference of the two given values. + * + * @param previousValue the value at the previous time step. + * @param currentValue the value at the current time step. + * @param dt the time step. + * @param derivativeToPack the vector used to store the derivative. Modified. + */ + public static void finiteDifference(Tuple2DReadOnly previousValue, Tuple2DReadOnly currentValue, double dt, Vector2DBasics derivativeToPack) + { + derivativeToPack.sub(currentValue, previousValue); + derivativeToPack.scale(1.0 / dt); + } + + /** + * Computes the finite difference of the two given values. + * + * @param previousValue the value at the previous time step. + * @param currentValue the value at the current time step. + * @param dt the time step. + * @param derivativeToPack the vector used to store the derivative. Modified. + */ + public static void finiteDifference(Tuple3DReadOnly previousValue, Tuple3DReadOnly currentValue, double dt, Vector3DBasics derivativeToPack) + { + derivativeToPack.sub(currentValue, previousValue); + derivativeToPack.scale(1.0 / dt); + } + + /** + * Computes the finite difference of the two given values. + * + * @param previousValue the value at the previous time step. + * @param currentValue the value at the current time step. + * @param dt the time step. + * @param derivativeToPack the vector used to store the derivative. Modified. + */ + public static void finiteDifference(Tuple4DReadOnly previousValue, Tuple4DReadOnly currentValue, double dt, Vector4DBasics derivativeToPack) + { + derivativeToPack.sub(currentValue, previousValue); + derivativeToPack.scale(1.0 / dt); + } + + /** + * Computes the angular velocity from the finite difference of the two given orientations. + * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param currentOrientation the orientation at the current time step. Not modified. + * @param dt the time step. + * @return the angular velocity in radians per second. + */ + public static double finiteDifference(Orientation2DReadOnly previousOrientation, Orientation2DReadOnly currentOrientation, double dt) + { + return finiteDifferenceAngle(previousOrientation.getYaw(), currentOrientation.getYaw(), dt); + } + /** * Computes the angular velocity from the finite difference of the two given orientations. *@@ -1483,6 +1572,32 @@ else if (currentOrientation instanceof AxisAngleReadOnly aaCurr) throw newUnsupportedFiniteDifferenceException(previousOrientation, currentOrientation); } + /** + * Computes the linear and angular velocities from the finite difference of the two given transforms. + *
+ * Note that: + *
+ * Note that the angular velocity is expected to be expressed in the local coordinates of the orientation. If not, perform the following operation before + * calling this method: {@code previousOrientation.inverseTransform(angularVelocity)}. + *
+ * + * @param previousOrientation the orientation at the previous time step. Not modified. + * @param angularVelocity the angular velocity (in radians per second) expressed in the local coordinates of the orientation. Not modified. + * @param dt the time step. + * @param currentOrientationToPack the orientation used to store the current orientation. Modified. + */ + public static void integrate(Orientation3DReadOnly previousOrientation, + Vector3DReadOnly angularVelocity, + double dt, + Orientation3DBasics currentOrientationToPack) + { + double rx = angularVelocity.getX() * dt; + double ry = angularVelocity.getY() * dt; + double rz = angularVelocity.getZ() * dt; + + if (currentOrientationToPack instanceof QuaternionBasics qCurr) + { + QuaternionTools.appendRotationVector(previousOrientation, rx, ry, rz, qCurr); + } + else if (currentOrientationToPack instanceof RotationMatrixBasics rCurr) + { + RotationMatrixTools.appendRotationVector(previousOrientation, rx, ry, rz, rCurr); + } + else if (currentOrientationToPack instanceof AxisAngleBasics aaCurr) + { + AxisAngleTools.appendRotationVector(previousOrientation, rx, ry, rz, aaCurr); + } + else if (currentOrientationToPack instanceof YawPitchRollBasics yprCurr) + { + YawPitchRollTools.appendRotationVector(previousOrientation, rx, ry, rz, yprCurr); + } + else + { + throw newUnsupportedOrientationException(previousOrientation, currentOrientationToPack); + } + } + + /** + * First order integration of the given derivative to compute the current transform. + *+ * Note that: + *