diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidCoreMissingTools.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidCoreMissingTools.java deleted file mode 100644 index 0505d8ff3..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidCoreMissingTools.java +++ /dev/null @@ -1,1530 +0,0 @@ -package us.ihmc.robotics; - -import static us.ihmc.euclid.geometry.tools.EuclidGeometryTools.ONE_MILLIONTH; -import static us.ihmc.euclid.geometry.tools.EuclidGeometryTools.ONE_TRILLIONTH; -import static us.ihmc.euclid.tools.EuclidCoreRandomTools.nextDouble; -import static us.ihmc.euclid.tools.EuclidCoreRandomTools.nextMatrix3D; -import static us.ihmc.euclid.tools.EuclidCoreTools.normSquared; - -import java.lang.reflect.Field; -import java.util.Random; - -import org.ejml.data.DMatrixRMaj; - -import us.ihmc.commons.MathTools; -import us.ihmc.euclid.Axis3D; -import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; -import us.ihmc.euclid.matrix.Matrix3D; -import us.ihmc.euclid.matrix.RotationMatrix; -import us.ihmc.euclid.matrix.interfaces.CommonMatrix3DBasics; -import us.ihmc.euclid.matrix.interfaces.Matrix3DReadOnly; -import us.ihmc.euclid.orientation.interfaces.Orientation3DBasics; -import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameTuple3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.ReferenceFrameHolder; -import us.ihmc.euclid.referenceFrame.tools.EuclidFrameFactories; -import us.ihmc.euclid.tools.EuclidCoreIOTools; -import us.ihmc.euclid.tools.EuclidCoreTools; -import us.ihmc.euclid.tools.TupleTools; -import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly; -import us.ihmc.euclid.tuple2D.Point2D; -import us.ihmc.euclid.tuple2D.interfaces.Point2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.Tuple2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.UnitVector3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; -public class EuclidCoreMissingTools -{ - public static final String DEGREE_SYMBOL = "\u00B0"; - - public static void transform(Matrix3DReadOnly matrix, double xOriginal, double yOriginal, double zOriginal, Tuple3DBasics tupleTransformed) - { - double x = matrix.getM00() * xOriginal + matrix.getM01() * yOriginal + matrix.getM02() * zOriginal; - double y = matrix.getM10() * xOriginal + matrix.getM11() * yOriginal + matrix.getM12() * zOriginal; - double z = matrix.getM20() * xOriginal + matrix.getM21() * yOriginal + matrix.getM22() * zOriginal; - tupleTransformed.set(x, y, z); - } - - public static void floorToGivenPrecision(Tuple3DBasics tuple3d, double precision) - { - tuple3d.setX(MathTools.floorToPrecision(tuple3d.getX(), precision)); - tuple3d.setY(MathTools.floorToPrecision(tuple3d.getY(), precision)); - tuple3d.setZ(MathTools.floorToPrecision(tuple3d.getZ(), precision)); - } - - public static void roundToGivenPrecision(Tuple3DBasics tuple3d, double precision) - { - tuple3d.setX(MathTools.roundToPrecision(tuple3d.getX(), precision)); - tuple3d.setY(MathTools.roundToPrecision(tuple3d.getY(), precision)); - tuple3d.setZ(MathTools.roundToPrecision(tuple3d.getZ(), precision)); - } - - public static void floorToGivenPrecision(Tuple2DBasics tuple2d, double precision) - { - tuple2d.setX(MathTools.floorToPrecision(tuple2d.getX(), precision)); - tuple2d.setY(MathTools.floorToPrecision(tuple2d.getY(), precision)); - } - - public static void roundToGivenPrecision(Tuple2DBasics tuple2d, double precision) - { - tuple2d.setX(MathTools.roundToPrecision(tuple2d.getX(), precision)); - tuple2d.setY(MathTools.roundToPrecision(tuple2d.getY(), precision)); - } - - public static boolean isFinite(Tuple3DBasics tuple) - { - return Double.isFinite(tuple.getX()) && Double.isFinite(tuple.getY()) && Double.isFinite(tuple.getZ()); - } - - /** - * Tests if the point 2D is located on the infinitely long line 2D. - *

- * The test is performed by computing the distance between the point and the line, if that distance - * is below {@link EuclidGeometryTools#IS_POINT_ON_LINE_EPS} this method returns {@code true}. - *

- * - * @param lineSegmentStart the first endpoint of the line segment. Not modified. - * @param lineSegmentEnd the second endpoint of the line segment. Not modified. - * @return {@code true} if the query is considered to be lying on the line, {@code false} otherwise. - */ - public static boolean isPoint2DOnLineSegment2D(double pointX, double pointY, Point2DReadOnly lineSegmentStart, Point2DReadOnly lineSegmentEnd) - { - return EuclidGeometryTools.distanceSquaredFromPoint2DToLineSegment2D(pointX, - pointY, - lineSegmentStart.getX(), - lineSegmentStart.getY(), - lineSegmentEnd.getX(), - lineSegmentEnd.getY()) < EuclidGeometryTools.IS_POINT_ON_LINE_EPS; - } - - /** - * Projects the provided {@code rotation} onto {@code axis} such that the original rotation can be - * decomposed into a rotation around {@code axis} and one around an orthogonal axis. - *

- * rotation = orthogonalRotation * result - *

- * - * @param rotation is the original rotation to be projected onto {@code axis} - * @param axis is the desired rotation axis of the result. - * @param result will be modified to contain the component of {@code rotation} that is around - * {@code axis} - */ - public static void projectRotationOnAxis(QuaternionReadOnly rotation, Vector3DReadOnly axis, QuaternionBasics result) - { - double dotProduct = rotation.getX() * axis.getX() + rotation.getY() * axis.getY() + rotation.getZ() * axis.getZ(); - - double scale = dotProduct / axis.normSquared(); - double projectedX = scale * axis.getX(); - double projectedY = scale * axis.getY(); - double projectedZ = scale * axis.getZ(); - - result.set(projectedX, projectedY, projectedZ, rotation.getS()); - result.normalize(); - } - - public static boolean intersectionBetweenTwoLine2Ds(Point2DReadOnly firstPointOnLine1, - Point2DReadOnly secondPointOnLine1, - Point2DReadOnly firstPointOnLine2, - Point2DReadOnly secondPointOnLine2, - Point2DBasics intersectionToPack) - { - double pointOnLine1x = firstPointOnLine1.getX(); - double pointOnLine1y = firstPointOnLine1.getY(); - double lineDirection1x = secondPointOnLine1.getX() - firstPointOnLine1.getX(); - double lineDirection1y = secondPointOnLine1.getY() - firstPointOnLine1.getY(); - double pointOnLine2x = firstPointOnLine2.getX(); - double pointOnLine2y = firstPointOnLine2.getY(); - double lineDirection2x = secondPointOnLine2.getX() - firstPointOnLine2.getX(); - double lineDirection2y = secondPointOnLine2.getY() - firstPointOnLine2.getY(); - return EuclidGeometryTools.intersectionBetweenTwoLine2Ds(pointOnLine1x, - pointOnLine1y, - lineDirection1x, - lineDirection1y, - pointOnLine2x, - pointOnLine2y, - lineDirection2x, - lineDirection2y, - intersectionToPack); - } - - /** - * Computes the intersection between two infinitely long 2D lines each defined by a 2D point and a - * 2D direction and returns a percentage {@code alpha} along the first line such that the - * intersection coordinates can be computed as follows:
- * {@code intersection = pointOnLine1 + alpha * lineDirection1} - *

- * Edge cases: - *

- *

- * - * @param startPoint1x x-coordinate of a point located on the first line. - * @param startPoint1y y-coordinate of a point located on the first line. - * @param segmentTravel1x x-component of the first line direction. - * @param segmentTravel1y y-component of the first line direction. - * @param startPoint2x x-coordinate of a point located on the second line. - * @param startPoint2y y-coordinate of a point located on the second line. - * @param segmentTravel2x x-component of the second line direction. - * @param segmentTravel2y y-component of the second line direction. - * @return {@code alpha} the percentage along the first line of the intersection location. This - * method returns {@link Double#NaN} if the lines do not intersect. - */ - public static double percentageOfIntersectionBetweenTwoLine2DsInfCase(double startPoint1x, - double startPoint1y, - double segmentTravel1x, - double segmentTravel1y, - double startPoint2x, - double startPoint2y, - double segmentTravel2x, - double segmentTravel2y) - { - // We solve for x the problem of the form: A * x = b - // A * x = b - // / segmentTravel1x -segmentTravel2x \ / alpha \ / startPoint2x - startPoint1x \ - // | | * | | = | | - // \ segmentTravel1y -segmentTravel2y / \ beta / \ startPoint2y - startPoint1y / - // Here, only alpha or beta is needed. - - double determinant = -segmentTravel1x * segmentTravel2y + segmentTravel1y * segmentTravel2x; - - double dx = startPoint2x - startPoint1x; - double dy = startPoint2y - startPoint1y; - - if (Math.abs(determinant) < EuclidGeometryTools.ONE_TRILLIONTH) - { // The lines are parallel - // Check if they are collinear - double cross = dx * segmentTravel1y - dy * segmentTravel1x; - if (Math.abs(cross) < EuclidGeometryTools.ONE_TRILLIONTH) - { - /* - * The two lines are collinear. There's an infinite number of intersection. Let's set the result to - * infinity, i.e. alpha = infinity so it can be handled. - */ - return Double.POSITIVE_INFINITY; - } - else - { - return Double.NaN; - } - } - else - { - double oneOverDeterminant = 1.0 / determinant; - double AInverse00 = oneOverDeterminant * -segmentTravel2y; - double AInverse01 = oneOverDeterminant * segmentTravel2x; - - double alpha = AInverse00 * dx + AInverse01 * dy; - - return alpha; - } - } - - /** - * This method implements the same operation as - * {@link EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics)} - * except that it does not rely on {@code Math#acos(double)} making it faster. - * - * @param vector the vector that is rotated with respect to {@code zUp}. Not modified. - * @param rotationToPack the minimum rotation from {@code zUp} to the given {@code vector}. - * Modified. - * @see EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics) - */ - public static void rotationMatrix3DFromZUpToVector3D(Vector3DReadOnly vector, CommonMatrix3DBasics rotationToPack) - { - rotationMatrix3DFromFirstToSecondVector3D(Axis3D.Z, vector, rotationToPack); - } - - /** - * This method implements the same operation as - * {@link EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, Vector3DReadOnly, Orientation3DBasics)} - * except that it does not rely on {@code Math#acos(double)} making it faster. - * - * @param firstVector the first vector. Not modified. - * @param secondVector the second vector that is rotated with respect to the first vector. Not - * modified. - * @param rotationToPack the minimum rotation from {@code firstVector} to the {@code secondVector}. - * Modified. - * @see EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, - * Vector3DReadOnly, Orientation3DBasics) - */ - public static void rotationMatrix3DFromFirstToSecondVector3D(Vector3DReadOnly firstVector, - Vector3DReadOnly secondVector, - CommonMatrix3DBasics rotationToPack) - { - rotationMatrix3DFromFirstToSecondVector3D(firstVector.getX(), - firstVector.getY(), - firstVector.getZ(), - secondVector.getX(), - secondVector.getY(), - secondVector.getZ(), - rotationToPack); - } - - /** - * This method implements the same operation as - * {@link EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(double, double, double, double, double, double, Orientation3DBasics)} - * except that it does not rely on {@code Math#acos(double)} making it faster. - * - * @param firstVectorX x-component of the first vector. - * @param firstVectorY y-component of the first vector. - * @param firstVectorZ z-component of the first vector. - * @param secondVectorX x-component of the second vector that is rotated with respect to the first - * vector. - * @param secondVectorY y-component of the second vector that is rotated with respect to the first - * vector. - * @param secondVectorZ z-component of the second vector that is rotated with respect to the first - * vector. - * @param rotationToPack the minimum rotation from {@code firstVector} to the {@code secondVector}. - * Modified. - * @see EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(double, double, double, double, - * double, double, Orientation3DBasics) - */ - public static void rotationMatrix3DFromFirstToSecondVector3D(double firstVectorX, - double firstVectorY, - double firstVectorZ, - double secondVectorX, - double secondVectorY, - double secondVectorZ, - CommonMatrix3DBasics rotationToPack) - { - double firstVectorLengthInv = 1.0 / Math.sqrt(normSquared(firstVectorX, firstVectorY, firstVectorZ)); - double secondVectorLengthInv = 1.0 / Math.sqrt(normSquared(secondVectorX, secondVectorY, secondVectorZ)); - firstVectorX *= firstVectorLengthInv; - firstVectorY *= firstVectorLengthInv; - firstVectorZ *= firstVectorLengthInv; - secondVectorX *= secondVectorLengthInv; - secondVectorY *= secondVectorLengthInv; - secondVectorZ *= secondVectorLengthInv; - - double rotationAxisX = firstVectorY * secondVectorZ - firstVectorZ * secondVectorY; - double rotationAxisY = firstVectorZ * secondVectorX - firstVectorX * secondVectorZ; - double rotationAxisZ = firstVectorX * secondVectorY - firstVectorY * secondVectorX; - double sinAngle = Math.sqrt(normSquared(rotationAxisX, rotationAxisY, rotationAxisZ)); - - boolean normalsAreParallel = sinAngle < EuclidGeometryTools.ONE_TEN_MILLIONTH; - - if (normalsAreParallel) - { - rotationToPack.setIdentity(); - return; - } - - rotationAxisX /= sinAngle; - rotationAxisY /= sinAngle; - rotationAxisZ /= sinAngle; - - double cosAngle = firstVectorX * secondVectorX + firstVectorY * secondVectorY + firstVectorZ * secondVectorZ; - - if (cosAngle > 1.0) - cosAngle = 1.0; - else if (cosAngle < -1.0) - cosAngle = -1.0; - - double t = 1.0 - cosAngle; - - double xz = rotationAxisX * rotationAxisZ; - double xy = rotationAxisX * rotationAxisY; - double yz = rotationAxisY * rotationAxisZ; - - double m00 = t * rotationAxisX * rotationAxisX + cosAngle; - double m01 = t * xy - sinAngle * rotationAxisZ; - double m02 = t * xz + sinAngle * rotationAxisY; - double m10 = t * xy + sinAngle * rotationAxisZ; - double m11 = t * rotationAxisY * rotationAxisY + cosAngle; - double m12 = t * yz - sinAngle * rotationAxisX; - double m20 = t * xz - sinAngle * rotationAxisY; - double m21 = t * yz + sinAngle * rotationAxisX; - double m22 = t * rotationAxisZ * rotationAxisZ + cosAngle; - - if (rotationToPack instanceof RotationMatrix) - ((RotationMatrix) rotationToPack).setUnsafe(m00, m01, m02, m10, m11, m12, m20, m21, m22); - else - rotationToPack.set(m00, m01, m02, m10, m11, m12, m20, m21, m22); - } -} - - /** - * Given two 2D line segments with finite length, this methods computes two points P ∈ - * lineSegment1 and Q ∈ lineSegment2 such that the distance || P - Q || is the minimum distance - * between the two 2D line segments. Useful - * link. - * - * @param lineSegmentStart1 the first endpoint of the first line segment. Not - * modified. - * @param lineSegmentEnd1 the second endpoint of the first line segment. Not - * modified. - * @param lineSegmentStart2 the first endpoint of the second line segment. Not - * modified. - * @param lineSegmentEnd2 the second endpoint of the second line segment. Not - * modified. - * @param closestPointOnLineSegment1ToPack the 2D coordinates of the point P are packed in this 2D - * point. Modified. Can be {@code null}. - * @param closestPointOnLineSegment2ToPack the 2D coordinates of the point Q are packed in this 2D - * point. Modified. Can be {@code null}. - * @return the minimum distance between the two line segments. - */ - public static double closestPoint2DsBetweenTwoLineSegment2Ds(Point2DReadOnly lineSegmentStart1, - Point2DReadOnly lineSegmentEnd1, - Point2DReadOnly lineSegmentStart2, - Point2DReadOnly lineSegmentEnd2, - Point2DBasics closestPointOnLineSegment1ToPack, - Point2DBasics closestPointOnLineSegment2ToPack) - { - // Switching to the notation used in http://geomalgorithms.com/a07-_distance.html. - // The line1 is defined by (P0, u) and the line2 by (Q0, v). - Point2DReadOnly P0 = lineSegmentStart1; - double ux = lineSegmentEnd1.getX() - lineSegmentStart1.getX(); - double uy = lineSegmentEnd1.getY() - lineSegmentStart1.getY(); - Point2DReadOnly Q0 = lineSegmentStart2; - double vx = lineSegmentEnd2.getX() - lineSegmentStart2.getX(); - double vy = lineSegmentEnd2.getY() - lineSegmentStart2.getY(); - - Point2DBasics Psc = closestPointOnLineSegment1ToPack; - Point2DBasics Qtc = closestPointOnLineSegment2ToPack; - - double w0X = P0.getX() - Q0.getX(); - double w0Y = P0.getY() - Q0.getY(); - - double a = ux * ux + uy * uy; - double b = ux * vx + uy * vy; - double c = vx * vx + vy * vy; - double d = ux * w0X + uy * w0Y; - double e = vx * w0X + vy * w0Y; - - double delta = a * c - b * b; - - double sc, sNumerator, sDenominator = delta; - double tc, tNumerator, tDenominator = delta; - - // check to see if the lines are parallel - if (delta <= ONE_MILLIONTH) - { - /* - * The lines are parallel, there's an infinite number of pairs, but for one chosen point on one of - * the lines, there's only one closest point to it on the other line. So let's choose arbitrarily a - * point on the lineSegment1 and calculate the point that is closest to it on the lineSegment2. - */ - sNumerator = 0.0; - sDenominator = 1.0; - tNumerator = e; - tDenominator = c; - } - else - { - sNumerator = b * e - c * d; - tNumerator = a * e - b * d; - - if (sNumerator < 0.0) - { - sNumerator = 0.0; - tNumerator = e; - tDenominator = c; - } - else if (sNumerator > sDenominator) - { - sNumerator = sDenominator; - tNumerator = e + b; - tDenominator = c; - } - } - - if (tNumerator < 0.0) - { - tNumerator = 0.0; - sNumerator = -d; - if (sNumerator < 0.0) - sNumerator = 0.0; - else if (sNumerator > a) - sNumerator = a; - sDenominator = a; - } - else if (tNumerator > tDenominator) - { - tNumerator = tDenominator; - sNumerator = -d + b; - if (sNumerator < 0.0) - sNumerator = 0.0; - else if (sNumerator > a) - sNumerator = a; - sDenominator = a; - } - - sc = Math.abs(sNumerator) < ONE_MILLIONTH ? 0.0 : sNumerator / sDenominator; - tc = Math.abs(tNumerator) < ONE_MILLIONTH ? 0.0 : tNumerator / tDenominator; - - double PscX = sc * ux + P0.getX(); - double PscY = sc * uy + P0.getY(); - - double QtcX = tc * vx + Q0.getX(); - double QtcY = tc * vy + Q0.getY(); - - if (Psc != null) - Psc.set(PscX, PscY); - if (Qtc != null) - Qtc.set(QtcX, QtcY); - - double dx = PscX - QtcX; - double dy = PscY - QtcY; - return Math.sqrt(normSquared(dx, dy)); - } - - /** - * This methods computes the minimum distance between the two 2D line segments with finite length. - * Useful link. - * - * @param lineSegmentStart1 the first endpoint of the first line segment. Not modified. - * @param lineSegmentEnd1 the second endpoint of the first line segment. Not modified. - * @param lineSegmentStart2 the first endpoint of the second line segment. Not modified. - * @param lineSegmentEnd2 the second endpoint of the second line segment. Not modified. - * @return the minimum distance between the two line segments. - */ - public static double distanceBetweenTwoLineSegment2Ds(Point2DReadOnly lineSegmentStart1, - Point2DReadOnly lineSegmentEnd1, - Point2DReadOnly lineSegmentStart2, - Point2DReadOnly lineSegmentEnd2) - { - return closestPoint2DsBetweenTwoLineSegment2Ds(lineSegmentStart1, lineSegmentEnd1, lineSegmentStart2, lineSegmentEnd2, null, null); - } - - public static double distanceSquaredFromPoint2DToLineSegment2D(double pointX, - double pointY, - double lineSegmentStartX, - double lineSegmentStartY, - double lineSegmentEndX, - double lineSegmentEndY, - Point2D intersectionPointToPack) - { - double percentage = EuclidGeometryTools.percentageAlongLineSegment2D(pointX, - pointY, - lineSegmentStartX, - lineSegmentStartY, - lineSegmentEndX, - lineSegmentEndY); - - if (percentage > 1.0) - percentage = 1.0; - else if (percentage < 0.0) - percentage = 0.0; - - double projectionX = (1.0 - percentage) * lineSegmentStartX + percentage * lineSegmentEndX; - double projectionY = (1.0 - percentage) * lineSegmentStartY + percentage * lineSegmentEndY; - - if (intersectionPointToPack != null) - intersectionPointToPack.set(projectionX, projectionY); - - double dx = projectionX - pointX; - double dy = projectionY - pointY; - return dx * dx + dy * dy; - } - - public static boolean epsilonEquals(RigidBodyTransformReadOnly a, RigidBodyTransformReadOnly b, double rotationEpsilon, double translationEpsilon) - { - return a.getRotation().geometricallyEquals(b.getRotation(), rotationEpsilon) - && a.getTranslation().geometricallyEquals(b.getTranslation(), translationEpsilon); - } - - /** - * Calculates the 3D part of the given {@code input} that is parallel to the given - * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the normal part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. - */ - public static void extractNormalPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics inputNormalPartToPack) - { - double normalX = normalAxis.getX(); - double normalY = normalAxis.getY(); - double normalZ = normalAxis.getZ(); - double normalLengthSquared = EuclidCoreTools.normSquared(normalX, normalY, normalZ); - - double dot = TupleTools.dot(normalAxis, input) / normalLengthSquared; - inputNormalPartToPack.setAndScale(dot, normalAxis); - } - - /** - * Calculates the 3D part of the given {@code input} that is parallel to the given - * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the normal part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. - * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference - * frame. - */ - public static void extractNormalPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FixedFrameTuple3DBasics inputNormalPartToPack) - { - input.checkReferenceFrameMatch(normalAxis, inputNormalPartToPack); - extractNormalPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputNormalPartToPack); - } - - /** - * Calculates the 3D part of the given {@code input} that is parallel to the given - * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the normal part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. - * @throws ReferenceFrameMismatchException if {@code input} and {@code normalAxis} are not expressed - * in the same reference frame. - */ - public static void extractNormalPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FrameTuple3DBasics inputNormalPartToPack) - { - input.checkReferenceFrameMatch(normalAxis); - inputNormalPartToPack.setReferenceFrame(input.getReferenceFrame()); - extractNormalPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputNormalPartToPack); - } - - /** - * Calculates the 3D part of the given {@code input} that is orthogonal to the given - * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the tangential part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputTangentialPartToPack the tuple used to store the tangential part of the input. - * Modified. - */ - public static void extractTangentialPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics inputTagentialPartToPack) - { - double normalX = normalAxis.getX(); - double normalY = normalAxis.getY(); - double normalZ = normalAxis.getZ(); - double normalLengthSquared = EuclidCoreTools.normSquared(normalX, normalY, normalZ); - - double dot = TupleTools.dot(normalX, normalY, normalZ, input) / normalLengthSquared; - double normalPartX = dot * normalX; - double normalPartY = dot * normalY; - double normalPartZ = dot * normalZ; - - inputTagentialPartToPack.set(input); - inputTagentialPartToPack.sub(normalPartX, normalPartY, normalPartZ); - } - - /** - * Calculates the 3D part of the given {@code input} that is orthogonal to the given - * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the tangential part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputTangentialPartToPack the tuple used to store the tangential part of the input. - * Modified. - * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference - * frame. - */ - public static void extractTangentialPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FixedFrameTuple3DBasics inputTangentialPartToPack) - { - input.checkReferenceFrameMatch(normalAxis, inputTangentialPartToPack); - extractTangentialPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputTangentialPartToPack); - } - - /** - * Calculates the 3D part of the given {@code input} that is orthogonal to the given - * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. - *

- * The result has the following properties: - *

- * where: - * - *

- * - * @param input the tuple to extract the tangential part of. Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not - * modified. - * @param inputTangentialPartToPack the tuple used to store the tangential part of the input. - * Modified. - * @throws ReferenceFrameMismatchException if {@code input} and {@code normalAxis} are not expressed - * in the same reference frame. - */ - public static void extractTangentialPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FrameTuple3DBasics inputTangentialPartToPack) - { - input.checkReferenceFrameMatch(normalAxis); - inputTangentialPartToPack.setReferenceFrame(input.getReferenceFrame()); - extractTangentialPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputTangentialPartToPack); - } - - /** - * Modifies {@code tupleToModify} such that its normal part is equal to the normal part of - * {@code input}. - *

- * This method performs the following calculation: x = x - (x.n)n + (y.n)n, where: - *

- *

- * - * @param input the tuple containing the normal part used to update {@code tupleToModify}. - * Not modified. - * @param normalAxis the normal vector. It is normalized internally if needed. Not modified. - * @param tupleToModify the tuple to modify the normal part of. Modified. - */ - public static void setNormalPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics tupleToModify) - { - double normalX = normalAxis.getX(); - double normalY = normalAxis.getY(); - double normalZ = normalAxis.getZ(); - double normalLengthSquared = EuclidCoreTools.normSquared(normalX, normalY, normalZ); - double dot = (TupleTools.dot(normalAxis, input) - TupleTools.dot(normalAxis, tupleToModify)) / normalLengthSquared; - tupleToModify.scaleAdd(dot, normalAxis, tupleToModify); - } - - public static int intersectionBetweenLineSegment2DAndCylinder3D(double circleRadius, - Point2DReadOnly circlePosition, - Point2DReadOnly startPoint, - Point2DReadOnly endPoint, - Point2DBasics firstIntersectionToPack, - Point2DBasics secondIntersectionToPack) - { - return intersectionBetweenLine2DAndCircle(circleRadius, - circlePosition.getX(), - circlePosition.getY(), - startPoint.getX(), - startPoint.getY(), - false, - endPoint.getX(), - endPoint.getY(), - false, - firstIntersectionToPack, - secondIntersectionToPack); - } - - public static int intersectionBetweenRay2DAndCircle(double circleRadius, - Point2DReadOnly circlePosition, - Point2DReadOnly startPoint, - Point2DReadOnly pointOnRay, - Point2DBasics firstIntersectionToPack, - Point2DBasics secondIntersectionToPack) - { - return intersectionBetweenLine2DAndCircle(circleRadius, - circlePosition.getX(), - circlePosition.getY(), - startPoint.getX(), - startPoint.getY(), - false, - pointOnRay.getX(), - pointOnRay.getY(), - true, - firstIntersectionToPack, - secondIntersectionToPack); - } - - public static int intersectionBetweenRay2DAndCircle(double circleRadius, - Point2DReadOnly circlePosition, - Point2DReadOnly startPoint, - Vector2DReadOnly direction, - Point2DBasics firstIntersectionToPack, - Point2DBasics secondIntersectionToPack) - { - return intersectionBetweenLine2DAndCircle(circleRadius, - circlePosition.getX(), - circlePosition.getY(), - startPoint.getX(), - startPoint.getY(), - false, - startPoint.getX() + direction.getX(), - startPoint.getY() + direction.getY(), - true, - firstIntersectionToPack, - secondIntersectionToPack); - } - - public static int intersectionBetweenLine2DAndCircle(double circleRadius, - Point2DReadOnly circlePosition, - Point2DReadOnly pointOnLine, - Vector2DReadOnly direction, - Point2DBasics firstIntersectionToPack, - Point2DBasics secondIntersectionToPack) - { - return intersectionBetweenLine2DAndCircle(circleRadius, - circlePosition.getX(), - circlePosition.getY(), - pointOnLine.getX(), - pointOnLine.getY(), - true, - pointOnLine.getX() + direction.getX(), - pointOnLine.getY() + direction.getY(), - true, - firstIntersectionToPack, - secondIntersectionToPack); - } - - private static int intersectionBetweenLine2DAndCircle(double circleRadius, - double circlePositionX, - double circlePositionY, - double startX, - double startY, - boolean canIntersectionOccurBeforeStart, - double endX, - double endY, - boolean canIntersectionOccurAfterEnd, - Point2DBasics firstIntersectionToPack, - Point2DBasics secondIntersectionToPack) - { - if (circleRadius < 0.0) - throw new IllegalArgumentException("The circle radius has to be positive."); - - if (firstIntersectionToPack != null) - firstIntersectionToPack.setToNaN(); - if (secondIntersectionToPack != null) - secondIntersectionToPack.setToNaN(); - - if (circleRadius == 0.0) - return 0; - - double radiusSquared = circleRadius * circleRadius; - - double dx = endX - startX; - double dy = endY - startY; - - double dIntersection1 = Double.NaN; - double dIntersection2 = Double.NaN; - - // Compute possible intersections with the circle - // - double deltaPX = startX - circlePositionX; - double deltaPY = startY - circlePositionY; - - double A = EuclidCoreTools.normSquared(dx, dy); - double B = 2.0 * (dx * deltaPX + dy * deltaPY); - double C = EuclidCoreTools.normSquared(deltaPX, deltaPY) - radiusSquared; - - double delta = EuclidCoreTools.squareRoot(B * B - 4 * A * C); - - if (Double.isFinite(delta)) - { - double oneOverTwoA = 0.5 / A; - double dCircle1 = -(B + delta) * oneOverTwoA; - double dCircle2 = -(B - delta) * oneOverTwoA; - - double intersection1X = dCircle1 * dx + startX; - double intersection1Y = dCircle1 * dy + startY; - - if (Math.abs(EuclidGeometryTools.percentageAlongLine2D(intersection1X, intersection1Y, circlePositionX, circlePositionY, 1.0, 0.0)) > circleRadius - - ONE_TRILLIONTH) - dCircle1 = Double.NaN; - - if (Double.isFinite(dCircle1)) - { - if (Double.isNaN(dIntersection1) || Math.abs(dCircle1 - dIntersection1) < ONE_TRILLIONTH) - { - dIntersection1 = dCircle1; - } - else if (dCircle1 < dIntersection1) - { - dIntersection2 = dIntersection1; - dIntersection1 = dCircle1; - } - else - { - dIntersection2 = dCircle1; - } - } - - double intersection2X = dCircle2 * dx + startX; - double intersection2Y = dCircle2 * dy + startY; - - if (Math.abs(EuclidGeometryTools.percentageAlongLine2D(intersection2X, intersection2Y, circlePositionX, circlePositionY, 1.0, 0.0)) > circleRadius - - ONE_TRILLIONTH) - dCircle2 = Double.NaN; - else if (Math.abs(dCircle1 - dCircle2) < ONE_TRILLIONTH) - dCircle2 = Double.NaN; - - if (Double.isFinite(dCircle2)) - { - if (Double.isNaN(dIntersection1)) - { - dIntersection1 = dCircle2; - } - else if (dCircle2 < dIntersection1) - { - dIntersection2 = dIntersection1; - dIntersection1 = dCircle2; - } - else - { - dIntersection2 = dCircle2; - } - } - - } - - if (!canIntersectionOccurBeforeStart) - { - if (dIntersection2 < 0.0) - dIntersection2 = Double.NaN; - - if (dIntersection1 < 0.0) - { - dIntersection1 = dIntersection2; - dIntersection2 = Double.NaN; - } - } - - if (!canIntersectionOccurAfterEnd) - { - if (dIntersection2 > 1.0) - dIntersection2 = Double.NaN; - - if (dIntersection1 > 1.0) - { - dIntersection1 = dIntersection2; - dIntersection2 = Double.NaN; - } - } - - if (Double.isNaN(dIntersection1)) - return 0; - - if (firstIntersectionToPack != null) - { - firstIntersectionToPack.set(dx, dy); - firstIntersectionToPack.scale(dIntersection1); - firstIntersectionToPack.add(startX, startY); - } - - if (Double.isNaN(dIntersection2)) - return 1; - - if (secondIntersectionToPack != null) - { - secondIntersectionToPack.set(dx, dy); - secondIntersectionToPack.scale(dIntersection2); - secondIntersectionToPack.add(startX, startY); - } - - return 2; - } - - /** - * Computes the angle in radians from the first 3D vector to the second 3D vector. The computed - * angle is in the range [0; pi]. - * - * @param firstVector the first vector. Not modified. - * @param secondVector the second vector. Not modified. - * @return the angle in radians from the first vector to the second vector. - */ - public static double angleFromFirstToSecondVector3D(Vector3DReadOnly firstVector, Vector3DReadOnly secondVector) - { - return angleFromFirstToSecondVector3D(firstVector.getX(), - firstVector.getY(), - firstVector.getZ(), - firstVector instanceof UnitVector3DReadOnly, - secondVector.getX(), - secondVector.getY(), - secondVector.getZ(), - secondVector instanceof UnitVector3DReadOnly); - } - - private static double angleFromFirstToSecondVector3D(double firstVectorX, - double firstVectorY, - double firstVectorZ, - boolean isFirstVectorUnitary, - double secondVectorX, - double secondVectorY, - double secondVectorZ, - boolean isSecondVectorUnitary) - { - double firstVectorLength = isFirstVectorUnitary ? 1.0 : EuclidCoreTools.norm(firstVectorX, firstVectorY, firstVectorZ); - double secondVectorLength = isSecondVectorUnitary ? 1.0 : EuclidCoreTools.norm(secondVectorX, secondVectorY, secondVectorZ); - - double dotProduct = firstVectorX * secondVectorX + firstVectorY * secondVectorY + firstVectorZ * secondVectorZ; - dotProduct /= firstVectorLength * secondVectorLength; - - if (dotProduct > 1.0) - dotProduct = 1.0; - else if (dotProduct < -1.0) - dotProduct = -1.0; - - return EuclidCoreTools.acos(dotProduct); - } - - /** - * Computes the coordinates of the intersection between a plane and an infinitely long line. - * Useful link . - *

- * Edge cases: - *

- *

- * - * @param intersectionToPack point in which the coordinates of the intersection are stored. - * @return {@code true} if the method succeeds, {@code false} otherwise. - */ - public static boolean intersectionBetweenLine3DAndPlane3D(double pointOnPlaneX, - double pointOnPlaneY, - double pointOnPlaneZ, - double planeNormalX, - double planeNormalY, - double planeNormalZ, - double pointOnLineX, - double pointOnLineY, - double pointOnLineZ, - double lineDirectionX, - double lineDirectionY, - double lineDirectionZ, - Point3DBasics intersectionToPack) - { - // Switching to the notation used in https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection - // Note: the algorithm is independent from the magnitudes of planeNormal and lineDirection - - // Let's compute the value of the coefficient d = ( (p0 - l0).n ) / ( l.n ) - double numerator, denominator; - numerator = (pointOnPlaneX - pointOnLineX) * planeNormalX; - numerator += (pointOnPlaneY - pointOnLineY) * planeNormalY; - numerator += (pointOnPlaneZ - pointOnLineZ) * planeNormalZ; - denominator = planeNormalX * lineDirectionX + planeNormalY * lineDirectionY + planeNormalZ * lineDirectionZ; - - // Check if the line is parallel to the plane - if (Math.abs(denominator) < ONE_TRILLIONTH) - { - return false; - } - else - { - double d = numerator / denominator; - - intersectionToPack.setX(d * lineDirectionX + pointOnLineX); - intersectionToPack.setY(d * lineDirectionY + pointOnLineY); - intersectionToPack.setZ(d * lineDirectionZ + pointOnLineZ); - return true; - } - } - - public static boolean intersectionBetweenRay2DAndLine2D(Point2DReadOnly rayOrigin, - Vector2DReadOnly rayDirection, - Point2DReadOnly lineOrigin, - Vector2DReadOnly lineDirection, - Point2DBasics intersectionToPack) - { - return intersectionBetweenRay2DAndLine2D(rayOrigin.getX(), - rayOrigin.getY(), - rayDirection.getX(), - rayDirection.getY(), - lineOrigin.getX(), - lineOrigin.getY(), - lineDirection.getX(), - lineDirection.getY(), - intersectionToPack); - } - - public static boolean intersectionBetweenRay2DAndLine2D(Point2DReadOnly rayOrigin, - Vector2DReadOnly rayDirection, - Point2DReadOnly linePoint1, - Point2DReadOnly linePoint2, - Point2DBasics intersectionToPack) - { - return intersectionBetweenRay2DAndLine2D(rayOrigin.getX(), - rayOrigin.getY(), - rayDirection.getX(), - rayDirection.getY(), - linePoint1.getX(), - linePoint1.getY(), - linePoint2.getX() - linePoint1.getX(), - linePoint2.getY() - linePoint1.getY(), - intersectionToPack); - } - - /** - * Just a more thorough version - */ - public static boolean intersectionBetweenRay2DAndLine2D(double rayOriginX, - double rayOriginY, - double rayDirectionX, - double rayDirectionY, - double lineOriginX, - double lineOriginY, - double lineDirectionX, - double lineDirectionY, - Point2DBasics intersectionToPack) - { - double start1x = rayOriginX; - double start1y = rayOriginY; - double end1x = rayOriginX + rayDirectionX; - double end1y = rayOriginY + rayDirectionY; - double start2x = lineOriginX; - double start2y = lineOriginY; - double end2x = lineOriginX + lineDirectionX; - double end2y = lineOriginY + lineDirectionY; - return intersectionBetweenTwoLine2DsImpl(start1x, start1y, false, end1x, end1y, true, start2x, start2y, true, end2x, end2y, true, intersectionToPack); - } - - /** - * This is only included here because it's a private method in the euclid class - */ - private static boolean intersectionBetweenTwoLine2DsImpl(double start1x, - double start1y, - boolean canIntersectionOccurBeforeStart1, - double end1x, - double end1y, - boolean canIntersectionOccurBeforeEnd1, - double start2x, - double start2y, - boolean canIntersectionOccurBeforeStart2, - double end2x, - double end2y, - boolean canIntersectionOccurBeforeEnd2, - Point2DBasics intersectionToPack) - { - double epsilon = EuclidGeometryTools.ONE_TEN_MILLIONTH; - - double direction1x = end1x - start1x; - double direction1y = end1y - start1y; - double direction2x = end2x - start2x; - double direction2y = end2y - start2y; - - double determinant = -direction1x * direction2y + direction1y * direction2x; - - double zeroish = 0.0 - epsilon; - - if (Math.abs(determinant) < epsilon) - { // The lines are parallel - // Check if they are collinear - double dx = start2x - start1x; - double dy = start2y - start1y; - double cross = dx * direction1y - dy * direction1x; - - if (Math.abs(cross) < epsilon) - { - if (canIntersectionOccurBeforeStart1 && canIntersectionOccurBeforeEnd1) - { // (start1, end1) represents a line - if (canIntersectionOccurBeforeStart2 && canIntersectionOccurBeforeEnd2) - { // (start2, end2) represents a line - if (intersectionToPack != null) - intersectionToPack.set(start1x, start1y); - return true; - } - - if (intersectionToPack != null) - intersectionToPack.set(start2x, start2y); - return true; - } - - if (canIntersectionOccurBeforeStart2 && canIntersectionOccurBeforeEnd2) - { // (start2, end2) represents a line - if (intersectionToPack != null) - intersectionToPack.set(start1x, start1y); - return true; - } - - // Let's find the first endpoint that is inside the other line segment and return it. - double direction1LengthSquare = EuclidCoreTools.normSquared(direction1x, direction1y); - double dot; - - // Check if start2 is inside (start1, end1) - dx = start2x - start1x; - dy = start2y - start1y; - dot = dx * direction1x + dy * direction1y; - - if ((canIntersectionOccurBeforeStart1 || zeroish < dot) && (canIntersectionOccurBeforeEnd1 || dot < direction1LengthSquare + epsilon)) - { - if (intersectionToPack != null) - intersectionToPack.set(start2x, start2y); - return true; - } - - // Check if end2 is inside (start1, end1) - dx = end2x - start1x; - dy = end2y - start1y; - dot = dx * direction1x + dy * direction1y; - - if ((canIntersectionOccurBeforeStart1 || zeroish < dot) && (canIntersectionOccurBeforeEnd1 || dot < direction1LengthSquare + epsilon)) - { - if (intersectionToPack != null) - intersectionToPack.set(end2x, end2y); - return true; - } - - double direction2LengthSquare = EuclidCoreTools.normSquared(direction2x, direction2y); - - // Check if start1 is inside (start2, end2) - dx = start1x - start2x; - dy = start1y - start2y; - dot = dx * direction2x + dy * direction2y; - - if ((canIntersectionOccurBeforeStart2 || zeroish < dot) && (canIntersectionOccurBeforeEnd2 || dot < direction2LengthSquare + epsilon)) - { - if (intersectionToPack != null) - intersectionToPack.set(start1x, start1y); - return true; - } - - // Check if end1 is inside (start2, end2) - dx = end1x - start2x; - dy = end1y - start2y; - dot = dx * direction2x + dy * direction2y; - - if ((canIntersectionOccurBeforeStart2 || zeroish < dot) && (canIntersectionOccurBeforeEnd2 || dot < direction2LengthSquare + epsilon)) - { - if (intersectionToPack != null) - intersectionToPack.set(end1x, end1y); - return true; - } - - // (start1, end1) and (start2, end2) represent ray and/or line segment and they are collinear but do not overlap. - if (intersectionToPack != null) - intersectionToPack.setToNaN(); - return false; - } - // The lines are parallel but are not collinear, they do not intersect. - else - { - if (intersectionToPack != null) - intersectionToPack.setToNaN(); - return false; - } - } - - double dx = start2x - start1x; - double dy = start2y - start1y; - - double oneOverDeterminant = 1.0 / determinant; - double AInverse00 = -direction2y; - double AInverse01 = direction2x; - double AInverse10 = -direction1y; - double AInverse11 = direction1x; - - double alpha = oneOverDeterminant * (AInverse00 * dx + AInverse01 * dy); - double beta = oneOverDeterminant * (AInverse10 * dx + AInverse11 * dy); - - double oneish = 1.0 + epsilon; - - boolean areIntersecting = (canIntersectionOccurBeforeStart1 || zeroish < alpha) && (canIntersectionOccurBeforeEnd1 || alpha < oneish); - if (areIntersecting) - areIntersecting = (canIntersectionOccurBeforeStart2 || zeroish < beta) && (canIntersectionOccurBeforeEnd2 || beta < oneish); - - if (intersectionToPack != null) - { - if (areIntersecting) - { - intersectionToPack.set(start1x + alpha * direction1x, start1y + alpha * direction1y); - } - else - { - intersectionToPack.setToNaN(); - } - } - - return areIntersecting; - } - - /** - * Calculate the angular velocity by differentiating orientation. - * - * @param qStart the initial orientation at time t. - * @param qEnd the final orientation at time t + duration. - * @param duration the time interval between the 2 orientations. - * @param angularVelocityToPack the angular velocity. - */ - public static void differentiateOrientation(QuaternionReadOnly qStart, QuaternionReadOnly qEnd, double duration, Vector3DBasics angularVelocityToPack) - { - double q1x = qStart.getX(); - double q1y = qStart.getY(); - double q1z = qStart.getZ(); - double q1s = qStart.getS(); - - double q2x = qEnd.getX(); - double q2y = qEnd.getY(); - double q2z = qEnd.getZ(); - double q2s = qEnd.getS(); - - double diffx = q1s * q2x - q1x * q2s - q1y * q2z + q1z * q2y; - double diffy = q1s * q2y + q1x * q2z - q1y * q2s - q1z * q2x; - double diffz = q1s * q2z - q1x * q2y + q1y * q2x - q1z * q2s; - double diffs = q1s * q2s + q1x * q2x + q1y * q2y + q1z * q2z; - - if (diffs < 0.0) - { - diffx = -diffx; - diffy = -diffy; - diffz = -diffz; - diffs = -diffs; - } - - double sinHalfTheta = EuclidCoreTools.norm(diffx, diffy, diffz); - - double angle; - if (EuclidCoreTools.epsilonEquals(1.0, diffs, 1.0e-12)) - angle = 2.0 * sinHalfTheta / diffs; - else - angle = 2.0 * EuclidCoreTools.atan2(sinHalfTheta, diffs); - angularVelocityToPack.set(diffx, diffy, diffz); - angularVelocityToPack.scale(angle / (sinHalfTheta * duration)); - } - - // *** NOTE ***: The 4x4 output matrix produced by this method assumes a Quaternion component ordering of: - // Quat = [ Qs - // Qx - // Qy - // Qz ] - public static DMatrixRMaj quaternionDotToOmegaTransform(QuaternionReadOnly rotatingFrameQuaternion) - { - double qs = rotatingFrameQuaternion.getS(); - double qx = rotatingFrameQuaternion.getX(); - double qy = rotatingFrameQuaternion.getY(); - double qz = rotatingFrameQuaternion.getZ(); - - DMatrixRMaj E = new DMatrixRMaj(4,4); - - E.set(0,0, qs); E.set(0,1, qx); E.set(0,2, qy); E.set(0,3, qz); - E.set(1,0,-qx); E.set(1,1, qs); E.set(1,2, qz); E.set(1,3,-qy); - E.set(2,0,-qy); E.set(2,1,-qz); E.set(2,2, qs); E.set(2,3, qx); - E.set(3,0,-qz); E.set(3,1, qy); E.set(3,2,-qx); E.set(3,3, qs); - - return E; - } - - /** - * Sets the yaw pitch roll but the doubles are given in degrees. - */ - public static void setYawPitchRollDegrees(Orientation3DBasics orientation3DBasics, double yaw, double pitch, double roll) - { - orientation3DBasics.setYawPitchRoll(Math.toRadians(yaw), Math.toRadians(pitch), Math.toRadians(roll)); - } - - /** - * Get the orientation as yaw pitch roll String but they are in degrees. - * Says yaw-pitch-roll. - */ - public static String getYawPitchRollStringDegrees(Orientation3DBasics orientation3DBasics) - { - // Degree symbol placed at the end so you don't have to remove it when copy and pasting - return EuclidCoreIOTools.getYawPitchRollString(EuclidCoreIOTools.DEFAULT_FORMAT, - Math.toDegrees(orientation3DBasics.getYaw()), - Math.toDegrees(orientation3DBasics.getPitch()), - Math.toDegrees(orientation3DBasics.getRoll())) + DEGREE_SYMBOL; - } - - /** - * Get the orientation as yaw pitch roll String but they are in degrees. - * Doesn't say yaw-pitch-roll. - */ - public static String getYawPitchRollValuesStringDegrees(Orientation3DBasics orientation3DBasics) - { - // Degree symbol placed at the end so you don't have to remove it when copy and pasting - return EuclidCoreIOTools.getStringOf("(", ")", ", ", - EuclidCoreIOTools.DEFAULT_FORMAT, - Math.toDegrees(orientation3DBasics.getYaw()), - Math.toDegrees(orientation3DBasics.getPitch()), - Math.toDegrees(orientation3DBasics.getRoll())) + DEGREE_SYMBOL; - } - - /** - * Generates a random positive definite matrix. - *

- * {@code matrix}ij ∈ [-1.0; 1.0]. - *

- *

- * The approach used here generates a random 3D matrix with values in [-1.0, 1.0], and then performs A * AT which is guaranteed to result in a - * symmetric positive semi-definite matrix. We then add diagonal terms to make the matrix positive definite, and finally scale the matrix by a random double - * that upper bounds the absolute values of the positive definite matrix elements to 1.0. - *

- * - * @param random the random generator to use. - * @return the random positive definite matrix. - */ - public static Matrix3D nextPositiveDefiniteMatrix3D(Random random) - { - return nextPositiveDefiniteMatrix3D(random, 1.0); - } - - /** - * Generates a random positive definite matrix. - *

- * {@code matrix}ij ∈ [-minMaxValue, minMaxValue] - *

- *

- * The approach used here generates a random 3D matrix with values in [{@code -minMaxValue}, {@code minMaxValue}], and then performs A * AT, - * which is guaranteed to result in a symmetric positive semi-definite matrix. We then add diagonal terms to make the matrix positive definite, and finally - * scale the matrix by a random double that upper bounds the absolute values of the positive definite matrix elements to {@code minMaxValue}. - *

- * - * @param random the random generator to use. - * @param minMaxValue the maximum value for each element. - * @return the random positive definite matrix. - * @throws RuntimeException if {@code minMaxValue < 0}. - */ - public static Matrix3D nextPositiveDefiniteMatrix3D(Random random, double minMaxValue) - { - Matrix3D matrix3D = nextMatrix3D(random, minMaxValue); - matrix3D.multiplyTransposeOther(matrix3D); - - double diagonalDominanceScalar = Math.abs(minMaxValue); - matrix3D.addM00(diagonalDominanceScalar); - matrix3D.addM11(diagonalDominanceScalar); - matrix3D.addM22(diagonalDominanceScalar); - - double scalarToShrinkMatrixWithinBounds = nextDouble(random, 0.0, minMaxValue / matrix3D.maxAbsElement()); - matrix3D.scale(scalarToShrinkMatrixWithinBounds); - return matrix3D; - } - - /** - * Remove when this issue is fixed: - * https://github.com/ihmcrobotics/euclid/issues/57 - */ - private static final Field referenceFrameHasBeenRemoved; - static - { - try - { - referenceFrameHasBeenRemoved = ReferenceFrame.class.getDeclaredField("hasBeenRemoved"); - referenceFrameHasBeenRemoved.setAccessible(true); - } - catch (NoSuchFieldException e) - { - throw new RuntimeException(e); - } - } - - public static boolean hasBeenRemoved(ReferenceFrame referenceFrame) - { - try - { - return referenceFrameHasBeenRemoved.getBoolean(referenceFrame); - } - catch (IllegalAccessException e) - { - throw new RuntimeException(e); - } - } - - /** - * Remove when this issue is fixed: - * https://github.com/ihmcrobotics/euclid/issues/57 - */ - private static final Field referenceFrameName; - static - { - try - { - referenceFrameName = ReferenceFrame.class.getDeclaredField("frameName"); - referenceFrameName.setAccessible(true); - } - catch (NoSuchFieldException e) - { - throw new RuntimeException(e); - } - } - - public static String frameName(ReferenceFrame referenceFrame) - { - try - { - return referenceFrameName.get(referenceFrame).toString(); - } - catch (IllegalAccessException e) - { - throw new RuntimeException(e); - } - } - - public static FrameVector3DReadOnly newLinkedFrameVector3DReadOnly(ReferenceFrameHolder referenceFrameHolder, DMatrixRMaj source) - { - return newLinkedFrameVector3DReadOnly(referenceFrameHolder, 0, source); - } - - public static FrameVector3DReadOnly newLinkedFrameVector3DReadOnly(ReferenceFrameHolder referenceFrameHolder, int startIndex, DMatrixRMaj source) - { - int xIndex = startIndex; - int yIndex = startIndex + 1; - int zIndex = startIndex + 2; - return EuclidFrameFactories.newLinkedFrameVector3DReadOnly(referenceFrameHolder, - () -> source.get(xIndex, 0), - () -> source.get(yIndex, 0), - () -> source.get(zIndex, 0)); - } -} \ No newline at end of file diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryMissingTools.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryMissingTools.java deleted file mode 100644 index b56e4c0a2..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryMissingTools.java +++ /dev/null @@ -1,118 +0,0 @@ -package us.ihmc.robotics; - -import us.ihmc.euclid.geometry.BoundingBox2D; -import us.ihmc.euclid.geometry.BoundingBox3D; -import us.ihmc.euclid.geometry.interfaces.BoundingBox2DReadOnly; -import us.ihmc.euclid.geometry.interfaces.BoundingBox3DReadOnly; -import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; -import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; - -public class EuclidGeometryMissingTools -{ - public static double getZOnPlane(Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal, double pointX, double pointY) - { - // The three components of the plane origin - double x0 = pointOnPlane.getX(); - double y0 = pointOnPlane.getY(); - double z0 = pointOnPlane.getZ(); - // The three components of the plane normal - double a = planeNormal.getX(); - double b = planeNormal.getY(); - double c = planeNormal.getZ(); - - // Given the plane equation: a*x + b*y + c*z + d = 0, with d = -(a*x0 + b*y0 + c*z0), we find z: - double z = a / c * (x0 - pointX) + b / c * (y0 - pointY) + z0; - return z; - } - - /** - * Returns the minimum XY distance between a 3D point and an infinitely long 3D line defined by two - * points. - *

- * Edge cases: - *

- *

- *

- * WARNING: the 3D arguments are projected onto the XY-plane to perform the actual computation in - * 2D. - *

- * - * @param point the 3D point is projected onto the xy-plane. It's projection is used to - * compute the distance from the line. Not modified. - * @param firstPointOnLine the projection of this 3D onto the xy-plane refers to the first point on - * the 2D line. Not modified. - * @param secondPointOnLine the projection of this 3D onto the xy-plane refers to the second point - * one the 2D line. Not modified. - * @return the minimum distance between the 2D point and the 2D line. - * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference - * frame. - */ - public static double distanceXYFromPoint3DToLine3D(FramePoint3DReadOnly point, FramePoint3DReadOnly firstPointOnLine, FramePoint3DReadOnly secondPointOnLine) - { - point.checkReferenceFrameMatch(firstPointOnLine); - point.checkReferenceFrameMatch(secondPointOnLine); - - double pointOnLineX = firstPointOnLine.getX(); - double pointOnLineY = firstPointOnLine.getY(); - double lineDirectionX = secondPointOnLine.getX() - firstPointOnLine.getX(); - double lineDirectionY = secondPointOnLine.getY() - firstPointOnLine.getY(); - return EuclidGeometryTools.distanceFromPoint2DToLine2D(point.getX(), point.getY(), pointOnLineX, pointOnLineY, lineDirectionX, lineDirectionY); - } - - /** - * Finds the intersection of two bounding boxes defined by a bounding box Allocates a new - * BoundingBox2D. TODO: Check, Unit test, move where BoundingBox union is - * - * @param a - * @param b - * @return the intersection bounding box, or null if no intersection - */ - public static BoundingBox2D computeIntersectionOfTwoBoundingBoxes(BoundingBox2DReadOnly a, BoundingBox2DReadOnly b) - { - double maxX = Math.min(a.getMaxX(), b.getMaxX()); - double maxY = Math.min(a.getMaxY(), b.getMaxY()); - double minX = Math.max(a.getMinX(), b.getMinX()); - double minY = Math.max(a.getMinY(), b.getMinY()); - - if ((maxX <= minX) || (maxY <= minY)) - return null; - - return new BoundingBox2D(minX, minY, maxX, maxY); - } - - /** - * Finds the intersection of two bounding boxes defined by a bounding box Allocates a new boundingBox3D. - * - * @param a - * @param b - * @return the intersection bounding box, or null if no intersection - */ - public static BoundingBox3D computeIntersectionOfTwoBoundingBoxes(BoundingBox3DReadOnly a, BoundingBox3DReadOnly b) - { - double maxX = Math.min(a.getMaxX(), b.getMaxX()); - double maxY = Math.min(a.getMaxY(), b.getMaxY()); - double maxZ = Math.min(a.getMaxZ(), b.getMaxZ()); - - double minX = Math.max(a.getMinX(), b.getMinX()); - double minY = Math.max(a.getMinY(), b.getMinY()); - double minZ = Math.max(a.getMinZ(), b.getMinZ()); - - if ((maxX <= minX) || (maxY <= minY) || (maxZ <= minZ)) - return null; - - return new BoundingBox3D(minX, minY, minZ, maxX, maxY, maxZ); - } - - public static double computeBoundingBoxVolume3D(BoundingBox3DReadOnly boundingBox) - { - return Math.abs(boundingBox.getMaxX() - boundingBox.getMinX()) - * Math.abs(boundingBox.getMaxY() - boundingBox.getMinY()) - * Math.abs(boundingBox.getMaxZ() - boundingBox.getMinZ()); - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryPolygonMissingTools.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryPolygonMissingTools.java deleted file mode 100644 index 24908a4c0..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/EuclidGeometryPolygonMissingTools.java +++ /dev/null @@ -1,46 +0,0 @@ -package us.ihmc.robotics; - -import us.ihmc.euclid.geometry.tools.EuclidGeometryPolygonTools; -import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; -import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; - -import java.util.List; - -public class EuclidGeometryPolygonMissingTools -{ - public static boolean doLineSegment2DAndConvexPolygon2DIntersect(Point2DReadOnly lineSegmentStart, - Point2DReadOnly lineSegmentEnd, - List convexPolygon2D, - int numberOfVertices) - { - checkNumberOfVertices(convexPolygon2D, numberOfVertices); - - if (numberOfVertices == 0) - return false; - - if (numberOfVertices == 1) - return EuclidGeometryTools.isPoint2DOnLineSegment2D(convexPolygon2D.get(0), lineSegmentStart, lineSegmentEnd); - - if (numberOfVertices == 2) - return EuclidGeometryTools.doLineSegment2DsIntersect(convexPolygon2D.get(0), convexPolygon2D.get(1), lineSegmentStart, lineSegmentEnd); - - - for (int edgeIndex = 0; edgeIndex < numberOfVertices; edgeIndex++) - { - Point2DReadOnly edgeStart = convexPolygon2D.get(edgeIndex); - Point2DReadOnly edgeEnd = convexPolygon2D.get(EuclidGeometryPolygonTools.next(edgeIndex, numberOfVertices)); - - if (EuclidGeometryTools.doLineSegment2DsIntersect(edgeStart, edgeEnd, lineSegmentStart, lineSegmentEnd)) - return true; - - } - - return false; - } - - private static void checkNumberOfVertices(List convexPolygon2D, int numberOfVertices) - { - if (numberOfVertices < 0 || numberOfVertices > convexPolygon2D.size()) - throw new IllegalArgumentException("Illegal numberOfVertices: " + numberOfVertices + ", expected a value in ] 0, " + convexPolygon2D.size() + "]."); - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/geometry/shapes/FramePlane3d.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/geometry/shapes/FramePlane3d.java deleted file mode 100644 index 992976848..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/geometry/shapes/FramePlane3d.java +++ /dev/null @@ -1,313 +0,0 @@ -package us.ihmc.robotics.geometry.shapes; - -import us.ihmc.euclid.Axis3D; -import us.ihmc.euclid.geometry.Plane3D; -import us.ihmc.euclid.geometry.interfaces.Line3DReadOnly; -import us.ihmc.euclid.referenceFrame.FrameLine3D; -import us.ihmc.euclid.referenceFrame.FramePoint2D; -import us.ihmc.euclid.referenceFrame.FramePoint3D; -import us.ihmc.euclid.referenceFrame.FrameVector3D; -import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.referenceFrame.interfaces.ReferenceFrameHolder; -import us.ihmc.euclid.transform.RigidBodyTransform; -import us.ihmc.euclid.tuple3D.Point3D; -import us.ihmc.euclid.tuple3D.Vector3D; -import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; - -public class FramePlane3d implements ReferenceFrameHolder -{ - private ReferenceFrame referenceFrame; - private final Plane3D plane3d; - - private final RigidBodyTransform temporaryTransformToDesiredFrame = new RigidBodyTransform(); - private final Vector3D temporaryVector = new Vector3D(); - private final Point3D temporaryPoint = new Point3D(); - - public FramePlane3d() - { - this(ReferenceFrame.getWorldFrame()); - } - - public FramePlane3d(ReferenceFrame referenceFrame, Plane3D plane3d) - { - this.referenceFrame = referenceFrame; - this.plane3d = plane3d; - } - - public FramePlane3d(ReferenceFrame referenceFrame) - { - this.referenceFrame = referenceFrame; - this.plane3d = new Plane3D(); - } - - public FramePlane3d(FramePlane3d framePlane3d) - { - this.referenceFrame = framePlane3d.referenceFrame; - this.plane3d = new Plane3D(framePlane3d.plane3d); - } - - public FramePlane3d(FrameVector3D normal, FramePoint3D point) - { - normal.checkReferenceFrameMatch(point); - this.referenceFrame = normal.getReferenceFrame(); - this.plane3d = new Plane3D(point, normal); - } - - public FramePlane3d(ReferenceFrame referenceFrame, Point3DReadOnly point, Vector3DReadOnly normal) - { - this.referenceFrame = referenceFrame; - this.plane3d = new Plane3D(point, normal); - } - - @Override - public ReferenceFrame getReferenceFrame() - { - return referenceFrame; - } - - public void getNormal(FrameVector3D normalToPack) - { - checkReferenceFrameMatch(normalToPack.getReferenceFrame()); - temporaryVector.set(this.plane3d.getNormal()); - normalToPack.set(temporaryVector); - } - - public FrameVector3D getNormalCopy() - { - FrameVector3D returnVector = new FrameVector3D(this.referenceFrame); - getNormal(returnVector); - return returnVector; - } - - public Vector3DBasics getNormal() - { - return plane3d.getNormal(); - } - - public void setNormal(double x, double y, double z) - { - plane3d.getNormal().set(x, y, z); - } - - public void setNormal(Vector3DReadOnly normal) - { - plane3d.getNormal().set(normal); - } - - public void getPoint(FramePoint3D pointToPack) - { - checkReferenceFrameMatch(pointToPack.getReferenceFrame()); - temporaryPoint.set(this.plane3d.getPoint()); - pointToPack.set(temporaryPoint); - } - - public FramePoint3D getPointCopy() - { - FramePoint3D pointToReturn = new FramePoint3D(this.getReferenceFrame()); - this.getPoint(pointToReturn); - return pointToReturn; - } - - public Point3DBasics getPoint() - { - return plane3d.getPoint(); - } - - public void setPoint(double x, double y, double z) - { - plane3d.getPoint().set(x, y, z); - } - - public void setPoint(Point3DReadOnly point) - { - plane3d.getPoint().set(point); - } - - public void setPoints(FramePoint3D pointA, FramePoint3D pointB, FramePoint3D pointC) - { - pointA.checkReferenceFrameMatch(referenceFrame); - pointB.checkReferenceFrameMatch(referenceFrame); - pointC.checkReferenceFrameMatch(referenceFrame); - - plane3d.set(pointA, pointB, pointC); - } - - public void changeFrame(ReferenceFrame desiredFrame) - { - if (desiredFrame != referenceFrame) - { - referenceFrame.getTransformToDesiredFrame(temporaryTransformToDesiredFrame, desiredFrame); - plane3d.applyTransform(temporaryTransformToDesiredFrame); - referenceFrame = desiredFrame; - } - - // otherwise: in the right frame already, so do nothing - } - - public boolean isOnOrAbove(FramePoint3D pointToTest) - { - checkReferenceFrameMatch(pointToTest); - - return plane3d.isOnOrAbove(pointToTest); - } - - public boolean isOnOrAbove(Point3DReadOnly pointToTest) - { - return plane3d.isOnOrAbove(pointToTest); - } - - public boolean isOnOrAbove(Point3DReadOnly pointToTest, double epsilon) - { - return plane3d.isOnOrAbove(pointToTest, epsilon); - } - - public boolean isOnOrBelow(FramePoint3D pointToTest) - { - checkReferenceFrameMatch(pointToTest); - - return plane3d.isOnOrBelow(pointToTest); - } - - /** - * Tests if the two planes are parallel by testing if their normals are collinear. - * The latter is done given a tolerance on the angle between the two normal axes in the range ]0; pi/2[. - * - *

- * Edge cases: - *

- *

- * - * @param otherPlane the other plane to do the test with. Not modified. - * @param angleEpsilon tolerance on the angle in radians. - * @return {@code true} if the two planes are parallel, {@code false} otherwise. - */ - public boolean isParallel(FramePlane3d otherPlane, double angleEpsilon) - { - checkReferenceFrameMatch(otherPlane); - return plane3d.isParallel(otherPlane.plane3d, angleEpsilon); - } - - /** - * Tests if this plane and the given plane are coincident: - * - *

- * Edge cases: - *

- *

- * - * @param otherPlane the other plane to do the test with. Not modified. - * @param angleEpsilon tolerance on the angle in radians to determine if the plane normals are collinear. - * @param distanceEpsilon tolerance on the distance to determine if {@code otherPlane.point} belongs to this plane. - * @return {@code true} if the two planes are coincident, {@code false} otherwise. - */ - public boolean isCoincident(FramePlane3d otherPlane, double angleEpsilon, double distanceEpsilon) - { - checkReferenceFrameMatch(otherPlane); - return plane3d.isCoincident(otherPlane.plane3d, angleEpsilon, distanceEpsilon); - } - - public FramePoint3D orthogonalProjectionCopy(FramePoint3D point) - { - checkReferenceFrameMatch(point); - FramePoint3D returnPoint = new FramePoint3D(point); - orthogonalProjection(returnPoint); - - return returnPoint; - } - - public void orthogonalProjection(FramePoint3D point) - { - checkReferenceFrameMatch(point); - plane3d.orthogonalProjection(point); - } - - public double getZOnPlane(FramePoint2D xyPoint) - { - checkReferenceFrameMatch(xyPoint.getReferenceFrame()); - return plane3d.getZOnPlane(xyPoint.getX(), xyPoint.getY()); - } - - public double distance(FramePoint3D point) - { - checkReferenceFrameMatch(point); - return plane3d.distance(point); - } - - public boolean epsilonEquals(FramePlane3d plane, double epsilon) - { - checkReferenceFrameMatch(plane.getReferenceFrame()); - - return ((referenceFrame == plane.getReferenceFrame()) && (plane.plane3d.epsilonEquals(this.plane3d, epsilon))); - } - - public FramePlane3d applyTransformCopy(RigidBodyTransform transformation) - { - FramePlane3d returnPlane = new FramePlane3d(this); - returnPlane.applyTransform(transformation); - - return returnPlane; - } - - public void applyTransform(RigidBodyTransform transformation) - { - plane3d.applyTransform(transformation); - } - - public void setIncludingFrame(ReferenceFrame referenceFrame, double pointX, double pointY, double pointZ, double normalX, double normalY, double normalZ) - { - this.referenceFrame = referenceFrame; - plane3d.getPoint().set(pointX, pointY, pointZ); - plane3d.getNormal().set(normalX, normalY, normalZ); - } - - public void getIntersectionWithLine(FramePoint3D pointToPack, FrameLine3D line) - { - checkReferenceFrameMatch(line.getReferenceFrame()); - checkReferenceFrameMatch(pointToPack.getReferenceFrame()); - - Point3D intersectionToPack = new Point3D(); - plane3d.intersectionWith(intersectionToPack, line.getPoint(), line.getDirection()); - pointToPack.set(intersectionToPack); - } - - public void getIntersectionWithLine(Point3D pointToPack, Line3DReadOnly line) - { - plane3d.intersectionWith(pointToPack, line.getPoint(), line.getDirection()); - } - - public void setToZero() - { - getPoint().setToZero(); - getNormal().set(Axis3D.Z); - } - - public void setToZero(ReferenceFrame referenceFrame) - { - setReferenceFrame(referenceFrame); - setToZero(); - } - - public void setReferenceFrame(ReferenceFrame referenceFrame) - { - this.referenceFrame = referenceFrame; - } - - @Override - public String toString() - { - StringBuilder builder = new StringBuilder(); - builder.append("ReferenceFrame = " + referenceFrame + ", " + plane3d.toString()); - - return builder.toString(); - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/math/QuaternionCalculus.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/math/QuaternionCalculus.java deleted file mode 100644 index 8f3af913c..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/math/QuaternionCalculus.java +++ /dev/null @@ -1,243 +0,0 @@ -package us.ihmc.robotics.math; - -import us.ihmc.euclid.axisAngle.AxisAngle; -import us.ihmc.euclid.rotationConversion.QuaternionConversion; -import us.ihmc.euclid.tools.QuaternionTools; -import us.ihmc.euclid.tuple3D.Vector3D; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; -import us.ihmc.euclid.tuple4D.Quaternion; -import us.ihmc.euclid.tuple4D.Vector4D; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.Vector4DBasics; -import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; -import us.ihmc.commons.MathTools; - -public class QuaternionCalculus -{ - public QuaternionCalculus() - { - } - - private final AxisAngle axisAngleForLog = new AxisAngle(); - - /** - * This computes: resultToPack = log(q) - * @param q is a unit quaternion and describes a orientation. - * @param resultToPack is used to store the result and is a pure quaternion (w = 0.0). - */ - public void log(QuaternionReadOnly q, Vector4DBasics resultToPack) - { - axisAngleForLog.set(q); - resultToPack.set(axisAngleForLog.getX(), axisAngleForLog.getY(), axisAngleForLog.getZ(), 0.0); - resultToPack.scale(axisAngleForLog.getAngle()); - } - - /** - * This computes: resultToPack = log(q) - * @param q is a unit quaternion and describes a orientation. - * @param resultToPack is used to store the result. - */ - public void log(QuaternionReadOnly q, Vector3DBasics resultToPack) - { - axisAngleForLog.set(q); - resultToPack.set(axisAngleForLog.getX(), axisAngleForLog.getY(), axisAngleForLog.getZ()); - resultToPack.scale(axisAngleForLog.getAngle()); - } - - public void exp(Vector3DReadOnly rotationVector, QuaternionBasics quaternionToPack) - { - QuaternionConversion.convertRotationVectorToQuaternion(rotationVector, quaternionToPack); - } - - /** - * Interpolation from q0 to q1 for a given alpha = [0, 1] using SLERP. - * Computes: resultToPack = q0 (q0^-1 q1)^alpha. - */ - public void interpolate(double alpha, QuaternionReadOnly q0, QuaternionReadOnly q1, QuaternionBasics qInterpolatedToPack) - { - interpolate(alpha, q0, q1, qInterpolatedToPack, true); - } - - private final Quaternion tempQ1ForInterpolation = new Quaternion(); - - public void interpolate(double alpha, QuaternionReadOnly q0, QuaternionReadOnly q1, QuaternionBasics qInterpolatedToPack, boolean preventExtraSpin) - { - tempQ1ForInterpolation.set(q1); - - if (preventExtraSpin && q0.dot(tempQ1ForInterpolation) < 0.0) - { - tempQ1ForInterpolation.negate(); - } - - computeQuaternionDifference(q0, tempQ1ForInterpolation, qInterpolatedToPack); - pow(qInterpolatedToPack, alpha, qInterpolatedToPack); - qInterpolatedToPack.multiply(q0, qInterpolatedToPack); - } - - private final AxisAngle axisAngleForPow = new AxisAngle(); - - /** - * This computes: resultToPack = q^power. - * @param q is a unit quaternion and describes a orientation. - * @param resultToPack is used to store the result. - */ - public void pow(QuaternionReadOnly q, double power, QuaternionBasics resultToPack) - { - axisAngleForPow.set(q); - axisAngleForPow.setAngle(power * axisAngleForPow.getAngle()); - resultToPack.set(axisAngleForPow); - } - - private final Quaternion qConj = new Quaternion(); - - public void computeAngularVelocityInBodyFixedFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DBasics angularVelocityToPack) - { - qConj.setAndConjugate(q); - multiply(qConj, qDot, angularVelocityToPack); - angularVelocityToPack.scale(2.0); - } - - public void computeAngularVelocityInWorldFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DBasics angularVelocityToPack) - { - qConj.setAndConjugate(q); - multiply(qDot, qConj, angularVelocityToPack); - angularVelocityToPack.scale(2.0); - } - - public void computeQDotInWorldFrame(QuaternionReadOnly q, Vector3DReadOnly angularVelocityInWorld, Vector4DBasics qDotToPack) - { - multiply(angularVelocityInWorld, q, qDotToPack); - qDotToPack.scale(0.5); - } - - public void computeQDotInBodyFixedFrame(QuaternionReadOnly q, Vector3DReadOnly angularVelocityInBody, Vector4DBasics qDotToPack) - { - multiply(q, angularVelocityInBody, qDotToPack); - qDotToPack.scale(0.5); - } - - private final Vector4D intermediateQDot = new Vector4D(); - private final Vector4D qDotConj = new Vector4D(); - private final Vector3D intermediateAngularAcceleration = new Vector3D(); - private final Vector3D intermediateAngularVelocity = new Vector3D(); - private final Vector4D intermediateQDDot = new Vector4D(); - - public void computeQDDotInWorldFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DReadOnly angularAcceleration, Vector4DBasics qDDotToPack) - { - computeAngularVelocityInWorldFrame(q, qDot, intermediateAngularVelocity); - computeQDDotInWorldFrame(q, qDot, intermediateAngularVelocity, angularAcceleration, qDDotToPack); - } - - public void computeQDDotInWorldFrame(QuaternionReadOnly q, Vector3DReadOnly angularVelocityInWorld, Vector3DReadOnly angularAccelerationInWorld, Vector4DBasics qDDotToPack) - { - computeQDotInWorldFrame(q, angularVelocityInWorld, intermediateQDot); - computeQDDotInWorldFrame(q, intermediateQDot, angularVelocityInWorld, angularAccelerationInWorld, qDDotToPack); - } - - public void computeQDDotInWorldFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DReadOnly angularVelocity, Vector3DReadOnly angularAcceleration, Vector4DBasics qDDotToPack) - { - multiply(angularAcceleration, q, intermediateQDDot); - multiply(angularVelocity, qDot, qDDotToPack); - qDDotToPack.add(intermediateQDDot); - qDDotToPack.scale(0.5); - } - - public void computeAngularAccelerationInWorldFrame(QuaternionReadOnly q, Vector4DReadOnly qDDot, Vector3DReadOnly angularVelocityInWorld, Vector3DBasics angularAccelerationToPack) - { - computeQDotInWorldFrame(q, angularVelocityInWorld, intermediateQDot); - computeAngularAcceleration(q, intermediateQDot, qDDot, angularAccelerationToPack); - } - - public void computeAngularAcceleration(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector4DReadOnly qDDot, Vector3DBasics angularAccelerationToPack) - { - qConj.setAndConjugate(q); - qDotConj.set(-qDot.getX(), -qDot.getY(), -qDot.getZ(), qDot.getS()); - multiply(qDot, qDotConj, intermediateAngularAcceleration); - multiply(qDDot, qConj, angularAccelerationToPack); - angularAccelerationToPack.add(intermediateAngularAcceleration); - angularAccelerationToPack.scale(2.0); - } - - public void computeQDotByFiniteDifferenceCentral(QuaternionReadOnly qPrevious, QuaternionReadOnly qNext, double dt, Vector4DBasics qDotToPack) - { - qDotToPack.set(qNext); - qDotToPack.sub(qPrevious); - qDotToPack.scale(0.5 / dt); - } - - public void computeQDDotByFiniteDifferenceCentral(QuaternionReadOnly qPrevious, QuaternionReadOnly q, QuaternionReadOnly qNext, double dt, Vector4DBasics qDDotToPack) - { - qDDotToPack.set(qNext); - qDDotToPack.sub(q); - qDDotToPack.sub(q); - qDDotToPack.add(qPrevious); - qDDotToPack.scale(1.0 / MathTools.square(dt)); - } - - private final Quaternion qInv = new Quaternion(); - - /** - * This computes the product: resultToPack = (q0^-1 q1) - */ - public void computeQuaternionDifference(QuaternionReadOnly q0, QuaternionReadOnly q1, QuaternionBasics resultToPack) - { - resultToPack.setAndConjugate(q0); - resultToPack.multiply(q1); - } - - private final Vector4D pureQuatForMultiply = new Vector4D(); - - public void multiply(QuaternionReadOnly q, Vector3DReadOnly v, Vector4DBasics resultToPack) - { - setAsPureQuaternion(v, resultToPack); - QuaternionTools.multiply(q, resultToPack, resultToPack); - } - - public void multiply(Vector3DReadOnly v, Vector4DReadOnly q, Vector4DBasics resultToPack) - { - setAsPureQuaternion(v, resultToPack); - QuaternionTools.multiply(resultToPack, q, resultToPack); - } - - public void multiply(Vector3DReadOnly v, QuaternionReadOnly q, Vector4DBasics resultToPack) - { - setAsPureQuaternion(v, resultToPack); - QuaternionTools.multiply(resultToPack, q, resultToPack); - } - - public void multiply(Vector4DReadOnly q1, Vector4DReadOnly q2, Vector3DBasics resultToPack) - { - QuaternionTools.multiply(q1, q2, pureQuatForMultiply); - setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); - } - - public void multiply(Vector4DReadOnly q1, QuaternionReadOnly q2, Vector3DBasics resultToPack) - { - QuaternionTools.multiply(q1, q2, pureQuatForMultiply); - setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); - } - - public void multiply(QuaternionReadOnly q1, Vector4DReadOnly q2, Vector3DBasics resultToPack) - { - QuaternionTools.multiply(q1, q2, pureQuatForMultiply); - setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); - } - - public void inverseMultiply(QuaternionReadOnly q1, QuaternionReadOnly q2, QuaternionBasics resultToPack) - { - qInv.setAndConjugate(q1); - resultToPack.multiply(qInv, q2); - } - - public void setAsPureQuaternion(Vector3DReadOnly vector, Vector4DBasics pureQuaternionToSet) - { - pureQuaternionToSet.set(vector.getX(), vector.getY(), vector.getZ(), 0.0); - } - - public void setVectorFromPureQuaternion(Vector4DReadOnly pureQuaternion, Vector3DBasics vectorToPack) - { - vectorToPack.set(pureQuaternion.getX(), pureQuaternion.getY(), pureQuaternion.getZ()); - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/Pose2dReferenceFrame.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/Pose2dReferenceFrame.java deleted file mode 100644 index 1ce6a4f86..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/Pose2dReferenceFrame.java +++ /dev/null @@ -1,80 +0,0 @@ -package us.ihmc.robotics.referenceFrames; - -import us.ihmc.euclid.referenceFrame.FrameOrientation2D; -import us.ihmc.euclid.referenceFrame.FramePoint2D; -import us.ihmc.euclid.referenceFrame.FramePose2D; -import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.transform.RigidBodyTransform; -import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; - -public class Pose2dReferenceFrame extends ReferenceFrame -{ - private final FramePose2D originPose; - - public Pose2dReferenceFrame(String frameName, ReferenceFrame parentFrame) - { - super(frameName, parentFrame); - - originPose = new FramePose2D(parentFrame); - } - - public Pose2dReferenceFrame(String frameName, FramePose2D pose) - { - this(frameName, pose.getReferenceFrame()); - setPoseAndUpdate(pose); - } - - @Override - protected void updateTransformToParent(RigidBodyTransform transformToParent) - { - originPose.get(transformToParent); - } - - public void setPositionAndUpdate(FramePoint2D framePoint) - { - framePoint.checkReferenceFrameMatch(getParent()); - originPose.getPosition().set(framePoint); - this.update(); - } - - public void setOrientationAndUpdate(FrameOrientation2D frameOrientation) - { - frameOrientation.checkReferenceFrameMatch(getParent()); - originPose.getOrientation().set(frameOrientation); - this.update(); - } - - public void setPoseAndUpdate(Point2DReadOnly position, double orientation) - { - originPose.getPosition().set(position); - originPose.getOrientation().setYaw(orientation); - this.update(); - } - - public void setPoseAndUpdate(FramePose2D pose) - { - originPose.set(pose); - this.update(); - } - - public void setPoseAndUpdate(FramePoint2D position, FrameOrientation2D orientation) - { - position.changeFrame(originPose.getReferenceFrame()); - originPose.getPosition().set(position); - - orientation.changeFrame(originPose.getReferenceFrame()); - originPose.getOrientation().set(orientation); - this.update(); - } - - public FramePose2D getPoseCopy() - { - return new FramePose2D(originPose); - } - - @Override - public String toString() - { - return super.toString() + ", originPose = " + originPose; - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/PoseReferenceFrame.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/PoseReferenceFrame.java deleted file mode 100644 index fdb1d879b..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/PoseReferenceFrame.java +++ /dev/null @@ -1,274 +0,0 @@ -package us.ihmc.robotics.referenceFrames; - -import us.ihmc.euclid.geometry.interfaces.Pose3DReadOnly; -import us.ihmc.euclid.matrix.RotationMatrix; -import us.ihmc.euclid.orientation.interfaces.Orientation3DBasics; -import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; -import us.ihmc.euclid.referenceFrame.FramePoint2D; -import us.ihmc.euclid.referenceFrame.FramePoint3D; -import us.ihmc.euclid.referenceFrame.FramePose2D; -import us.ihmc.euclid.referenceFrame.FramePose3D; -import us.ihmc.euclid.referenceFrame.FrameQuaternion; -import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FramePose3DReadOnly; -import us.ihmc.euclid.transform.RigidBodyTransform; -import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly; -import us.ihmc.euclid.tuple3D.Point3D; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; - -public class PoseReferenceFrame extends ReferenceFrame -{ - private final FramePose3D originPose; - - public PoseReferenceFrame(String frameName, ReferenceFrame parentFrame) - { - super(frameName, parentFrame, parentFrame.isAStationaryFrame(), false); - - originPose = new FramePose3D(parentFrame); - } - - public PoseReferenceFrame(String frameName, FramePose3DReadOnly pose) - { - this(frameName, pose.getReferenceFrame()); - setPoseAndUpdate(pose); - } - - public boolean containsNaN() - { - return originPose.containsNaN(); - } - - public void setX(double x) - { - originPose.setX(x); - } - - public void setY(double y) - { - originPose.setY(y); - } - - public void setZ(double z) - { - originPose.setZ(z); - } - - public void setPoseAndUpdate(FramePose3DReadOnly pose) - { - originPose.set(pose); - this.update(); - } - - public void setPoseAndUpdate(PoseReferenceFrame poseReferenceFrame) - { - originPose.set(poseReferenceFrame.originPose); - this.update(); - } - - public void setPoseAndUpdate(FramePoint3DReadOnly position, FrameOrientation3DReadOnly orientation) - { - originPose.set(position, orientation); - this.update(); - } - - public void setPoseAndUpdate(Point3DReadOnly position, Orientation3DReadOnly orientation) - { - originPose.set(position, orientation); - this.update(); - } - - public void setPoseAndUpdate(Pose3DReadOnly pose) - { - originPose.set(pose); - this.update(); - } - - public void setPoseAndUpdate(RigidBodyTransformReadOnly transformToParent) - { - originPose.set(transformToParent); - this.update(); - } - - public void setPositionWithoutChecksAndUpdate(Point3DReadOnly position) - { - originPose.getPosition().set(position); - this.update(); - } - - public void setPositionWithoutChecksAndUpdate(double x, double y, double z) - { - originPose.getPosition().set(x, y, z); - this.update(); - } - - public void setPositionAndUpdate(FramePoint3DReadOnly framePoint) - { - framePoint.checkReferenceFrameMatch(getParent()); - originPose.getPosition().set(framePoint); - this.update(); - } - - public void setOrientationAndUpdate(Orientation3DReadOnly orientation3D) - { - originPose.getOrientation().set(orientation3D); - this.update(); - } - - public void setOrientationAndUpdate(double qx, double qy, double qz, double qs) - { - originPose.getOrientation().set(qx, qy, qz, qs); - update(); - } - - public void setOrientationAndUpdate(FrameOrientation3DReadOnly frameOrientation) - { - frameOrientation.checkReferenceFrameMatch(getParent()); - originPose.getOrientation().set(frameOrientation); - this.update(); - } - - public void setXYFromPosition2dAndUpdate(FramePoint2DReadOnly position2d) - { - position2d.checkReferenceFrameMatch(getParent()); - originPose.getPosition().set(position2d); - this.update(); - } - - public void translateAndUpdate(double x, double y, double z) - { - originPose.prependTranslation(x, y, z); - this.update(); - } - - public double getX() - { - return originPose.getX(); - } - - public double getY() - { - return originPose.getY(); - } - - public double getZ() - { - return originPose.getZ(); - } - - public double getYaw() - { - return originPose.getYaw(); - } - - public double getPitch() - { - return originPose.getPitch(); - } - - public double getRoll() - { - return originPose.getRoll(); - } - - public void getPose(Point3D pointToPack, Orientation3DBasics quaternionToPack) - { - originPose.get(pointToPack, quaternionToPack); - } - - /** - * Same as the inherited method getTransformToParent(Transform3D) from ReferenceFrame, it is just for readability. - * @param transformToPack - */ - public void getPose(RigidBodyTransform transformToPack) - { - getTransformToParent(transformToPack); - } - - public void getPoseIncludingFrame(FramePoint3D framePointToPack, FrameOrientation3DBasics frameOrientationToPack) - { - originPose.get(framePointToPack, frameOrientationToPack); - } - - public void getPoseIncludingFrame(FramePose3D framePoseToPack) - { - framePoseToPack.setIncludingFrame(originPose); - } - - public Point3DReadOnly getPosition() - { - return originPose.getPosition(); - } - - public void getPosition(Point3D pointToPack) - { - pointToPack.set(originPose.getPosition()); - } - - public void getPositionIncludingFrame(FramePoint3D framePointToPack) - { - framePointToPack.setIncludingFrame(originPose.getPosition()); - } - - public QuaternionReadOnly getOrientation() - { - return originPose.getOrientation(); - } - - public void getOrientation(Orientation3DBasics quaternionToPack) - { - quaternionToPack.set(originPose.getOrientation()); - } - - public void getOrientation(RotationMatrix matrixToPack) - { - matrixToPack.set(originPose.getOrientation()); - } - - public void getOrientationIncludingFrame(FrameQuaternion frameOrientationToPack) - { - frameOrientationToPack.setIncludingFrame(originPose.getOrientation()); - } - - public void getPose2dIncludingFrame(FramePose2D framePose2dToPack) - { - framePose2dToPack.setIncludingFrame(originPose); - } - - public void getPosition2dIncludingFrame(FramePoint2D framePoint2dToPack) - { - framePoint2dToPack.setIncludingFrame(originPose.getPosition()); - } - - public void interpolate(FramePose3D framePose1, FramePose3D framePose2, double alpha) - { - originPose.interpolate(framePose1, framePose2, alpha); - } - - public void interpolate(PoseReferenceFrame poseReferenceFrame1, PoseReferenceFrame poseReferenceFrame2, double alpha) - { - originPose.interpolate(poseReferenceFrame1.originPose, poseReferenceFrame2.originPose, alpha); - } - - public boolean epsilonEquals(PoseReferenceFrame otherPoseReferenceFrame, double epsilon) - { - return originPose.epsilonEquals(otherPoseReferenceFrame.originPose, epsilon); - } - - @Override - protected void updateTransformToParent(RigidBodyTransform transformToParent) - { - originPose.checkReferenceFrameMatch(getParent()); - originPose.get(transformToParent); - } - - @Override - public String toString() - { - return super.toString() + ", originPose = " + originPose; - } -} diff --git a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/TranslationReferenceFrame.java b/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/TranslationReferenceFrame.java deleted file mode 100644 index a0c8c509d..000000000 --- a/ihmc-robotics-toolkit/src/main/java/us/ihmc/robotics/referenceFrames/TranslationReferenceFrame.java +++ /dev/null @@ -1,38 +0,0 @@ -package us.ihmc.robotics.referenceFrames; - -import us.ihmc.euclid.referenceFrame.FrameVector3D; -import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; -import us.ihmc.euclid.transform.RigidBodyTransform; -import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; - -public class TranslationReferenceFrame extends ReferenceFrame -{ - public final FrameVector3D originVector; - - public TranslationReferenceFrame(String frameName, ReferenceFrame parentFrame) - { - super(frameName, parentFrame, false, parentFrame.isZupFrame()); - - originVector = new FrameVector3D(parentFrame); - } - - @Override - protected void updateTransformToParent(RigidBodyTransform transformToParent) - { - transformToParent.setIdentity(); - transformToParent.getTranslation().set(originVector); - } - - public void updateTranslation(FrameTuple3DReadOnly frameVector) - { - originVector.set(frameVector); - this.update(); - } - - public void updateTranslation(Tuple3DReadOnly translation) - { - originVector.set(translation); - this.update(); - } -} diff --git a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/FramePlane3D.java b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/FramePlane3D.java new file mode 100644 index 000000000..87f18d735 --- /dev/null +++ b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/FramePlane3D.java @@ -0,0 +1,175 @@ +package us.ihmc.euclid.referenceFrame; + +import us.ihmc.euclid.Axis3D; +import us.ihmc.euclid.geometry.interfaces.Plane3DBasics; +import us.ihmc.euclid.geometry.interfaces.Plane3DReadOnly; +import us.ihmc.euclid.interfaces.Settable; +import us.ihmc.euclid.referenceFrame.interfaces.*; +import us.ihmc.euclid.referenceFrame.tools.EuclidFrameFactories; +import us.ihmc.euclid.tools.EuclidCoreIOTools; +import us.ihmc.euclid.tools.EuclidHashCodeTools; +import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; + +/** + * {@link FramePlane3D} is a 3D plane expressed in a given reference frame. + *

+ * In addition to representing a {@link Plane3DBasics}, a {@link ReferenceFrame} is associated to a + * {@link FramePlane3D}. This allows, for instance, to enforce, at runtime, that operations on planes + * occur in the same coordinate system. Also, via the method {@link #changeFrame(ReferenceFrame)}, + * one can easily calculates the value of a point in different reference frames. + *

+ *

+ * Because a {@link FramePlane3D} extends {@link Plane3DBasics}, it is compatible with methods only + * requiring {@link Plane3DBasics}. However, these methods do NOT assert that the operation occur in + * the proper coordinate system. Use this feature carefully and always prefer using methods + * requiring {@link FramePlane3D}. + *

+ */ +public class FramePlane3D implements FramePlane3DBasics, Settable +{ + /** The reference frame in which this plane is expressed. */ + private ReferenceFrame referenceFrame; + private final FixedFramePoint3DBasics point = EuclidFrameFactories.newFixedFramePoint3DBasics(this); + private final FixedFrameUnitVector3DBasics normal = EuclidFrameFactories.newFixedFrameUnitVector3DBasics(this, Axis3D.Z); + + /** + * Default constructor that initializes its {@code point} to zero, its {@code normal} to + * {@link Axis3D#Z} and the reference frame to {@code ReferenceFrame.getWorldFrame()}. + */ + public FramePlane3D() + { + setToZero(ReferenceFrame.getWorldFrame()); + } + + /** + * Creates a new plane and initializes both {@link #point} and {@link #normal} to zero and the + * reference frame to the given {@code referenceFrame}. + * + * @param referenceFrame the initial reference frame for this plane. + */ + public FramePlane3D(ReferenceFrame referenceFrame) + { + setToZero(referenceFrame); + } + + /** + * Creates a new plane and initializes both {@link #point} and {@link #normal} to the {@param plane3D} and the + * reference frame to the given {@param referenceFrame}. + * + * @param referenceFrame the initial reference frame for this plane. + * @param plane3D the initial value for this plane. + */ + public FramePlane3D(ReferenceFrame referenceFrame, Plane3DReadOnly plane3D) + { + setIncludingFrame(referenceFrame, plane3D); + } + + /** + * Creates a new plane and initializes both {@link #point} and {@link #normal} to the {@param plane3D} and the + * reference frame to the given {@param plane3D}. + * + * @param framePlane3D the initial value and frame for this plane. + */ + public FramePlane3D(FramePlane3DReadOnly framePlane3D) + { + setIncludingFrame(framePlane3D); + } + + /** + * Creates a new plane and initializes both {@link #point} and {@link #normal} to the {@param point} and {@param normal} frame and value. + * + * @param point the initial point for this plane. + * @param normal the initial normal for this plane. + * @throws us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException if the reference frames of {@param point} and {@param normal} are not the same. + */ + public FramePlane3D(FramePoint3DReadOnly point, FrameVector3DReadOnly normal) + { + setIncludingFrame(point, normal); + } + + /** + * Creates a new plane and initializes both {@link #point} and {@link #normal} to the {@param point} and {@param normal} frame and value. + * + * @param referenceFrame the initial + * @param point the initial point for this plane. + * @param normal the initial normal for this plane. + */ + public FramePlane3D(ReferenceFrame referenceFrame, Point3DReadOnly point, Vector3DReadOnly normal) + { + setIncludingFrame(referenceFrame, point, normal); + } + + /** {@inheritDoc} */ + @Override + public void set(FramePlane3D other) + { + FramePlane3DBasics.super.set(other); + } + + /** {@inheritDoc} */ + @Override + public void setReferenceFrame(ReferenceFrame referenceFrame) + { + this.referenceFrame = referenceFrame; + } + + /** {@inheritDoc} */ + @Override + public FixedFramePoint3DBasics getPoint() + { + return point; + } + + /** {@inheritDoc} */ + @Override + public FixedFrameUnitVector3DBasics getNormal() + { + return normal; + } + + /** {@inheritDoc} */ + @Override + public ReferenceFrame getReferenceFrame() + { + return referenceFrame; + } + + /** + * Tests if the given {@code object}'s class is the same as this, in which case the method returns + * {@link #equals(EuclidFrameGeometry)}, it returns {@code false} otherwise. + * + * @param object the object to compare against this. Not modified. + * @return {@code true} if {@code object} and this are exactly equal, {@code false} otherwise. + */ + @Override + public boolean equals(Object object) + { + if (object instanceof FramePlane3DReadOnly) + return equals((EuclidFrameGeometry) object); + else + return false; + } + + /** + * Provides a {@code String} representation of this frame plane 3D as follows:
+ * Plane 3D: point = (x, y, z), normal = (x, y, z)-worldFrame + * + * @return the {@code String} representing this plane 3D. + */ + @Override + public String toString() + { + return toString(EuclidCoreIOTools.DEFAULT_FORMAT); + } + /** + * Calculates and returns a hash code value from the value of each component of this plane 3D. + * + * @return the hash code value for this plane 3D. + */ + @Override + public int hashCode() + { + return EuclidHashCodeTools.toIntHashCode(point, normal); + } +} diff --git a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FixedFramePlane3DBasics.java b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FixedFramePlane3DBasics.java new file mode 100644 index 000000000..66389088a --- /dev/null +++ b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FixedFramePlane3DBasics.java @@ -0,0 +1,103 @@ +package us.ihmc.euclid.referenceFrame.interfaces; + +import us.ihmc.euclid.geometry.interfaces.Plane3DBasics; +import us.ihmc.euclid.geometry.interfaces.Plane3DReadOnly; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; + +public interface FixedFramePlane3DBasics extends FramePlane3DReadOnly, Plane3DBasics +{ + @Override + FixedFramePoint3DBasics getPoint(); + + @Override + FixedFrameUnitVector3DBasics getNormal(); + + /** + * Redefines this plane with a new point and a new normal. + * + * @param referenceFrame the reference frame in which the given plane is expressed. + * @param pointOnPlaneX the new x-coordinate of the point on this plane. + * @param pointOnPlaneY the new y-coordinate of the point on this plane. + * @param pointOnPlaneZ the new z-coordinate of the point on this plane. + * @param planeNormalX the new x-component of the normal of this plane. + * @param planeNormalY the new y-component of the normal of this plane. + * @param planeNormalZ the new z-component of the normal of this plane. + */ + default void set(ReferenceFrame referenceFrame, double pointOnPlaneX, double pointOnPlaneY, double pointOnPlaneZ, double planeNormalX, double planeNormalY, double planeNormalZ) + { + checkReferenceFrameMatch(referenceFrame); + set(pointOnPlaneX, pointOnPlaneY, pointOnPlaneZ, planeNormalX, planeNormalY, planeNormalZ); + } + + /** + * Sets this plane to be the same as the given plane. + * + * @param other the other plane to copy. Not modified. + */ + default void set(FramePlane3DReadOnly other) + { + checkReferenceFrameMatch(other.getReferenceFrame()); + Plane3DBasics.super.set(other); + } + + /** + * Sets this plane to be the same as the given plane, and then transforms the data to match the current frame {@link #getReferenceFrame()}. + * + * @param other the other plane to copy. Not modified. + */ + default void setMatchingFrame(FramePlane3DReadOnly other) + { + setMatchingFrame(other.getReferenceFrame(), other); + } + + /** + * Sets this plane to be the same as the given plane. + * + * @param referenceFrame the reference frame in which the given plane is expressed. + * @param other the other plane to copy. Not modified. + */ + default void set(ReferenceFrame referenceFrame, Plane3DReadOnly other) + { + checkReferenceFrameMatch(referenceFrame); + Plane3DBasics.super.set(other); + } + + /** + * Sets this plane to be the same as the given plane, and then transforms the data to match the current frame {@link #getReferenceFrame()}. + * + * @param referenceFrame the reference frame in which the given plane is expressed. + * @param other the other plane to copy. Not modified. + */ + default void setMatchingFrame(ReferenceFrame referenceFrame, Plane3DReadOnly other) + { + Plane3DBasics.super.set(other); + referenceFrame.transformFromThisToDesiredFrame(getReferenceFrame(), this); + } + + /** + * Redefines this plane such that it goes through the three given points. + * + * @param firstPointOnPlane first point on this plane. Not modified. + * @param secondPointOnPlane second point on this plane. Not modified. + * @param thirdPointOnPlane second point on this plane. Not modified. + * @return {@code true} if the method succeeded, {@code false} if the method failed to estimate the + * normal. + */ + default boolean set(FramePoint3DReadOnly firstPointOnPlane, FramePoint3DReadOnly secondPointOnPlane, FramePoint3DReadOnly thirdPointOnPlane) + { + checkReferenceFrameMatch(firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane); + return Plane3DBasics.super.set(firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane); + } + + /** + * Redefines this plane with a new point and a new normal. + * + * @param pointOnPlane new point on this plane. Not modified. + * @param planeNormal new normal of this plane. Not modified. + */ + default void set(FramePoint3DReadOnly pointOnPlane, FrameVector3DReadOnly planeNormal) + { + checkReferenceFrameMatch(pointOnPlane, planeNormal); + Plane3DBasics.super.set(pointOnPlane, planeNormal); + } +} diff --git a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DBasics.java b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DBasics.java new file mode 100644 index 000000000..2dd6b4003 --- /dev/null +++ b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DBasics.java @@ -0,0 +1,147 @@ +package us.ihmc.euclid.referenceFrame.interfaces; + +import us.ihmc.euclid.Axis3D; +import us.ihmc.euclid.geometry.interfaces.Plane3DReadOnly; +import us.ihmc.euclid.referenceFrame.ReferenceFrame; +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; + +public interface FramePlane3DBasics extends FixedFramePlane3DBasics, FrameChangeable +{ + /** + * Sets the reference frame of this plane 3D without updating or modifying its point or direction. + * + * @param referenceFrame the new reference frame for this frame plane 3D. + */ + @Override + void setReferenceFrame(ReferenceFrame referenceFrame); + + /** + * Sets the point of this plane to zero, its normal to {@link Axis3D#Z}, and sets the current + * reference frame to {@code referenceFrame}. + * + * @param referenceFrame the new reference frame to be associated with this plane 3D. + */ + default void setToZero(ReferenceFrame referenceFrame) + { + setReferenceFrame(referenceFrame); + setToZero(); + } + + /** + * Sets the point and direction parts of this plane 3D to {@link Double#NaN} and sets the current + * reference frame to {@code referenceFrame}. + * + * @param referenceFrame the new reference frame to be associated with this plane 3D. + */ + default void setToNaN(ReferenceFrame referenceFrame) + { + setReferenceFrame(referenceFrame); + setToNaN(); + } + + /** + * Redefines this plane with a new point, a new normal vector, and a new reference frame. + * + * @param referenceFrame the new reference frame for this frame plane. + * @param pointOnPlaneX the new x-coordinate of the point on this plane. + * @param pointOnPlaneY the new y-coordinate of the point on this plane. + * @param pointOnPlaneZ the new z-coordinate of the point on this plane. + * @param planeNormalX the new x-component of the normal of this plane. + * @param planeNormalY the new y-component of the normal of this plane. + * @param planeNormalZ the new z-component of the normal of this plane. + */ + default void setIncludingFrame(ReferenceFrame referenceFrame, + double pointOnPlaneX, + double pointOnPlaneY, + double pointOnPlaneZ, + double planeNormalX, + double planeNormalY, + double planeNormalZ) + { + setReferenceFrame(referenceFrame); + set(pointOnPlaneX, pointOnPlaneY, pointOnPlaneZ, planeNormalX, planeNormalY, planeNormalZ); + } + + /** + * Sets this plane to be the same as the given plane. + * + * @param referenceFrame the new reference frame for this frame plane. + * @param plane3DReadOnly the plane to copy. Not modified. + */ + default void setIncludingFrame(ReferenceFrame referenceFrame, Plane3DReadOnly plane3DReadOnly) + { + setReferenceFrame(referenceFrame); + set(plane3DReadOnly); + } + + /** + * Redefines this plane with a new point, a new normal vector, and a new reference frame. + * + * @param referenceFrame the new reference frame for this frame plane. + * @param pointOnPlane new point on this plane. Not modified. + * @param planeNormal new direction of this plane. Not modified. + */ + default void setIncludingFrame(ReferenceFrame referenceFrame, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) + { + setReferenceFrame(referenceFrame); + set(pointOnPlane, planeNormal); + } + + /** + * Redefines this plane such that it intersects the three given points in the given reference frame. + * + * @param referenceFrame the new reference frame for this frame plane. + * @param firstPointOnPlane first point on this plane. Not modified. + * @param secondPointOnPlane second point on this plane. Not modified. + * @param thirdPointOnPlane second point on this plane. Not modified. + */ + default void setIncludingFrame(ReferenceFrame referenceFrame, + Point3DReadOnly firstPointOnPlane, + Point3DReadOnly secondPointOnPlane, + Point3DReadOnly thirdPointOnPlane) + { + setReferenceFrame(referenceFrame); + set(firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane); + } + + /** + * Sets this plane to be the same as the given plane. + * + * @param other the other plane to copy. Not modified. + */ + default void setIncludingFrame(FramePlane3DReadOnly other) + { + setIncludingFrame(other.getReferenceFrame(), other); + } + + /** + * Redefines this plane with a new point, a new direction vector, and a new reference frame. + * + * @param pointOnPlane new point on this plane. Not modified. + * @param normalVector new direction of this plane. Not modified. + * @throws ReferenceFrameMismatchException if {@code pointOnPlane} and {@code normalVector} are not + * expressed in the same reference frame + */ + default void setIncludingFrame(FramePoint3DReadOnly pointOnPlane, FrameVector3DReadOnly normalVector) + { + pointOnPlane.checkReferenceFrameMatch(normalVector); + setIncludingFrame(pointOnPlane.getReferenceFrame(), pointOnPlane, normalVector); + } + + /** + * Redefines this plane such that it intersects the three given points in the given reference frame. + * + * @param firstPointOnPlane first point on this plane. Not modified. + * @param secondPointOnPlane second point on this plane. Not modified. + * @param thirdPointOnPlane second point on this plane. Not modified. + * @throws ReferenceFrameMismatchException if {@code firstPointOnPlane}, {@code secondPointOnPlane}, and {@code thirdPointOnPlane} + * are not expressed in the same reference frame + */ + default void setIncludingFrame(FramePoint3DReadOnly firstPointOnPlane, FramePoint3DReadOnly secondPointOnPlane, FramePoint3DReadOnly thirdPointOnPlane) + { + firstPointOnPlane.checkReferenceFrameMatch(secondPointOnPlane, thirdPointOnPlane); + setIncludingFrame(firstPointOnPlane.getReferenceFrame(), firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane); + } +} diff --git a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DReadOnly.java b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DReadOnly.java new file mode 100644 index 000000000..5453ad95d --- /dev/null +++ b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/interfaces/FramePlane3DReadOnly.java @@ -0,0 +1,300 @@ +package us.ihmc.euclid.referenceFrame.interfaces; + +import us.ihmc.euclid.geometry.interfaces.Plane3DReadOnly; +import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; +import us.ihmc.euclid.interfaces.EuclidGeometry; +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; + +public interface FramePlane3DReadOnly extends Plane3DReadOnly, EuclidFrameGeometry +{ + @Override + FramePoint3DReadOnly getPoint(); + + @Override + FrameUnitVector3DReadOnly getNormal(); + + /** + * Computes the minimum distance to the given 3D point and this plane. Frame of the point must be in this {@link #getReferenceFrame()}. If not, throws + * {@link ReferenceFrameMismatchException}. + * + * @param point - 3D point to compute the distance from the plane. Not modified. + * @return the minimum distance between the 3D point and this 3D plane. + */ + default double distance(FramePoint3DReadOnly point) + { + checkReferenceFrameMatch(point); + return distance((Point3DReadOnly) point); + } + + /** + * Computes the minimum signed distance to the given 3D point and this plane. Frame of the point must be in this {@link #getReferenceFrame()}. If not, throws + * {@link ReferenceFrameMismatchException}. The returned value is negative when the query is located below thep lane, positive otherwise. + * + * @param point - 3D point to compute the distance from the plane. Not modified. + * @return the minimum distance between the 3D point and this 3D plane. + */ + default double signedDistance(FramePoint3DReadOnly point) + { + checkReferenceFrameMatch(point); + return signedDistance((Point3DReadOnly) point); + } + + /** + * Computes the coordinates of the intersection between this plane and an infinitely long 3D line. + *

+ * Edge cases: + *

+ *

+ * + * @param line the line that may intersect this plane. Not modified. + * @param intersectionToPack point in which the coordinates of the intersection are stored. + * @return {@code true} if the method succeeds, {@code false} otherwise. + */ + default boolean intersectionWith(FrameLine3DReadOnly line, FramePoint3DBasics intersectionToPack) + { + checkReferenceFrameMatch(line, intersectionToPack); + return intersectionWith(intersectionToPack, line.getPoint(), line.getDirection()); + } + + /** + * Computes the coordinates of the intersection between this plane and an infinitely long 3D line. + *

+ * Edge cases: + *

+ *

+ * + * @param pointOnLine a point located on the line. Not modified. + * @param lineDirection the direction of the line. Not modified. + * @param intersectionToPack point in which the coordinates of the intersection are stored. + * @return {@code true} if the method succeeds, {@code false} otherwise. + */ + default boolean intersectionWith(FramePoint3DBasics intersectionToPack, FramePoint3DReadOnly pointOnLine, FrameVector3DReadOnly lineDirection) + { + checkReferenceFrameMatch(intersectionToPack, pointOnLine, lineDirection); + return EuclidGeometryTools.intersectionBetweenLine3DAndPlane3D(getPoint(), getNormal(), pointOnLine, lineDirection, intersectionToPack); + } + + /** + * Tests if this plane and the given plane are coincident: + * + *

+ * Edge cases: + *

+ *

+ * + * @param otherPlane the other plane to do the test with. Not modified. + * @param angleEpsilon tolerance on the angle in radians to determine if the plane normals are + * collinear. + * @param distanceEpsilon tolerance on the distance to determine if {@code otherPlane.point} belongs + * to this plane. + * @return {@code true} if the two planes are coincident, {@code false} otherwise. + * @throws IllegalArgumentException if angleEpsilon ∉ [0; pi/2] + */ + default boolean isCoincident(FramePlane3DReadOnly otherPlane, double angleEpsilon, double distanceEpsilon) + { + checkReferenceFrameMatch(otherPlane); + return EuclidGeometryTools.arePlane3DsCoincident(getPoint(), getNormal(), otherPlane.getPoint(), otherPlane.getNormal(), angleEpsilon, distanceEpsilon); + } + + /** + * Tests if the query point is located strictly on or above this plane. + *

+ * Above is defined as the side of the plane toward which the normal is pointing. + *

+ * + * @param pointToTest the coordinates of the query. Not modified. + * @return {@code true} if the query is strictly on or above this plane, {@code false} otherwise. + */ + default boolean isOnOrAbove(FramePoint3DReadOnly pointToTest) + { + return isOnOrAbove(pointToTest, 0.0); + } + + /** + * Tests if the query point is located on or above this plane given the tolerance {@code epsilon}. + *

+ * Above is defined as the side of the plane toward which the normal is pointing. + *

+ *

+ *

    + *
  1. if {@code epsilon == 0}, the query has to be either exactly on the plane or strictly above + * for this method to return {@code true}. + *
  2. if {@code epsilon > 0}, this method returns {@code true} if the query meets the requirements + * of 1., in addition, this method returns also {@code true} if the query is below the plane at a + * distance less or equal than {@code epsilon}. + *
  3. if {@code epsilon < 0}, this method returns {@code true} only if the query is above the plane + * and at a distance of at least {@code Math.abs(epsilon)}. + *
+ *

+ * + * @param pointToTest the coordinates of the query. Not modified. + * @param epsilon the tolerance to use for the test. + * @return {@code true} if the query is considered to be on or above this plane, {@code false} + * otherwise. + */ + default boolean isOnOrAbove(FramePoint3DReadOnly pointToTest, double epsilon) + { + checkReferenceFrameMatch(pointToTest); + return isOnOrAbove(pointToTest.getX(), pointToTest.getY(), pointToTest.getZ(), epsilon); + } + + /** + * Tests if the query point is located strictly on or below this plane. + *

+ * Below is defined as the side of the plane which the normal is pointing away from. + *

+ * + * @param pointToTest the coordinates of the query. Not modified. + * @return {@code true} if the query is strictly on or below this plane, {@code false} otherwise. + */ + default boolean isOnOrBelow(FramePoint3DReadOnly pointToTest) + { + return isOnOrBelow(pointToTest, 0.0); + } + + /** + * Tests if the query point is located on or below this plane given the tolerance {@code epsilon}. + *

+ * Below is defined as the side of the plane which the normal is pointing away from. + *

+ *

+ *

    + *
  1. if {@code epsilon == 0}, the query has to be either exactly on the plane or strictly below + * for this method to return {@code true}. + *
  2. if {@code epsilon > 0}, this method returns {@code true} if the query meets the requirements + * of 1., in addition, this method returns also {@code true} if the query is above the plane at a + * distance less or equal than {@code epsilon}. + *
  3. if {@code epsilon < 0}, this method returns {@code true} only if the query is below the plane + * and at a distance of at least {@code Math.abs(epsilon)}. + *
+ *

+ * + * @param pointToTest the coordinates of the query. Not modified. + * @param epsilon the tolerance to use for the test. + * @return {@code true} if the query is considered to be on or below this plane, {@code false} + * otherwise. + */ + default boolean isOnOrBelow(FramePoint3DReadOnly pointToTest, double epsilon) + { + checkReferenceFrameMatch(pointToTest); + return isOnOrBelow(pointToTest.getX(), pointToTest.getY(), pointToTest.getZ(), epsilon); + } + + + /** + * Tests if the two planes are parallel by testing if their normals are collinear. The latter is + * done given a tolerance on the angle between the two normal axes in the range ]0; pi/2[. + *

+ * Edge cases: + *

+ *

+ * + * @param otherPlane the other plane to do the test with. Not modified. + * @param angleEpsilon tolerance on the angle in radians. + * @return {@code true} if the two planes are parallel, {@code false} otherwise. + * @throws IllegalArgumentException if angleEpsilon ∉ [0; pi/2] + */ + default boolean isParallel(FramePlane3DReadOnly otherPlane, double angleEpsilon) + { + checkReferenceFrameMatch(otherPlane); + return EuclidGeometryTools.areVector3DsParallel(getNormal(), otherPlane.getNormal(), angleEpsilon); + } + + /** + * Computes the orthogonal projection of the given 3D point on this 3D plane. + * + * @param pointToProject the point to project on this plane. Modified. + * @return whether the method succeeded or not. + */ + default boolean orthogonalProjection(FramePoint3DBasics pointToProject) + { + checkReferenceFrameMatch(pointToProject); + return orthogonalProjection(pointToProject, pointToProject); + } + + /** + * Computes the orthogonal projection of the given 3D point on this 3D plane. + * + * @param pointToProject the point to compute the projection of. Not modified. + * @param projectionToPack point in which the projection of the point onto the plane is stored. + * Modified. + * @return whether the method succeeded or not. + */ + default boolean orthogonalProjection(FramePoint3DReadOnly pointToProject, FramePoint3DBasics projectionToPack) + { + checkReferenceFrameMatch(pointToProject, projectionToPack); + return EuclidGeometryTools.orthogonalProjectionOnPlane3D(pointToProject, getPoint(), getNormal(), projectionToPack); + } + + + /** + * {@inheritDoc} + *

+ * This method will return {@code false} if the two planes are physically the same but either the + * point or vector of each plane is different. For instance, if {@code this.point == other.point} + * and {@code this.normal == - other.normal}, the two planes are physically the same but this method + * returns {@code false}. + *

+ */ + @Override + default boolean epsilonEquals(EuclidFrameGeometry geometry, double epsilon) + { + Plane3DReadOnly.super.epsilonEquals(geometry, epsilon); + if (geometry == this) + return true; + if (geometry == null) + return false; + if (!(geometry instanceof FramePlane3DReadOnly other)) + return false; + if (getReferenceFrame() != other.getReferenceFrame()) + return false; + return other.getNormal().epsilonEquals(getNormal(), epsilon) && other.getPoint().epsilonEquals(getPoint(), epsilon); + } + + /** {@inheritDoc} */ + @Override + default boolean equals(EuclidGeometry geometry) + { + if (geometry == this) + return true; + if (geometry == null) + return false; + if (!(geometry instanceof FramePlane3DReadOnly other)) + return false; + if (getReferenceFrame() != other.getReferenceFrame()) + return false; + return getPoint().equals(other.getPoint()) && getNormal().equals(other.getNormal()); + } + + /** {@inheritDoc} */ + @Override + default boolean geometricallyEquals(EuclidGeometry geometry, double epsilon) + { + if (geometry == this) + return true; + if (geometry == null) + return false; + if (!(geometry instanceof FramePlane3DReadOnly other)) + return false; + if (getReferenceFrame() != other.getReferenceFrame()) + return false; + return isCoincident(other, epsilon, epsilon); + } + +} diff --git a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameShapeTools.java b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameShapeTools.java index 27c60a2e6..eefdf0c27 100644 --- a/src/frame-shape/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameShapeTools.java +++ b/src/frame-shape/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameShapeTools.java @@ -1,16 +1,15 @@ package us.ihmc.euclid.referenceFrame.tools; import us.ihmc.euclid.geometry.interfaces.BoundingBox3DBasics; +import us.ihmc.euclid.geometry.interfaces.Line3DBasics; +import us.ihmc.euclid.geometry.interfaces.Plane3DReadOnly; +import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; import us.ihmc.euclid.matrix.interfaces.RotationMatrixBasics; import us.ihmc.euclid.matrix.interfaces.RotationMatrixReadOnly; +import us.ihmc.euclid.referenceFrame.FrameVector3D; import us.ihmc.euclid.referenceFrame.ReferenceFrame; -import us.ihmc.euclid.referenceFrame.interfaces.FrameBox3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameCapsule3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameCylinder3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameEllipsoid3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameRamp3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameSphere3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameTorus3DReadOnly; +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.referenceFrame.interfaces.*; import us.ihmc.euclid.referenceFrame.polytope.interfaces.FrameConvexPolytope3DReadOnly; import us.ihmc.euclid.shape.convexPolytope.interfaces.ConvexPolytope3DReadOnly; import us.ihmc.euclid.shape.convexPolytope.interfaces.Vertex3DReadOnly; @@ -47,6 +46,273 @@ private EuclidFrameShapeTools() // Suppresses default constructor, ensuring non-instantiability. } + /** + * Returns the minimum XY distance between a 3D point and an infinitely long 3D line defined by two + * points. + *

+ * Edge cases: + *

+ *

+ *

+ * WARNING: the 3D arguments are projected onto the XY-plane to perform the actual computation in + * 2D. + *

+ * + * @param point the 3D point is projected onto the xy-plane. It's projection is used to + * compute the distance from the line. Not modified. + * @param firstPointOnLine the projection of this 3D onto the xy-plane refers to the first point on + * the 2D line. Not modified. + * @param secondPointOnLine the projection of this 3D onto the xy-plane refers to the second point + * one the 2D line. Not modified. + * @return the minimum distance between the 2D point and the 2D line. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static double distanceXYFromPoint3DToLine3D(FramePoint3DReadOnly point, FramePoint3DReadOnly firstPointOnLine, FramePoint3DReadOnly secondPointOnLine) + { + point.checkReferenceFrameMatch(firstPointOnLine); + point.checkReferenceFrameMatch(secondPointOnLine); + + double pointOnLineX = firstPointOnLine.getX(); + double pointOnLineY = firstPointOnLine.getY(); + double lineDirectionX = secondPointOnLine.getX() - firstPointOnLine.getX(); + double lineDirectionY = secondPointOnLine.getY() - firstPointOnLine.getY(); + return EuclidGeometryTools.distanceFromPoint2DToLine2D(point.getX(), point.getY(), pointOnLineX, pointOnLineY, lineDirectionX, lineDirectionY); + } + + /** + * Test if a given line segment intersects a given plane. + *

+ * Edge cases: + *

+ *

+ * + * @param pointOnPlane a point located on the plane. Not modified. + * @param planeNormal the normal of the plane. Not modified. + * @param lineSegmentStart first endpoint of the line segment. Not modified. + * @param lineSegmentEnd second endpoint of the line segment. Not modified. + * @return {@code true} if an intersection line segment - plane exists, {@code false} otherwise. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static boolean isLineSegmentIntersectingPlane(FramePoint3DReadOnly pointOnPlane, + FrameVector3DReadOnly planeNormal, + FramePoint3DReadOnly lineSegmentStart, + FramePoint3DReadOnly lineSegmentEnd) + { + pointOnPlane.checkReferenceFrameMatch(planeNormal, lineSegmentStart, lineSegmentEnd); + + return EuclidGeometryTools.doesLineSegment3DIntersectPlane3D(pointOnPlane, planeNormal, lineSegmentStart, lineSegmentEnd); + } + + /** + * Computes the minimum distance between a given point and a plane. + * + * @param point the 3D query. Not modified. + * @param pointOnPlane a point located on the plane. Not modified. + * @param planeNormal the normal of the plane. Not modified. + * @return the distance between the point and the plane. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static double distanceFromPointToPlane(FramePoint3DReadOnly point, FramePoint3DReadOnly pointOnPlane, FrameVector3DReadOnly planeNormal) + { + point.checkReferenceFrameMatch(pointOnPlane, planeNormal); + + return EuclidGeometryTools.distanceFromPoint3DToPlane3D(point, pointOnPlane, planeNormal); + } + + /** + * This methods calculates the line of intersection between two planes each defined by a point and a normal. The result is packed in a 3D point located on the intersection line and the 3D direction of the intersection. + * Useful link 1 , useful link 2 . + * Edge cases: + * When the length of either the plane normal is below ONE_TRILLIONTH, this methods fails and returns false. + * When the angle between the two planes is below ONE_MILLIONTH, this methods fails and returns false. + * When there is no intersection, this method returns false and pointOnIntersectionToPack and intersectionDirectionToPack are set to Double. NaN. + * + * Uses + * {@link EuclidGeometryTools#intersectionBetweenTwoPlane3Ds(Point3DReadOnly, Vector3DReadOnly, Point3DReadOnly, Vector3DReadOnly, Point3DBasics, Vector3DBasics)} + * + * @param plane1 first plane of which to compute the intersection + * @param plane2 second plane of which to compute the inersection + * @param intersectionToPack line of intersection between the two planes + * @return success (not parallel) + */ + public static boolean getIntersectionBetweenTwoPlanes(Plane3DReadOnly plane1, Plane3DReadOnly plane2, Line3DBasics intersectionToPack) + { + return EuclidGeometryTools.intersectionBetweenTwoPlane3Ds(plane1.getPoint(), + plane1.getNormal(), + plane2.getPoint(), + plane2.getNormal(), + intersectionToPack.getPoint(), + intersectionToPack.getDirection()); + } + + /** + * Computes the normal of a plane that is defined by three points. + *

+ * Edge cases: + *

+ *

+ *

+ * WARNING: This method generates garbage. + *

+ * + * @param firstPointOnPlane first point on the plane. Not modified. + * @param secondPointOnPlane second point on the plane. Not modified. + * @param thirdPointOnPlane third point on the plane. Not modified. + * @return the plane normal or {@code null} when the normal could not be determined. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static FrameVector3D getPlaneNormalGivenThreePoints(FramePoint3DReadOnly firstPointOnPlane, + FramePoint3DReadOnly secondPointOnPlane, + FramePoint3DReadOnly thirdPointOnPlane) + { + FrameVector3D normal = new FrameVector3D(); + boolean success = getPlaneNormalGivenThreePoints(firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane, normal); + if (!success) + return null; + else + return normal; + } + + /** + * Computes the normal of a plane that is defined by three points. + *

+ * Edge cases: + *

+ *

+ * + * @param firstPointOnPlane first point on the plane. Not modified. + * @param secondPointOnPlane second point on the plane. Not modified. + * @param thirdPointOnPlane third point on the plane. Not modified. + * @param normalToPack the vector in which the result is stored. Modified. + * @return whether the plane normal is properly determined. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame, except for {@code normalToPack}. + */ + public static boolean getPlaneNormalGivenThreePoints(FramePoint3DReadOnly firstPointOnPlane, + FramePoint3DReadOnly secondPointOnPlane, + FramePoint3DReadOnly thirdPointOnPlane, + FrameVector3DBasics normalToPack) + { + firstPointOnPlane.checkReferenceFrameMatch(secondPointOnPlane, thirdPointOnPlane); + + normalToPack.setToZero(firstPointOnPlane.getReferenceFrame()); + + return EuclidGeometryTools.normal3DFromThreePoint3Ds(firstPointOnPlane, secondPointOnPlane, thirdPointOnPlane, normalToPack); + } + + /** + * Computes the perpendicular defined by an infinitely long 3D line (defined by two 3D points) and a + * 3D point. To do so, the orthogonal projection of the {@code point} on line is first computed. The + * perpendicular vector is computed as follows: + * {@code perpendicularVector = point - orthogonalProjection}, resulting in a vector going from the + * computed projection to the given {@code point} with a length equal to the distance between the + * point and the line. + *

+ * Edge cases: + *

+ *

+ *

+ * WARNING: This method generates garbage. + *

+ * + * @param point the 3D point towards which the perpendicular vector should be + * pointing at. Not modified. + * @param firstPointOnLine a first point on the line. Not modified. + * @param secondPointOnLine a second point on the line. Not modified. + * @param orthogonalProjectionToPack a 3D point in which the projection of {@code point} onto the + * line is stored. Modified. Can be {@code null}. + * @return the vector perpendicular to the line and pointing to the {@code point}, or {@code null} + * when the method fails. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame, except for {@code orthogonalProjectionToPack}. + */ + public static FrameVector3D getPerpendicularVectorFromLineToPoint(FramePoint3DReadOnly point, + FramePoint3DReadOnly firstPointOnLine, + FramePoint3DReadOnly secondPointOnLine, + FramePoint3DBasics orthogonalProjectionToPack) + { + FrameVector3D perpendicularVector = new FrameVector3D(); + + boolean success = getPerpendicularVectorFromLineToPoint(point, firstPointOnLine, secondPointOnLine, orthogonalProjectionToPack, perpendicularVector); + if (!success) + return null; + else + return perpendicularVector; + } + + /** + * Computes the perpendicular defined by an infinitely long 3D line (defined by two 3D points) and a + * 3D point. To do so, the orthogonal projection of the {@code point} on line is first computed. The + * perpendicular vector is computed as follows: + * {@code perpendicularVector = point - orthogonalProjection}, resulting in a vector going from the + * computed projection to the given {@code point} with a length equal to the distance between the + * point and the line. + *

+ * Edge cases: + *

+ *

+ * + * @param point the 3D point towards which the perpendicular vector should be + * pointing at. Not modified. + * @param firstPointOnLine a first point on the line. Not modified. + * @param secondPointOnLine a second point on the line. Not modified. + * @param orthogonalProjectionToPack a 3D point in which the projection of {@code point} onto the + * line is stored. Modified. Can be {@code null}. + * @param perpendicularVectorToPack a 3D vector in which the vector perpendicular to the line and + * pointing to the {@code point} is stored. Modified. Can NOT be + * {@code null}. + * @return {@code true} if the method succeeded, {@code false} otherwise. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame, except for {@code orthogonalProjectionToPack} and + * {@code perpendicularVectorToPack}. + */ + public static boolean getPerpendicularVectorFromLineToPoint(FramePoint3DReadOnly point, + FramePoint3DReadOnly firstPointOnLine, + FramePoint3DReadOnly secondPointOnLine, + FramePoint3DBasics orthogonalProjectionToPack, + FrameVector3DBasics perpendicularVectorToPack) + { + point.checkReferenceFrameMatch(firstPointOnLine, secondPointOnLine); + perpendicularVectorToPack.setReferenceFrame(point.getReferenceFrame()); + + if (orthogonalProjectionToPack == null) + { + return EuclidGeometryTools.perpendicularVector3DFromLine3DToPoint3D(point, firstPointOnLine, secondPointOnLine, null, perpendicularVectorToPack); + } + else + { + orthogonalProjectionToPack.setReferenceFrame(point.getReferenceFrame()); + return EuclidGeometryTools.perpendicularVector3DFromLine3DToPoint3D(point, + firstPointOnLine, + secondPointOnLine, + orthogonalProjectionToPack, + perpendicularVectorToPack); + } + } + private static interface BoundingBoxRotationPartCalculator { void computeBoundingBoxZeroRotation(T shape, BoundingBox3DBasics boundingBoxToPack); diff --git a/src/frame/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrame.java b/src/frame/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrame.java new file mode 100644 index 000000000..cfa40d96e --- /dev/null +++ b/src/frame/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrame.java @@ -0,0 +1,183 @@ +package us.ihmc.euclid.referenceFrame; + +import us.ihmc.euclid.geometry.interfaces.Pose2DReadOnly; +import us.ihmc.euclid.orientation.interfaces.Orientation2DReadOnly; +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.referenceFrame.interfaces.*; +import us.ihmc.euclid.transform.RigidBodyTransform; +import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; + +/** + * This defines a mutable {@link ReferenceFrame}, where the pose of this frame relative to the parent {@link ReferenceFrame}, which is accessible as + * {@link #getParent()} can be updated via a variety of methods, but exists only in the 2D plane. + */ +public class Pose2DReferenceFrame extends ReferenceFrame +{ + /** + * The 2D pose of this frame relative to its parent. + */ + private final FramePose2D originPose; + + /** + *

+ * Creates a new pose 2D reference frame as a child of the given {@param parentFrame} and initializes the transform to its parent frame. + *

+ *

+ * This new reference frame is defined in the {@param parentFrame} and moves with it. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods outlined in this class, including setting + * the pose {@link #setPoseAndUpdate(FramePose2DReadOnly)}, setting the pose from a reference frame {@link #setPoseAndUpdate(Pose2DReferenceFrame)}, setting + * the translation {@link #setPositionAndUpdate(FramePoint2DReadOnly)}, or setting the orientation + * {@link #setOrientationAndUpdate(FrameOrientation2DReadOnly)}. + *

+ * + * @param frameName the name of the new frame. + * @param parentFrame the parent of the frame. It has to extend a {@link ReferenceFrame}. + */ + public Pose2DReferenceFrame(String frameName, ReferenceFrame parentFrame) + { + super(frameName, parentFrame); + + originPose = new FramePose2D(parentFrame); + } + + public Pose2DReferenceFrame(String frameName, FramePose2DReadOnly poseInParentFrame) + { + this(frameName, poseInParentFrame.getReferenceFrame()); + setPoseAndUpdate(poseInParentFrame); + } + + /** {@inheritDoc} */ + @Override + protected void updateTransformToParent(RigidBodyTransform transformToParent) + { + originPose.get(transformToParent); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in tuple {@param pose} must be defined as + * {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPoseAndUpdate(Pose2DReadOnly)}. + * + * @param pose pose of this frame relative to parent to set. + */ + public void setPoseAndUpdate(FramePose2DReadOnly pose) + { + pose.checkReferenceFrameMatch(getParent()); + setPoseAndUpdate((Pose2DReadOnly) pose); + } + + /** + * Sets the position of this reference frame relative to its parent. The frame of the passed in tuple {@param position} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPositionAndUpdate(Point2DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + */ + public void setPositionAndUpdate(FramePoint2DReadOnly position) + { + position.checkReferenceFrameMatch(getParent()); + setPositionAndUpdate((Point2DReadOnly) position); + } + + /** + * Sets the orientation of this reference frame relative to its parent. The frame of the passed in tuple {@param orientation} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setOrientationAndUpdate(Orientation2DReadOnly)}. + * + * @param orientation orientation of this frame relative to parent to set. + */ + public void setOrientationAndUpdate(FrameOrientation2DReadOnly orientation) + { + orientation.checkReferenceFrameMatch(getParent()); + setOrientationAndUpdate((Orientation2DReadOnly) orientation); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in tuples {@param position} and {@param orientation} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPoseAndUpdate(Pose2DReadOnly)} or {@link #setPoseAndUpdate(Point2DReadOnly, Orientation2DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + * @param orientation orientation of this frame relative to parent to set. + */ + public void setPoseAndUpdate(FramePoint2DReadOnly position, FrameOrientation2DReadOnly orientation) + { + position.checkReferenceFrameMatch(getParent()); + orientation.checkReferenceFrameMatch(getParent()); + setPoseAndUpdate((Point2DReadOnly) position, (Orientation2DReadOnly) orientation); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in {@param pose} must be defined as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see {@link #setPoseAndUpdate(FramePose2DReadOnly)}. + * + * @param pose pose of this frame relative to parent to set. + */ + public void setPoseAndUpdate(Pose2DReadOnly pose) + { + setPoseAndUpdate(pose.getPosition(), pose.getOrientation()); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in {@param position} and {@param orientation} must be defined as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see + * {@link #setPoseAndUpdate(FramePoint2DReadOnly, FrameOrientation2DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + * @param orientation orientation of this frame relative to parent to set. + */ + public void setPoseAndUpdate(Point2DReadOnly position, Orientation2DReadOnly orientation) + { + setPoseAndUpdate(position, orientation.getYaw()); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in {@param pose} must be defined as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see + * {@link #setPoseAndUpdate(FramePoint2DReadOnly, FrameOrientation2DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + * @param orientation orientation of this frame relative to parent to set. + */ + public void setPoseAndUpdate(Point2DReadOnly position, double orientation) + { + originPose.getPosition().set(position); + originPose.getOrientation().setYaw(orientation); + this.update(); + } + + /** + * Sets the position of this reference frame relative to its parent. This does not perform a frame check. For frame safe operations, please use + * {@link #setPositionAndUpdate(FramePoint2DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + */ + public void setPositionAndUpdate(Point2DReadOnly position) + { + originPose.getPosition().set(position); + this.update(); + } + + /** + * Sets the orientation of this reference frame relative to its parent. This does not perform a frame check. For frame safe operations, please use + * {@link #setOrientationAndUpdate(FrameOrientation2DReadOnly)}. + * + * @param orientation orientation of this frame relative to parent to set. + */ + public void setOrientationAndUpdate(Orientation2DReadOnly orientation) + { + originPose.getOrientation().set(orientation); + this.update(); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return super.toString() + ", originPose = " + originPose; + } +} diff --git a/src/frame/java/us/ihmc/euclid/referenceFrame/PoseReferenceFrame.java b/src/frame/java/us/ihmc/euclid/referenceFrame/PoseReferenceFrame.java new file mode 100644 index 000000000..4019432b3 --- /dev/null +++ b/src/frame/java/us/ihmc/euclid/referenceFrame/PoseReferenceFrame.java @@ -0,0 +1,335 @@ +package us.ihmc.euclid.referenceFrame; + +import us.ihmc.euclid.geometry.interfaces.Pose3DReadOnly; +import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FramePose3DReadOnly; +import us.ihmc.euclid.transform.RigidBodyTransform; +import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; + +/** + * This defines a mutable {@link ReferenceFrame}, where the pose of this frame relative to the parent {@link ReferenceFrame}, which is accessible as + * {@link #getParent()} can be updated via a variety of methods. + */ +public class PoseReferenceFrame extends ReferenceFrame +{ + /** + * The pose of this frame relative to its parent. + */ + private final FramePose3D originPose; + + /** + *

+ * Creates a new pose reference frame as a child of the given {@param parentFrame} and initializes the transform to its parent frame. + *

+ *

+ * This new reference frame is defined in the {@param parentFrame} and moves with it. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods outlined in this class, including setting + * the pose {@link #setPoseAndUpdate(FramePose3DReadOnly)}, setting the pose from a reference frame {@link #setPoseAndUpdate(PoseReferenceFrame)}, setting + * the translation {@link #setPositionAndUpdate(FramePoint3DReadOnly)}, setting the orientation {@link #setOrientationAndUpdate(FrameOrientation3DReadOnly)}, + * or setting the transform {@link #setTransformAndUpdate(RigidBodyTransformReadOnly)} + *

+ * + * @param frameName the name of the new frame. + * @param parentFrame the parent of the frame. It has to extend a {@link ReferenceFrame}. + */ + public PoseReferenceFrame(String frameName, ReferenceFrame parentFrame) + { + super(frameName, parentFrame, parentFrame.isAStationaryFrame(), false); + + originPose = new FramePose3D(parentFrame); + } + + /** + *

+ * Creates a new pose reference frame with a defined pose relative to the parent frame, both of which are defined by {@param poseInParentFrame}. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods outlined in this class, including setting + * the pose {@link #setPoseAndUpdate(FramePose3DReadOnly)}, setting the pose from a reference frame {@link #setPoseAndUpdate(PoseReferenceFrame)}, setting + * the translation {@link #setPositionAndUpdate(FramePoint3DReadOnly)}, setting the orientation {@link #setOrientationAndUpdate(FrameOrientation3DReadOnly)}, + * or setting the transform {@link #setTransformAndUpdate(RigidBodyTransformReadOnly)} + *

+ * + * @param frameName the name of the new frame. + * @param poseInParentFrame pose of the frame, with the parent frame provided by {@param poseInParentFrame.getParent()}. + */ + public PoseReferenceFrame(String frameName, FramePose3DReadOnly poseInParentFrame) + { + this(frameName, poseInParentFrame.getReferenceFrame()); + setPoseAndUpdate(poseInParentFrame); + } + + /** + *

+ * Creates a new pose reference frame as a child of the given {@param parentFrame} and initializes the transform to its parent frame. + *

+ *

+ * This new reference frame is defined with a transform relative to the parent {@param parentFrame} as transform {@param transformToParent}. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods outlined in this class, including setting + * the pose {@link #setPoseAndUpdate(FramePose3DReadOnly)}, setting the pose from a reference frame {@link #setPoseAndUpdate(PoseReferenceFrame)}, setting + * the translation {@link #setPositionAndUpdate(FramePoint3DReadOnly)}, setting the orientation {@link #setOrientationAndUpdate(FrameOrientation3DReadOnly)}, + * or setting the transform {@link #setTransformAndUpdate(RigidBodyTransformReadOnly)} + *

+ * + * @param frameName the name of the new frame. + * @param parentFrame the parent of the frame. It has to extend a {@link ReferenceFrame}. + */ + public PoseReferenceFrame(String frameName, ReferenceFrame parentFrame, RigidBodyTransformReadOnly transformToParent) + { + this(frameName, parentFrame); + setTransformAndUpdate(transformToParent); + } + + /** + * Sets the pose of this reference frame relative to its parent to match that of {@param poseReferenceFrame}. The parent frame of the passed in frame + * {@param poseReferenceFrame} must match this frame's. If it does not, this method will throw a {@link ReferenceFrameMismatchException}. + * + * @param poseReferenceFrame frame containing the pose to copy. + */ + public void setPoseAndUpdate(PoseReferenceFrame poseReferenceFrame) + { + getParent().checkReferenceFrameMatch(poseReferenceFrame.getParent()); + setPoseAndUpdate(poseReferenceFrame.originPose); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in tuple {@param pose} must be defined as + * {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPoseAndUpdate(Pose3DReadOnly)} + * + * @param pose pose of this frame relative to parent to set. + */ + public void setPoseAndUpdate(FramePose3DReadOnly pose) + { + getParent().checkReferenceFrameMatch(pose); + setPoseAndUpdate((Pose3DReadOnly) pose); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in tuples {@param position} and {@param orientation} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPoseAndUpdate(Pose3DReadOnly)} or {@link #setPoseAndUpdate(Point3DReadOnly, Orientation3DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + * @param orientation orientation of this frame relative to parent to set. + */ + public void setPoseAndUpdate(FramePoint3DReadOnly position, FrameOrientation3DReadOnly orientation) + { + position.checkReferenceFrameMatch(getParent()); + orientation.checkReferenceFrameMatch(getParent()); + setPoseAndUpdate((Point3DReadOnly) position, (Orientation3DReadOnly) orientation); + } + + /** + * Sets the position of this reference frame relative to its parent. The frame of the passed in tuple {@param position} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setPositionAndUpdate(Point3DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + */ + public void setPositionAndUpdate(FramePoint3DReadOnly position) + { + position.checkReferenceFrameMatch(getParent()); + setPositionAndUpdate((Point3DReadOnly) position); + } + + /** + * Sets the orientation of this reference frame relative to its parent. The frame of the passed in tuple {@param orientation} must be defined + * as {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setOrientationAndUpdate(Orientation3DReadOnly)}. + * + * @param orientation orientation of this frame relative to parent to set. + */ + public void setOrientationAndUpdate(FrameOrientation3DReadOnly orientation) + { + orientation.checkReferenceFrameMatch(getParent()); + setOrientationAndUpdate((Orientation3DReadOnly) orientation); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in {@param pose} must be defined as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see {@link #setPoseAndUpdate(FramePose3DReadOnly)}. + * + * @param pose pose of this frame relative to parent to set. + */ + public void setPoseAndUpdate(Pose3DReadOnly pose) + { + setPoseAndUpdate(pose.getPosition(), pose.getOrientation()); + } + + /** + * Sets the pose of this reference frame relative to its parent. The frame of the passed in {@param position} and {@param orientation} must be defined as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see + * {@link #setPoseAndUpdate(FramePoint3DReadOnly, FrameOrientation3DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + * @param orientation orientation of this frame relative to parent to set. + */ + public void setPoseAndUpdate(Point3DReadOnly position, Orientation3DReadOnly orientation) + { + originPose.set(position, orientation); + this.update(); + } + + /** + * Sets the transform of this reference frame relative to its parent to be {@param transform}. + * + * @param transformToParent transform relative to parent to set. + */ + public void setTransformAndUpdate(RigidBodyTransformReadOnly transformToParent) + { + originPose.set(transformToParent); + this.update(); + } + + /** + * Sets the position of this reference frame relative to its parent. This does not perform a frame check. For frame safe operations, please use + * {@link #setPositionAndUpdate(FramePoint3DReadOnly)}. + * + * @param position position of this frame relative to parent to set. + */ + public void setPositionAndUpdate(Point3DReadOnly position) + { + setPositionAndUpdate(position.getX(), position.getY(), position.getZ()); + } + + /** + * Sets the orientation of this reference frame relative to its parent. This does not perform a frame check. For frame safe operations, please use + * {@link #setOrientationAndUpdate(FrameOrientation3DReadOnly)}. + * + * @param orientation orientation of this frame relative to parent to set. + */ + public void setOrientationAndUpdate(Orientation3DReadOnly orientation) + { + originPose.getOrientation().set(orientation); + this.update(); + } + + /** + * Sets the position of this reference frame relative to its parent to the corresponding x, y, and z locations. + * + * @param x x position of this frame relative to parent to set. + * @param y y position of this frame relative to parent to set. + * @param z z position of this frame relative to parent to set. + */ + public void setPositionAndUpdate(double x, double y, double z) + { + originPose.getPosition().set(x, y, z); + this.update(); + } + + /** + * Sets the quaternion coordinations of this reference frame relative to its parent to the corresponding qx, qy, qz, and qs locations. + * + * @param qx quaternion value qx of this frame relative to parent to set. + * @param qy quaternion value qy of this frame relative to parent to set. + * @param qz quaternion value qz of this frame relative to parent to set. + * @param qs quaternion value qs of this frame relative to parent to set. + */ + public void setOrientationAndUpdate(double qx, double qy, double qz, double qs) + { + originPose.getOrientation().set(qx, qy, qz, qs); + update(); + } + + /** + * Prepends a translation to this reference frame, relative to its parent, of a magnitude of x, y, and z. + * + * @param x x translation to prepend relative to the parent. + * @param y y translation to prepend relative to the parent. + * @param z z translation to prepend relative to the parent. + */ + public void prependTranslationAndUpdate(double x, double y, double z) + { + originPose.prependTranslation(x, y, z); + this.update(); + } + + /** + * Returns the x translation of this frame relative to the parent frame. + * + * @return x translation + */ + public double getTranslationX() + { + return getPose().getX(); + } + + /** + * Returns the y translation of this frame relative to the parent frame. + * + * @return y translation + */ + public double getTranslationY() + { + return getPose().getY(); + } + + /** + * Returns the z translation of this frame relative to the parent frame. + * + * @return z translation + */ + public double getTranslationZ() + { + return getPose().getZ(); + } + + /** + * Returns the pose of this frame relative to the parent frame. + * + * @return pose of this frame relative to the parent. + */ + public Pose3DReadOnly getPose() + { + return originPose; + } + + /** + * Returns the translation of this frame relative to the parent frame. + * + * @return translation of this frame relative to the parent. + */ + public Point3DReadOnly getTranslation() + { + return getPose().getPosition(); + } + + /** + * Returns the orientation of this frame relative to the parent frame. + * + * @return orientation of this frame relative to the parent. + */ + public QuaternionReadOnly getOrientation() + { + return getPose().getOrientation(); + } + + /** + * {@inheritDoc} + */ + @Override + protected void updateTransformToParent(RigidBodyTransform transformToParentToPack) + { + originPose.checkReferenceFrameMatch(getParent()); + transformToParentToPack.set(originPose); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return super.toString() + ", originPose = " + originPose; + } +} diff --git a/src/frame/java/us/ihmc/euclid/referenceFrame/TranslationReferenceFrame.java b/src/frame/java/us/ihmc/euclid/referenceFrame/TranslationReferenceFrame.java new file mode 100644 index 000000000..9b4051cf7 --- /dev/null +++ b/src/frame/java/us/ihmc/euclid/referenceFrame/TranslationReferenceFrame.java @@ -0,0 +1,98 @@ +package us.ihmc.euclid.referenceFrame; + +import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; +import us.ihmc.euclid.referenceFrame.interfaces.FrameTuple3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly; +import us.ihmc.euclid.transform.RigidBodyTransform; +import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; + +/** + * This defines a mutable {@link ReferenceFrame}, where the translation to the parent {@link ReferenceFrame}, which is accessible as {@link #getParent()} can be + * updated via {@link #setTranslationAndUpdate(Tuple3DReadOnly)} or {@link #setTranslationAndUpdate(FrameTuple3DReadOnly)}. + */ +public class TranslationReferenceFrame extends ReferenceFrame +{ + /** + * The translation of this frame relative to its parent. + */ + public final FrameVector3D translationVector; + + /** + *

+ * Creates a new translation reference frame as a child of the given {@param parentFrame} and initializes the transform to its parent frame. + *

+ *

+ * This new reference frame is defined in the {@param parentFrame} and moves with it. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods + * {@link #setTranslationAndUpdate(FrameTuple3DReadOnly)} and {@link #setTranslationAndUpdate(Tuple3DReadOnly)}. + *

+ * + * @param frameName the name of the new frame. + * @param parentFrame the parent of the frame. It has to extend a {@link ReferenceFrame}. + */ + public TranslationReferenceFrame(String frameName, ReferenceFrame parentFrame) + { + super(frameName, parentFrame, false, parentFrame.isZupFrame()); + + translationVector = new FrameVector3D(parentFrame); + } + + /** + *

+ * Creates a new translation reference frame as a child of the given {@param parentFrame} and initializes the transform to its parent frame. + *

+ *

+ * This new reference frame is defined with a constant translation of {@param translationToParent} relative to {@param parentFrame} and moves with it. + *

+ *

+ * Its pose with respect to the parent frame can be modified at runtime by changing the transform in the methods + * {@link #setTranslationAndUpdate(FrameTuple3DReadOnly)} and {@link #setTranslationAndUpdate(Tuple3DReadOnly)}. + *

+ * + * @param frameName the name of the new frame. + * @param parentFrame the parent of the frame. It has to extend a {@link ReferenceFrame}. + * @param translationToParent the initial transform to the parent frame. + */ + public TranslationReferenceFrame(String frameName, ReferenceFrame parentFrame, FrameVector3DReadOnly translationToParent) + { + this(frameName, parentFrame); + setTranslationAndUpdate(translationToParent); + } + + /** + * {@inheritDoc} + **/ + @Override + protected void updateTransformToParent(RigidBodyTransform transformToParentToPack) + { + transformToParentToPack.setIdentity(); + transformToParentToPack.getTranslation().set(translationVector); + } + + /** + * Sets the translation of this reference frame relative to its parent. The frame of the passed in tuple {@param translation} must be defined as + * {@link #getParent()}. If it is not, this method will throw a {@link ReferenceFrameMismatchException}. To avoid performing a frame check, please use + * {@link #setTranslationAndUpdate(Tuple3DReadOnly)} + * + * @param translation translate relative to parent to set. + */ + public void setTranslationAndUpdate(FrameTuple3DReadOnly translation) + { + translationVector.checkReferenceFrameMatch(translation); + setTranslationAndUpdate((Tuple3DReadOnly) translation); + } + + /** + * Sets the translation of this reference frame relative to its parent. The frame of the passed in tuple {@param translation} is assumed to be the same as + * {@link #getParent()}. This method does not perform a frame check. For frame safe operations, see {@link #setTranslationAndUpdate(FrameTuple3DReadOnly)}. + * + * @param translation translate relative to parent to set. + */ + public void setTranslationAndUpdate(Tuple3DReadOnly translation) + { + translationVector.set(translation); + this.update(); + } +} diff --git a/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameFactories.java b/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameFactories.java index 211fce99c..11474ede5 100644 --- a/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameFactories.java +++ b/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameFactories.java @@ -88,12 +88,7 @@ import us.ihmc.euclid.tuple3D.Point3D; import us.ihmc.euclid.tuple3D.UnitVector3D; import us.ihmc.euclid.tuple3D.Vector3D; -import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.UnitVector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.UnitVector3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.*; import us.ihmc.euclid.tuple4D.Quaternion; import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; diff --git a/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameTools.java b/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameTools.java index 662f0d9ae..8ec308352 100644 --- a/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameTools.java +++ b/src/frame/java/us/ihmc/euclid/referenceFrame/tools/EuclidFrameTools.java @@ -6,36 +6,27 @@ import us.ihmc.euclid.Axis3D; import us.ihmc.euclid.Location; import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.geometry.BoundingBox2D; +import us.ihmc.euclid.geometry.BoundingBox3D; import us.ihmc.euclid.geometry.exceptions.BoundingBoxException; +import us.ihmc.euclid.geometry.interfaces.BoundingBox2DReadOnly; +import us.ihmc.euclid.geometry.interfaces.BoundingBox3DReadOnly; +import us.ihmc.euclid.geometry.interfaces.ConvexPolygon2DReadOnly; import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; +import us.ihmc.euclid.matrix.interfaces.CommonMatrix3DBasics; import us.ihmc.euclid.orientation.interfaces.Orientation3DBasics; -import us.ihmc.euclid.referenceFrame.FramePoint2D; -import us.ihmc.euclid.referenceFrame.FramePoint3D; -import us.ihmc.euclid.referenceFrame.FrameVector2D; -import us.ihmc.euclid.referenceFrame.FrameVector3D; +import us.ihmc.euclid.referenceFrame.*; import us.ihmc.euclid.referenceFrame.exceptions.ReferenceFrameMismatchException; -import us.ihmc.euclid.referenceFrame.interfaces.EuclidFrameGeometry; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameOrientation3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePoint2DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFramePoint3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector2DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FixedFrameVector3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameOrientation3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint2DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FramePoint3DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameVector2DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameVector2DReadOnly; -import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DBasics; -import us.ihmc.euclid.referenceFrame.interfaces.FrameVector3DReadOnly; +import us.ihmc.euclid.referenceFrame.interfaces.*; +import us.ihmc.euclid.tools.EuclidCoreTools; import us.ihmc.euclid.tuple2D.Point2D; +import us.ihmc.euclid.tuple2D.interfaces.Point2DBasics; +import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; import us.ihmc.euclid.tuple3D.Point3D; import us.ihmc.euclid.tuple3D.Vector3D; -import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.*; +import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; /** * Extension of the tools provided in {@link EuclidGeometryTools} for frame geometries. @@ -7903,4 +7894,470 @@ public static boolean geometricallyEquals(EuclidFrameGeometry a, EuclidFrameGeom { return (a == b) || (a != null && a.geometricallyEquals(b, epsilon)); } + + /** + * Calculates the 3D part of the given {@code input} that is parallel to the given + * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. + *

+ * The result has the following properties: + *

+ * where: + * + *

+ * + * @param input the tuple to extract the normal part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static void extractNormalPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FixedFrameTuple3DBasics inputNormalPartToPack) + { + input.checkReferenceFrameMatch(normalAxis, inputNormalPartToPack); + EuclidCoreTools.extractNormalPart(input, normalAxis, inputNormalPartToPack); + } + + /** + * Calculates the 3D part of the given {@code input} that is parallel to the given + * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. + *

+ * The result has the following properties: + *

+ * where: + * + *

+ * + * @param input the tuple to extract the normal part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. + * @throws ReferenceFrameMismatchException if {@code input} and {@code normalAxis} are not expressed + * in the same reference frame. + */ + public static void extractNormalPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FrameTuple3DBasics inputNormalPartToPack) + { + input.checkReferenceFrameMatch(normalAxis); + inputNormalPartToPack.setReferenceFrame(input.getReferenceFrame()); + EuclidCoreTools.extractNormalPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputNormalPartToPack); + } + + /** + * Calculates the 3D part of the given {@code input} that is orthogonal to the given + * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. + *

+ * The result has the following properties: + *

+ * where: + * + *

+ * + * @param input the tuple to extract the tangential part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputTangentialPartToPack the tuple used to store the tangential part of the input. + * Modified. + * @throws ReferenceFrameMismatchException if the arguments are not expressed in the same reference + * frame. + */ + public static void extractTangentialPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FixedFrameTuple3DBasics inputTangentialPartToPack) + { + input.checkReferenceFrameMatch(normalAxis, inputTangentialPartToPack); + EuclidCoreTools.extractTangentialPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputTangentialPartToPack); + } + + /** + * Calculates the 3D part of the given {@code input} that is orthogonal to the given + * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. + *

+ * The result has the following properties: + *

+ * where: + * + *

+ * + * @param input the tuple to extract the tangential part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputTangentialPartToPack the tuple used to store the tangential part of the input. + * Modified. + * @throws ReferenceFrameMismatchException if {@code input} and {@code normalAxis} are not expressed + * in the same reference frame. + */ + public static void extractTangentialPart(FrameTuple3DReadOnly input, FrameVector3DReadOnly normalAxis, FrameTuple3DBasics inputTangentialPartToPack) + { + input.checkReferenceFrameMatch(normalAxis); + inputTangentialPartToPack.setReferenceFrame(input.getReferenceFrame()); + EuclidCoreTools.extractTangentialPart((Tuple3DReadOnly) input, (Vector3DReadOnly) normalAxis, (Tuple3DBasics) inputTangentialPartToPack); + } + + /** + * This method implements the same operation as + * {@link EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, Vector3DReadOnly, Orientation3DBasics)} + * except that it does not rely on {@code Math#acos(double)} making it faster. + * + * @param firstVector the first vector. Not modified. + * @param secondVector the second vector that is rotated with respect to the first vector. Not + * modified. + * @param rotationToPack the minimum rotation from {@code firstVector} to the {@code secondVector}. + * Modified. + * @see EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, + * Vector3DReadOnly, Orientation3DBasics) + */ + public static void rotationMatrix3DFromFirstToSecondVector3D(FrameVector3DReadOnly firstVector, + FrameVector3DReadOnly secondVector, + FixedFrameCommonMatrix3DBasics rotationToPack) + { + firstVector.checkReferenceFrameMatch(secondVector, rotationToPack); + EuclidGeometryTools.rotationMatrix3DFromFirstToSecondVector3D(firstVector, secondVector, rotationToPack); + } + + /** + * Computes and packs the intersecting points between a 2d line segment and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting position of the line segment. + * @param endPoint end position of the line segment. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenLineSegment2DAndCylinder3D(double circleRadius, + FramePoint2DReadOnly circlePosition, + FramePoint2DReadOnly startPoint, + FramePoint2DReadOnly endPoint, + FixedFramePoint2DBasics firstIntersectionToPack, + FixedFramePoint2DBasics secondIntersectionToPack) + { + circlePosition.checkReferenceFrameMatch(startPoint, endPoint); + circlePosition.checkReferenceFrameMatch(firstIntersectionToPack, secondIntersectionToPack); + return EuclidGeometryTools.intersectionBetweenLineSegment2DAndCylinder3D(circleRadius, + circlePosition, + startPoint, + endPoint, + firstIntersectionToPack, + secondIntersectionToPack); + } + /** + * Computes and packs the intersecting points between a 2d line and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param pointOnLine point on the line + * @param direction direction of the line. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenLine2DAndCircle(double circleRadius, + FramePoint2DReadOnly circlePosition, + FramePoint2DReadOnly pointOnLine, + FrameVector2DReadOnly direction, + FixedFramePoint2DBasics firstIntersectionToPack, + FixedFramePoint2DBasics secondIntersectionToPack) + { + circlePosition.checkReferenceFrameMatch(pointOnLine, direction); + circlePosition.checkReferenceFrameMatch(firstIntersectionToPack, secondIntersectionToPack); + return EuclidGeometryTools.intersectionBetweenLine2DAndCircle(circleRadius, + circlePosition, + pointOnLine, + direction, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes and packs the intersecting points between a 2d ray and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting point of the line. + * @param direction direction of the line. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenRay2DAndCircle(double circleRadius, + FramePoint2DReadOnly circlePosition, + FramePoint2DReadOnly startPoint, + FrameVector2DReadOnly direction, + FixedFramePoint2DBasics firstIntersectionToPack, + FixedFramePoint2DBasics secondIntersectionToPack) + { + circlePosition.checkReferenceFrameMatch(startPoint, direction); + circlePosition.checkReferenceFrameMatch(firstIntersectionToPack, secondIntersectionToPack); + return EuclidGeometryTools.intersectionBetweenRay2DAndCircle(circleRadius, + circlePosition, + startPoint, + direction, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes and packs the intersecting points between a 2d ray and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting point of the line. + * @param pointOnRay point on the line. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenRay2DAndCircle(double circleRadius, + FramePoint2DReadOnly circlePosition, + FramePoint2DReadOnly startPoint, + FramePoint2DReadOnly pointOnRay, + FixedFramePoint2DBasics firstIntersectionToPack, + FixedFramePoint2DBasics secondIntersectionToPack) + { + circlePosition.checkReferenceFrameMatch(startPoint, pointOnRay); + circlePosition.checkReferenceFrameMatch(firstIntersectionToPack, secondIntersectionToPack); + return EuclidGeometryTools.intersectionBetweenRay2DAndCircle(circleRadius, + circlePosition, + startPoint, + pointOnRay, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes the intersection between an infinitely long 2D line (defined by a 2D point and a 2D + * direction) and a 2D ray segment (defined by the start and direction 2D endpoints). + *

+ * Edge cases: + *

+ *

+ * + * @param rayOrigin start point of the ray. + * @param rayDirection direction of the ray. + * @param linePoint1 first point located on the line. + * @param linePoint2 second point located on the line. + * @param intersectionToPack the 2D point in which the result is stored. Can be {@code null}. + * Modified. + * @return {@code true} if the line intersects the line segment, {@code false} otherwise. + */ + public static boolean intersectionBetweenRay2DAndLine2D(FramePoint2DReadOnly rayOrigin, + FrameVector2DReadOnly rayDirection, + FramePoint2DReadOnly linePoint1, + FramePoint2DReadOnly linePoint2, + FixedFramePoint2DBasics intersectionToPack) + { + rayOrigin.checkReferenceFrameMatch(rayDirection, linePoint1); + rayOrigin.checkReferenceFrameMatch(linePoint2, intersectionToPack); + return EuclidGeometryTools.intersectionBetweenRay2DAndLine2D(rayOrigin, rayDirection, linePoint1, linePoint2, intersectionToPack); + } + + /** + * Computes the intersection between an infinitely long 2D line (defined by a 2D point and a 2D + * direction) and a 2D ray segment (defined by the start and direction 2D endpoints). + *

+ * Edge cases: + *

+ *

+ * + * @param rayOrigin start point of the ray. + * @param rayDirection direction of the ray. + * @param pointOnLine point located on the line. + * @param lineDirection direction of the line. + * @param intersectionToPack the 2D point in which the result is stored. Can be {@code null}. + * Modified. + * @return {@code true} if the line intersects the line segment, {@code false} otherwise. + */ + public static boolean intersectionBetweenRay2DAndLine2D(FramePoint2DReadOnly rayOrigin, + FrameVector2DReadOnly rayDirection, + FramePoint2DReadOnly pointOnLine, + FrameVector2DReadOnly lineDirection, + FixedFramePoint2DBasics intersectionToPack) + { + rayOrigin.checkReferenceFrameMatch(rayDirection, pointOnLine); + rayOrigin.checkReferenceFrameMatch(lineDirection, intersectionToPack); + return EuclidGeometryTools.intersectionBetweenRay2DAndLine2D(rayOrigin, rayDirection, pointOnLine, lineDirection, intersectionToPack); + } + + /** + * Finds the intersection of two bounding boxes defined by a bounding box. + *

+ * WARNING: generates garbage + *

+ * Allocates a new BoundingBox2D. TODO: Check, Unit test, move where BoundingBox union is + * + * @param a first bounding box to check + * @param b second bounding box to check + * @return the intersection bounding box, or null if no intersection + */ + public static FrameBoundingBox2D computeIntersectionOfTwoBoundingBoxes(FrameBoundingBox2DReadOnly a, FrameBoundingBox2DReadOnly b) + { + a.checkReferenceFrameMatch(b); + BoundingBox2D unframedBox = EuclidGeometryTools.computeIntersectionOfTwoBoundingBoxes(a, b); + if (unframedBox == null) + return null; + return new FrameBoundingBox2D(a.getReferenceFrame(), unframedBox); + } + + /** + * Finds the intersection of two bounding boxes defined by a bounding box. + *

+ * WARNING: generates garbage + *

+ * Allocates a new BoundingBox2D. TODO: Check, Unit test, move where BoundingBox union is + * + * @param a first bounding box to check + * @param b second bounding box to check + * @return the intersection bounding box, or null if no intersection + */ + public static FrameBoundingBox3D computeIntersectionOfTwoBoundingBoxes(FrameBoundingBox3DReadOnly a, FrameBoundingBox3DReadOnly b) + { + a.checkReferenceFrameMatch(b); + BoundingBox3D unframedBox = EuclidGeometryTools.computeIntersectionOfTwoBoundingBoxes(a, b); + if (unframedBox == null) + return null; + return new FrameBoundingBox3D(a.getReferenceFrame(), unframedBox); + } + + /** + * Determines if the polygonToTest is fully inside the convex polygon by some epsilon distance. + * + * @return {@code true} if {@code polygonToTest} is fully contained inside {@code polygon}. {@code false} otherwise. + */ + public static boolean isPolygonInside(FrameConvexPolygon2DReadOnly polygonToTest, double epsilon, FrameConvexPolygon2DReadOnly polygon) + { + polygonToTest.checkReferenceFrameMatch(polygon); + return EuclidGeometryTools.isPolygonInside(polygonToTest, epsilon, polygon); + } + + /** + * Determines if the polygonToTest is fully inside the convex polygon by some epsilon distance. + * + * @return {@code true} if {@code polygonToTest} is fully contained inside {@code polygon}. {@code false} otherwise. + */ + public static boolean isPolygonInside(FrameConvexPolygon2DReadOnly polygonToTest, FrameConvexPolygon2DReadOnly polygon) + { + polygonToTest.checkReferenceFrameMatch(polygon); + return EuclidGeometryTools.isPolygonInside(polygonToTest, polygon); + } + + /** + * This method implements the same operation as + * {@link EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics)} + * except that it does not rely on {@code Math#acos(double)} making it faster. + * + * @param vector the vector that is rotated with respect to {@code zUp}. Not modified. + * @param rotationToPack the minimum rotation from {@code zUp} to the given {@code vector}. + * Modified. + * @see EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics) + */ + public static void rotationMatrix3DFromZUpToVector3D(FrameVector3DReadOnly vector, FixedFrameCommonMatrix3DBasics rotationToPack) + { + vector.checkReferenceFrameMatch(rotationToPack); + EuclidGeometryTools.rotationMatrix3DFromZUpToVector3D(vector, rotationToPack); + } + + /** + * Compute Intersection-over-Union (IoU) of two 3D bounding boxes. This is the percentage of volume of the two bounding boxes that are intersecting. + *

+ * WARNING: generates garbage. + */ + public static double computeIntersectionOverUnionOfTwoBoundingBoxes(FrameBoundingBox3DReadOnly a, FrameBoundingBox3DReadOnly b) + { + a.checkReferenceFrameMatch(b); + return EuclidGeometryTools.computeIntersectionOverUnionOfTwoBoundingBoxes(a, b); + } + + /** + * Compute Intersection-over-Union (IoU) of two 3D bounding boxes. This is the percentage of volume of the two bounding boxes that are intersecting. + *

+ * WARNING: generates garbage. + */ + public static double computeIntersectionOverSmallerOfTwoBoundingBoxes(FrameBoundingBox3DReadOnly a, FrameBoundingBox3DReadOnly b) + { + a.checkReferenceFrameMatch(b); + return EuclidGeometryTools.computeIntersectionOverSmallerOfTwoBoundingBoxes(a, b); + } + + /** + * Finds the projection of a 3D point onto a 3D plane given in general form. + * Uses: projectedPoint = point - (normal.dot(point) + planeScalar) * (normal) + *

+ * WARNING: generates garbage. + * + * @param plane Coefficients of the general form of plane equation (ax + by + cz + d = 0) as Vector4D + * @param point Point to be projected onto the plane as Point3D + * @return Projected point onto the plane as Point3D + */ + public static FramePoint3D projectPointOntoPlane(FrameVector4DReadOnly plane, FramePoint3DReadOnly point) + { + plane.checkReferenceFrameMatch(point); + return new FramePoint3D(plane.getReferenceFrame(), EuclidGeometryTools.projectPointOntoPlane(plane, point)); + } } diff --git a/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryPolygonTools.java b/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryPolygonTools.java index c1336f74a..3f36f81c9 100644 --- a/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryPolygonTools.java +++ b/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryPolygonTools.java @@ -2870,6 +2870,7 @@ else if (candidateCoord1 == bestCoord1 && bound2.isFirstBetter(candidateCoord2, * @return the wrapped index. * @deprecated Use {@link EuclidCoreTools#wrap(int,int)} instead */ + @Deprecated public static int wrap(int index, int listSize) { return EuclidCoreTools.wrap(index, listSize); @@ -2891,6 +2892,7 @@ public static int wrap(int index, int listSize) * @return the wrapped incremented index. * @deprecated Use {@link EuclidCoreTools#next(int,int)} instead */ + @Deprecated public static int next(int index, int listSize) { return EuclidCoreTools.next(index, listSize); @@ -2912,6 +2914,7 @@ public static int next(int index, int listSize) * @return the wrapped decremented index. * @deprecated Use {@link EuclidCoreTools#previous(int,int)} instead */ + @Deprecated public static int previous(int index, int listSize) { return EuclidCoreTools.previous(index, listSize); diff --git a/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryTools.java b/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryTools.java index 8953a1516..41c7c7b8a 100644 --- a/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryTools.java +++ b/src/geometry/java/us/ihmc/euclid/geometry/tools/EuclidGeometryTools.java @@ -1,34 +1,35 @@ package us.ihmc.euclid.geometry.tools; -import static us.ihmc.euclid.tools.EuclidCoreTools.normSquared; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - import us.ihmc.euclid.Axis2D; import us.ihmc.euclid.Axis3D; import us.ihmc.euclid.Location; import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.geometry.BoundingBox2D; +import us.ihmc.euclid.geometry.BoundingBox3D; import us.ihmc.euclid.geometry.exceptions.BoundingBoxException; +import us.ihmc.euclid.geometry.interfaces.BoundingBox2DReadOnly; +import us.ihmc.euclid.geometry.interfaces.BoundingBox3DReadOnly; +import us.ihmc.euclid.geometry.interfaces.ConvexPolygon2DReadOnly; +import us.ihmc.euclid.matrix.RotationMatrix; +import us.ihmc.euclid.matrix.interfaces.CommonMatrix3DBasics; import us.ihmc.euclid.orientation.interfaces.Orientation3DBasics; import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; import us.ihmc.euclid.tools.EuclidCoreTools; import us.ihmc.euclid.tools.TupleTools; import us.ihmc.euclid.tuple2D.Point2D; import us.ihmc.euclid.tuple2D.Vector2D; -import us.ihmc.euclid.tuple2D.interfaces.Point2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.UnitVector2DReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.Vector2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.*; import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.UnitVector3D; import us.ihmc.euclid.tuple3D.Vector3D; -import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.UnitVector3DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; -import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; +import us.ihmc.euclid.tuple3D.interfaces.*; +import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static us.ihmc.euclid.tools.EuclidCoreTools.normSquared; /** * This class provides a large variety of basics geometry operations. @@ -37,15 +38,25 @@ */ public class EuclidGeometryTools { - /** Tolerance used to identify edge cases. */ + /** + * Tolerance used to identify edge cases. + */ public static final double ONE_MILLIONTH = 1.0e-6; - /** Tolerance used to identify edge cases. */ + /** + * Tolerance used to identify edge cases. + */ public static final double ONE_TEN_MILLIONTH = 1.0e-7; - /** Tolerance used to identify edge cases. */ + /** + * Tolerance used to identify edge cases. + */ public static final double ONE_TRILLIONTH = 1.0e-12; - /** Tolerance used to identify edge cases. */ + /** + * Tolerance used to identify edge cases. + */ public static final double IS_POINT_ON_LINE_EPS = 1.0e-8; - /** Constant used to save some computation. */ + /** + * Constant used to save some computation. + */ public static final double HALF_PI = 0.5 * Math.PI; private EuclidGeometryTools() @@ -1589,7 +1600,7 @@ public static double triangleArea(Point3DReadOnly a, Point3DReadOnly b, Point3DR * s = 0.5(a + b + c) * A = √{s(s - a)(s - b)(s - c)} * - * + *

* No specific ordering of the arguments is required. *

* Note that the method {@link #triangleArea(Point2DReadOnly, Point2DReadOnly, Point2DReadOnly)} is @@ -1614,7 +1625,7 @@ public static double triangleAreaHeron1(double lengthA, double lengthB, double l *

     * A = 0.25 * √{4(a2b2 + a2c2 + b2c2) - (a2 + b2 + c2)2}
     * 
- * + *

* No specific ordering of the arguments is required. *

* Note that the method {@link #triangleArea(Point2DReadOnly, Point2DReadOnly, Point2DReadOnly)} is @@ -2613,7 +2624,6 @@ public static double signedDistanceFromPoint3DToPlane3D(double pointX, double planeNormalY = planeFirstTangentZ * planeSecondTangentX - planeFirstTangentX * planeSecondTangentZ; double planeNormalZ = planeFirstTangentX * planeSecondTangentY - planeFirstTangentY * planeSecondTangentX; return signedDistanceFromPoint3DToPlane3D(pointX, pointY, pointZ, pointOnPlaneX, pointOnPlaneY, pointOnPlaneZ, planeNormalX, planeNormalY, planeNormalZ); - } /** @@ -3261,10 +3271,10 @@ public static double dotProduct(Point3DReadOnly start1, Point3DReadOnly end1, Po * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0 or 2. + * 0 or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine2DAndBoundingBox2D(Point2DReadOnly boundingBoxMin, Point2DReadOnly boundingBoxMax, @@ -3315,10 +3325,10 @@ public static int intersectionBetweenLine2DAndBoundingBox2D(Point2DReadOnly boun * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine2DAndBoundingBox2D(Point2DReadOnly boundingBoxMin, Point2DReadOnly boundingBoxMax, @@ -3390,10 +3400,10 @@ public static int intersectionBetweenLine2DAndBoundingBox2D(Point2DReadOnly boun * @param secondIntersectionToPack the coordinate of the second intersection. Can be * {@code null}. Modified. * @return the number of intersections between the line/line-segment/ray and the bounding box. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ private static int intersectionBetweenLine2DAndBoundingBox2DImpl(Point2DReadOnly boundingBoxMin, Point2DReadOnly boundingBoxMax, @@ -3573,7 +3583,7 @@ private static int intersectionBetweenLine2DAndBoundingBox2DImpl(Point2DReadOnly // if we get here we have a line-segment or a ray colinear with one of the box surfaces and we need to check for edge cases if (canIntersectionOccurAfterEnd) {// we have a ray and need to test for the edge-case that can only occur with ray at this point - // check if ray origin lies on bounding box surface and ray or line are colinear with that bounding box surface + // check if ray origin lies on bounding box surface and ray or line are colinear with that bounding box surface boolean isRayOriginOnBox = false; if (isColinearX) @@ -3613,7 +3623,7 @@ else if (isColinearY) } else {// we have a line-segment and need to test for the edge-case that can only occur with a line-segment at this point - // check if start- and/or end- point lies on bounding box surface and the line-segment is colinear with that bounding box surface + // check if start- and/or end- point lies on bounding box surface and the line-segment is colinear with that bounding box surface boolean isLineSegmentStartOnBox = false; boolean isLineSegmentEndOnBox = false; double deltaXEndmin = boundingBoxMin.getX() - endX; @@ -3632,7 +3642,6 @@ else if (isColinearY) { // line-segment end point also lies on x surface of box isLineSegmentEndOnBox = true; } - } else if (isColinearY) {// check if start point of ray lies on bounding box surface @@ -3644,7 +3653,6 @@ else if (isColinearY) if (deltaXEndmin <= 0.0 && deltaXEndmax >= 0.0) { // line-segment end point also lies on x surface of box isLineSegmentEndOnBox = true; - } } @@ -3834,7 +3842,7 @@ private static boolean intersectionBetweenTwoLine2DsImpl(double start1x, if (Math.abs(determinant) < epsilon) { // The lines are parallel - // Check if they are collinear + // Check if they are collinear double dx = start2x - start1x; double dy = start2y - start1y; double cross = dx * direction1y - dy * direction1x; @@ -4064,7 +4072,7 @@ public static boolean intersectionBetweenLine2DAndLineSegment2D(Point2DReadOnly * intersects with the bounding box are returned as intersection points. * *

- * + * * @param boundingBoxMin the minimum coordinate of the bounding box. Not modified. * @param boundingBoxMax the maximum coordinate of the bounding box. Not modified. * @param firstPointOnLine a first point located on the infinitely long line. Not modified. @@ -4074,10 +4082,10 @@ public static boolean intersectionBetweenLine2DAndLineSegment2D(Point2DReadOnly * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boundingBoxMin, Point3DReadOnly boundingBoxMax, @@ -4120,7 +4128,7 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boun * intersects with the bounding box are returned as intersection points. * *

- * + * * @param boundingBoxMin the minimum coordinate of the bounding box. Not modified. * @param boundingBoxMax the maximum coordinate of the bounding box. Not modified. * @param pointOnLine a point located on the infinitely long line. Not modified. @@ -4130,10 +4138,10 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boun * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boundingBoxMin, Point3DReadOnly boundingBoxMax, @@ -4174,7 +4182,7 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boun * intersects with the bounding box are returned as intersection points. * *

- * + * * @param boundingBoxMinX the minimum x-coordinate of the bounding box. * @param boundingBoxMinY the minimum y-coordinate of the bounding box. * @param boundingBoxMinZ the minimum z-coordinate of the bounding box. @@ -4188,10 +4196,10 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(Point3DReadOnly boun * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMinX, double boundingBoxMinY, @@ -4248,7 +4256,7 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMi * intersects with the bounding box are returned as intersection points. * *

- * + * * @param boundingBoxMinX the minimum x-coordinate of the bounding box. * @param boundingBoxMinY the minimum y-coordinate of the bounding box. * @param boundingBoxMinZ the minimum z-coordinate of the bounding box. @@ -4266,10 +4274,10 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMi * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMinX, double boundingBoxMinY, @@ -4338,7 +4346,7 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMi * intersection points. * *

- * + * * @param boundingBoxMin the minimum coordinate of the bounding box. Not modified. * @param boundingBoxMax the maximum coordinate of the bounding box. Not modified. * @param startX the x-coordinate of a point located on the @@ -4362,10 +4370,10 @@ public static int intersectionBetweenLine3DAndBoundingBox3D(double boundingBoxMi * @param secondIntersectionToPack the coordinate of the second intersection. Can be * {@code null}. Modified. * @return the number of intersections between the line/line-segment/ray and the bounding box. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ private static int intersectionBetweenLine3DAndBoundingBox3DImpl(Point3DReadOnly boundingBoxMin, Point3DReadOnly boundingBoxMax, @@ -4454,10 +4462,10 @@ private static int intersectionBetweenLine3DAndBoundingBox3DImpl(Point3DReadOnly * @param secondIntersectionToPack the coordinate of the second intersection. Can be * {@code null}. Modified. * @return the number of intersections between the line/line-segment/ray and the bounding box. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ private static int intersectionBetweenLine3DAndBoundingBox3DImpl(double boundingBoxMinX, double boundingBoxMinY, @@ -4692,7 +4700,7 @@ private static int intersectionBetweenLine3DAndBoundingBox3DImpl(double bounding // if we get here we have a line-segment or a ray colinear with one of the box surfaces and we need to check for edge cases if (canIntersectionOccurAfterEnd) {// we have a ray and need to test for the edge-case that can only occur with ray at this point - // check if ray origin lies on bounding box surface and ray or line are colinear with that bounding box surface + // check if ray origin lies on bounding box surface and ray or line are colinear with that bounding box surface boolean isRayOriginOnBox = false; if (isColinearX) @@ -4742,7 +4750,7 @@ else if (isColinearZ) } else {// we have a line-segment and need to test for the edge-case that can only occur with a line-segment at this point - // check if start- and/or end- point lies on bounding box surface and the line-segment is colinear with that bounding box surface + // check if start- and/or end- point lies on bounding box surface and the line-segment is colinear with that bounding box surface boolean isLineSegmentStartOnBox = false; boolean isLineSegmentEndOnBox = false; double deltaXEndmin = boundingBoxMinX - endX; @@ -4763,7 +4771,6 @@ else if (isColinearZ) { // line-segment end point also lies on x surface of box isLineSegmentEndOnBox = true; } - } else if (isColinearY) {// check if start point of ray lies on bounding box surface @@ -4775,7 +4782,6 @@ else if (isColinearY) if (deltaXEndmin <= 0.0 && deltaXEndmax >= 0.0 && deltaZEndmin <= 0.0 && deltaZEndmax >= 0.0) { // line-segment end point also lies on x surface of box isLineSegmentEndOnBox = true; - } } else if (isColinearZ) @@ -4789,7 +4795,6 @@ else if (isColinearZ) { // line-segment end point also lies on x surface of box isLineSegmentEndOnBox = true; } - } if (isLineSegmentStartOnBox && isLineSegmentEndOnBox) { // start and end are on the box, we consider them intersections @@ -4841,7 +4846,6 @@ else if (isLineSegmentStartOnBox) } return numberOfIntersections; - } /** @@ -4880,9 +4884,9 @@ else if (isLineSegmentStartOnBox) * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the cylinder. It is either equal to 0, - * 1, or 2. + * 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, double cylinderRadius, @@ -4956,9 +4960,9 @@ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the cylinder. It is either equal to 0, - * 1, or 2. + * 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, double cylinderRadius, @@ -5030,9 +5034,9 @@ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the cylinder. It is either equal to 0, - * 1, or 2. + * 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, double cylinderRadius, @@ -5135,9 +5139,9 @@ public static int intersectionBetweenLine3DAndCylinder3D(double cylinderLength, * @param secondIntersectionToPack the coordinate of the second intersection. Can be * {@code null}. Modified. * @return the number of intersections between the line/line-segment/ray and the cylinder. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ private static int intersectionBetweenLine3DAndCylinder3DImpl(double cylinderLength, double cylinderRadius, @@ -5244,8 +5248,8 @@ else if (dBottom < dIntersection1) // If dIntersection2 is not NaN, that means two intersections were found which is the max, so no need to check with the cylinder part. if (Double.isNaN(dIntersection2)) { // Compute possible intersections with the cylinder part - // Notation used: cylinder axis: pa + va * d; line equation: p + v * d - // Need to solve quadratic equation of the form A * d^2 + B * d + C = 0 + // Notation used: cylinder axis: pa + va * d; line equation: p + v * d + // Need to solve quadratic equation of the form A * d^2 + B * d + C = 0 // (v, va)*va double scaledAxisX1 = lineDirectionDotCylinderAxis * axisX; @@ -5430,9 +5434,9 @@ else if (dCylinder2 < dIntersection1) * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, double radiusY, @@ -5495,9 +5499,9 @@ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, double radiusY, @@ -5556,9 +5560,9 @@ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, double radiusY, @@ -5642,9 +5646,9 @@ public static int intersectionBetweenLine3DAndEllipsoid3D(double radiusX, * @param secondIntersectionToPack the coordinate of the second intersection. Can be * {@code null}. Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ private static int intersectionBetweenLine3DAndEllipsoid3DImpl(double radiusX, double radiusY, @@ -5785,7 +5789,7 @@ private static int intersectionBetweenLine3DAndEllipsoid3DImpl(double radiusX, * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the coordinates of the intersection, or {@code null} if the line is parallel to the - * plane. + * plane. */ public static Point3D intersectionBetweenLine3DAndPlane3D(Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal, @@ -5892,10 +5896,10 @@ public static boolean intersectionBetweenLine3DAndPlane3D(Point3DReadOnly pointO * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line segment and the bounding box. It is either - * equal to 0, 1, or 2. + * equal to 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLineSegment2DAndBoundingBox2D(Point2DReadOnly boundingBoxMin, Point2DReadOnly boundingBoxMax, @@ -5947,7 +5951,7 @@ public static int intersectionBetweenLineSegment2DAndBoundingBox2D(Point2DReadOn * first/last intersects with the bounding box boundary are returned as intersection points. * *

- * + * * @param boundingBoxMin the minimum coordinate of the bounding box. Not modified. * @param boundingBoxMax the maximum coordinate of the bounding box. Not modified. * @param lineSegmentStart the first endpoint of the line segment. Not modified. @@ -5957,10 +5961,10 @@ public static int intersectionBetweenLineSegment2DAndBoundingBox2D(Point2DReadOn * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line segment and the bounding box. It is either - * equal to 0, 1, or 2. + * equal to 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenLineSegment3DAndBoundingBox3D(Point3DReadOnly boundingBoxMin, Point3DReadOnly boundingBoxMax, @@ -6011,9 +6015,9 @@ public static int intersectionBetweenLineSegment3DAndBoundingBox3D(Point3DReadOn * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line segment and the cylinder. It is either equal - * to 0, 1, or 2. + * to 0, 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ public static int intersectionBetweenLineSegment3DAndCylinder3D(double cylinderLength, double cylinderRadius, @@ -6085,9 +6089,9 @@ public static int intersectionBetweenLineSegment3DAndCylinder3D(double cylinderL * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ public static int intersectionBetweenLineSegment3DAndEllipsoid3D(double radiusX, double radiusY, @@ -6199,10 +6203,10 @@ public static Point3D intersectionBetweenLineSegment3DAndPlane3D(Point3DReadOnly * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the ray and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenRay2DAndBoundingBox2D(Point2DReadOnly boundingBoxMin, Point2DReadOnly boundingBoxMax, @@ -6273,7 +6277,6 @@ public static boolean intersectionBetweenRay2DAndLineSegment2D(double rayOriginX double end2x = lineSegmentEndX; double end2y = lineSegmentEndY; return intersectionBetweenTwoLine2DsImpl(start1x, start1y, false, end1x, end1y, true, start2x, start2y, false, end2x, end2y, false, intersectionToPack); - } /** @@ -6392,7 +6395,7 @@ public static boolean intersectionBetweenRay2DAndLineSegment2D(Point2DReadOnly r * intersection points. * *

- * + * * @param boundingBoxMin the minimum coordinate of the bounding box. Not modified. * @param boundingBoxMax the maximum coordinate of the bounding box. Not modified. * @param rayOrigin the coordinate of the ray origin. Not modified. @@ -6402,10 +6405,10 @@ public static boolean intersectionBetweenRay2DAndLineSegment2D(Point2DReadOnly r * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the ray and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws BoundingBoxException if any of the minimum coordinates of the bounding box is strictly - * greater than the maximum coordinate of the bounding box on the same - * axis. + * greater than the maximum coordinate of the bounding box on the same + * axis. */ public static int intersectionBetweenRay3DAndBoundingBox3D(Point3DReadOnly boundingBoxMin, Point3DReadOnly boundingBoxMax, @@ -6457,10 +6460,10 @@ public static int intersectionBetweenRay3DAndBoundingBox3D(Point3DReadOnly bound * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line and the 3D box. It is either equal to 0, 1, - * or 2. If the ray origin is on the surface of the 3D box it is considered an intersection. + * or 2. If the ray origin is on the surface of the 3D box it is considered an intersection. * @throws IllegalArgumentException if {@code boxSize} contains values <= 0.0 * @see #intersectionBetweenRay3DAndBoundingBox3D(Point3DReadOnly, Point3DReadOnly, Point3DReadOnly, - * Vector3DReadOnly, Point3DBasics, Point3DBasics) + * Vector3DReadOnly, Point3DBasics, Point3DBasics) */ public static int intersectionBetweenRay3DAndBox3D(Point3DReadOnly boxPosition, Orientation3DReadOnly boxOrientation, @@ -6561,9 +6564,9 @@ public static int intersectionBetweenRay3DAndBox3D(Point3DReadOnly boxPosition, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the ray and the bounding box. It is either equal to - * 0, 1, or 2. + * 0, 1, or 2. * @throws IllegalArgumentException if either {@code cylinderLength < 0} or - * {@code cylinderRadius < 0}. + * {@code cylinderRadius < 0}. */ public static int intersectionBetweenRay3DAndCylinder3D(double cylinderLength, double cylinderRadius, @@ -6635,9 +6638,9 @@ public static int intersectionBetweenRay3DAndCylinder3D(double cylinderLength, * @param secondIntersectionToPack the coordinate of the second intersection. Can be {@code null}. * Modified. * @return the number of intersections between the line/line-segment/ray and the ellipsoid. It is - * either equal to 0, 1, or 2. + * either equal to 0, 1, or 2. * @throws IllegalArgumentException if either {@code radiusX}, {@code radiusY}, or {@code radiusZ} - * is negative. + * is negative. */ public static int intersectionBetweenRay3DAndEllipsoid3D(double radiusX, double radiusY, @@ -7096,9 +7099,9 @@ public static boolean intersectionBetweenTwoPlane3Ds(Point3DReadOnly pointOnPlan intersectionDirectionToPack.scale(1.0 / EuclidCoreTools.squareRoot(det)); double normal3DotPoint1 = intersectionDirectionToPack.getX() * pointOnPlane1.getX() + intersectionDirectionToPack.getY() * pointOnPlane1.getY() - + intersectionDirectionToPack.getZ() * pointOnPlane1.getZ(); + + intersectionDirectionToPack.getZ() * pointOnPlane1.getZ(); double normal3DotPoint2 = intersectionDirectionToPack.getX() * pointOnPlane2.getX() + intersectionDirectionToPack.getY() * pointOnPlane2.getY() - + intersectionDirectionToPack.getZ() * pointOnPlane2.getZ(); + + intersectionDirectionToPack.getZ() * pointOnPlane2.getZ(); double d3 = 0.5 * (normal3DotPoint1 + normal3DotPoint2); pointOnIntersectionToPack.set(d1 * normal3Cross2X + d2 * normal1Cross3X + d3 * normal2Cross1X, @@ -7171,7 +7174,7 @@ public static boolean intersectionBetweenTwoPlane3Ds(Point3DReadOnly pointOnPlan * @param lengthSideB the length of the side B. * @param lengthSideC the length of the side C. * @return {@code true} if the lengths represents the three sides of a triangle, {@code false} - * otherwise. + * otherwise. * @throws IllegalArgumentException if any of the three lengths is negative. */ public static boolean isFormingTriangle(double lengthSideA, double lengthSideB, double lengthSideC) @@ -7203,8 +7206,8 @@ public static boolean isFormingTriangle(double lengthSideA, double lengthSideB, * @param rayDirectionX the x-component of the ray's direction. * @param rayDirectionY the y-component of the ray's direction. * @return {@link Location#AHEAD} if the query is located in front of the ray, - * {@link Location#BEHIND} if the query is behind the ray, and {@code null} if the query's - * projection onto the ray is exactly equal to the ray origin. + * {@link Location#BEHIND} if the query is behind the ray, and {@code null} if the query's + * projection onto the ray is exactly equal to the ray origin. */ public static Location whichPartOfRay2DIsPoint2DOn(double pointX, double pointY, @@ -7232,8 +7235,8 @@ else if (dotProduct < 0.0) * @param rayOrigin the ray's origin. Not modified. * @param rayDirection the ray's direction. Not modified. * @return {@link Location#AHEAD} if the query is located in front of the ray, - * {@link Location#BEHIND} if the query is behind the ray, and {@code null} if the query's - * projection onto the ray is exactly equal to the ray origin. + * {@link Location#BEHIND} if the query is behind the ray, and {@code null} if the query's + * projection onto the ray is exactly equal to the ray origin. */ public static Location whichPartOfRay2DIsPoint2DOn(Point2DReadOnly point, Point2DReadOnly rayOrigin, Vector2DReadOnly rayDirection) { @@ -7280,7 +7283,7 @@ public static boolean isPoint2DInFrontOfRay2D(Point2DReadOnly point, Point2DRead * @param b second vertex of the triangle. Not modified. * @param c third vertex of the triangle. Not modified. * @return {@code true} if the query is exactly inside the triangle. {@code false} if the query - * point is outside triangle or exactly on an edge of the triangle. + * point is outside triangle or exactly on an edge of the triangle. */ public static boolean isPoint2DInsideTriangleABC(Point2DReadOnly point, Point2DReadOnly a, Point2DReadOnly b, Point2DReadOnly c) { @@ -7388,7 +7391,7 @@ public static boolean isPoint2DOnLineSegment2D(Point2DReadOnly point, Point2DRea * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return {@link Location.LEFT}/{@link Location.RIGHT} if the point is on the left/right side of - * the line, or {@code null} if the point is exactly on the line. + * the line, or {@code null} if the point is exactly on the line. */ public static Location whichSideOfLine2DIsPoint2DOn(double pointX, double pointY, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -7417,7 +7420,7 @@ public static Location whichSideOfLine2DIsPoint2DOn(double pointX, double pointY * @param pointOnLine a point positioned on the infinite line. Not modified. * @param lineDirection the direction of the infinite line. Not modified. * @return {@link Location.LEFT}/{@link Location.RIGHT} if the point is on the left/right side of - * the line, or {@code null} if the point is exactly on the line. + * the line, or {@code null} if the point is exactly on the line. */ public static Location whichSideOfLine2DIsPoint2DOn(double pointX, double pointY, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -7446,7 +7449,7 @@ public static Location whichSideOfLine2DIsPoint2DOn(double pointX, double pointY * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return {@link Location.LEFT}/{@link Location.RIGHT} if the point is on the left/right side of - * the line, or {@code null} if the point is exactly on the line. + * the line, or {@code null} if the point is exactly on the line. */ public static Location whichSideOfLine2DIsPoint2DOn(Point2DReadOnly point, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -7470,7 +7473,7 @@ public static Location whichSideOfLine2DIsPoint2DOn(Point2DReadOnly point, Point * @param pointOnLine a point positioned on the infinite line. Not modified. * @param lineDirection the direction of the infinite line. Not modified. * @return {@link Location.LEFT}/{@link Location.RIGHT} if the point is on the left/right side of - * the line, or {@code null} if the point is exactly on the line. + * the line, or {@code null} if the point is exactly on the line. */ public static Location whichSideOfLine2DIsPoint2DOn(Point2DReadOnly point, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -7497,7 +7500,7 @@ public static Location whichSideOfLine2DIsPoint2DOn(Point2DReadOnly point, Point * @param lineDirectionX the x-component of the direction of the infinite line. * @param lineDirectionY the y-component of the direction of the infinite line. * @return {@link Location.LEFT}/{@link Location.RIGHT} if the point is on the left/right side of - * the line, or {@code null} if the point is exactly on the line. + * the line, or {@code null} if the point is exactly on the line. */ public static Location whichSideOfLine2DIsPoint2DOn(double pointX, double pointY, @@ -7532,7 +7535,7 @@ else if (crossProduct < 0.0) * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return {@code true} if the point is on the left side of the line, {@code false} if the point is - * on the right side or exactly on the line. + * on the right side or exactly on the line. */ public static boolean isPoint2DOnLeftSideOfLine2D(Point2DReadOnly point, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -7554,7 +7557,7 @@ public static boolean isPoint2DOnLeftSideOfLine2D(Point2DReadOnly point, Point2D * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return {@code true} if the point is on the right side of the line, {@code false} if the point is - * on the left side or exactly on the line. + * on the left side or exactly on the line. */ public static boolean isPoint2DOnRightSideOfLine2D(Point2DReadOnly point, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -7583,10 +7586,10 @@ public static boolean isPoint2DOnRightSideOfLine2D(Point2DReadOnly point, Point2 * @param testLeftSide the query of the side, when equal to {@code true} this will test for the * left side, {@code false} this will test for the right side. * @return {@code true} if the point is on the query side of the line, {@code false} if the point is - * on the opposite side or exactly on the line. + * on the opposite side or exactly on the line. * @deprecated Use - * {@link #whichSideOfLine2DIsPoint2DOn(double, double, double, double, double, double)} - * instead. + * {@link #whichSideOfLine2DIsPoint2DOn(double, double, double, double, double, double)} + * instead. */ @Deprecated public static boolean isPoint2DOnSideOfLine2D(double pointX, @@ -7622,10 +7625,10 @@ public static boolean isPoint2DOnSideOfLine2D(double pointX, * @param testLeftSide the query of the side, when equal to {@code true} this will test for the * left side, {@code false} this will test for the right side. * @return {@code true} if the point is on the query side of the line, {@code false} if the point is - * on the opposite side or exactly on the line. + * on the opposite side or exactly on the line. * @deprecated Use - * {@link #whichSideOfLine2DIsPoint2DOn(double, double, Point2DReadOnly, Point2DReadOnly)} - * instead. + * {@link #whichSideOfLine2DIsPoint2DOn(double, double, Point2DReadOnly, Point2DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint2DOnSideOfLine2D(double pointX, @@ -7658,10 +7661,10 @@ public static boolean isPoint2DOnSideOfLine2D(double pointX, * @param testLeftSide the query of the side, when equal to {@code true} this will test for the * left side, {@code false} this will test for the right side. * @return {@code true} if the point is on the query side of the line, {@code false} if the point is - * on the opposite side or exactly on the line. + * on the opposite side or exactly on the line. * @deprecated Use - * {@link #whichSideOfLine2DIsPoint2DOn(double, double, Point2DReadOnly, Vector2DReadOnly)} - * instead. + * {@link #whichSideOfLine2DIsPoint2DOn(double, double, Point2DReadOnly, Vector2DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint2DOnSideOfLine2D(double pointX, @@ -7694,10 +7697,10 @@ public static boolean isPoint2DOnSideOfLine2D(double pointX, * @param testLeftSide the query of the side, when equal to {@code true} this will test for the * left side, {@code false} this will test for the right side. * @return {@code true} if the point is on the query side of the line, {@code false} if the point is - * on the opposite side or exactly on the line. + * on the opposite side or exactly on the line. * @deprecated Use - * {@link #whichSideOfLine2DIsPoint2DOn(Point2DReadOnly, Point2DReadOnly, Point2DReadOnly)} - * instead. + * {@link #whichSideOfLine2DIsPoint2DOn(Point2DReadOnly, Point2DReadOnly, Point2DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint2DOnSideOfLine2D(Point2DReadOnly point, @@ -7728,10 +7731,10 @@ public static boolean isPoint2DOnSideOfLine2D(Point2DReadOnly point, * @param testLeftSide the query of the side, when equal to {@code true} this will test for the * left side, {@code false} this will test for the right side. * @return {@code true} if the point is on the query side of the line, {@code false} if the point is - * on the opposite side or exactly on the line. + * on the opposite side or exactly on the line. * @deprecated Use - * {@link #whichSideOfLine2DIsPoint2DOn(Point2DReadOnly, Point2DReadOnly, Vector2DReadOnly)} - * instead. + * {@link #whichSideOfLine2DIsPoint2DOn(Point2DReadOnly, Point2DReadOnly, Vector2DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint2DOnSideOfLine2D(Point2DReadOnly point, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection, boolean testLeftSide) @@ -7763,7 +7766,7 @@ public static boolean isPoint2DOnSideOfLine2D(Point2DReadOnly point, Point2DRead * @param planeNormalY the y-component of the normal of the infinite plane. * @param planeNormalZ the z-component of the normal of the infinite plane. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. */ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, double pointY, @@ -7807,7 +7810,7 @@ else if (signedDistance < 0.0) * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. */ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, double pointY, double pointZ, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -7839,7 +7842,7 @@ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, double point * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. */ public static Location whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly point, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -7872,9 +7875,9 @@ public static Location whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly point, Poin * @param planeSecondTangentY the y-component of a second tangent of the infinite plane. * @param planeSecondTangentZ the z-component of a second tangent of the infinite plane. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. * @see #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, - * double, double) + * double, double) */ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, double pointY, @@ -7916,9 +7919,9 @@ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. * @see #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, - * double, double) + * double, double) */ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, double pointY, @@ -7960,9 +7963,9 @@ public static Location whichSideOfPlane3DIsPoint3DOn(double pointX, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@link Location#ABOVE}/{@link Location#BELOW} if the point is above/below the plane, - * {@code null} if the point is exactly on the plane. + * {@code null} if the point is exactly on the plane. * @see #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, - * double, double) + * double, double) */ public static Location whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly point, Point3DReadOnly pointOnPlane, @@ -7997,10 +8000,10 @@ public static Location whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly point, * @param testForAbove the query of the side, when equal to {@code true} this will test for the * above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, double, double)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, double, double)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, @@ -8047,10 +8050,10 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, * @param testForAbove the query of the side, when equal to {@code true} this will test for the * above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, Point3DReadOnly, Vector3DReadOnly, boolean)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, Point3DReadOnly, Vector3DReadOnly, boolean)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, @@ -8083,10 +8086,10 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, * @param testForAbove the query of the side, when equal to {@code true} this will test for the * above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly, Point3DReadOnly, Vector3DReadOnly)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly, Point3DReadOnly, Vector3DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(Point3DReadOnly point, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal, boolean testForAbove) @@ -8114,7 +8117,7 @@ public static boolean isPoint3DAboveOrBelowPlane3D(Point3DReadOnly point, Point3 * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@code true} if the point is strictly above the plane, {@code false} if the point is - * below or exactly on the plane. + * below or exactly on the plane. */ public static boolean isPoint3DAbovePlane3D(double pointX, double pointY, double pointZ, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -8138,7 +8141,7 @@ public static boolean isPoint3DAbovePlane3D(double pointX, double pointY, double * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@code true} if the point is strictly above the plane, {@code false} if the point is - * below or exactly on the plane. + * below or exactly on the plane. */ public static boolean isPoint3DAbovePlane3D(Point3DReadOnly point, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -8164,7 +8167,7 @@ public static boolean isPoint3DAbovePlane3D(Point3DReadOnly point, Point3DReadOn * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@code true} if the point is strictly below the plane, {@code false} if the point is - * above or exactly on the plane. + * above or exactly on the plane. */ public static boolean isPoint3DBelowPlane3D(double pointX, double pointY, double pointZ, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -8188,7 +8191,7 @@ public static boolean isPoint3DBelowPlane3D(double pointX, double pointY, double * @param pointOnPlane the coordinates of a point positioned on the infinite plane. Not modified. * @param planeNormal the normal of the infinite plane. Not modified. * @return {@code true} if the point is strictly below the plane, {@code false} if the point is - * above or exactly on the plane. + * above or exactly on the plane. */ public static boolean isPoint3DBelowPlane3D(Point3DReadOnly point, Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal) { @@ -8223,12 +8226,12 @@ public static boolean isPoint3DBelowPlane3D(Point3DReadOnly point, Point3DReadOn * @param testForAbove the query of the side, when equal to {@code true} this will test for * the above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, double, double, double, double, double)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, double, double, double, double, double, double, double, double, double)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, @@ -8258,7 +8261,6 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, planeSecondTangentY, planeSecondTangentZ); return testForAbove ? side == Location.ABOVE : side == Location.BELOW; - } /** @@ -8284,12 +8286,12 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, * @param testForAbove the query of the side, when equal to {@code true} this will test for * the above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, Point3DReadOnly, Vector3DReadOnly, Vector3DReadOnly)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(double, double, double, Point3DReadOnly, Vector3DReadOnly, Vector3DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, @@ -8302,7 +8304,6 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, { Location side = whichSideOfPlane3DIsPoint3DOn(pointX, pointY, pointZ, pointOnPlane, planeFirstTangent, planeSecondTangent); return testForAbove ? side == Location.ABOVE : side == Location.BELOW; - } /** @@ -8326,12 +8327,12 @@ public static boolean isPoint3DAboveOrBelowPlane3D(double pointX, * @param testForAbove the query of the side, when equal to {@code true} this will test for * the above side, {@code false} this will test for the below side. * @return {@code true} if the point is on the query side of the plane, {@code false} if the point - * is on the opposite side or exactly on the plane. + * is on the opposite side or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) * @deprecated Use - * {@link #whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly, Point3DReadOnly, Vector3DReadOnly, Vector3DReadOnly)} - * instead. + * {@link #whichSideOfPlane3DIsPoint3DOn(Point3DReadOnly, Point3DReadOnly, Vector3DReadOnly, Vector3DReadOnly)} + * instead. */ @Deprecated public static boolean isPoint3DAboveOrBelowPlane3D(Point3DReadOnly point, @@ -8342,7 +8343,6 @@ public static boolean isPoint3DAboveOrBelowPlane3D(Point3DReadOnly point, { Location side = whichSideOfPlane3DIsPoint3DOn(point, pointOnPlane, planeFirstTangent, planeSecondTangent); return testForAbove ? side == Location.ABOVE : side == Location.BELOW; - } /** @@ -8366,9 +8366,9 @@ public static boolean isPoint3DAboveOrBelowPlane3D(Point3DReadOnly point, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@code true} if the point is strictly above the plane, {@code false} if the point is - * below or exactly on the plane. + * below or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) */ public static boolean isPoint3DAbovePlane3D(double pointX, double pointY, @@ -8399,9 +8399,9 @@ public static boolean isPoint3DAbovePlane3D(double pointX, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@code true} if the point is strictly above the plane, {@code false} if the point is - * below or exactly on the plane. + * below or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) */ public static boolean isPoint3DAbovePlane3D(Point3DReadOnly point, Point3DReadOnly pointOnPlane, @@ -8432,9 +8432,9 @@ public static boolean isPoint3DAbovePlane3D(Point3DReadOnly point, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@code true} if the point is strictly below the plane, {@code false} if the point is - * above or exactly on the plane. + * above or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) */ public static boolean isPoint3DBelowPlane3D(double pointX, double pointY, @@ -8465,9 +8465,9 @@ public static boolean isPoint3DBelowPlane3D(double pointX, * @param planeFirstTangent a first tangent of the infinite plane. Not modified. * @param planeSecondTangent a second tangent of the infinite plane. Not modified. * @return {@code true} if the point is strictly below the plane, {@code false} if the point is - * above or exactly on the plane. + * above or exactly on the plane. * @see #isPoint3DAboveOrBelowPlane3D(double, double, double, double, double, double, double, - * double, double, boolean) + * double, double, boolean) */ public static boolean isPoint3DBelowPlane3D(Point3DReadOnly point, Point3DReadOnly pointOnPlane, @@ -9245,8 +9245,8 @@ public static boolean orthogonalProjectionOnPlane3D(double x, projectionToPack.set(x, y, z); projectionToPack.sub(pointOnPlane); - double signedDistance = projectionToPack.getX() * planeNormal.getX() + projectionToPack.getY() * planeNormal.getY() - + projectionToPack.getZ() * planeNormal.getZ(); + double signedDistance = + projectionToPack.getX() * planeNormal.getX() + projectionToPack.getY() * planeNormal.getY() + projectionToPack.getZ() * planeNormal.getZ(); signedDistance /= normalMagnitude * normalMagnitude; projectionToPack.set(x - signedDistance * planeNormal.getX(), y - signedDistance * planeNormal.getY(), z - signedDistance * planeNormal.getZ()); @@ -9276,7 +9276,7 @@ public static boolean orthogonalProjectionOnPlane3D(double x, * @param lineDirection2x x-component of the second line direction. * @param lineDirection2y y-component of the second line direction. * @return {@code alpha} the percentage along the first line of the intersection location. This - * method returns {@link Double#NaN} if the lines do not intersect. + * method returns {@link Double#NaN} if the lines do not intersect. */ public static double percentageOfIntersectionBetweenTwoLine2Ds(double pointOnLine1x, double pointOnLine1y, @@ -9301,7 +9301,7 @@ public static double percentageOfIntersectionBetweenTwoLine2Ds(double pointOnLin if (Math.abs(determinant) < ONE_TRILLIONTH) { // The lines are parallel - // Check if they are collinear + // Check if they are collinear double cross = dx * lineDirection1y - dy * lineDirection1x; if (Math.abs(cross) < ONE_TRILLIONTH) { @@ -9347,7 +9347,7 @@ public static double percentageOfIntersectionBetweenTwoLine2Ds(double pointOnLin * @param pointOnLine2 a point located on the second line. Not modified. * @param lineDirection2 the second line direction. Not modified. * @return {@code alpha} the percentage along the first line of the intersection location. This - * method returns {@link Double#NaN} if the lines do not intersect. + * method returns {@link Double#NaN} if the lines do not intersect. */ public static double percentageOfIntersectionBetweenTwoLine2Ds(Point2DReadOnly pointOnLine1, Vector2DReadOnly lineDirection1, @@ -9387,7 +9387,7 @@ public static double percentageOfIntersectionBetweenTwoLine2Ds(Point2DReadOnly p * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the line direction. Not modified. * @return {@code alpha} the percentage along the line segment of the intersection location. This - * method returns {@link Double#NaN} if the line segment and the line do not intersect. + * method returns {@link Double#NaN} if the line segment and the line do not intersect. */ public static double percentageOfIntersectionBetweenLineSegment2DAndLine2D(Point2DReadOnly lineSegmentStart, Point2DReadOnly lineSegmentEnd, @@ -9437,7 +9437,7 @@ else if (alpha > 1.0) * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine2D(Point2DReadOnly point, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -9464,7 +9464,7 @@ public static double percentageAlongLine2D(Point2DReadOnly point, Point2DReadOnl * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine2D(double pointX, double pointY, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -9493,7 +9493,7 @@ public static double percentageAlongLine2D(double pointX, double pointY, Point2D * @param lineDirectionX x-component of the direction of the line. * @param lineDirectionY y-component of the direction of the line. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine2D(double pointX, double pointY, @@ -9543,7 +9543,7 @@ public static double percentageAlongLine2D(double pointX, * @param lineSegmentEndX the x-coordinate of the line segment second endpoint. * @param lineSegmentEndY the y-coordinate of the line segment second endpoint. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment2D(double pointX, double pointY, @@ -9593,7 +9593,7 @@ public static double percentageAlongLineSegment2D(double pointX, * @param lineSegmentStart the line segment first endpoint. Not modified. * @param lineSegmentEnd the line segment second endpoint. Not modified. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment2D(double pointX, double pointY, Point2DReadOnly lineSegmentStart, Point2DReadOnly lineSegmentEnd) { @@ -9625,7 +9625,7 @@ public static double percentageAlongLineSegment2D(double pointX, double pointY, * @param lineSegmentStart the line segment first endpoint. Not modified. * @param lineSegmentEnd the line segment second endpoint. Not modified. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment2D(Point2DReadOnly point, Point2DReadOnly lineSegmentStart, Point2DReadOnly lineSegmentEnd) { @@ -9651,7 +9651,7 @@ public static double percentageAlongLineSegment2D(Point2DReadOnly point, Point2D * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine3D(Point3DReadOnly point, Point3DReadOnly pointOnLine, Vector3DReadOnly lineDirection) { @@ -9687,7 +9687,7 @@ public static double percentageAlongLine3D(Point3DReadOnly point, Point3DReadOnl * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine3D(double pointX, double pointY, double pointZ, Point3DReadOnly pointOnLine, Vector3DReadOnly lineDirection) { @@ -9727,7 +9727,7 @@ public static double percentageAlongLine3D(double pointX, double pointY, double * @param lineDirectionY y-component of the direction of the line. * @param lineDirectionZ z-component of the direction of the line. * @return the computed percentage along the line representing where the point projection is - * located. + * located. */ public static double percentageAlongLine3D(double pointX, double pointY, @@ -9784,7 +9784,7 @@ public static double percentageAlongLine3D(double pointX, * @param lineSegmentEndY the y-coordinate of the line segment second endpoint. * @param lineSegmentEndZ the z-coordinate of the line segment second endpoint. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment3D(double pointX, double pointY, @@ -9840,7 +9840,7 @@ public static double percentageAlongLineSegment3D(double pointX, * @param lineSegmentStart the line segment first endpoint. Not modified. * @param lineSegmentEnd the line segment second endpoint. Not modified. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment3D(double pointX, double pointY, @@ -9884,7 +9884,7 @@ public static double percentageAlongLineSegment3D(double pointX, * @param lineSegmentStart the line segment first endpoint. Not modified. * @param lineSegmentEnd the line segment second endpoint. Not modified. * @return the computed percentage along the line segment representing where the point projection is - * located. + * located. */ public static double percentageAlongLineSegment3D(Point3DReadOnly point, Point3DReadOnly lineSegmentStart, Point3DReadOnly lineSegmentEnd) { @@ -10090,7 +10090,7 @@ public static void perpendicularVector2D(Vector2DReadOnly vector, Vector2DBasics * @param orthogonalProjectionToPack a 3D point in which the projection of {@code point} onto the * line is stored. Modified. Can be {@code null}. * @return the vector perpendicular to the line and pointing to the {@code point}, or {@code null} - * when the method fails. + * when the method fails. */ public static Vector3D perpendicularVector3DFromLine3DToPoint3D(Point3DReadOnly point, Point3DReadOnly firstPointOnLine, @@ -10188,7 +10188,7 @@ public static boolean perpendicularVector3DFromLine3DToPoint3D(Point3DReadOnly p * @param cathetusA the length of the cathetus A. * @return the length of the cathetus B. * @throws IllegalArgumentException if the length of the cathetus A is negative or greater than the - * hypotenuse C. + * hypotenuse C. */ public static double pythagorasGetCathetus(double hypotenuseC, double cathetusA) { @@ -10262,7 +10262,7 @@ public static double radiusOfArc(double chordLength, double chordAngle) * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return the minimum distance between the 2D point and the 2D line. The distance is negative if - * the query is located on the right side of the line. + * the query is located on the right side of the line. */ public static double signedDistanceFromPoint2DToLine2D(double pointX, double pointY, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -10292,7 +10292,7 @@ public static double signedDistanceFromPoint2DToLine2D(double pointX, double poi * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the minimum distance between the 2D point and the 2D line. The distance is negative if - * the query is located on the right side of the line. + * the query is located on the right side of the line. */ public static double signedDistanceFromPoint2DToLine2D(double pointX, double pointY, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -10323,7 +10323,7 @@ public static double signedDistanceFromPoint2DToLine2D(double pointX, double poi * @param firstPointOnLine a first point located on the line. Not modified. * @param secondPointOnLine a second point located on the line. Not modified. * @return the minimum distance between the 2D point and the 2D line. The distance is negative if - * the query is located on the right side of the line. + * the query is located on the right side of the line. */ public static double signedDistanceFromPoint2DToLine2D(Point2DReadOnly point, Point2DReadOnly firstPointOnLine, Point2DReadOnly secondPointOnLine) { @@ -10348,7 +10348,7 @@ public static double signedDistanceFromPoint2DToLine2D(Point2DReadOnly point, Po * @param pointOnLine a point located on the line. Not modified. * @param lineDirection the direction of the line. Not modified. * @return the minimum distance between the 2D point and the 2D line. The distance is negative if - * the query is located on the right side of the line. + * the query is located on the right side of the line. */ public static double signedDistanceFromPoint2DToLine2D(Point2DReadOnly point, Point2DReadOnly pointOnLine, Vector2DReadOnly lineDirection) { @@ -10376,7 +10376,7 @@ public static double signedDistanceFromPoint2DToLine2D(Point2DReadOnly point, Po * @param lineDirectionX x-component of the line direction. * @param lineDirectionY y-component of the line direction. * @return the minimum distance between the 2D point and the 2D line. The distance is negative if - * the query is located on the right side of the line. + * the query is located on the right side of the line. */ public static double signedDistanceFromPoint2DToLine2D(double pointX, double pointY, @@ -10631,8 +10631,8 @@ public static double triangleCircumradius(double lengthA, double lengthB, double public static double triangleIsoscelesHeight(double legLength, double baseLength) { if (legLength < 0.5 * baseLength) - throw new IllegalArgumentException("Malformed isosceles triangle, expected legLength > baseLength/2, was legLength: " + legLength + ", baseLength/2: " - + 0.5 * baseLength); + throw new IllegalArgumentException( + "Malformed isosceles triangle, expected legLength > baseLength/2, was legLength: " + legLength + ", baseLength/2: " + 0.5 * baseLength); if (baseLength < 0.0) throw new IllegalArgumentException("The base cannot have a negative length, baseLength = " + baseLength); @@ -10650,14 +10650,15 @@ public static double triangleIsoscelesHeight(double legLength, double baseLength * @param lengthOppositeSideC the length of the side C. * @return the value in radians of the unknown angle. * @throws IllegalArgumentException if the lengths do not describe a triangle, see - * {@link #isFormingTriangle(double, double, double)}. + * {@link #isFormingTriangle(double, double, double)}. */ public static double unknownTriangleAngleByLawOfCosine(double lengthNeighbourSideA, double lengthNeighbourSideB, double lengthOppositeSideC) { if (!isFormingTriangle(lengthNeighbourSideA, lengthNeighbourSideB, lengthOppositeSideC)) { - throw new IllegalArgumentException("Unable to build a Triangle of the given triangle sides a: " + lengthNeighbourSideA + " b: " + lengthNeighbourSideB - + " c: " + lengthOppositeSideC); + throw new IllegalArgumentException( + "Unable to build a Triangle of the given triangle sides a: " + lengthNeighbourSideA + " b: " + lengthNeighbourSideB + " c: " + + lengthOppositeSideC); } double numerator = lengthNeighbourSideA * lengthNeighbourSideA + lengthNeighbourSideB * lengthNeighbourSideB - lengthOppositeSideC * lengthOppositeSideC; @@ -10681,7 +10682,7 @@ public static double unknownTriangleAngleByLawOfCosine(double lengthNeighbourSid * @param angleBetweenAAndB the angle between the sides A and B. * @return the value of the unknown side length. * @throws IllegalArgumentException if {@code lengthSideA} and/or {@code lengthSideB} are negative, - * if {@code angleBetweenAAndB} is greater than pi. + * if {@code angleBetweenAAndB} is greater than pi. */ public static double unknownTriangleSideLengthByLawOfCosine(double lengthSideA, double lengthSideB, double angleBetweenAAndB) { @@ -10692,7 +10693,1006 @@ public static double unknownTriangleSideLengthByLawOfCosine(double lengthSideA, if (Math.abs(angleBetweenAAndB) > Math.PI) throw new IllegalArgumentException("angleBetweenAAndB " + angleBetweenAAndB + " does not define a triangle."); - return EuclidCoreTools.squareRoot(lengthSideA * lengthSideA + lengthSideB * lengthSideB - - 2.0 * lengthSideA * lengthSideB * EuclidCoreTools.cos(angleBetweenAAndB)); + return EuclidCoreTools.squareRoot( + lengthSideA * lengthSideA + lengthSideB * lengthSideB - 2.0 * lengthSideA * lengthSideB * EuclidCoreTools.cos(angleBetweenAAndB)); + } + + /** + * Tests if the point 2D is located on the infinitely long line 2D. + *

+ * The test is performed by computing the distance between the point and the line, if that distance + * is below {@link EuclidGeometryTools#IS_POINT_ON_LINE_EPS} this method returns {@code true}. + *

+ * + * @param lineSegmentStart the first endpoint of the line segment. Not modified. + * @param lineSegmentEnd the second endpoint of the line segment. Not modified. + * @return {@code true} if the query is considered to be lying on the line, {@code false} otherwise. + */ + public static boolean isPoint2DOnLineSegment2D(double pointX, double pointY, Point2DReadOnly lineSegmentStart, Point2DReadOnly lineSegmentEnd) + { + return distanceSquaredFromPoint2DToLineSegment2D(pointX, + pointY, + lineSegmentStart.getX(), + lineSegmentStart.getY(), + lineSegmentEnd.getX(), + lineSegmentEnd.getY()) < IS_POINT_ON_LINE_EPS; + } + + /** + * This method implements the same operation as + * {@link EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(double, double, double, double, double, double, Orientation3DBasics)} + * except that it does not rely on {@code Math#acos(double)} making it faster. + * + * @param firstVectorX x-component of the first vector. + * @param firstVectorY y-component of the first vector. + * @param firstVectorZ z-component of the first vector. + * @param secondVectorX x-component of the second vector that is rotated with respect to the first + * vector. + * @param secondVectorY y-component of the second vector that is rotated with respect to the first + * vector. + * @param secondVectorZ z-component of the second vector that is rotated with respect to the first + * vector. + * @param rotationToPack the minimum rotation from {@code firstVector} to the {@code secondVector}. + * Modified. + * @see EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(double, double, double, double, + * double, double, Orientation3DBasics) + */ + public static void rotationMatrix3DFromFirstToSecondVector3D(double firstVectorX, + double firstVectorY, + double firstVectorZ, + double secondVectorX, + double secondVectorY, + double secondVectorZ, + CommonMatrix3DBasics rotationToPack) + { + double firstVectorLengthInv = 1.0 / Math.sqrt(normSquared(firstVectorX, firstVectorY, firstVectorZ)); + double secondVectorLengthInv = 1.0 / Math.sqrt(normSquared(secondVectorX, secondVectorY, secondVectorZ)); + firstVectorX *= firstVectorLengthInv; + firstVectorY *= firstVectorLengthInv; + firstVectorZ *= firstVectorLengthInv; + secondVectorX *= secondVectorLengthInv; + secondVectorY *= secondVectorLengthInv; + secondVectorZ *= secondVectorLengthInv; + + double rotationAxisX = firstVectorY * secondVectorZ - firstVectorZ * secondVectorY; + double rotationAxisY = firstVectorZ * secondVectorX - firstVectorX * secondVectorZ; + double rotationAxisZ = firstVectorX * secondVectorY - firstVectorY * secondVectorX; + double sinAngle = Math.sqrt(normSquared(rotationAxisX, rotationAxisY, rotationAxisZ)); + + boolean normalsAreParallel = sinAngle < ONE_TEN_MILLIONTH; + + if (normalsAreParallel) + { + rotationToPack.setIdentity(); + return; + } + + rotationAxisX /= sinAngle; + rotationAxisY /= sinAngle; + rotationAxisZ /= sinAngle; + + double cosAngle = firstVectorX * secondVectorX + firstVectorY * secondVectorY + firstVectorZ * secondVectorZ; + + if (cosAngle > 1.0) + cosAngle = 1.0; + else if (cosAngle < -1.0) + cosAngle = -1.0; + + double t = 1.0 - cosAngle; + + double xz = rotationAxisX * rotationAxisZ; + double xy = rotationAxisX * rotationAxisY; + double yz = rotationAxisY * rotationAxisZ; + + double m00 = t * rotationAxisX * rotationAxisX + cosAngle; + double m01 = t * xy - sinAngle * rotationAxisZ; + double m02 = t * xz + sinAngle * rotationAxisY; + double m10 = t * xy + sinAngle * rotationAxisZ; + double m11 = t * rotationAxisY * rotationAxisY + cosAngle; + double m12 = t * yz - sinAngle * rotationAxisX; + double m20 = t * xz - sinAngle * rotationAxisY; + double m21 = t * yz + sinAngle * rotationAxisX; + double m22 = t * rotationAxisZ * rotationAxisZ + cosAngle; + + if (rotationToPack instanceof RotationMatrix) + ((RotationMatrix) rotationToPack).setUnsafe(m00, m01, m02, m10, m11, m12, m20, m21, m22); + else + rotationToPack.set(m00, m01, m02, m10, m11, m12, m20, m21, m22); + } + + /** + * This method implements the same operation as + * {@link EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, Vector3DReadOnly, Orientation3DBasics)} + * except that it does not rely on {@code Math#acos(double)} making it faster. + * + * @param firstVector the first vector. Not modified. + * @param secondVector the second vector that is rotated with respect to the first vector. Not + * modified. + * @param rotationToPack the minimum rotation from {@code firstVector} to the {@code secondVector}. + * Modified. + * @see EuclidGeometryTools#orientation3DFromFirstToSecondVector3D(Vector3DReadOnly, + * Vector3DReadOnly, Orientation3DBasics) + */ + public static void rotationMatrix3DFromFirstToSecondVector3D(Vector3DReadOnly firstVector, + Vector3DReadOnly secondVector, + CommonMatrix3DBasics rotationToPack) + { + rotationMatrix3DFromFirstToSecondVector3D(firstVector.getX(), + firstVector.getY(), + firstVector.getZ(), + secondVector.getX(), + secondVector.getY(), + secondVector.getZ(), + rotationToPack); + } + + /** + * This method implements the same operation as + * {@link EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics)} + * except that it does not rely on {@code Math#acos(double)} making it faster. + * + * @param vector the vector that is rotated with respect to {@code zUp}. Not modified. + * @param vector the vector that is rotated with respect to {@code zUp}. Not modified. + * @param rotationToPack the minimum rotation from {@code zUp} to the given {@code vector}. + * Modified. + * @see EuclidGeometryTools#orientation3DFromZUpToVector3D(Vector3DReadOnly, Orientation3DBasics) + */ + public static void rotationMatrix3DFromZUpToVector3D(Vector3DReadOnly vector, CommonMatrix3DBasics rotationToPack) + { + rotationMatrix3DFromFirstToSecondVector3D(Axis3D.Z, vector, rotationToPack); + } + + /** + * Returns the square of the minimum distance between a point and a given line segment, and packs the closest point on the line segment in + * {@code pointOnSegmentToPack} + *

+ * Edge cases: + *

+ *

+ * + * @param pointX x coordinate of point to be tested. + * @param pointY y coordinate of point to be tested. + * @param lineSegmentStartX the x-coordinate of the line segment first endpoint. + * @param lineSegmentStartY the y-coordinate of the line segment first endpoint. + * @param lineSegmentEndX the x-coordinate of the line segment second endpoint. + * @param lineSegmentEndY the y-coordinate of the line segment second endpoint. + * @param pointOnSegmentToPack the closest point on the line segment to the point to be tested. Modified. + * @return the square of the minimum distance between the 2D point and the 2D line segment. + */ + public static double distanceSquaredFromPoint2DToLineSegment2D(double pointX, + double pointY, + double lineSegmentStartX, + double lineSegmentStartY, + double lineSegmentEndX, + double lineSegmentEndY, + Point2DBasics pointOnSegmentToPack) + { + double percentage = percentageAlongLineSegment2D(pointX, pointY, lineSegmentStartX, lineSegmentStartY, lineSegmentEndX, lineSegmentEndY); + + if (percentage > 1.0) + percentage = 1.0; + else if (percentage < 0.0) + percentage = 0.0; + + double projectionX = (1.0 - percentage) * lineSegmentStartX + percentage * lineSegmentEndX; + double projectionY = (1.0 - percentage) * lineSegmentStartY + percentage * lineSegmentEndY; + + if (pointOnSegmentToPack != null) + pointOnSegmentToPack.set(projectionX, projectionY); + + double dx = projectionX - pointX; + double dy = projectionY - pointY; + return dx * dx + dy * dy; + } + + /** + * Computes and packs the intersecting points between a 2d line segment and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting position of the line segment. + * @param endPoint end position of the line segment. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenLineSegment2DAndCylinder3D(double circleRadius, + Point2DReadOnly circlePosition, + Point2DReadOnly startPoint, + Point2DReadOnly endPoint, + Point2DBasics firstIntersectionToPack, + Point2DBasics secondIntersectionToPack) + { + return intersectionBetweenLine2DAndCircle(circleRadius, + circlePosition.getX(), + circlePosition.getY(), + startPoint.getX(), + startPoint.getY(), + false, + endPoint.getX(), + endPoint.getY(), + false, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes and packs the intersecting points between a 2d line and a circle. Returns the number of found intersections. If the 2d line is represented as a + * line segment, then {@code canIntersectionOccurBeforeStart} and {@code canIntersectionOccurAfterEnd} should both be false. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePositionX x position of the center of the circle. + * @param circlePositionY y position of the center of the circle. + * @param startX x position of a point on the line, or starting x position of the line segment. + * @param startY y position of a point on the line, or starting y position of the line segment. + * @param canIntersectionOccurBeforeStart {@code true} if this is a line, {@code false} if this is a line segment or a ray. + * @param endX x position of a second point on the line, or end x position of the line segment. + * @param endY y position of a second point on the line, or end y position of the line segment. + * @param canIntersectionOccurAfterEnd {@code true} if this is a line or ray, {@code false} if this is a line segment. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenLine2DAndCircle(double circleRadius, + double circlePositionX, + double circlePositionY, + double startX, + double startY, + boolean canIntersectionOccurBeforeStart, + double endX, + double endY, + boolean canIntersectionOccurAfterEnd, + Point2DBasics firstIntersectionToPack, + Point2DBasics secondIntersectionToPack) + { + if (circleRadius < 0.0) + throw new IllegalArgumentException("The circle radius has to be positive."); + + if (firstIntersectionToPack != null) + firstIntersectionToPack.setToNaN(); + if (secondIntersectionToPack != null) + secondIntersectionToPack.setToNaN(); + + if (circleRadius == 0.0) + return 0; + + double radiusSquared = circleRadius * circleRadius; + + double dx = endX - startX; + double dy = endY - startY; + + double dIntersection1 = Double.NaN; + double dIntersection2 = Double.NaN; + + // Compute possible intersections with the circle + // + double deltaPX = startX - circlePositionX; + double deltaPY = startY - circlePositionY; + + double A = EuclidCoreTools.normSquared(dx, dy); + double B = 2.0 * (dx * deltaPX + dy * deltaPY); + double C = EuclidCoreTools.normSquared(deltaPX, deltaPY) - radiusSquared; + + double delta = EuclidCoreTools.squareRoot(B * B - 4 * A * C); + + if (Double.isFinite(delta)) + { + double oneOverTwoA = 0.5 / A; + double dCircle1 = -(B + delta) * oneOverTwoA; + double dCircle2 = -(B - delta) * oneOverTwoA; + + double intersection1X = dCircle1 * dx + startX; + double intersection1Y = dCircle1 * dy + startY; + + if (Math.abs(percentageAlongLine2D(intersection1X, intersection1Y, circlePositionX, circlePositionY, 1.0, 0.0)) > circleRadius - ONE_TRILLIONTH) + dCircle1 = Double.NaN; + + if (Double.isFinite(dCircle1)) + { + if (Double.isNaN(dIntersection1) || Math.abs(dCircle1 - dIntersection1) < ONE_TRILLIONTH) + { + dIntersection1 = dCircle1; + } + else if (dCircle1 < dIntersection1) + { + dIntersection2 = dIntersection1; + dIntersection1 = dCircle1; + } + else + { + dIntersection2 = dCircle1; + } + } + + double intersection2X = dCircle2 * dx + startX; + double intersection2Y = dCircle2 * dy + startY; + + if (Math.abs(percentageAlongLine2D(intersection2X, intersection2Y, circlePositionX, circlePositionY, 1.0, 0.0)) > circleRadius - ONE_TRILLIONTH) + dCircle2 = Double.NaN; + else if (Math.abs(dCircle1 - dCircle2) < ONE_TRILLIONTH) + dCircle2 = Double.NaN; + + if (Double.isFinite(dCircle2)) + { + if (Double.isNaN(dIntersection1)) + { + dIntersection1 = dCircle2; + } + else if (dCircle2 < dIntersection1) + { + dIntersection2 = dIntersection1; + dIntersection1 = dCircle2; + } + else + { + dIntersection2 = dCircle2; + } + } + } + + if (!canIntersectionOccurBeforeStart) + { + if (dIntersection2 < 0.0) + dIntersection2 = Double.NaN; + + if (dIntersection1 < 0.0) + { + dIntersection1 = dIntersection2; + dIntersection2 = Double.NaN; + } + } + + if (!canIntersectionOccurAfterEnd) + { + if (dIntersection2 > 1.0) + dIntersection2 = Double.NaN; + + if (dIntersection1 > 1.0) + { + dIntersection1 = dIntersection2; + dIntersection2 = Double.NaN; + } + } + + if (Double.isNaN(dIntersection1)) + return 0; + + if (firstIntersectionToPack != null) + { + firstIntersectionToPack.set(dx, dy); + firstIntersectionToPack.scale(dIntersection1); + firstIntersectionToPack.add(startX, startY); + } + + if (Double.isNaN(dIntersection2)) + return 1; + + if (secondIntersectionToPack != null) + { + secondIntersectionToPack.set(dx, dy); + secondIntersectionToPack.scale(dIntersection2); + secondIntersectionToPack.add(startX, startY); + } + + return 2; + } + + /** + * Computes and packs the intersecting points between a 2d line and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param pointOnLine point on the line + * @param direction direction of the line. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenLine2DAndCircle(double circleRadius, + Point2DReadOnly circlePosition, + Point2DReadOnly pointOnLine, + Vector2DReadOnly direction, + Point2DBasics firstIntersectionToPack, + Point2DBasics secondIntersectionToPack) + { + return intersectionBetweenLine2DAndCircle(circleRadius, + circlePosition.getX(), + circlePosition.getY(), + pointOnLine.getX(), + pointOnLine.getY(), + true, + pointOnLine.getX() + direction.getX(), + pointOnLine.getY() + direction.getY(), + true, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes and packs the intersecting points between a 2d ray and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting point of the line. + * @param direction direction of the line. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenRay2DAndCircle(double circleRadius, + Point2DReadOnly circlePosition, + Point2DReadOnly startPoint, + Vector2DReadOnly direction, + Point2DBasics firstIntersectionToPack, + Point2DBasics secondIntersectionToPack) + { + return intersectionBetweenLine2DAndCircle(circleRadius, + circlePosition.getX(), + circlePosition.getY(), + startPoint.getX(), + startPoint.getY(), + false, + startPoint.getX() + direction.getX(), + startPoint.getY() + direction.getY(), + true, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes and packs the intersecting points between a 2d ray and a circle. Returns the number of found intersections. + *

+ * Edge cases: + *

+ *

+ * + * @param circleRadius radius of the circle in question. + * @param circlePosition position of the center of the circle. + * @param startPoint starting point of the line. + * @param pointOnRay additional point on the ray use to determine its direction. + * @param firstIntersectionToPack first possible intersecting point. + * @param secondIntersectionToPack second possible intersecting point. + * @return number of intersections found. Can be {@code 0} if no intersections, {@code 1} or {@code 2}. + */ + public static int intersectionBetweenRay2DAndCircle(double circleRadius, + Point2DReadOnly circlePosition, + Point2DReadOnly startPoint, + Point2DReadOnly pointOnRay, + Point2DBasics firstIntersectionToPack, + Point2DBasics secondIntersectionToPack) + { + return intersectionBetweenLine2DAndCircle(circleRadius, + circlePosition.getX(), + circlePosition.getY(), + startPoint.getX(), + startPoint.getY(), + false, + pointOnRay.getX(), + pointOnRay.getY(), + true, + firstIntersectionToPack, + secondIntersectionToPack); + } + + /** + * Computes the coordinates of the intersection between a plane and an infinitely long line. + * Useful link . + *

+ * Edge cases: + *

+ *

+ * + * @param pointOnPlaneX x-value of a point located on the plane. + * @param pointOnPlaneY y-value of a point located on the plane. + * @param pointOnPlaneZ z-value of a point located on the plane. + * @param planeNormalX x-value of the normal of the plane. + * @param planeNormalY y-value of the normal of the plane. + * @param planeNormalZ z-value of the normal of the plane. + * @param pointOnLineX x-value of a point located on the line. + * @param pointOnLineY y-value of a point located on the line. + * @param pointOnLineZ z-value of a point located on the line. + * @param lineDirectionX x-value of the direction of the line. + * @param lineDirectionY y-value of the direction of the line. + * @param lineDirectionZ z-value of the direction of the line. + * @param intersectionToPack point in which the coordinates of the intersection are stored. Modified. + * @return {@code true} if the method succeeds, {@code false} otherwise. + */ + public static boolean intersectionBetweenLine3DAndPlane3D(double pointOnPlaneX, + double pointOnPlaneY, + double pointOnPlaneZ, + double planeNormalX, + double planeNormalY, + double planeNormalZ, + double pointOnLineX, + double pointOnLineY, + double pointOnLineZ, + double lineDirectionX, + double lineDirectionY, + double lineDirectionZ, + Point3DBasics intersectionToPack) + { + // Switching to the notation used in https://en.wikipedia.org/wiki/Line%E2%80%93plane_intersection + // Note: the algorithm is independent from the magnitudes of planeNormal and lineDirection + + // Let's compute the value of the coefficient d = ( (p0 - l0).n ) / ( l.n ) + double numerator, denominator; + numerator = (pointOnPlaneX - pointOnLineX) * planeNormalX; + numerator += (pointOnPlaneY - pointOnLineY) * planeNormalY; + numerator += (pointOnPlaneZ - pointOnLineZ) * planeNormalZ; + denominator = planeNormalX * lineDirectionX + planeNormalY * lineDirectionY + planeNormalZ * lineDirectionZ; + + // Check if the line is parallel to the plane + if (Math.abs(denominator) < ONE_TRILLIONTH) + { + return false; + } + else + { + double d = numerator / denominator; + + intersectionToPack.setX(d * lineDirectionX + pointOnLineX); + intersectionToPack.setY(d * lineDirectionY + pointOnLineY); + intersectionToPack.setZ(d * lineDirectionZ + pointOnLineZ); + return true; + } + } + + /** + * Computes the intersection between an infinitely long 2D line (defined by a 2D point and a 2D + * direction) and a 2D ray segment (defined by the start and direction 2D endpoints). + *

+ * Edge cases: + *

+ *

+ * + * @param rayOrigin start point of the ray. + * @param rayDirection direction of the ray. + * @param linePoint1 first point located on the line. + * @param linePoint2 second point located on the line. + * @param intersectionToPack the 2D point in which the result is stored. Can be {@code null}. + * Modified. + * @return {@code true} if the line intersects the line segment, {@code false} otherwise. + */ + public static boolean intersectionBetweenRay2DAndLine2D(Point2DReadOnly rayOrigin, + Vector2DReadOnly rayDirection, + Point2DReadOnly linePoint1, + Point2DReadOnly linePoint2, + Point2DBasics intersectionToPack) + { + return intersectionBetweenRay2DAndLine2D(rayOrigin.getX(), + rayOrigin.getY(), + rayDirection.getX(), + rayDirection.getY(), + linePoint1.getX(), + linePoint1.getY(), + linePoint2.getX() - linePoint1.getX(), + linePoint2.getY() - linePoint1.getY(), + intersectionToPack); + } + + /** + * Computes the intersection between an infinitely long 2D line (defined by a 2D point and a 2D + * direction) and a 2D ray segment (defined by the start and direction 2D endpoints). + *

+ * Edge cases: + *

+ *

+ * + * @param rayOriginX x-coordinate of the start point of the ray. + * @param rayOriginY y-coordinate of the start point of the ray. + * @param rayDirectionX x-component of the ray direction. + * @param rayDirectionY y-component of the ray direction. + * @param pointOnLineX x-coordinate of a point located on the line. + * @param pointOnLineY y-coordinate of a point located on the line. + * @param lineDirectionX x-component of the line direction. + * @param lineDirectionY y-component of the line direction. + * @param intersectionToPack the 2D point in which the result is stored. Can be {@code null}. + * Modified. + * @return {@code true} if the line intersects the line segment, {@code false} otherwise. + */ + public static boolean intersectionBetweenRay2DAndLine2D(double rayOriginX, + double rayOriginY, + double rayDirectionX, + double rayDirectionY, + double pointOnLineX, + double pointOnLineY, + double lineDirectionX, + double lineDirectionY, + Point2DBasics intersectionToPack) + { + double start1x = rayOriginX; + double start1y = rayOriginY; + double end1x = rayOriginX + rayDirectionX; + double end1y = rayOriginY + rayDirectionY; + double start2x = pointOnLineX; + double start2y = pointOnLineY; + double end2x = pointOnLineX + lineDirectionX; + double end2y = pointOnLineY + lineDirectionY; + return intersectionBetweenTwoLine2DsImpl(start1x, start1y, false, end1x, end1y, true, start2x, start2y, true, end2x, end2y, true, intersectionToPack); + } + + /** + * Computes the intersection between an infinitely long 2D line (defined by a 2D point and a 2D + * direction) and a 2D ray segment (defined by the start and direction 2D endpoints). + *

+ * Edge cases: + *

+ *

+ * + * @param rayOrigin start point of the ray. + * @param rayDirection direction of the ray. + * @param pointOnLine point located on the line. + * @param lineDirection direction of the line. + * @param intersectionToPack the 2D point in which the result is stored. Can be {@code null}. + * Modified. + * @return {@code true} if the line intersects the line segment, {@code false} otherwise. + */ + public static boolean intersectionBetweenRay2DAndLine2D(Point2DReadOnly rayOrigin, + Vector2DReadOnly rayDirection, + Point2DReadOnly pointOnLine, + Vector2DReadOnly lineDirection, + Point2DBasics intersectionToPack) + { + return intersectionBetweenRay2DAndLine2D(rayOrigin.getX(), + rayOrigin.getY(), + rayDirection.getX(), + rayDirection.getY(), + pointOnLine.getX(), + pointOnLine.getY(), + lineDirection.getX(), + lineDirection.getY(), + intersectionToPack); + } + + /** + * Computes the intersection between two infinitely long 2D lines each defined by a 2D point and a + * 2D direction and returns a percentage {@code alpha} along the first line such that the + * intersection coordinates can be computed as follows:
+ * {@code intersection = pointOnLine1 + alpha * lineDirection1} + *

+ * Edge cases: + *

+ *

+ * + * @param startPoint1x x-coordinate of a point located on the first line. + * @param startPoint1y y-coordinate of a point located on the first line. + * @param segmentTravel1x x-component of the first line direction. + * @param segmentTravel1y y-component of the first line direction. + * @param startPoint2x x-coordinate of a point located on the second line. + * @param startPoint2y y-coordinate of a point located on the second line. + * @param segmentTravel2x x-component of the second line direction. + * @param segmentTravel2y y-component of the second line direction. + * @return {@code alpha} the percentage along the first line of the intersection location. This + * method returns {@link Double#NaN} if the lines do not intersect. + */ + public static double percentageOfIntersectionBetweenTwoLine2DsInfCase(double startPoint1x, + double startPoint1y, + double segmentTravel1x, + double segmentTravel1y, + double startPoint2x, + double startPoint2y, + double segmentTravel2x, + double segmentTravel2y) + { + // We solve for x the problem of the form: A * x = b + // A * x = b + // / segmentTravel1x -segmentTravel2x \ / alpha \ / startPoint2x - startPoint1x \ + // | | * | | = | | + // \ segmentTravel1y -segmentTravel2y / \ beta / \ startPoint2y - startPoint1y / + // Here, only alpha or beta is needed. + + double determinant = -segmentTravel1x * segmentTravel2y + segmentTravel1y * segmentTravel2x; + + double dx = startPoint2x - startPoint1x; + double dy = startPoint2y - startPoint1y; + + if (Math.abs(determinant) < ONE_TRILLIONTH) + { // The lines are parallel + // Check if they are collinear + double cross = dx * segmentTravel1y - dy * segmentTravel1x; + if (Math.abs(cross) < ONE_TRILLIONTH) + { + /* + * The two lines are collinear. There's an infinite number of intersection. Let's set the result to + * infinity, i.e. alpha = infinity so it can be handled. + */ + return Double.POSITIVE_INFINITY; + } + else + { + return Double.NaN; + } + } + else + { + double oneOverDeterminant = 1.0 / determinant; + double AInverse00 = oneOverDeterminant * -segmentTravel2y; + double AInverse01 = oneOverDeterminant * segmentTravel2x; + + double alpha = AInverse00 * dx + AInverse01 * dy; + + return alpha; + } + } + + /** + * Computes the z height of a Euclidean point defined by {@code pointX} and {@code pointY} on a plane defined by point {@code pointOnPlane} and normal + * {@code planeNormal}. + * + * @param pointOnPlane point located on a plane + * @param planeNormal vector describing the plane normal + * @param pointX x coordinate to query. + * @param pointY y coordinate to query. + * @return height of the plane located at {@code pointX} and {@code pointY} + */ + public static double getZOnPlane(Point3DReadOnly pointOnPlane, Vector3DReadOnly planeNormal, double pointX, double pointY) + { + // The three components of the plane origin + double x0 = pointOnPlane.getX(); + double y0 = pointOnPlane.getY(); + double z0 = pointOnPlane.getZ(); + // The three components of the plane normal + double a = planeNormal.getX(); + double b = planeNormal.getY(); + double c = planeNormal.getZ(); + + // Given the plane equation: a*x + b*y + c*z + d = 0, with d = -(a*x0 + b*y0 + c*z0), we find z: + double z = a / c * (x0 - pointX) + b / c * (y0 - pointY) + z0; + return z; + } + + /** + * Finds the intersection of two bounding boxes defined by a bounding box. + *

+ * WARNING: generates garbage + *

+ * Allocates a new BoundingBox2D. TODO: Check, Unit test, move where BoundingBox union is + * + * @param a first bounding box to check + * @param b second bounding box to check + * @return the intersection bounding box, or null if no intersection + */ + public static BoundingBox2D computeIntersectionOfTwoBoundingBoxes(BoundingBox2DReadOnly a, BoundingBox2DReadOnly b) + { + double maxX = Math.min(a.getMaxX(), b.getMaxX()); + double maxY = Math.min(a.getMaxY(), b.getMaxY()); + double minX = Math.max(a.getMinX(), b.getMinX()); + double minY = Math.max(a.getMinY(), b.getMinY()); + + if ((maxX <= minX) || (maxY <= minY)) + return null; + + return new BoundingBox2D(minX, minY, maxX, maxY); + } + + /** + * Finds the intersection of two bounding boxes defined by a bounding box. + *

+ * WARNING: generates garbage + *

+ * Allocates a new boundingBox3D. + * + * @param a first bounding box to check + * @param b second bounding box to check + * @return the intersection bounding box, or null if no intersection + */ + public static BoundingBox3D computeIntersectionOfTwoBoundingBoxes(BoundingBox3DReadOnly a, BoundingBox3DReadOnly b) + { + double maxX = Math.min(a.getMaxX(), b.getMaxX()); + double maxY = Math.min(a.getMaxY(), b.getMaxY()); + double maxZ = Math.min(a.getMaxZ(), b.getMaxZ()); + + double minX = Math.max(a.getMinX(), b.getMinX()); + double minY = Math.max(a.getMinY(), b.getMinY()); + double minZ = Math.max(a.getMinZ(), b.getMinZ()); + + if ((maxX <= minX) || (maxY <= minY) || (maxZ <= minZ)) + return null; + + return new BoundingBox3D(minX, minY, minZ, maxX, maxY, maxZ); + } + + /** + * Computes the volume of the bounding box. + * + * @param boundingBox bounding box to inspect + * @return total volume of the bounding box. + */ + public static double computeBoundingBoxVolume3D(BoundingBox3DReadOnly boundingBox) + { + return Math.abs(boundingBox.getMaxX() - boundingBox.getMinX()) * Math.abs(boundingBox.getMaxY() - boundingBox.getMinY()) * Math.abs( + boundingBox.getMaxZ() - boundingBox.getMinZ()); + } + + /** + * Inspects whether a 2d line segment ever intersects with a 2d convex polygon. + * + * @param lineSegmentStart starting point of the line segment. + * @param lineSegmentEnd ending point of the line segment. + * @param convexPolygonVertices list of points that compose the convex polygon. + * @param numberOfVertices number of vertices contained in the convex polygon. + * @return whether the line segment intersects with the polygon. + */ + public static boolean doLineSegment2DAndConvexPolygon2DIntersect(Point2DReadOnly lineSegmentStart, + Point2DReadOnly lineSegmentEnd, + List convexPolygonVertices, + int numberOfVertices) + { + checkNumberOfVertices(convexPolygonVertices, numberOfVertices); + + if (numberOfVertices == 0) + return false; + + if (numberOfVertices == 1) + return isPoint2DOnLineSegment2D(convexPolygonVertices.get(0), lineSegmentStart, lineSegmentEnd); + + if (numberOfVertices == 2) + return doLineSegment2DsIntersect(convexPolygonVertices.get(0), convexPolygonVertices.get(1), lineSegmentStart, lineSegmentEnd); + + for (int edgeIndex = 0; edgeIndex < numberOfVertices; edgeIndex++) + { + Point2DReadOnly edgeStart = convexPolygonVertices.get(edgeIndex); + Point2DReadOnly edgeEnd = convexPolygonVertices.get(EuclidGeometryPolygonTools.next(edgeIndex, numberOfVertices)); + + if (doLineSegment2DsIntersect(edgeStart, edgeEnd, lineSegmentStart, lineSegmentEnd)) + return true; + } + + return false; + } + + private static void checkNumberOfVertices(List convexPolygon2D, int numberOfVertices) + { + if (numberOfVertices < 0 || numberOfVertices > convexPolygon2D.size()) + throw new IllegalArgumentException("Illegal numberOfVertices: " + numberOfVertices + ", expected a value in ] 0, " + convexPolygon2D.size() + "]."); + } + + /** + * Compute Intersection-over-Union (IoU) of two 3D bounding boxes. This is the percentage of volume of the two bounding boxes that are intersecting. + *

+ * WARNING: generates garbage. + */ + public static double computeIntersectionOverUnionOfTwoBoundingBoxes(BoundingBox3DReadOnly a, BoundingBox3DReadOnly b) + { + BoundingBox3D intersection = computeIntersectionOfTwoBoundingBoxes(a, b); + + if (intersection == null) + return 0.0; + + double intersectionVolume = computeBoundingBoxVolume3D(intersection); + double volumeA = computeBoundingBoxVolume3D(a); + double volumeB = computeBoundingBoxVolume3D(b); + double unionVolume = volumeA + volumeB - intersectionVolume; + + return intersectionVolume / unionVolume; + } + + /** + * Compute Intersection-over-Union (IoU) of two 3D bounding boxes. This is the percentage of volume of the two bounding boxes that are intersecting. + *

+ * WARNING: generates garbage. + */ + public static double computeIntersectionOverSmallerOfTwoBoundingBoxes(BoundingBox3DReadOnly a, BoundingBox3DReadOnly b) + { + BoundingBox3D intersection = computeIntersectionOfTwoBoundingBoxes(a, b); + + if (intersection == null) + return 0.0; + + double intersectionVolume = computeBoundingBoxVolume3D(intersection); + double volumeA = computeBoundingBoxVolume3D(a); + double volumeB = computeBoundingBoxVolume3D(b); + double smallerVolume = Math.min(volumeA, volumeB); + + return intersectionVolume / smallerVolume; + } + + /** + * Determines if the polygonToTest is fully inside the convex polygon by some epsilon distance. + * + * @return {@code true} if {@code polygonToTest} is fully contained inside {@code polygon}. {@code false} otherwise. + */ + public static boolean isPolygonInside(ConvexPolygon2DReadOnly polygonToTest, double epsilon, ConvexPolygon2DReadOnly polygon) + { + for (int i = 0; i < polygonToTest.getNumberOfVertices(); i++) + { + if (!polygon.isPointInside(polygonToTest.getVertex(i), epsilon)) + return false; + } + + return true; + } + + /** + * Determines if the polygonToTest is inside the convex polygon. + * + * @return {@code true} if {@code polygonToTest} is fully contained inside {@code polygon}. {@code false} otherwise. + */ + public static boolean isPolygonInside(ConvexPolygon2DReadOnly polygonToTest, ConvexPolygon2DReadOnly polygon) + { + return isPolygonInside(polygonToTest, 0.0, polygon); + } + + /** + * Finds the projection of a 3D point onto a 3D plane given in general form. + * Uses: projectedPoint = point - (normal.dot(point) + planeScalar) * (normal) + *

+ * WARNING: generates garbage. + * + * @param plane Coefficients of the general form of plane equation (ax + by + cz + d = 0) as Vector4D + * @param point Point to be projected onto the plane as Point3D + * @return Projected point onto the plane as Point3D + */ + public static Point3D projectPointOntoPlane(Vector4DReadOnly plane, Point3DReadOnly point) + { + UnitVector3D planeNormal = new UnitVector3D(plane.getX(), plane.getY(), plane.getZ()); + + Vector3D scaledNormal = new Vector3D(planeNormal); + scaledNormal.scale(planeNormal.dot(point) + plane.getS()); + + Point3D projectedPoint = new Point3D(); + projectedPoint.sub(point, scaledNormal); + + return projectedPoint; } } diff --git a/src/main/java/us/ihmc/euclid/QuaternionCalculus.java b/src/main/java/us/ihmc/euclid/QuaternionCalculus.java new file mode 100644 index 000000000..31b6196c1 --- /dev/null +++ b/src/main/java/us/ihmc/euclid/QuaternionCalculus.java @@ -0,0 +1,500 @@ +package us.ihmc.euclid; + +import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.rotationConversion.QuaternionConversion; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.euclid.tools.QuaternionTools; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; +import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.Vector4D; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; +import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; +import us.ihmc.euclid.tuple4D.interfaces.Vector4DBasics; +import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; + +/** + * This class contains a number of tools related to computing dynamics of quaternions. Each method is guaranteed to be free of allocations, which requires + * instantiation for some methods. + * + *

+ * Specifically, it provides tools for exponential + * ({@link #exp(Vector3DReadOnly, QuaternionBasics)}) and logarithmic maps of quaternions, {@link #log(QuaternionReadOnly, Vector3DBasics)}. + *

+ * + *

+ * Importantly, it also provides the interfaces for the ability to perform spherical linear interpolation, which is smooth interpolation on the + * SO3 manifold. This is commonly referred to as the SLERP algorithm, and can be performed calling + * {@link #interpolate(double, QuaternionReadOnly, QuaternionReadOnly, QuaternionBasics)}. + * Additional tools are provided to perform SLERP interpolation guaranteeing interpolation along the minimal path, with + * {@link #interpolate(double, QuaternionReadOnly, QuaternionReadOnly, QuaternionBasics, boolean)}. + *

+ * + *

+ * This class also provides tools for mapping from and to angular velocities and accelerations and quaternion rates and accelerations. These can be done in + * both + * the local frame defined by the orientation, or the parent frame. These can be accessed using + * {@link #computeQDotInParentFrame(QuaternionReadOnly, Vector3DReadOnly, Vector4DBasics)}, + * {@link #computeQDotInRotatedFrame(QuaternionReadOnly, Vector3DReadOnly, Vector4DBasics)}, + * and various other methods. + *

+ *

+ * Lastly, this class provides tools for estimating quaternion rates and accelerations via finite differences, provided various quaternion samples. These are + * useful for testing quaternion changes from generating trajectories, but can be used for a variety of other things. These can be accessed by + * {@link #computeQDotByFiniteDifferenceCentral(QuaternionReadOnly, QuaternionReadOnly, double, Vector4DBasics)} and + * {@link #computeQDDotByFiniteDifferenceCentral(QuaternionReadOnly, QuaternionReadOnly, QuaternionReadOnly, double, Vector4DBasics)}. + *

+ */ +public class QuaternionCalculus +{ + private final AxisAngle axisAngleForLog = new AxisAngle(); + private final Quaternion tempQ1ForInterpolation = new Quaternion(); + private final AxisAngle axisAngleForPow = new AxisAngle(); + private final Quaternion qConj = new Quaternion(); + + private final Vector4D intermediateQDot = new Vector4D(); + private final Vector4D qDotConj = new Vector4D(); + private final Vector3D intermediateAngularAcceleration = new Vector3D(); + private final Vector3D intermediateAngularVelocity = new Vector3D(); + private final Vector4D intermediateQDDot = new Vector4D(); + private final Vector4D pureQuatForMultiply = new Vector4D(); + + public QuaternionCalculus() + { + } + + /** + * This computes the logarithm of the quaternion a stores it in {@code resultToPack}, which is a pure quaternion. + * This is the equation: + *

resultToPack = log(q)

+ * where q is a {@link QuaternionReadOnly}. + * + * @param q is a unit quaternion and describes a orientation. Not modified. + * @param resultToPack is used to store the result and is a pure quaternion (w = 0.0). Modified. + */ + public void log(QuaternionReadOnly q, Vector4DBasics resultToPack) + { + axisAngleForLog.set(q); + resultToPack.set(axisAngleForLog.getX(), axisAngleForLog.getY(), axisAngleForLog.getZ(), 0.0); + resultToPack.scale(axisAngleForLog.getAngle()); + } + + /** + * This computes the logarithm of the quaternion a stores it in {@code resultToPack}, which is a vector. + * This is the equation: + *

resultToPack = log(q)

+ * where q is a {@link QuaternionReadOnly}. + * + * @param q is a unit quaternion and describes a orientation. Not modified. + * @param resultToPack is used to store the result. Modified. + */ + public void log(QuaternionReadOnly q, Vector3DBasics resultToPack) + { + axisAngleForLog.set(q); + resultToPack.set(axisAngleForLog.getX(), axisAngleForLog.getY(), axisAngleForLog.getZ()); + resultToPack.scale(axisAngleForLog.getAngle()); + } + + /** + * Applies the exponential transformation to the rotation vector. This is the same as computing the equivalent quaternion from a rotation vector. + * + * @param rotationVector rotation of which to compute the exponential. Not modified. + * @param quaternionToPack is used to store the result. Modified. + */ + public static void exp(Vector3DReadOnly rotationVector, QuaternionBasics quaternionToPack) + { + QuaternionConversion.convertRotationVectorToQuaternion(rotationVector, quaternionToPack); + } + + /** + * Performs a spherical linear interpolation (SLERP) from a quaternion between the start {@code q0} and end {@code q1} for a given alpha = [0, 1] using + * SLERP. The results are stored in {@code qInterpolatedToPack}. This effectively applies a constant angular velocity to go from {@code q0} to {@code q1}. + *

+ * At {@code alpha = 0}, the output matches {@code q0}. At {@code alpha = 1}, the output matches {@code q1}. {@code alpha} is not constrained to be between + * [0, 1] + *

+ * SLERP Descriptions. + * Computes: resultToPack = q0 (q0-1 q1)alpha. + * + * @param alpha fraction to interpolate between {@code q0} and {@code q1}. Should be between [0, 1]. + * @param q0 quaternion value at the beginning of the interpolation (when {@code alpha} = 0). Not Modified. + * @param q1 quaternion value at the end of the interpolation (when {@code alpha} = 1). Not Modified. + * @param qInterpolatedToPack interpolated quaternion to store the results. Modified. + */ + public void interpolate(double alpha, QuaternionReadOnly q0, QuaternionReadOnly q1, QuaternionBasics qInterpolatedToPack) + { + interpolate(alpha, q0, q1, qInterpolatedToPack, true); + } + + /** + * Performs a spherical linear interpolation (SLERP) from a quaternion between the start {@code q0} and end {@code q1} for a given alpha = [0, 1] using + * SLERP. The results are stored in {@code qInterpolatedToPack}. This effectively applies a constant angular velocity to go from {@code q0} to {@code q1}. + *

+ * At {@code alpha = 0}, the output matches {@code q0}. At {@code alpha = 1}, the output matches {@code q1}. {@code alpha} is not constrained to be between + * [0, 1] + *

+ *

+ * When {@code preventExtraSpin} is true, this check ensures that the interpolation goes the minimum distance along the shortest path. As it's a spherical + * interpolation, {@code q0} can rotate either direction to reach {@code q1}. When {@code preventExtraSpin} is true, the math checks to make that the + * rotation + * from {@code q0} to {@code q1} is less than {@link Math#PI}. If it is greater than this value, then it negates {@code q1} to reverse the directionality + * of the rotation, ensuring that the interpolation is along the shortest path. + *

+ * SLERP Descriptions. + * Computes: resultToPack = q0 (q0-1 q1)alpha. + * + * @param alpha fraction to interpolate between {@code q0} and {@code q1}. Should be between [0, 1]. + * @param q0 quaternion value at the beginning of the interpolation (when {@code alpha} = 0). Not Modified. + * @param q1 quaternion value at the end of the interpolation (when {@code alpha} = 1). Not Modified. + * @param qInterpolatedToPack interpolated quaternion to store the results. Modified. + */ + public void interpolate(double alpha, QuaternionReadOnly q0, QuaternionReadOnly q1, QuaternionBasics qInterpolatedToPack, boolean preventExtraSpin) + { + tempQ1ForInterpolation.set(q1); + + // Checks if the interpolation is taking the shortest path. + if (preventExtraSpin && q0.dot(tempQ1ForInterpolation) < 0.0) + { + tempQ1ForInterpolation.negate(); + } + + qInterpolatedToPack.difference(q0, tempQ1ForInterpolation); + pow(qInterpolatedToPack, alpha, qInterpolatedToPack); + qInterpolatedToPack.multiply(q0, qInterpolatedToPack); + } + + /** + * This computes: resultToPack = q^power. + * + * @param q is a unit quaternion and describes an orientation. Not modifeid. + * @param power exponential to apply to the quaternion. + * @param resultToPack is used to store the result. Modified. + */ + public void pow(QuaternionReadOnly q, double power, QuaternionBasics resultToPack) + { + axisAngleForPow.set(q); + axisAngleForPow.setAngle(power * axisAngleForPow.getAngle()); + resultToPack.set(axisAngleForPow); + } + + /** + * This computes the angular velocity expressed in the rotation frame of a body from the rotation of the body expressed as a quaternion and its quaternion + * rate. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}. + *

+ *

+ * This method computes the local 3-dimensional angular velocity, expressed in the child frame Fchild, and stores it in + * {@code angularVelocityToPack}. + *

+ * + * @param q quaternion defining the rotation to the local frame. Not modified. + * @param qDot quaternion rate of change of {@code q}. Not modified. + * @param angularVelocityToPack angular velocity in the local frame. Modified. + */ + public void computeAngularVelocityInRotatedFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DBasics angularVelocityToPack) + { + qConj.setAndConjugate(q); + multiply(qConj, qDot, angularVelocityToPack); + angularVelocityToPack.scale(2.0); + } + + /** + * This computes the angular velocity expressed in the parent frame of a body from the rotation of the body expressed as a quaternion and its quaternion + * rate. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}. + *

+ *

+ * This method computes the 3-dimensional angular velocity, expressed in the parent frame Fparent, and stores it in + * {@code angularVelocityToPack}. + *

+ * + * @param q quaternion defining the rotation to the local frame. Not modified. + * @param qDot quaternion rate of change of {@code q}. Not modified. + * @param angularVelocityToPack angular velocity in the parent frame. Modified. + */ + public void computeAngularVelocityInParentFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DBasics angularVelocityToPack) + { + qConj.setAndConjugate(q); + multiply(qDot, qConj, angularVelocityToPack); + angularVelocityToPack.scale(2.0); + } + + /** + * This computes the quaternion rate from the rotation of a body expressed as a quaternion and its angular velocity expressed in the frame of its parent. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}. + *

+ *

+ * This method computes the quaternion rate, and stores it in {@code qDotToPack} + *

+ * + * @param q quaternion defining the rotation to the local frame. Not Modified. + * @param angularVelocityInParent angular velocity in the parent frame. Not Modified. + * @param qDotToPack quaternion rate of change of {@code q} to compute. Modified. + */ + public static void computeQDotInParentFrame(QuaternionReadOnly q, Vector3DReadOnly angularVelocityInParent, Vector4DBasics qDotToPack) + { + multiply(angularVelocityInParent, q, qDotToPack); + qDotToPack.scale(0.5); + } + + /** + * This computes the quaternion rate from the rotation of a body expressed as a quaternion and its angular velocity expressed in the rotated frame. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}. + *

+ *

+ * This method computes the quaternion rate, and stores it in {@code qDotToPack} + *

+ * + * @param q quaternion defining the rotation to the local frame. Not Modified. + * @param angularVelocityInRotated angular velocity in the rotated frame. Not Modified. + * @param qDotToPack quaternion rate of change of {@code q} to compute. Modified. + */ + public static void computeQDotInRotatedFrame(QuaternionReadOnly q, Vector3DReadOnly angularVelocityInRotated, Vector4DBasics qDotToPack) + { + multiply(q, angularVelocityInRotated, qDotToPack); + qDotToPack.scale(0.5); + } + + /** + * This computes the quaternion acceleration from the rotation of a body expressed as a quaternion, its quaternion rate, and its angular acceleration + * expressed in the frame of its parent. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}, and acceleratoin, which is defined by the + * acceleration of its quaternion coordinates, {@code qDDot}. + *

+ *

+ * This method computes the quaternion acceleration, and stores it in {@code qDDotToPack} + *

+ * + * @param q quaternion defining the rotation to the local frame. Not Modified. + * @param qDot quaternion rate of change of {@code q}. Not modified. + * @param angularAccelerationInParent angular acceleration in the parent frame. Not Modified. + * @param qDDotToPack quaternion acceleration of {@code q} to compute. Not modified. + */ + public void computeQDDotInParentFrame(QuaternionReadOnly q, Vector4DReadOnly qDot, Vector3DReadOnly angularAccelerationInParent, Vector4DBasics qDDotToPack) + { + computeAngularVelocityInParentFrame(q, qDot, intermediateAngularVelocity); + computeQDDotInParentFrame(q, qDot, intermediateAngularVelocity, angularAccelerationInParent, qDDotToPack); + } + + /** + * This computes the quaternion acceleration from the rotation of a body expressed as a quaternion and its angular velocity and acceleration + * expressed in the frame of its parent. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change of its quaternion coordinates, {@code qDot}, and acceleratoin, which is defined by the + * acceleration of its quaternion coordinates, {@code qDDot}. + *

+ *

+ * This method computes the quaternion acceleration, and stores it in {@code qDDotToPack} + *

+ * + * @param q quaternion defining the rotation to the local frame. Not Modified. + * @param angularVelocityInParent angular velocity in the parent frame. Not modified. + * @param angularAccelerationInParent angular acceleration in the parent frame. Not Modified. + * @param qDDotToPack quaternion acceleration of {@code q} to compute. Not modified. + */ + public void computeQDDotInParentFrame(QuaternionReadOnly q, + Vector3DReadOnly angularVelocityInParent, + Vector3DReadOnly angularAccelerationInParent, + Vector4DBasics qDDotToPack) + { + computeQDotInParentFrame(q, angularVelocityInParent, intermediateQDot); + computeQDDotInParentFrame(q, intermediateQDot, angularVelocityInParent, angularAccelerationInParent, qDDotToPack); + } + + /** + * This computes the angular acceleration expressed in the parent frame of a body from the rotation of the body expressed as a quaternion and its quaternion + * rate and acceleration. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change and acceleration of its quaternion coordinates, {@code qDot} and {@code qDDot}. + *

+ *

+ * This method computes the 3-dimensional angular acceleration, expressed in the parent frame Fparent, and stores it in + * {@code angularAccelerationToPack}. + *

+ * + * @param q quaternion defining the rotation to the local frame. Not modified. + * @param qDDot quaternion acceleration of {@code q}. Not modified. + * @param angularVelocityInParent angular velocity in the parent frame. Not modified. + * @param angularAccelerationInParentToPack angular acceleration in the parent frame. Modified. + */ + public void computeAngularAccelerationInParentFrame(QuaternionReadOnly q, + Vector4DReadOnly qDDot, + Vector3DReadOnly angularVelocityInParent, + Vector3DBasics angularAccelerationInParentToPack) + { + computeQDotInParentFrame(q, angularVelocityInParent, intermediateQDot); + computeAngularAccelerationInRotatedFrame(q, intermediateQDot, qDDot, angularAccelerationInParentToPack); + } + + /** + * This computes the angular acceleration expressed in the child frame of a body from the rotation of the body expressed as a quaternion and its quaternion + * rate and acceleration. + * + *

+ * The rotation from the parent frame, Fparent to the current frame Fchild is defined by a quaternion {@code q}. This quaternion has a + * corresponding velocity, which is defined by the rate of change and acceleration of its quaternion coordinates, {@code qDot} and {@code qDDot}. + *

+ *

+ * This method computes the 3-dimensional angular acceleration, expressed in the parent frame Fparent, and stores it in + * {@code angularAccelerationToPack}. + *

+ * + * @param q quaternion defining the rotation to the local frame. Not modified. + * @param qDot quaternion rate of change of {@code q}. Not modified. + * @param qDDot quaternion acceleration of {@code q}. Not modified. + * @param angularAccelerationInRotatedToPack angular acceleration in the child frame. Modified. + */ + public void computeAngularAccelerationInRotatedFrame(QuaternionReadOnly q, + Vector4DReadOnly qDot, + Vector4DReadOnly qDDot, + Vector3DBasics angularAccelerationInRotatedToPack) + { + qConj.setAndConjugate(q); + qDotConj.set(-qDot.getX(), -qDot.getY(), -qDot.getZ(), qDot.getS()); + multiply(qDot, qDotConj, intermediateAngularAcceleration); + multiply(qDDot, qConj, angularAccelerationInRotatedToPack); + angularAccelerationInRotatedToPack.add(intermediateAngularAcceleration); + angularAccelerationInRotatedToPack.scale(2.0); + } + + /** + * This is used to estimate the rate of change of the quaternion coordinates of a time series of data. + * + *

If the time series is represented by

+ *

[qprev, qcurrent, qnext]

+ *

where the data measurements are separated by a time discretisation of {@param dt}, this method is used to compute the quaternion rate of the + * quaternion at the current time. It uses the previous {@param qPrevious} and next {@param qNext} values. The results are stored in {@param qDotToPack}.

+ * + * @param qPrevious previous quaternion estimate. Not modified. + * @param qNext next quaternion estimate. Not modified. + * @param dt time between quaternion estimates + * @param qDotToPack estimated quaternion rate at the current value. Modified. + */ + public static void computeQDotByFiniteDifferenceCentral(QuaternionReadOnly qPrevious, QuaternionReadOnly qNext, double dt, Vector4DBasics qDotToPack) + { + qDotToPack.sub(qNext, qPrevious); + qDotToPack.scale(0.5 / dt); + } + + /** + * This is used to estimate the acceleration of the quaternion coordinates of a time series of data. + * + *

If the time series is represented by

+ *

[qprev, qcurrent, qnext]

+ *

where the data measurements are separated by a time discretisation of {@param dt}, this method is used to compute the quaternion acceleration + * at the current time. It uses the previous {@param qPrevious}, current {@param q}, and next {@param qNext} values. The results are stored in + * {@param qDotToPack}.

+ * + * @param qPrevious previous quaternion estimate. Not modified. + * @param q next quaternion estimate. Not modified. + * @param qNext next quaternion estimate. Not modified. + * @param dt time between quaternion estimates. + * @param qDDotToPack estimated quaternion rate at the current value. Modified. + */ + public static void computeQDDotByFiniteDifferenceCentral(QuaternionReadOnly qPrevious, + QuaternionReadOnly q, + QuaternionReadOnly qNext, + double dt, + Vector4DBasics qDDotToPack) + { + qDDotToPack.sub(qNext, q); + qDDotToPack.sub(q); + qDDotToPack.add(qPrevious); + qDDotToPack.scale(1.0 / EuclidCoreTools.square(dt)); + } + + /** + * This is an internal method used to compute the quaternion acceleration from the quaternion value, its rate, and the angular velocity and acceleration of + * that frame relative to the parent expressed in the parent. + */ + void computeQDDotInParentFrame(QuaternionReadOnly q, + Vector4DReadOnly qDot, + Vector3DReadOnly angularVelocityInParent, + Vector3DReadOnly angularAccelerationInParent, + Vector4DBasics qDDotToPack) + { + multiply(angularAccelerationInParent, q, intermediateQDDot); + multiply(angularVelocityInParent, qDot, qDDotToPack); + qDDotToPack.add(intermediateQDDot); + qDDotToPack.scale(0.5); + } + + private static void multiply(QuaternionReadOnly q, Vector3DReadOnly v, Vector4DBasics resultToPack) + { + setAsPureQuaternion(v, resultToPack); + QuaternionTools.multiply(q, resultToPack, resultToPack); + } + + private static void multiply(Vector3DReadOnly v, Vector4DReadOnly q, Vector4DBasics resultToPack) + { + setAsPureQuaternion(v, resultToPack); + QuaternionTools.multiply(resultToPack, q, resultToPack); + } + + private static void multiply(Vector3DReadOnly v, QuaternionReadOnly q, Vector4DBasics resultToPack) + { + setAsPureQuaternion(v, resultToPack); + QuaternionTools.multiply(resultToPack, q, resultToPack); + } + + private void multiply(Vector4DReadOnly q1, Vector4DReadOnly q2, Vector3DBasics resultToPack) + { + QuaternionTools.multiply(q1, q2, pureQuatForMultiply); + setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); + } + + private void multiply(Vector4DReadOnly q1, QuaternionReadOnly q2, Vector3DBasics resultToPack) + { + QuaternionTools.multiply(q1, q2, pureQuatForMultiply); + setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); + } + + private void multiply(QuaternionReadOnly q1, Vector4DReadOnly q2, Vector3DBasics resultToPack) + { + QuaternionTools.multiply(q1, q2, pureQuatForMultiply); + setVectorFromPureQuaternion(pureQuatForMultiply, resultToPack); + } + + /** + * Provides a convenience function for setting a 4D pure quaternion vector from a 3D vector, where the 4D vector is assumed to represent a pure quaternion (w + * = 0.0). + * + * @param vector rotation data source. Not modified. + * @param pureQuaternionToSet results to pack. Modified. + */ + private static void setAsPureQuaternion(Vector3DReadOnly vector, Vector4DBasics pureQuaternionToSet) + { + pureQuaternionToSet.set(vector.getX(), vector.getY(), vector.getZ(), 0.0); + } + + /** + * Provides a convenience function for setting a 3D vector from a 4D vector, where the 4D vector is assumed to represent a pure quaternion (w = 0.0). + * + * @param pureQuaternion quaternion data source. Not modified. + * @param vectorToPack results to pack. Modified. + */ + private static void setVectorFromPureQuaternion(Vector4DReadOnly pureQuaternion, Vector3DBasics vectorToPack) + { + vectorToPack.set(pureQuaternion.getX(), pureQuaternion.getY(), pureQuaternion.getZ()); + } +} diff --git a/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java b/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java index 9850d624c..7b25e94ce 100644 --- a/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java +++ b/src/main/java/us/ihmc/euclid/tools/EuclidCoreTools.java @@ -14,22 +14,9 @@ import us.ihmc.euclid.orientation.interfaces.Orientation3DReadOnly; import us.ihmc.euclid.transform.interfaces.RigidBodyTransformBasics; import us.ihmc.euclid.transform.interfaces.RigidBodyTransformReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.Point2DReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.Tuple2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; -import us.ihmc.euclid.tuple2D.interfaces.Vector2DBasics; -import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; -import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; -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.tuple3D.interfaces.Vector3DReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionBasics; -import us.ihmc.euclid.tuple4D.interfaces.QuaternionReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.Tuple4DBasics; -import us.ihmc.euclid.tuple4D.interfaces.Tuple4DReadOnly; -import us.ihmc.euclid.tuple4D.interfaces.Vector4DBasics; -import us.ihmc.euclid.tuple4D.interfaces.Vector4DReadOnly; +import us.ihmc.euclid.tuple2D.interfaces.*; +import us.ihmc.euclid.tuple3D.interfaces.*; +import us.ihmc.euclid.tuple4D.interfaces.*; import us.ihmc.euclid.yawPitchRoll.interfaces.YawPitchRollBasics; import us.ihmc.euclid.yawPitchRoll.interfaces.YawPitchRollReadOnly; @@ -285,7 +272,9 @@ public String toString() } }; - /** Constant representing the zero-matrix 3D, i.e. the 9 elements are zero. */ + /** + * Constant representing the zero-matrix 3D, i.e. the 9 elements are zero. + */ public static final Matrix3DReadOnly zeroMatrix3D = new Matrix3DReadOnly() { @Override @@ -1592,7 +1581,7 @@ else if (currentOrientation instanceof AxisAngleReadOnly aaCurr) *
  • the angular velocity is expressed in the local coordinates of the transform. * *

    - * + *

    * @param previousTransform the transform at the previous time step. Not modified. * * @param currentTransform the transform at the current time step. Not modified. @@ -1768,4 +1757,316 @@ private static UnsupportedOperationException newUnsupportedOrientationException( previousOrientation.getClass() .getSimpleName())); } + + /** + * Checks whether all the elements contained within this tuple are finite. + * + * @param tuple Tuple to inspect. + * @return {@code true} if all elements are finite, {@code false} if any is not. + */ + public static boolean isFinite(Tuple3DReadOnly tuple) + { + return Double.isFinite(tuple.getX()) && Double.isFinite(tuple.getY()) && Double.isFinite(tuple.getZ()); + } + + /** + * Checks whether two rigid body transforms are equal within some epislon. + * + * @param a first transform to check. + * @param b first transform to check. + * @param rotationEpsilon epsilon tolerance for the rotation between the two. + * @param translationEpsilon epsilon tolerance for the translation between the two. + * @return {@code true} if the two transforms are equal, {@code false} if not. + */ + public static boolean epsilonEquals(RigidBodyTransformReadOnly a, RigidBodyTransformReadOnly b, double rotationEpsilon, double translationEpsilon) + { + return a.getRotation().geometricallyEquals(b.getRotation(), rotationEpsilon) && a.getTranslation() + .geometricallyEquals(b.getTranslation(), translationEpsilon); + } + + /** + * Computes the angle in radians from the first 3D vector to the second 3D vector. The computed + * angle is in the range [0; pi]. + * + * @param firstVectorX x value of the first vector. + * @param firstVectorY y value of the first vector. + * @param firstVectorZ z value of the first vector. + * @param isFirstVectorUnitary whether the first vector is a unit vector. + * @param secondVectorX x value of the second vector. + * @param secondVectorY y value of the second vector. + * @param secondVectorZ z value of the second vector. + * @param isSecondVectorUnitary whether the second vector is a unit vector. + * @return the angle in radians from the first vector to the second vector. + */ + public static double angleFromFirstToSecondVector3D(double firstVectorX, + double firstVectorY, + double firstVectorZ, + boolean isFirstVectorUnitary, + double secondVectorX, + double secondVectorY, + double secondVectorZ, + boolean isSecondVectorUnitary) + { + double firstVectorLength = isFirstVectorUnitary ? 1.0 : norm(firstVectorX, firstVectorY, firstVectorZ); + double secondVectorLength = isSecondVectorUnitary ? 1.0 : norm(secondVectorX, secondVectorY, secondVectorZ); + + double dotProduct = firstVectorX * secondVectorX + firstVectorY * secondVectorY + firstVectorZ * secondVectorZ; + dotProduct /= firstVectorLength * secondVectorLength; + + if (dotProduct > 1.0) + dotProduct = 1.0; + else if (dotProduct < -1.0) + dotProduct = -1.0; + + return acos(dotProduct); + } + + /** + * Computes the angle in radians from the first 3D vector to the second 3D vector. The computed + * angle is in the range [0; pi]. + * + * @param firstVector the first vector. Not modified. + * @param secondVector the second vector. Not modified. + * @return the angle in radians from the first vector to the second vector. + */ + public static double angleFromFirstToSecondVector3D(Vector3DReadOnly firstVector, Vector3DReadOnly secondVector) + { + return angleFromFirstToSecondVector3D(firstVector.getX(), + firstVector.getY(), + firstVector.getZ(), + firstVector instanceof UnitVector3DReadOnly, + secondVector.getX(), + secondVector.getY(), + secondVector.getZ(), + secondVector instanceof UnitVector3DReadOnly); + } + + /** + * Calculate the angular velocity by differentiating orientation. + * + * @param qStart the initial orientation at time t. + * @param qEnd the final orientation at time t + duration. + * @param duration the time interval between the 2 orientations. + * @param angularVelocityToPack the angular velocity. + */ + public static void differentiateOrientation(QuaternionReadOnly qStart, QuaternionReadOnly qEnd, double duration, Vector3DBasics angularVelocityToPack) + { + double q1x = qStart.getX(); + double q1y = qStart.getY(); + double q1z = qStart.getZ(); + double q1s = qStart.getS(); + + double q2x = qEnd.getX(); + double q2y = qEnd.getY(); + double q2z = qEnd.getZ(); + double q2s = qEnd.getS(); + + double diffx = q1s * q2x - q1x * q2s - q1y * q2z + q1z * q2y; + double diffy = q1s * q2y + q1x * q2z - q1y * q2s - q1z * q2x; + double diffz = q1s * q2z - q1x * q2y + q1y * q2x - q1z * q2s; + double diffs = q1s * q2s + q1x * q2x + q1y * q2y + q1z * q2z; + + if (diffs < 0.0) + { + diffx = -diffx; + diffy = -diffy; + diffz = -diffz; + diffs = -diffs; + } + + double sinHalfTheta = norm(diffx, diffy, diffz); + + double angle; + if (epsilonEquals(1.0, diffs, 1.0e-12)) + angle = 2.0 * sinHalfTheta / diffs; + else + angle = 2.0 * atan2(sinHalfTheta, diffs); + angularVelocityToPack.set(diffx, diffy, diffz); + angularVelocityToPack.scale(angle / (sinHalfTheta * duration)); + } + + /** + * Sets the yaw pitch roll but the doubles are given in degrees. + */ + public static void setYawPitchRollDegrees(Orientation3DBasics orientation3DBasics, double yaw, double pitch, double roll) + { + orientation3DBasics.setYawPitchRoll(Math.toRadians(yaw), Math.toRadians(pitch), Math.toRadians(roll)); + } + + /** + * Projects the provided {@code rotation} onto {@code axis} such that the original rotation can be + * decomposed into a rotation around {@code axis} and one around an orthogonal axis. + *

    + * rotation = orthogonalRotation * result + *

    + * + * @param rotation is the original rotation to be projected onto {@code axis} + * @param axis is the desired rotation axis of the result. + * @param result will be modified to contain the component of {@code rotation} that is around + * {@code axis} + */ + public static void projectRotationOnAxis(QuaternionReadOnly rotation, Vector3DReadOnly axis, QuaternionBasics result) + { + double dotProduct = rotation.getX() * axis.getX() + rotation.getY() * axis.getY() + rotation.getZ() * axis.getZ(); + + double scale = dotProduct / axis.normSquared(); + double projectedX = scale * axis.getX(); + double projectedY = scale * axis.getY(); + double projectedZ = scale * axis.getZ(); + + result.set(projectedX, projectedY, projectedZ, rotation.getS()); + result.normalize(); + } + + /** + * Calculates the 3D part of the given {@code input} that is parallel to the given + * {@code normalAxis} and stores the result in {@code inputNormalPartToPack}. + *

    + * The result has the following properties: + *

      + *
    • x.n = xn.n + *
    • |xn×n| = 0 + *
    + * where: + *
      + *
    • x is {@code input}. + *
    • n is {@code normalAxis}. + *
    • xn is {@code inputNormalPartToPack}. + *
    + *

    + * + * @param input the tuple to extract the normal part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputNormalPartToPack the tuple used to store the normal part of the input. Modified. + */ + public static void extractNormalPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics inputNormalPartToPack) + { + double normalX = normalAxis.getX(); + double normalY = normalAxis.getY(); + double normalZ = normalAxis.getZ(); + double normalLengthSquared = normSquared(normalX, normalY, normalZ); + + double dot = TupleTools.dot(normalAxis, input) / normalLengthSquared; + inputNormalPartToPack.setAndScale(dot, normalAxis); + } + + /** + * Calculates the 3D part of the given {@code input} that is orthogonal to the given + * {@code normalAxis} and stores the result in {@code inputTangentialPartToPack}. + *

    + * The result has the following properties: + *

      + *
    • xt.n = 0 + *
    • |x - (x.n)n| = |xt| + *
    + * where: + *
      + *
    • x is {@code input}. + *
    • n is {@code normalAxis}. + *
    • xt is {@code inputTangentialPartToPack}. + *
    + *

    + * + * @param input the tuple to extract the tangential part of. Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not + * modified. + * @param inputTagentialPartToPack the tuple used to store the tangential part of the input. + * Modified. + */ + public static void extractTangentialPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics inputTagentialPartToPack) + { + double normalX = normalAxis.getX(); + double normalY = normalAxis.getY(); + double normalZ = normalAxis.getZ(); + double normalLengthSquared = normSquared(normalX, normalY, normalZ); + + double dot = TupleTools.dot(normalX, normalY, normalZ, input) / normalLengthSquared; + double normalPartX = dot * normalX; + double normalPartY = dot * normalY; + double normalPartZ = dot * normalZ; + + inputTagentialPartToPack.set(input); + inputTagentialPartToPack.sub(normalPartX, normalPartY, normalPartZ); + } + + /** + * Modifies {@code tupleToModify} such that its normal part is equal to the normal part of + * {@code input}. + *

    + * This method performs the following calculation: x = x - (x.n)n + (y.n)n, where: + *

      + *
    • x is {@code tupleToModify}. + *
    • n is {@code normalAxis}. + *
    • y is {@code input}. + *
    + *

    + * + * @param input the tuple containing the normal part used to update {@code tupleToModify}. + * Not modified. + * @param normalAxis the normal vector. It is normalized internally if needed. Not modified. + * @param tupleToModify the tuple to modify the normal part of. Modified. + */ + public static void setNormalPart(Tuple3DReadOnly input, Vector3DReadOnly normalAxis, Tuple3DBasics tupleToModify) + { + double normalX = normalAxis.getX(); + double normalY = normalAxis.getY(); + double normalZ = normalAxis.getZ(); + double normalLengthSquared = normSquared(normalX, normalY, normalZ); + double dot = (TupleTools.dot(normalAxis, input) - TupleTools.dot(normalAxis, tupleToModify)) / normalLengthSquared; + tupleToModify.scaleAdd(dot, normalAxis, tupleToModify); + } + + /** + * Generic transform operation to apply a rotation matrix {@code matrix} to a tuple 3d. + * + * @param matrix rotation matrix to apply + * @param xOriginal x value of the tuple + * @param yOriginal y value of the tuple + * @param zOriginal z value of the tuple + * @param tupleTransformed transformed tuple 3D to compute. + */ + public static void transform(Matrix3DReadOnly matrix, double xOriginal, double yOriginal, double zOriginal, Tuple3DBasics tupleTransformed) + { + double x = matrix.getM00() * xOriginal + matrix.getM01() * yOriginal + matrix.getM02() * zOriginal; + double y = matrix.getM10() * xOriginal + matrix.getM11() * yOriginal + matrix.getM12() * zOriginal; + double z = matrix.getM20() * xOriginal + matrix.getM21() * yOriginal + matrix.getM22() * zOriginal; + tupleTransformed.set(x, y, z); + } + + /** + * Assert on a component basis is the {@code tuple} is equal to (0, 0, 0) given the tolerance + * {@code epsilon}. + * + * @param tuple the query. Not modified. + * @param epsilon the tolerance. + * @return {@code true} if the tuple's component are all equal to zero, {@code false} otherwise. + */ + public static boolean isZero(Tuple3DReadOnly tuple, double epsilon) + { + if (!epsilonEquals(tuple.getX(), 0.0, epsilon)) + return false; + if (!epsilonEquals(tuple.getY(), 0.0, epsilon)) + return false; + if (!epsilonEquals(tuple.getZ(), 0.0, epsilon)) + return false; + return true; + } + + /** + * Assert on a component basis is the {@code tuple} is equal to (0, 0) given the tolerance + * {@code epsilon}. + * + * @param tuple the query. Not modified. + * @param epsilon the tolerance. + * @return {@code true} if the tuple's component are all equal to zero, {@code false} otherwise. + */ + public static boolean isZero(Tuple2DReadOnly tuple, double epsilon) + { + if (!epsilonEquals(tuple.getX(), 0.0, epsilon)) + return false; + if (!epsilonEquals(tuple.getY(), 0.0, epsilon)) + return false; + return true; + } } diff --git a/src/test/java/us/ihmc/euclid/QuaternionCalculusTest.java b/src/test/java/us/ihmc/euclid/QuaternionCalculusTest.java new file mode 100644 index 000000000..dd9536136 --- /dev/null +++ b/src/test/java/us/ihmc/euclid/QuaternionCalculusTest.java @@ -0,0 +1,331 @@ +package us.ihmc.euclid; + +import java.util.Random; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreTestTools; +import us.ihmc.euclid.tools.EuclidCoreTools; +import us.ihmc.euclid.tuple3D.Vector3D; +import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.Vector4D; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; + +import static java.lang.Math.PI; +import static org.junit.jupiter.api.Assertions.*; + +public class QuaternionCalculusTest +{ + private static final double EPSILON = 1.0e-10; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testLogAndExpAlgebra() + { + Random random = new Random(651651961L); + + for (int i = 0; i < 10000; i++) + { + QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); + Quaternion q = EuclidCoreRandomTools.nextQuaternion(random); + + Vector4D qLog = new Vector4D(); + Quaternion vExp = new Quaternion(); + + quaternionCalculus.log(q, qLog); + Vector3D v = new Vector3D(qLog.getX(),qLog.getY(),qLog.getZ()); + + QuaternionCalculus.exp(v, vExp); + + assertTrue(Math.abs(q.getX() - vExp.getX()) < 10e-10); + assertTrue(Math.abs(q.getY() - vExp.getY()) < 10e-10); + assertTrue(Math.abs(q.getZ() - vExp.getZ()) < 10e-10); + assertTrue(Math.abs(q.getS() - vExp.getS()) < 10e-10); + + } + } + + + @Test + public void testConversionQDotToAngularVelocityBackAndForth() + { + Random random = new Random(651651961L); + + for (int i = 0; i < 10000; i++) + { + QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); + Quaternion q = EuclidCoreRandomTools.nextQuaternion(random); + double length = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + Vector3D expectedAngularVelocity = EuclidCoreRandomTools.nextVector3D(random, length); + if (random.nextBoolean()) + expectedAngularVelocity.negate(); + Vector3D actualAngularVelocity = new Vector3D(); + Vector4D qDot = new Vector4D(); + + QuaternionCalculus.computeQDotInParentFrame(q, expectedAngularVelocity, qDot); + quaternionCalculus.computeAngularVelocityInParentFrame(q, qDot, actualAngularVelocity); + + EuclidCoreTestTools.assertEquals(expectedAngularVelocity, actualAngularVelocity, EPSILON); + } + } + + @Test + public void testConversionQDDotToAngularAccelerationBackAndForth() + { + Random random = new Random(651651961L); + + for (int i = 0; i < 10000; i++) + { + QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); + Quaternion q = EuclidCoreRandomTools.nextQuaternion(random); + double length = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + Vector3D angularVelocity = EuclidCoreRandomTools.nextVector3D(random, length); + if (random.nextBoolean()) + angularVelocity.negate(); + Vector3D expectedAngularAcceleration = EuclidCoreRandomTools.nextVector3D(random, length); + if (random.nextBoolean()) + expectedAngularAcceleration.negate(); + Vector3D actualAngularAcceleration = new Vector3D(); + Vector4D qDot = new Vector4D(); + Vector4D qDDot = new Vector4D(); + + QuaternionCalculus.computeQDotInParentFrame(q, angularVelocity, qDot); + + quaternionCalculus.computeQDDotInParentFrame(q, qDot, expectedAngularAcceleration, qDDot); + quaternionCalculus.computeAngularAccelerationInRotatedFrame(q, qDot, qDDot, actualAngularAcceleration); + EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, EPSILON); + + quaternionCalculus.computeQDDotInParentFrame(q, angularVelocity, actualAngularAcceleration, qDDot); + quaternionCalculus.computeAngularAccelerationInRotatedFrame(q, qDot, qDDot, actualAngularAcceleration); + EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, EPSILON); + + quaternionCalculus.computeQDDotInParentFrame(q, qDot, angularVelocity, actualAngularAcceleration, qDDot); + quaternionCalculus.computeAngularAccelerationInRotatedFrame(q, qDot, qDDot, actualAngularAcceleration); + EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, EPSILON); + + quaternionCalculus.computeQDDotInParentFrame(q, qDot, expectedAngularAcceleration, qDDot); + quaternionCalculus.computeAngularAccelerationInParentFrame(q, qDDot, angularVelocity, actualAngularAcceleration); + EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, EPSILON); + } + } + +// @Test +// public void testVelocityFromFDAgainstTrajectory() +// { +// QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); +// SimpleOrientationTrajectoryGenerator traj = new SimpleOrientationTrajectoryGenerator("traj", ReferenceFrame.getWorldFrame(), new YoRegistry("null")); +// double trajectoryTime = 1.0; +// traj.setTrajectoryTime(trajectoryTime); +// Random random = new Random(65265L); +// FrameQuaternion initialOrientation = EuclidFrameRandomTools.nextFrameQuaternion(random, ReferenceFrame.getWorldFrame()); +// FrameQuaternion finalOrientation = EuclidFrameRandomTools.nextFrameQuaternion(random, ReferenceFrame.getWorldFrame()); +// traj.setInitialOrientation(initialOrientation); +// traj.setFinalOrientation(finalOrientation); +// traj.initialize(); +// +// double dt = 1.0e-4; +// double dtForFD = 1.0e-6; +// +// FrameQuaternion orientation = new FrameQuaternion(); +// FrameVector3D expectedAngularVelocity = new FrameVector3D(); +// Quaternion q = new Quaternion(); +// Vector4D qDot = new Vector4D(); +// Quaternion qPrevious = new Quaternion(); +// Quaternion qNext = new Quaternion(); +// Vector3D actualAngularVelocity = new Vector3D(); +// +// for (double time = dt; time <= trajectoryTime - dt; time += dt) +// { +// traj.compute(time); +// expectedAngularVelocity.setIncludingFrame(traj.getAngularVelocity()); +// q.set(traj.getOrientation()); +// traj.compute(time - dtForFD); +// qPrevious.set(traj.getOrientation()); +// traj.compute(time + dtForFD); +// qNext.set(traj.getOrientation()); +// +// quaternionCalculus.computeQDotByFiniteDifferenceCentral(qPrevious, qNext, dtForFD, qDot); +// quaternionCalculus.computeAngularVelocityInWorldFrame(q, qDot, actualAngularVelocity); +// +// EuclidCoreTestTools.assertEquals(expectedAngularVelocity, actualAngularVelocity, 1e-8); +// } +// } + + @Test + public void testFDSimpleCase() + { + QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); + Random random = new Random(65265L); + double integrationTime = 1.0; + double angleVelocity = EuclidCoreRandomTools.nextDouble(random, 0.0, 2.0 * PI) / integrationTime; + Vector3D expectedAngularVelocity = new Vector3D(angleVelocity, 0.0, 0.0); + Vector3D expectedAngularAcceleration = new Vector3D(); + AxisAngle axisAnglePrevious = new AxisAngle(1.0, 0.0, 0.0, 0.0); + AxisAngle axisAngleCurrent = new AxisAngle(1.0, 0.0, 0.0, 0.0); + AxisAngle axisAngleNext = new AxisAngle(1.0, 0.0, 0.0, 0.0); + Quaternion qPrevious = new Quaternion(); + Quaternion qCurrent = new Quaternion(); + Quaternion qNext = new Quaternion(); + Vector4D qDot = new Vector4D(); + Vector4D qDDot = new Vector4D(); + + Vector3D actualAngularVelocity = new Vector3D(); + Vector3D actualAngularAcceleration = new Vector3D(); + + double dt = 1.0e-4; + for (double time = dt; time < integrationTime; time += dt) + { + axisAnglePrevious.setAngle(EuclidCoreTools.trimAngleMinusPiToPi(angleVelocity * (time - dt)) - PI); + qPrevious.set(axisAnglePrevious); + axisAngleCurrent.setAngle(EuclidCoreTools.trimAngleMinusPiToPi(angleVelocity * time) - PI); + qCurrent.set(axisAngleCurrent); + axisAngleNext.setAngle(EuclidCoreTools.trimAngleMinusPiToPi(angleVelocity * (time + dt)) - PI); + qNext.set(axisAngleNext); + + QuaternionCalculus.computeQDotByFiniteDifferenceCentral(qPrevious, qNext, dt, qDot); + quaternionCalculus.computeAngularVelocityInParentFrame(qCurrent, qDot, actualAngularVelocity); + + QuaternionCalculus.computeQDDotByFiniteDifferenceCentral(qPrevious, qCurrent, qNext, dt, qDDot); + quaternionCalculus.computeAngularAccelerationInRotatedFrame(qCurrent, qDot, qDDot, actualAngularAcceleration); + + boolean sameVelocity = expectedAngularVelocity.epsilonEquals(actualAngularVelocity, 1.0e-7); + if (!sameVelocity) + { + System.out.println("Expected angular velocity: " + expectedAngularVelocity); + System.out.println("Actual angular velocity: " + actualAngularVelocity); + } + assertTrue(sameVelocity); + EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, 1e-7); + } + } + +// @Test +// public void testAccelerationFromFDAgainstTrajectory() +// { +// QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); +// SimpleOrientationTrajectoryGenerator traj = new SimpleOrientationTrajectoryGenerator("traj", ReferenceFrame.getWorldFrame(), new YoRegistry("null")); +// double trajectoryTime = 1.0; +// traj.setTrajectoryTime(trajectoryTime); +// Random random = new Random(65265L); +// FrameQuaternion initialOrientation = EuclidFrameRandomTools.nextFrameQuaternion(random, ReferenceFrame.getWorldFrame()); +// FrameQuaternion finalOrientation = EuclidFrameRandomTools.nextFrameQuaternion(random, ReferenceFrame.getWorldFrame()); +// traj.setInitialOrientation(initialOrientation); +// traj.setFinalOrientation(finalOrientation); +// traj.initialize(); +// +// double dt = 1.0e-4; +// double dtForFD = 1.0e-4; +// +// FrameQuaternion orientation = new FrameQuaternion(); +// FrameVector3D expectedAngularAcceleration = new FrameVector3D(); +// Quaternion q = new Quaternion(); +// Vector4D qDot = new Vector4D(); +// Vector4D qDDot = new Vector4D(); +// Quaternion qPrevious = new Quaternion(); +// Quaternion qNext = new Quaternion(); +// Vector3D actualAngularAcceleration = new Vector3D(); +// +// for (double time = dt; time <= trajectoryTime - dt; time += dt) +// { +// traj.compute(time); +// expectedAngularAcceleration.setIncludingFrame(traj.getAngularAcceleration()); +// q.set(traj.getOrientation()); +// traj.compute(time - dtForFD); +// qPrevious.set(traj.getOrientation()); +// traj.compute(time + dtForFD); +// qNext.set(traj.getOrientation()); +// +// quaternionCalculus.computeQDotByFiniteDifferenceCentral(qPrevious, qNext, dtForFD, qDot); +// quaternionCalculus.computeQDDotByFiniteDifferenceCentral(qPrevious, q, qNext, dtForFD, qDDot); +// quaternionCalculus.computeAngularAcceleration(q, qDot, qDDot, actualAngularAcceleration); +// +// EuclidCoreTestTools.assertEquals(expectedAngularAcceleration, actualAngularAcceleration, 1e-5); +// } +// } + + @Test + public void testInterpolateAgainstQuat4d() + { + QuaternionCalculus quaternionCalculus = new QuaternionCalculus(); + Random random = new Random(6546545L); + Quaternion q0 = EuclidCoreRandomTools.nextQuaternion(random); + Quaternion q1 = EuclidCoreRandomTools.nextQuaternion(random); + Quaternion expectedQInterpolated = new Quaternion(); + Quaternion actualQInterpolated = new Quaternion(); + + for (double alpha = 0.0; alpha <= 1.0; alpha += 1.0e-6) + { + expectedQInterpolated.interpolate(q0, q1, alpha); + quaternionCalculus.interpolate(alpha, q0, q1, actualQInterpolated); + + EuclidCoreTestTools.assertEquals(expectedQInterpolated, actualQInterpolated, EPSILON); + } + } + + static double shiftAngleToStartOfRange(double angleToShift, double startOfAngleRange, double endOfAngleRange) + { + double ret = angleToShift; + startOfAngleRange = startOfAngleRange - EPSILON; + + if (angleToShift < startOfAngleRange) + { + ret = angleToShift + Math.ceil((startOfAngleRange - angleToShift) / endOfAngleRange) * endOfAngleRange; + } + + if (angleToShift >= (startOfAngleRange + endOfAngleRange)) + { + ret = angleToShift - Math.floor((angleToShift - startOfAngleRange) / endOfAngleRange) * endOfAngleRange; + } + + return ret; + } + @Test + public void testShiftAngleToStartOfRangeUnitless() + { + double range = Math.pow(2.0, 13.0); + + double endOfAngleRange = range; + double angleToShift = 1.6 * range; + double expectedReturn = 0.6 * range; + double actualReturn = shiftAngleToStartOfRange(angleToShift, 0.0, endOfAngleRange); + assertEquals(expectedReturn, actualReturn, 1e-12); + + angleToShift = -0.4 * range; + expectedReturn = 0.6 * range; + actualReturn = shiftAngleToStartOfRange(angleToShift, 0.0, endOfAngleRange); + assertEquals(expectedReturn, actualReturn, 1e-12); + + angleToShift = 0.4 * range; + expectedReturn = 0.4 * range; + actualReturn = shiftAngleToStartOfRange(angleToShift, 0.0, endOfAngleRange); + assertEquals(expectedReturn, actualReturn, 1e-12); + + int iters = 1000; + Random random = new Random(); + for (int i = 0; i < iters; i++) + { + double ratio = -6.0 + 12.0 * random.nextDouble(); + + angleToShift = ratio * range; + expectedReturn = angleToShift; + + if (angleToShift < 0.0) + expectedReturn = angleToShift + Math.ceil((-angleToShift) / endOfAngleRange) * endOfAngleRange; + + if (angleToShift >= endOfAngleRange) + expectedReturn = angleToShift - Math.floor((angleToShift) / endOfAngleRange) * endOfAngleRange; + + actualReturn = shiftAngleToStartOfRange(angleToShift, 0.0, endOfAngleRange); + assertEquals(expectedReturn, actualReturn, 1e-12); + } + } + +} diff --git a/src/test/java/us/ihmc/euclid/geometry/tools/ConvexPolygon2DTest.java b/src/test/java/us/ihmc/euclid/geometry/tools/ConvexPolygon2DTest.java new file mode 100644 index 000000000..7faadd770 --- /dev/null +++ b/src/test/java/us/ihmc/euclid/geometry/tools/ConvexPolygon2DTest.java @@ -0,0 +1,35 @@ +package us.ihmc.euclid.geometry.tools; + +import org.junit.jupiter.api.Test; +import us.ihmc.euclid.geometry.ConvexPolygon2D; +import us.ihmc.euclid.geometry.interfaces.ConvexPolygon2DBasics; +import us.ihmc.euclid.tuple2D.Point2D; +import us.ihmc.euclid.tuple2D.Vector2D; + +import static org.junit.jupiter.api.Assertions.*; + +public class ConvexPolygon2DTest +{ + private static final double epsilon = 1e-7; + + + @Test + public void testTranslatePolygon1() + { + ConvexPolygon2D polygon = new ConvexPolygon2D(); + polygon.addVertex(new Point2D(0.0, 0.0)); + polygon.addVertex(new Point2D(10.0, 0.0)); + polygon.addVertex(new Point2D(0.0, 10.0)); + polygon.update(); + + Vector2D translation1 = new Vector2D(0.0, 0.0); + ConvexPolygon2DBasics polygon1 = polygon.translateCopy(translation1); + assertTrue(polygon1.epsilonEquals(polygon, epsilon)); + + Vector2D translation2 = new Vector2D(1.0, 0.5); + ConvexPolygon2DBasics polygon2 = polygon.translateCopy(translation2); + assertTrue(polygon2.getVertex(2).epsilonEquals(new Point2D(1.0, 0.5), epsilon)); + assertTrue(polygon2.getVertex(1).epsilonEquals(new Point2D(11.0, 0.5), epsilon)); + assertTrue(polygon2.getVertex(0).epsilonEquals(new Point2D(1.0, 10.5), epsilon)); + } +} diff --git a/src/test/java/us/ihmc/euclid/geometry/tools/EuclidGeometryToolsTest.java b/src/test/java/us/ihmc/euclid/geometry/tools/EuclidGeometryToolsTest.java index d117e3492..eebcfe4d4 100644 --- a/src/test/java/us/ihmc/euclid/geometry/tools/EuclidGeometryToolsTest.java +++ b/src/test/java/us/ihmc/euclid/geometry/tools/EuclidGeometryToolsTest.java @@ -25,6 +25,9 @@ import us.ihmc.euclid.Axis3D; import us.ihmc.euclid.Location; import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.geometry.BoundingBox3D; +import us.ihmc.euclid.geometry.ConvexPolygon2D; +import us.ihmc.euclid.geometry.Plane3D; import us.ihmc.euclid.geometry.exceptions.BoundingBoxException; import us.ihmc.euclid.matrix.RotationMatrix; import us.ihmc.euclid.tools.EuclidCoreRandomTools; @@ -35,12 +38,14 @@ import us.ihmc.euclid.tuple2D.Point2D; import us.ihmc.euclid.tuple2D.Vector2D; import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.UnitVector3D; import us.ihmc.euclid.tuple3D.Vector3D; import us.ihmc.euclid.tuple3D.interfaces.Point3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Point3DReadOnly; import us.ihmc.euclid.tuple3D.interfaces.Vector3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Vector3DReadOnly; import us.ihmc.euclid.tuple4D.Quaternion; +import us.ihmc.euclid.tuple4D.Vector4D; public class EuclidGeometryToolsTest { @@ -14369,4 +14374,105 @@ public void testUnknownTriangleSideLengthByLawOfCosine() throws Exception // good } } + + + @Test + public void testGetZOnPlane() + { + Point3D point = new Point3D(1.0, 2.0, -3.0); + Vector3D normal = new Vector3D(0.2, 1.7, 0.4); + Plane3D plane = new Plane3D(point, normal); + + double x = 2.33; + double y = 1.97; + + double z = EuclidGeometryTools.getZOnPlane(point, normal, x, y); + + Point3D testPoint = new Point3D(x, y, z); + assertTrue(plane.distance(testPoint) < 1e-10); + + normal = new Vector3D(0.2, 1.7, 0.0); + + z = EuclidGeometryTools.getZOnPlane(point, normal, x, y); + assertTrue(Double.isNaN(z)); + } + + + @Test + public void testIsPolygonInside1() + { + ConvexPolygon2D polygon = new ConvexPolygon2D(); + polygon.addVertex(new Point2D(0.0, 0.0)); + polygon.addVertex(new Point2D(2.0, 1.0)); + polygon.addVertex(new Point2D(1.0, 2.0)); + polygon.update(); + + ConvexPolygon2D polygonToTest1 = new ConvexPolygon2D(); + polygonToTest1.addVertex(new Point2D(0.1, 0.1)); + polygonToTest1.addVertex(new Point2D(0.2, 0.2)); + polygonToTest1.update(); + assertTrue(EuclidGeometryTools.isPolygonInside(polygonToTest1, polygon)); + + ConvexPolygon2D polygonToTest2 = new ConvexPolygon2D(polygon); + assertTrue(EuclidGeometryTools.isPolygonInside(polygonToTest2, polygon)); + assertTrue(EuclidGeometryTools.isPolygonInside(polygonToTest2, EPSILON, polygon)); + assertFalse(EuclidGeometryTools.isPolygonInside(polygonToTest2, -EPSILON, polygon)); + + ConvexPolygon2D polygonToTest3 = new ConvexPolygon2D(); + polygonToTest3.addVertex(new Point2D(0.3, 0.9)); + polygonToTest3.addVertex(new Point2D(0.1, 0.1)); + polygonToTest3.addVertex(new Point2D(1.0, 1.2)); + polygonToTest3.update(); + assertFalse(EuclidGeometryTools.isPolygonInside(polygonToTest3, polygon)); + + ConvexPolygon2D polygonToTest4 = new ConvexPolygon2D(); + assertTrue(EuclidGeometryTools.isPolygonInside(polygonToTest4, polygon)); + + ConvexPolygon2D polygonToTest5 = new ConvexPolygon2D(); + polygonToTest5.addVertex(new Point2D(-0.1, 0.1)); + polygonToTest5.update(); + assertFalse(EuclidGeometryTools.isPolygonInside(polygonToTest5, polygon)); + } + + + @Test + public void testComputeBoundingBoxIntersection3D() + { + BoundingBox3D boundingBox1 = new BoundingBox3D(0.0, 0.0, 0.0, 1.0, 1.0, 1.0); + BoundingBox3D boundingBox2 = new BoundingBox3D(0.5, 0.0, 0.0, 1.5, 1.0, 1.0); + + assertEquals(1.0, EuclidGeometryTools.computeBoundingBoxVolume3D(boundingBox1), EPSILON, "[FAILED] Volume should be 1.0"); + assertEquals(1.0, EuclidGeometryTools.computeBoundingBoxVolume3D(boundingBox2), EPSILON, "[FAILED] Volume should be 1.0"); + + assertEquals(1/3.0, EuclidGeometryTools.computeIntersectionOverUnionOfTwoBoundingBoxes(boundingBox1, boundingBox2), EPSILON, "[FAILED] Intersection should be 1/3.0"); + assertEquals(0.5, EuclidGeometryTools.computeIntersectionOverSmallerOfTwoBoundingBoxes(boundingBox2, boundingBox1), EPSILON, "[FAILED] Intersection should be 0.5"); + + boundingBox2 = new BoundingBox3D(0.5, 0.5, 0.5, 1.5, 1.5, 1.5); + + assertEquals(1.0, EuclidGeometryTools.computeBoundingBoxVolume3D(boundingBox2), EPSILON, "[FAILED] Volume should be 1.0"); + + assertEquals(1/15.0, EuclidGeometryTools.computeIntersectionOverUnionOfTwoBoundingBoxes(boundingBox1, boundingBox2), EPSILON, "[FAILED] Intersection should be 1/3.0"); + assertEquals( 1/8.0, EuclidGeometryTools.computeIntersectionOverSmallerOfTwoBoundingBoxes(boundingBox2, boundingBox1), EPSILON, "[FAILED] Intersection should be 1/15.0"); + } + + @Test + public void testPointProjectionsOntoPlane() + { + Point3D pointOnPlane = new Point3D(1.0, 1.0, 1.0); + UnitVector3D planeNormal = new UnitVector3D(1.0, 1.0, 1.0); + + Vector4D plane = new Vector4D(planeNormal.getX(), planeNormal.getY(), planeNormal.getZ(), -pointOnPlane.dot(planeNormal)); + + Point3D pointToProject = new Point3D(0.0, 0.0, 0.0); + + Point3D projectedPoint = EuclidGeometryTools.projectPointOntoPlane(plane, pointToProject); + + + Point3D expectedProjection = new Point3D(1.0, 1.0, 1.0); + + System.out.println("Projected point: " + projectedPoint); + System.out.println("Expected projection: " + expectedProjection); + + assertEquals(0.0, projectedPoint.distance(expectedProjection), EPSILON); + } } diff --git a/src/test/java/us/ihmc/euclid/referenceFrame/FramePlane3DTest.java b/src/test/java/us/ihmc/euclid/referenceFrame/FramePlane3DTest.java new file mode 100644 index 000000000..5ad001cb3 --- /dev/null +++ b/src/test/java/us/ihmc/euclid/referenceFrame/FramePlane3DTest.java @@ -0,0 +1,120 @@ +package us.ihmc.euclid.referenceFrame; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.transform.RigidBodyTransform; +import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.Vector3D; + +import static org.junit.jupiter.api.Assertions.*; + +public class FramePlane3DTest +{ + private static ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame(); + private static double epsilon = 1e-14; + + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + @Test + public void testIsOnOrAbove() + { + FramePlane3D plane = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + FramePoint3D q = new FramePoint3D(worldFrame, 0.0, 0.0, 2.0); + assertTrue(plane.isOnOrAbove(q)); + q = new FramePoint3D(worldFrame, 0.0, 0.0, 0.0); + assertTrue(plane.isOnOrAbove(q)); + q = new FramePoint3D(worldFrame, 0.0, 0.0, -2.0); + assertFalse(plane.isOnOrAbove(q)); + } + + @Test + public void testIsOnOrBelow() + { + FramePlane3D plane = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + FramePoint3D q = new FramePoint3D(worldFrame, 0.0, 0.0, -2.0); + assertTrue(plane.isOnOrBelow(q)); + q.set(0.0, 0.0, 1.0); + assertFalse(plane.isOnOrBelow(q)); + } + + @Test + public void testOrthogonalProjection() + { + FramePoint3D point = new FramePoint3D(worldFrame, 1.0, 2.0, -3.0); + FramePoint3D expectedPoint = new FramePoint3D(worldFrame, 1.0, 2.0, 0.0); + FramePlane3D plane = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + assertTrue(expectedPoint.epsilonEquals(plane.orthogonalProjectionCopy(point), 1e-14)); + + point.set(3.0, 3.0, -4.0); + plane.getPoint().set(1.0, 1.0, 1.0); + plane.getNormal().set(2.0, 0.0, 0.0); + expectedPoint.set(1.0, 3.0, -4.0); + assertTrue(expectedPoint.epsilonEquals(plane.orthogonalProjectionCopy(point), 1e-14)); + } + + @Test + public void testDistance() + { + FramePoint3D point = new FramePoint3D(worldFrame, 1.0, 1.0, 1.0); + FramePlane3D plane = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + assertEquals(1.0, plane.distance(point), 1e-14); + + point.set(0.0, 0.0, 0.0); + plane.getNormal().set(1.0, 1.0, 0.0); + plane.getPoint().set(1.0, 1.0, 0.0); + assertEquals(Math.sqrt(2), plane.distance(point), epsilon); + } + + @Test + public void testApplyTransform() + { + RigidBodyTransform transformation = new RigidBodyTransform(); + transformation.setRotationYawAndZeroTranslation(2.3); + FramePlane3D plane = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + plane.applyTransform(transformation); + FrameVector3D expectedNormal = new FrameVector3D(worldFrame, 0.0, 0.0, 1.0); + FramePoint3D expectedPoint = new FramePoint3D(worldFrame, 0.0, 0.0, 0.0); + assertTrue(plane.epsilonEquals(new FramePlane3D(expectedPoint, expectedNormal), epsilon)); + + RigidBodyTransform transformation2 = new RigidBodyTransform(); + transformation2.getTranslation().set(new Vector3D(1.0, 2.0, 3.0)); + FramePlane3D plane2 = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + plane2.applyTransform(transformation2); + expectedNormal.set(0.0, 0.0, 1.0); + expectedPoint.set(1.0, 2.0, 3.0); + assertTrue(plane2.epsilonEquals(new FramePlane3D(expectedPoint, expectedNormal), epsilon)); + + RigidBodyTransform transformation3 = new RigidBodyTransform(); + transformation3.setRotationPitchAndZeroTranslation(Math.PI / 2); + transformation3.getTranslation().set(new Vector3D(1.0, 2.0, 3.0)); + FramePlane3D plane3 = new FramePlane3D(worldFrame, new Point3D(), new Vector3D(0.0, 0.0, 1.0)); + plane3.applyTransform(transformation3); + expectedNormal.set(1.0, 0.0, 0.0); + expectedPoint.set(1.0, 2.0, 3.0); + assertTrue(plane3.epsilonEquals(new FramePlane3D(expectedPoint, expectedNormal), epsilon)); + } + + @Test + public void testIntersectionWithLine() + { + FrameVector3D normal = new FrameVector3D(worldFrame, 0.0, 0.0, 1.0); + FramePoint3D point = new FramePoint3D(worldFrame, 0.0, 0.0, 0.0); + FramePlane3D plane = new FramePlane3D(point, normal); + + FramePoint3D origin = new FramePoint3D(worldFrame, 0.0, 1.0, -1.0); + FrameVector3D direction = new FrameVector3D(worldFrame, 1.0, 0.0, 1.0); + FrameLine3D line = new FrameLine3D(origin, direction); + + FramePoint3D pointToPack = new FramePoint3D(worldFrame); + plane.intersectionWith(line, pointToPack); + + FramePoint3D expectedIntersection = new FramePoint3D(worldFrame, 1.0, 1.0, 0.0); + assertTrue(pointToPack.epsilonEquals(expectedIntersection, epsilon)); + } +} diff --git a/src/test/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrameTest.java b/src/test/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrameTest.java new file mode 100644 index 000000000..31845df95 --- /dev/null +++ b/src/test/java/us/ihmc/euclid/referenceFrame/Pose2DReferenceFrameTest.java @@ -0,0 +1,164 @@ + +package us.ihmc.euclid.referenceFrame; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import us.ihmc.euclid.referenceFrame.tools.EuclidFrameTestTools; +import us.ihmc.euclid.referenceFrame.tools.ReferenceFrameTools; +import us.ihmc.euclid.tools.EuclidCoreRandomTools; +import us.ihmc.euclid.tuple2D.Point2D; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.*; + +public class Pose2DReferenceFrameTest +{ + @AfterEach + public void tearDown() + { + ReferenceFrameTools.clearWorldFrameTree(); + } + + + @Test + public void testAsynchronousUpdatesOne() + { + ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame(); + Pose2DReferenceFrame poseFrame0 = new Pose2DReferenceFrame("poseFrame0", worldFrame); + Pose2DReferenceFrame poseFrame00 = new Pose2DReferenceFrame("poseFrame00", poseFrame0); + Pose2DReferenceFrame poseFrame000 = new Pose2DReferenceFrame("poseFrame000", poseFrame00); + FramePoint3D framePoint = new FramePoint3D(poseFrame000, 1.0, 2.8, 4.4); + + Random random = new Random(1776L); + + doRandomPoseChangeAndUpdate(poseFrame0, random); + doRandomPoseChangeAndUpdate(poseFrame00, random); + doRandomPoseChangeAndUpdate(poseFrame000, random); + + // Make sure that after a pose change of an intermediate frame, that the point before and after is different. + FramePoint3D framePointInWorldOne = new FramePoint3D(framePoint); + framePointInWorldOne.changeFrame(worldFrame); + doRandomPoseChangeAndUpdate(poseFrame0, random); + FramePoint3D framePointInWorldTwo = new FramePoint3D(framePoint); + framePointInWorldTwo.changeFrame(worldFrame); + + assertFalse(framePointInWorldOne.epsilonEquals(framePointInWorldTwo, 1e-7)); + + // But make sure that additional updates doesn't make it any different. + poseFrame0.update(); + poseFrame00.update(); + poseFrame000.update(); + + FramePoint3D framePointInWorldThree = new FramePoint3D(framePoint); + framePointInWorldThree.changeFrame(worldFrame); + + EuclidFrameTestTools.assertEquals(framePointInWorldThree, framePointInWorldTwo, 1e-7); + } + + @Test + public void testLongChainEfficiency() + { + ReferenceFrame worldFrame = ReferenceFrame.getWorldFrame(); + + int numberOfFramesInChain = 100; + + ReferenceFrame previousFrame = worldFrame; + for (int i=0; i P2_RO = Point2DReadOnly.class; private static final Class P2_BA = Point2DBasics.class; private static final Class D = double.class; + private static final Class B = boolean.class; + private static final Class I = int.class; private static final double EPSILON = 1.0e-12; @Test @@ -54,6 +56,11 @@ public void testAPIIsComplete() signaturesToIgnore.add(new MethodSignature("closestPoint2DsBetweenTwoLineSegment2Ds", D, D, D, D, D, D, D, D, P2_BA, P2_BA)); signaturesToIgnore.add(new MethodSignature("closestPoint3DsBetweenTwoLineSegment3Ds", D, D, D, D, D, D, D, D, D, D, D, D, P3_BA, P3_BA)); signaturesToIgnore.add(new MethodSignature("axisAngleFromZUpToVector3D", Vector3DReadOnly.class, AxisAngleBasics.class)); + signaturesToIgnore.add(new MethodSignature("getZOnPlane", Vector3DReadOnly.class, AxisAngleBasics.class)); + signaturesToIgnore.add(new MethodSignature("getZOnPlane", Point3DReadOnly.class, Vector3DReadOnly.class, D, D)); + signaturesToIgnore.add(new MethodSignature("isPoint2DOnLineSegment2D", D, D, Point2DReadOnly.class, Point2DReadOnly.class)); + signaturesToIgnore.add(new MethodSignature("intersectionBetweenLine2DAndCircle", D, D, D, D, D, B, D, D, B, Point2DBasics.class, Point2DBasics.class)); + signaturesToIgnore.add(new MethodSignature("doLineSegment2DAndConvexPolygon2DIntersect", Point2DReadOnly.class, Point2DReadOnly.class, List.class, I)); Predicate methodFilter = EuclidFrameAPITester.methodFilterFromSignature(signaturesToIgnore); EuclidFrameAPITester tester = new EuclidFrameAPITester(new EuclidFrameAPIDefaultConfiguration()); diff --git a/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java b/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java index ed9a8e511..a21b8800f 100644 --- a/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java +++ b/src/test/java/us/ihmc/euclid/tools/EuclidCoreToolsTest.java @@ -1,8 +1,11 @@ package us.ihmc.euclid.tools; import org.junit.jupiter.api.Test; +import us.ihmc.euclid.Axis3D; import us.ihmc.euclid.axisAngle.AxisAngle; +import us.ihmc.euclid.geometry.tools.EuclidGeometryTools; import us.ihmc.euclid.matrix.Matrix3D; +import us.ihmc.euclid.matrix.RotationMatrix; import us.ihmc.euclid.orientation.Orientation2D; import us.ihmc.euclid.orientation.interfaces.Orientation2DReadOnly; import us.ihmc.euclid.orientation.interfaces.Orientation3DBasics; @@ -13,7 +16,9 @@ import us.ihmc.euclid.tuple2D.interfaces.Tuple2DBasics; import us.ihmc.euclid.tuple2D.interfaces.Tuple2DReadOnly; import us.ihmc.euclid.tuple2D.interfaces.Vector2DBasics; +import us.ihmc.euclid.tuple2D.interfaces.Vector2DReadOnly; import us.ihmc.euclid.tuple3D.Point3D; +import us.ihmc.euclid.tuple3D.UnitVector3D; import us.ihmc.euclid.tuple3D.Vector3D; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DBasics; import us.ihmc.euclid.tuple3D.interfaces.Tuple3DReadOnly; @@ -1121,4 +1126,456 @@ public void testIntegrate() EuclidCoreTestTools.assertEquals(expectedCurrTransform, actualCurrTransform, 1.0e-12); } } + + private static final int iters = 1000; + + + @Test + public void testProjectRotationOnAxis() + { + Random random = new Random(9429424L); + Quaternion fullRotation = new Quaternion(); + Quaternion actualRotation = new Quaternion(); + + for (int i = 0; i < 10000; i++) + { + // Create random axis and a rotation around that axis. + Vector3D axis = EuclidCoreRandomTools.nextVector3DWithFixedLength(random, 1.0); + double angle = EuclidCoreRandomTools.nextDouble(random, -Math.PI, Math.PI); + Quaternion expectedRotation = new Quaternion(new AxisAngle(axis, angle)); + + // Create an orthogonal rotation. + Vector3D orthogonalAxis = EuclidCoreRandomTools.nextOrthogonalVector3D(random, axis, true); + double orthogonalAngle = EuclidCoreRandomTools.nextDouble(random, -Math.PI, Math.PI); + Quaternion orthogonalRotation = new Quaternion(new AxisAngle(orthogonalAxis, orthogonalAngle)); + + // From the combined rotation and the original axis back out the rotation around the original axis. + fullRotation.multiply(orthogonalRotation, expectedRotation); + EuclidCoreTools.projectRotationOnAxis(fullRotation, axis, actualRotation); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(expectedRotation, actualRotation, 1.0e-10); + } + } + + @Test + public void testRotationMatrix3DFromFirstToSecondVector3D() + { + Random random = new Random(43634); + + for (int i = 0; i < 10000; i++) + { + Vector3D firstVector = EuclidCoreRandomTools.nextVector3D(random); + Vector3D secondVector = EuclidCoreRandomTools.nextVector3D(random); + + RotationMatrix actualRotationMatrix = new RotationMatrix(); + AxisAngle actualAxisAngle = new AxisAngle(); + AxisAngle expectedAxisAngle = new AxisAngle(); + + EuclidGeometryTools.rotationMatrix3DFromFirstToSecondVector3D(firstVector, secondVector, actualRotationMatrix); + actualAxisAngle.set(actualRotationMatrix); + EuclidGeometryTools.orientation3DFromFirstToSecondVector3D(firstVector, secondVector, expectedAxisAngle); + EuclidCoreTestTools.assertOrientation3DGeometricallyEquals(expectedAxisAngle, actualAxisAngle, 1.0e-7); + } + } + + @Test + public void testDistanceBetweenTwoLineSegment2Ds() + { + Point2D closestPointOnLineSegment1 = new Point2D(); + Point2D closestPointOnLineSegment2 = new Point2D(); + + Vector2D lineSegmentDirection1 = new Vector2D(); + Vector2D lineSegmentDirection2 = new Vector2D(); + + Random random = new Random(11762L); + + // Parallel case, expecting expectedPointOnLineSegment1 = + // lineSegmentStart1 + for (int i = 0; i < iters; i++) + { + Point2D lineSegmentStart1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentStart1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + Point2D lineSegmentEnd1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentEnd1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + + lineSegmentDirection1.sub(lineSegmentEnd1, lineSegmentStart1); + lineSegmentDirection1.normalize(); + + // expectedPointOnLineSegment1 = lineSegmentStart1 + closestPointOnLineSegment1.set(lineSegmentStart1); + + // Create the closest point of line segment 2 + Vector2D orthogonalToLineSegment1 = nextOrthogonalVector2D(random, lineSegmentDirection1, true); + double expectedMinimumDistance = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + closestPointOnLineSegment2.scaleAdd(expectedMinimumDistance, orthogonalToLineSegment1, closestPointOnLineSegment1); + + // Set the lineSegmentDirection2 = lineSegmentDirection1 + lineSegmentDirection2.set(lineSegmentDirection1); + + // Set the end points of the line segment 2 around the expected + // closest point. + Point2D lineSegmentStart2 = new Point2D(); + Point2D lineSegmentEnd2 = new Point2D(); + lineSegmentStart2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, -10.0, 0.0), lineSegmentDirection2, closestPointOnLineSegment2); + lineSegmentEnd2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0), lineSegmentDirection2, closestPointOnLineSegment2); + + double actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + + // Set the end points of the line segment 2 before the expected + // closest point, so we have expectedClosestPointOnLineSegment2 = + // lineSegmentEnd2 + double shiftStartFromExpected = EuclidCoreRandomTools.nextDouble(random, -20.0, -10.0); + double shiftEndFromExpected = EuclidCoreRandomTools.nextDouble(random, -10.0, 0.0); + lineSegmentStart2.scaleAdd(shiftStartFromExpected, lineSegmentDirection2, closestPointOnLineSegment2); + lineSegmentEnd2.scaleAdd(shiftEndFromExpected, lineSegmentDirection2, closestPointOnLineSegment2); + closestPointOnLineSegment2.set(lineSegmentEnd2); + expectedMinimumDistance = closestPointOnLineSegment1.distance(closestPointOnLineSegment2); + + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentEnd2, + lineSegmentStart2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + } + + // Case: on closest point on lineSegment1 outside end points. + for (int i = 0; i < iters; i++) + { + Point2D lineSegmentStart1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentStart1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + Point2D lineSegmentEnd1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentEnd1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + + lineSegmentDirection1.sub(lineSegmentEnd1, lineSegmentStart1); + lineSegmentDirection1.normalize(); + + // Put the first closest to the start of line segment 1 + closestPointOnLineSegment1.set(lineSegmentStart1); + + // Create the closest point of line segment 2 such that it reaches + // out of line segment 1 + Vector2D oppositeOflineSegmentDirection1 = new Vector2D(); + oppositeOflineSegmentDirection1.setAndNegate(lineSegmentDirection1); + Vector2D orthogonalToLineSegment1 = nextOrthogonalVector2D(random, lineSegmentDirection1, true); + Vector2D shiftVector = new Vector2D(); + shiftVector.interpolate(orthogonalToLineSegment1, oppositeOflineSegmentDirection1, EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0)); + closestPointOnLineSegment2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0), shiftVector, closestPointOnLineSegment1); + + // Set the line direction 2 to orthogonal to the shift vector + lineSegmentDirection2 = nextOrthogonalVector2D(random, shiftVector, true); + + // Set the end points of the line segment 2 around the expected + // closest point. + Point2D lineSegmentStart2 = new Point2D(); + Point2D lineSegmentEnd2 = new Point2D(); + lineSegmentStart2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, -10.0, 0.0), lineSegmentDirection2, closestPointOnLineSegment2); + lineSegmentEnd2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0), lineSegmentDirection2, closestPointOnLineSegment2); + double expectedMinimumDistance = closestPointOnLineSegment1.distance(closestPointOnLineSegment2); + + double actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentEnd2, + lineSegmentStart2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentEnd1, + lineSegmentStart1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentEnd1, + lineSegmentStart1, + lineSegmentEnd2, + lineSegmentStart2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + } + + // Edge case: both closest points are outside bounds of each line + // segment + for (int i = 0; i < iters; i++) + { + Point2D lineSegmentStart1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentStart1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + Point2D lineSegmentEnd1 = EuclidCoreRandomTools.nextPoint2D(random); + lineSegmentEnd1.scale(EuclidCoreRandomTools.nextDouble(random, 10.0)); + + lineSegmentDirection1.sub(lineSegmentEnd1, lineSegmentStart1); + lineSegmentDirection1.normalize(); + + // Put the first closest to the start of line segment 1 + closestPointOnLineSegment1.set(lineSegmentStart1); + + // Create the closest point of line segment 2 such that it reaches + // out of line segment 1 + Vector2D oppositeOflineSegmentDirection1 = new Vector2D(); + oppositeOflineSegmentDirection1.setAndNegate(lineSegmentDirection1); + Vector2D orthogonalToLineSegment1 = nextOrthogonalVector2D(random, lineSegmentDirection1, true); + Vector2D shiftVector = new Vector2D(); + double alpha = EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0); + shiftVector.interpolate(orthogonalToLineSegment1, oppositeOflineSegmentDirection1, alpha); + closestPointOnLineSegment2.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0), shiftVector, closestPointOnLineSegment1); + + // set the start of the second line segment to the expected closest + // point + Point2D lineSegmentStart2 = new Point2D(closestPointOnLineSegment2); + + // Set the line direction 2 to point somewhat in the same direction + // as the shift vector + Vector2D orthogonalToShiftVector = nextOrthogonalVector2D(random, shiftVector, true); + lineSegmentDirection2.interpolate(shiftVector, orthogonalToShiftVector, EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0)); + + // Set the end points of the line segment 2 around the expected + // closest point. + Point2D lineSegmentEnd2 = new Point2D(); + double alpha2 = EuclidCoreRandomTools.nextDouble(random, 0.1, 10.0); + lineSegmentEnd2.scaleAdd(alpha2, lineSegmentDirection2, closestPointOnLineSegment2); + + double expectedMinimumDistance = closestPointOnLineSegment1.distance(closestPointOnLineSegment2); + double actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentStart1, + lineSegmentEnd1, + lineSegmentEnd2, + lineSegmentStart2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentEnd1, + lineSegmentStart1, + lineSegmentStart2, + lineSegmentEnd2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + actualMinimumDistance = EuclidGeometryTools.distanceBetweenTwoLineSegment2Ds(lineSegmentEnd1, + lineSegmentStart1, + lineSegmentEnd2, + lineSegmentStart2); + assertEquals(expectedMinimumDistance, actualMinimumDistance, 1e-12); + } + } + + @Test + public void testExtractNormalPart() + { + Random random = new Random(6457); + + for (int i = 0; i < iters; i++) + { // We build the normal and tangential parts of the vector and then assemble it and expect to get the normal part pack. + Vector3D normalAxis = EuclidCoreRandomTools.nextVector3DWithFixedLength(random, 1.0); + Vector3D tangentialAxis = EuclidCoreRandomTools.nextOrthogonalVector3D(random, normalAxis, true); + Vector3D normalPart = new Vector3D(); + Vector3D tangentialPart = new Vector3D(); + + double normalMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + double tangentialMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + normalPart.setAndScale(normalMagnitude, normalAxis); + tangentialPart.setAndScale(tangentialMagnitude, tangentialAxis); + + Vector3D input = new Vector3D(); + input.add(normalPart, tangentialPart); + + Vector3D actualNormalPart = new Vector3D(); + EuclidCoreTools.extractNormalPart(input, normalAxis, actualNormalPart); + EuclidCoreTestTools.assertEquals(normalPart, actualNormalPart, 1e-12); + + // Randomize the axis + normalAxis.scale(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0)); + EuclidCoreTools.extractNormalPart(input, normalAxis, actualNormalPart); + EuclidCoreTestTools.assertEquals(normalPart, actualNormalPart, 1e-12); + } + } + + @Test + public void testExtractTangentialPart() + { + Random random = new Random(63457); + + for (int i = 0; i < iters; i++) + { // We build the normal and tangential parts of the vector and then assemble it and expect to get the normal part pack. + // Setting trivial setup using Axis3D + Axis3D normalAxis = EuclidCoreRandomTools.nextAxis3D(random); + Axis3D tangentialAxis = random.nextBoolean() ? normalAxis.next() : normalAxis.previous(); + Vector3D normalPart = new Vector3D(); + Vector3D tangentialPart = new Vector3D(); + + double normalMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + double tangentialMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + normalPart.setAndScale(normalMagnitude, normalAxis); + tangentialPart.setAndScale(tangentialMagnitude, tangentialAxis); + + Vector3D input = new Vector3D(); + input.add(normalPart, tangentialPart); + + Vector3D actualTangentialPart = new Vector3D(); + EuclidCoreTools.extractTangentialPart(input, normalAxis, actualTangentialPart); + EuclidCoreTestTools.assertEquals("Iteration: " + i, tangentialPart, actualTangentialPart, 1e-12); + } + + for (int i = 0; i < iters; i++) + { // We build the normal and tangential parts of the vector and then assemble it and expect to get the normal part pack. + Vector3D normalAxis = EuclidCoreRandomTools.nextVector3DWithFixedLength(random, 1.0); + Vector3D tangentialAxis = EuclidCoreRandomTools.nextOrthogonalVector3D(random, normalAxis, true); + Vector3D normalPart = new Vector3D(); + Vector3D tangentialPart = new Vector3D(); + + double normalMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + double tangentialMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + normalPart.setAndScale(normalMagnitude, normalAxis); + tangentialPart.setAndScale(tangentialMagnitude, tangentialAxis); + + Vector3D input = new Vector3D(); + input.add(normalPart, tangentialPart); + + Vector3D actualTangentialPart = new Vector3D(); + EuclidCoreTools.extractTangentialPart(input, normalAxis, actualTangentialPart); + EuclidCoreTestTools.assertEquals(tangentialPart, actualTangentialPart, 1e-12); + + // Randomize the axis + normalAxis.scale(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0)); + EuclidCoreTools.extractTangentialPart(input, normalAxis, actualTangentialPart); + EuclidCoreTestTools.assertEquals(tangentialPart, actualTangentialPart, 1e-12); + } + } + + @Test + public void testIntersectionBetweenRay2DAndLine2D() + { + Point2D rayOrigin = new Point2D(8.689, 0.5687); + Point2D pointOnRay = new Point2D(8.6432, 0.4951); + Vector2D rayDirection = new Vector2D(); + rayDirection.sub(pointOnRay, rayOrigin); + + Point2D lineStart = new Point2D(8.4521, 0.4323); + Point2D lineEnd = new Point2D(8.776, 0.5267); + Vector2D lineDirection = new Vector2D(); + lineDirection.sub(lineEnd, lineStart); + + Point2D intersectionToPac = new Point2D(); + assertTrue(EuclidGeometryTools.intersectionBetweenRay2DAndLine2D(rayOrigin, rayDirection, lineStart, lineDirection, intersectionToPac)); + + Point2D intersectionExpected = new Point2D(); + EuclidGeometryTools.intersectionBetweenLine2DAndLineSegment2D(rayOrigin, rayDirection, lineStart, lineEnd, intersectionExpected); + + EuclidCoreTestTools.assertPoint2DGeometricallyEquals(intersectionExpected, intersectionToPac, 1e-5); + } + + @Test + public void testSetNormalPart() + { + Random random = new Random(897632); + + for (int i = 0; i < iters; i++) + { + // Setting trivial setup using Axis3D + Axis3D normalAxis = EuclidCoreRandomTools.nextAxis3D(random); + Axis3D tangentialAxis = random.nextBoolean() ? normalAxis.next() : normalAxis.previous(); + Vector3D normalPart = new Vector3D(); + Vector3D tangentialPart = new Vector3D(); + double normalMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + double tangentialMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + normalPart.setAndScale(normalMagnitude, normalAxis); + tangentialPart.setAndScale(tangentialMagnitude, tangentialAxis); + + Point3D input = new Point3D(normalPart); + input.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 10.0), tangentialPart, input); + + Vector3D tupleToModify = new Vector3D(tangentialPart); + tupleToModify.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 10.0), normalPart, tupleToModify); + + Vector3D expected = new Vector3D(); + expected.add(normalPart, tangentialPart); + + EuclidCoreTools.setNormalPart(input, normalAxis, tupleToModify); + EuclidCoreTestTools.assertEquals(expected, tupleToModify, 1e-12); + } + + for (int i = 0; i < iters; i++) + { + Vector3D normalAxis = EuclidCoreRandomTools.nextVector3DWithFixedLength(random, 1.0); + Vector3D tangentialAxis = EuclidCoreRandomTools.nextOrthogonalVector3D(random, normalAxis, true); + Vector3D normalPart = new Vector3D(); + Vector3D tangentialPart = new Vector3D(); + double normalMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + double tangentialMagnitude = EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0); + normalPart.setAndScale(normalMagnitude, normalAxis); + tangentialPart.setAndScale(tangentialMagnitude, tangentialAxis); + + Point3D input = new Point3D(normalPart); + input.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 10.0), tangentialPart, input); + + Vector3D tupleToModify = new Vector3D(tangentialPart); + tupleToModify.scaleAdd(EuclidCoreRandomTools.nextDouble(random, 10.0), normalPart, tupleToModify); + + Vector3D expected = new Vector3D(); + expected.add(normalPart, tangentialPart); + + EuclidCoreTools.setNormalPart(input, normalAxis, tupleToModify); + EuclidCoreTestTools.assertEquals(expected, tupleToModify, 1e-12); + + // Randomize the axis + normalAxis.scale(EuclidCoreRandomTools.nextDouble(random, 0.0, 10.0)); + EuclidCoreTools.setNormalPart(input, normalAxis, tupleToModify); + EuclidCoreTestTools.assertEquals(expected, tupleToModify, 1e-12); + } + } + + @Test + public void testDifferentiateOrientation() + { + Random random = new Random(23423); + + for (int i = 0; i < iters; i++) + { + Quaternion qStart = EuclidCoreRandomTools.nextQuaternion(random); + double duration = EuclidCoreRandomTools.nextDouble(random, 0.0, 1.0e-2); + double angle = EuclidCoreRandomTools.nextDouble(random, 0.0, Math.PI); + UnitVector3D velocityAxis = EuclidCoreRandomTools.nextUnitVector3D(random); + Vector3D expectedAngularVelocity = new Vector3D(); + expectedAngularVelocity.setAndScale(angle / duration, velocityAxis); + + Quaternion qEnd = new Quaternion(); + qEnd.setRotationVector(velocityAxis.getX() * angle, velocityAxis.getY() * angle, velocityAxis.getZ() * angle); + qEnd.prepend(qStart); + + Vector3D actualAngularVelocity = new Vector3D(); + EuclidCoreTools.differentiateOrientation(qStart, qEnd, duration, actualAngularVelocity); + + EuclidCoreTestTools.assertEquals(expectedAngularVelocity, actualAngularVelocity, 1e-12 * Math.max(1.0, expectedAngularVelocity.norm())); + } + } + + /** + * Generates a random vector that is perpendicular to {@code vectorToBeOrthogonalTo}. + * + * @param random the random generator to use. + * @param vectorToBeOrthogonalTo the vector to be orthogonal to. Not modified. + * @param normalize whether to normalize the generated vector or not. + * @return the random vector. + */ + public static Vector2D nextOrthogonalVector2D(Random random, Vector2DReadOnly vectorToBeOrthogonalTo, boolean normalize) + { + Vector2D v1 = new Vector2D(vectorToBeOrthogonalTo.getY(), -vectorToBeOrthogonalTo.getX()); + + Vector2D randomPerpendicular = new Vector2D(); + double a = EuclidCoreRandomTools.nextDouble(random, 1.0); + randomPerpendicular.scaleAdd(a, v1, randomPerpendicular); + + if (normalize) + randomPerpendicular.normalize(); + + return randomPerpendicular; + } }