📄 rotation.java
字号:
// we try other vectors Vector3D u3 = Vector3D.crossProduct(u1, u2); Vector3D v3 = Vector3D.crossProduct(v1, v2); double u3x = u3.getX(); double u3y = u3.getY(); double u3z = u3.getZ(); double v3x = v3.getX(); double v3y = v3.getY(); double v3z = v3.getZ(); double dx3 = v3x - u3x; double dy3 = v3y - u3y; double dz3 = v3z - u3z; k = new Vector3D(dy1 * dz3 - dz1 * dy3, dz1 * dx3 - dx1 * dz3, dx1 * dy3 - dy1 * dx3); c = k.getX() * (u1y * u3z - u1z * u3y) + k.getY() * (u1z * u3x - u1x * u3z) + k.getZ() * (u1x * u3y - u1y * u3x); if (c == 0) { // the (q1, q2, q3) vector is aligned with u1: // we try (u2, u3) and (v2, v3) k = new Vector3D(dy2 * dz3 - dz2 * dy3, dz2 * dx3 - dx2 * dz3, dx2 * dy3 - dy2 * dx3); c = k.getX() * (u2y * u3z - u2z * u3y) + k.getY() * (u2z * u3x - u2x * u3z) + k.getZ() * (u2x * u3y - u2y * u3x); if (c == 0) { // the (q1, q2, q3) vector is aligned with everything // this is really the identity rotation q0 = 1.0; q1 = 0.0; q2 = 0.0; q3 = 0.0; return; } // we will have to use u2 and v2 to compute the scalar part uRef = u2; vRef = v2; } } // compute the vectorial part c = Math.sqrt(c); double inv = 1.0 / (c + c); q1 = inv * k.getX(); q2 = inv * k.getY(); q3 = inv * k.getZ(); // compute the scalar part k = new Vector3D(uRef.getY() * q3 - uRef.getZ() * q2, uRef.getZ() * q1 - uRef.getX() * q3, uRef.getX() * q2 - uRef.getY() * q1); c = Vector3D.dotProduct(k, k); q0 = Vector3D.dotProduct(vRef, k) / (c + c); } /** Build one of the rotations that transform one vector into another one. * <p>Except for a possible scale factor, if the instance were * applied to the vector u it will produce the vector v. There is an * infinite number of such rotations, this constructor choose the * one with the smallest associated angle (i.e. the one whose axis * is orthogonal to the (u, v) plane). If u and v are colinear, an * arbitrary rotation axis is chosen.</p> * @param u origin vector * @param v desired image of u by the rotation * @exception IllegalArgumentException if the norm of one of the vectors is zero */ public Rotation(Vector3D u, Vector3D v) { double normProduct = u.getNorm() * v.getNorm(); if (normProduct == 0) { throw new IllegalArgumentException("zero norm for rotation defining vector"); } double dot = Vector3D.dotProduct(u, v); if (dot < ((2.0e-15 - 1.0) * normProduct)) { // special case u = -v: we select a PI angle rotation around // an arbitrary vector orthogonal to u Vector3D w = u.orthogonal(); q0 = 0.0; q1 = -w.getX(); q2 = -w.getY(); q3 = -w.getZ(); } else { // general case: (u, v) defines a plane, we select // the shortest possible rotation: axis orthogonal to this plane q0 = Math.sqrt(0.5 * (1.0 + dot / normProduct)); double coeff = 1.0 / (2.0 * q0 * normProduct); q1 = coeff * (v.getY() * u.getZ() - v.getZ() * u.getY()); q2 = coeff * (v.getZ() * u.getX() - v.getX() * u.getZ()); q3 = coeff * (v.getX() * u.getY() - v.getY() * u.getX()); } } /** Build a rotation from three Cardan or Euler elementary rotations. * <p>Cardan rotations are three successive rotations around the * canonical axes X, Y and Z, each axis beeing used once. There are * 6 such sets of rotations (XYZ, XZY, YXZ, YZX, ZXY and ZYX). Euler * rotations are three successive rotations around the canonical * axes X, Y and Z, the first and last rotations beeing around the * same axis. There are 6 such sets of rotations (XYX, XZX, YXY, * YZY, ZXZ and ZYZ), the most popular one being ZXZ.</p> * <p>Beware that many people routinely use the term Euler angles even * for what really are Cardan angles (this confusion is especially * widespread in the aerospace business where Roll, Pitch and Yaw angles * are often wrongly tagged as Euler angles).</p> * @param order order of rotations to use * @param alpha1 angle of the first elementary rotation * @param alpha2 angle of the second elementary rotation * @param alpha3 angle of the third elementary rotation */ public Rotation(RotationOrder order, double alpha1, double alpha2, double alpha3) { Rotation r1 = new Rotation(order.getA1(), alpha1); Rotation r2 = new Rotation(order.getA2(), alpha2); Rotation r3 = new Rotation(order.getA3(), alpha3); Rotation composed = r1.applyTo(r2.applyTo(r3)); q0 = composed.q0; q1 = composed.q1; q2 = composed.q2; q3 = composed.q3; } /** Revert a rotation. * Build a rotation which reverse the effect of another * rotation. This means that if r(u) = v, then r.revert(v) = u. The * instance is not changed. * @return a new rotation whose effect is the reverse of the effect * of the instance */ public Rotation revert() { return new Rotation(-q0, q1, q2, q3, false); } /** Get the scalar coordinate of the quaternion. * @return scalar coordinate of the quaternion */ public double getQ0() { return q0; } /** Get the first coordinate of the vectorial part of the quaternion. * @return first coordinate of the vectorial part of the quaternion */ public double getQ1() { return q1; } /** Get the second coordinate of the vectorial part of the quaternion. * @return second coordinate of the vectorial part of the quaternion */ public double getQ2() { return q2; } /** Get the third coordinate of the vectorial part of the quaternion. * @return third coordinate of the vectorial part of the quaternion */ public double getQ3() { return q3; } /** Get the normalized axis of the rotation. * @return normalized axis of the rotation */ public Vector3D getAxis() { double squaredSine = q1 * q1 + q2 * q2 + q3 * q3; if (squaredSine == 0) { return new Vector3D(1, 0, 0); } else if (q0 < 0) { double inverse = 1 / Math.sqrt(squaredSine); return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); } double inverse = -1 / Math.sqrt(squaredSine); return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse); } /** Get the angle of the rotation. * @return angle of the rotation (between 0 and π) */ public double getAngle() { if ((q0 < -0.1) || (q0 > 0.1)) { return 2 * Math.asin(Math.sqrt(q1 * q1 + q2 * q2 + q3 * q3)); } else if (q0 < 0) { return 2 * Math.acos(-q0); } return 2 * Math.acos(q0); } /** Get the Cardan or Euler angles corresponding to the instance. * <p>The equations show that each rotation can be defined by two * different values of the Cardan or Euler angles set. For example * if Cardan angles are used, the rotation defined by the angles * a<sub>1</sub>, a<sub>2</sub> and a<sub>3</sub> is the same as * the rotation defined by the angles π + a<sub>1</sub>, π * - a<sub>2</sub> and π + a<sub>3</sub>. This method implements * the following arbitrary choices:</p> * <ul> * <li>for Cardan angles, the chosen set is the one for which the * second angle is between -π/2 and π/2 (i.e its cosine is * positive),</li> * <li>for Euler angles, the chosen set is the one for which the * second angle is between 0 and π (i.e its sine is positive).</li> * </ul> * <p>Cardan and Euler angle have a very disappointing drawback: all * of them have singularities. This means that if the instance is * too close to the singularities corresponding to the given * rotation order, it will be impossible to retrieve the angles. For * Cardan angles, this is often called gimbal lock. There is * <em>nothing</em> to do to prevent this, it is an intrinsic problem * with Cardan and Euler representation (but not a problem with the * rotation itself, which is perfectly well defined). For Cardan * angles, singularities occur when the second angle is close to * -π/2 or +π/2, for Euler angle singularities occur when the * second angle is close to 0 or π, this implies that the identity * rotation is always singular for Euler angles!</p> * @param order rotation order to use * @return an array of three angles, in the order specified by the set * @exception CardanEulerSingularityException if the rotation is * singular with respect to the angles set specified */ public double[] getAngles(RotationOrder order) throws CardanEulerSingularityException { if (order == RotationOrder.XYZ) { // r (Vector3D.plusK) coordinates are : // sin (theta), -cos (theta) sin (phi), cos (theta) cos (phi) // (-r) (Vector3D.plusI) coordinates are : // cos (psi) cos (theta), -sin (psi) cos (theta), sin (theta) // and we can choose to have theta in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusK); Vector3D v2 = applyInverseTo(Vector3D.plusI); if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(-(v1.getY()), v1.getZ()), Math.asin(v2.getZ()), Math.atan2(-(v2.getY()), v2.getX()) }; } else if (order == RotationOrder.XZY) { // r (Vector3D.plusJ) coordinates are : // -sin (psi), cos (psi) cos (phi), cos (psi) sin (phi) // (-r) (Vector3D.plusI) coordinates are : // cos (theta) cos (psi), -sin (psi), sin (theta) cos (psi) // and we can choose to have psi in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusJ); Vector3D v2 = applyInverseTo(Vector3D.plusI); if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(v1.getZ(), v1.getY()), -Math.asin(v2.getY()), Math.atan2(v2.getZ(), v2.getX()) }; } else if (order == RotationOrder.YXZ) { // r (Vector3D.plusK) coordinates are : // cos (phi) sin (theta), -sin (phi), cos (phi) cos (theta) // (-r) (Vector3D.plusJ) coordinates are : // sin (psi) cos (phi), cos (psi) cos (phi), -sin (phi) // and we can choose to have phi in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusK); Vector3D v2 = applyInverseTo(Vector3D.plusJ); if ((v2.getZ() < -0.9999999999) || (v2.getZ() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(v1.getX(), v1.getZ()), -Math.asin(v2.getZ()), Math.atan2(v2.getX(), v2.getY()) }; } else if (order == RotationOrder.YZX) { // r (Vector3D.plusI) coordinates are : // cos (psi) cos (theta), sin (psi), -cos (psi) sin (theta) // (-r) (Vector3D.plusJ) coordinates are : // sin (psi), cos (phi) cos (psi), -sin (phi) cos (psi) // and we can choose to have psi in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusI); Vector3D v2 = applyInverseTo(Vector3D.plusJ); if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(-(v1.getZ()), v1.getX()), Math.asin(v2.getX()), Math.atan2(-(v2.getZ()), v2.getY()) }; } else if (order == RotationOrder.ZXY) { // r (Vector3D.plusJ) coordinates are : // -cos (phi) sin (psi), cos (phi) cos (psi), sin (phi) // (-r) (Vector3D.plusK) coordinates are : // -sin (theta) cos (phi), sin (phi), cos (theta) cos (phi) // and we can choose to have phi in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusJ); Vector3D v2 = applyInverseTo(Vector3D.plusK); if ((v2.getY() < -0.9999999999) || (v2.getY() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(-(v1.getX()), v1.getY()), Math.asin(v2.getY()), Math.atan2(-(v2.getX()), v2.getZ()) }; } else if (order == RotationOrder.ZYX) { // r (Vector3D.plusI) coordinates are : // cos (theta) cos (psi), cos (theta) sin (psi), -sin (theta) // (-r) (Vector3D.plusK) coordinates are : // -sin (theta), sin (phi) cos (theta), cos (phi) cos (theta) // and we can choose to have theta in the interval [-PI/2 ; +PI/2] Vector3D v1 = applyTo(Vector3D.plusI); Vector3D v2 = applyInverseTo(Vector3D.plusK); if ((v2.getX() < -0.9999999999) || (v2.getX() > 0.9999999999)) { throw new CardanEulerSingularityException(true); } return new double[] { Math.atan2(v1.getY(), v1.getX()), -Math.asin(v2.getX()),
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -