Skip to content

Commit 9183f0e

Browse files
authored
testcase and fix for issue #1388 (Quaternion javadoc is misleading) (#1451)
* testcase and fix for issue #1388 (Quaternion javadoc is misleading) * privatize 2 methods to please Codacy * Quaternion: distinguish intrinsic-vs-extrinsic rotation order in cmts * TestIssue1388: verify intrinsic rotation order
1 parent d653d6b commit 9183f0e

File tree

2 files changed

+158
-32
lines changed

2 files changed

+158
-32
lines changed

jme3-core/src/main/java/com/jme3/math/Quaternion.java

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2009-2020 jMonkeyEngine
2+
* Copyright (c) 2009-2021 jMonkeyEngine
33
* All rights reserved.
44
*
55
* Redistribution and use in source and binary forms, with or without
@@ -185,12 +185,13 @@ public Quaternion set(Quaternion q) {
185185
}
186186

187187
/**
188-
* Constructor instantiates a new <code>Quaternion</code> object from a
189-
* collection of rotation angles.
188+
* Instantiate a new <code>Quaternion</code> from Tait-Bryan angles,
189+
* applying the rotations in x-z-y extrinsic order or y-z'-x" intrinsic
190+
* order.
190191
*
191-
* @param angles
192-
* the angles of rotation (x, y, z) that will define the
193-
* <code>Quaternion</code>.
192+
* @param angles an array of Tait-Bryan angles (in radians, exactly 3
193+
* elements, the X angle in angles[0], the Y angle in angles[1], and the Z
194+
* angle in angles[2], unaffected)
194195
*/
195196
public Quaternion(float[] angles) {
196197
fromAngles(angles);
@@ -244,11 +245,13 @@ public boolean isIdentity() {
244245
}
245246

246247
/**
247-
* <code>fromAngles</code> builds a quaternion from the Euler rotation
248-
* angles (x,y,z) aka (pitch, yaw, roll).
248+
* Reconfigure this <code>Quaternion</code> based on Tait-Bryan angles,
249+
* applying the rotations in x-z-y extrinsic order or y-z'-x" intrinsic
250+
* order.
249251
*
250-
* @param angles
251-
* the Euler angles of rotation (in radians).
252+
* @param angles an array of Tait-Bryan angles (in radians, exactly 3
253+
* elements, the X angle in angles[0], the Y angle in angles[1], and the Z
254+
* angle in angles[2], unaffected)
252255
* @return this
253256
*/
254257
public Quaternion fromAngles(float[] angles) {
@@ -261,22 +264,15 @@ public Quaternion fromAngles(float[] angles) {
261264
}
262265

263266
/**
264-
* <code>fromAngles</code> builds a Quaternion from the Euler rotation
265-
* angles (x,y,z) aka (pitch, yaw, roll)).
266-
* Note that we are applying in order: (y, x, z) aka (yaw, pitch, roll)
267-
* but we've ordered them in x, y, and z for convenience.
267+
* Reconfigure this Quaternion based on Tait-Bryan rotations, applying the
268+
* rotations in x-z-y extrinsic order or y-z'-x" intrinsic order.
268269
*
269-
* @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm</a>
270+
* @see
271+
* <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm</a>
270272
*
271-
* @param xAngle
272-
* the Euler pitch of rotation (in radians). (aka Attitude, often rot
273-
* around x)
274-
* @param yAngle
275-
* the Euler yaw of rotation (in radians). (aka Heading, often
276-
* rot around y)
277-
* @param zAngle
278-
* the Euler roll of rotation (in radians). (aka Bank, often
279-
* rot around z)
273+
* @param xAngle the X angle (in radians)
274+
* @param yAngle the Y angle (in radians)
275+
* @param zAngle the Z angle (in radians)
280276
* @return this
281277
*/
282278
public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) {
@@ -308,16 +304,20 @@ public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) {
308304
}
309305

310306
/**
311-
* <code>toAngles</code> returns this quaternion converted to Euler rotation
312-
* angles (x,y,z) aka (pitch, yaw, roll).
307+
* Convert this <code>Quaternion</code> to Tait-Bryan angles, to be applied
308+
* in x-z-y intrinsic order or y-z'-x" extrinsic order, for instance by
309+
* {@link #fromAngles(float[])}.
313310
*
314-
* Note that the result is not always 100% accurate due to the implications of euler angles.
315-
* @see <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm</a>
311+
* Note that the result is not always 100% accurate due to the implications
312+
* of Tait-Bryan angles.
316313
*
317-
* @param angles
318-
* the float[] in which the angles should be stored, or null if
319-
* you want a new float[] to be created
320-
* @return the float[] in which the angles are stored.
314+
* @see
315+
* <a href="http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm">http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm</a>
316+
*
317+
* @param angles an array of 3 floats in which to store the result, or else
318+
* null (If null, a new array will be allocated.)
319+
* @return an array of 3 angles (in radians, the X angle in angles[0], the Y
320+
* angle in angles[1], and the Z angle in angles[2])
321321
*/
322322
public float[] toAngles(float[] angles) {
323323
if (angles == null) {
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/*
2+
* Copyright (c) 2020-2021 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.math;
33+
34+
import org.junit.Assert;
35+
import org.junit.Test;
36+
37+
/**
38+
* Verify the order in which Tait-Bryan angles are applied by the Quaternion
39+
* class. This was issue #1388 at GitHub.
40+
*
41+
* @author Stephen Gold
42+
*/
43+
public class TestIssue1388 {
44+
45+
@Test
46+
public void testIssue1388() {
47+
Vector3f in = new Vector3f(4f, 6f, 9f); // test vector, never modified
48+
Vector3f saveIn = in.clone();
49+
/*
50+
* Three arbitrary rotation angles between -PI/2 and +PI/2
51+
*/
52+
final float xAngle = 1.23f;
53+
final float yAngle = 0.765f;
54+
final float zAngle = -0.456f;
55+
float[] angles = new float[]{xAngle, yAngle, zAngle};
56+
float[] saveAngles = new float[]{xAngle, yAngle, zAngle};
57+
/*
58+
* Part 1: verify that the extrinsic rotation order is x-z-y
59+
*
60+
* Apply extrinsic rotations to the "in" vector in x-z-y order.
61+
*/
62+
Quaternion qx = new Quaternion().fromAngleAxis(xAngle, Vector3f.UNIT_X);
63+
Quaternion qy = new Quaternion().fromAngleAxis(yAngle, Vector3f.UNIT_Y);
64+
Quaternion qz = new Quaternion().fromAngleAxis(zAngle, Vector3f.UNIT_Z);
65+
Vector3f outXZY = qx.mult(in);
66+
qz.mult(outXZY, outXZY);
67+
qy.mult(outXZY, outXZY);
68+
/*
69+
* Construct a Quaternion using fromAngles(float, float, float),
70+
* use it to rotate the "in" vector, and compare.
71+
*/
72+
Quaternion q1 = new Quaternion().fromAngles(xAngle, yAngle, zAngle);
73+
Vector3f out1 = q1.mult(in);
74+
assertEquals(outXZY, out1, 1e-5f);
75+
/*
76+
* Construct a Quaternion using fromAngles(float[]),
77+
* use it to rotate the "in" vector, and compare.
78+
*/
79+
Quaternion q2 = new Quaternion().fromAngles(angles);
80+
Vector3f out2 = q2.mult(in);
81+
assertEquals(outXZY, out2, 1e-5f);
82+
/*
83+
* Construct a Quaternion using only the constructor,
84+
* use it to rotate the "in" vector, and compare.
85+
*/
86+
Quaternion q3 = new Quaternion(angles);
87+
Vector3f out3 = q3.mult(in);
88+
assertEquals(outXZY, out3, 1e-5f);
89+
/*
90+
* Verify that fromAngles() reverses toAngles() for the chosen angles.
91+
*/
92+
float[] out4 = q1.toAngles(null);
93+
assertEquals(angles, out4, 1e-5f);
94+
float[] out5 = q2.toAngles(null);
95+
assertEquals(angles, out5, 1e-5f);
96+
float[] out6 = q3.toAngles(null);
97+
assertEquals(angles, out6, 1e-5f);
98+
/*
99+
* Part 2: verify intrinsic rotation order
100+
*
101+
* Apply intrinsic rotations to the "in" vector in y-z'-x" order.
102+
*/
103+
Quaternion q4 = qy.mult(qz).mult(qx);
104+
Vector3f out7 = q4.mult(in);
105+
assertEquals(outXZY, out7, 1e-5f);
106+
/*
107+
* Verify that the values of "saveAngles" and "in" haven't changed.
108+
*/
109+
assertEquals(saveAngles, angles, 0f);
110+
assertEquals(saveIn, in, 0f);
111+
}
112+
113+
private void assertEquals(float[] expected, float[] actual,
114+
float tolerance) {
115+
Assert.assertEquals(expected[0], actual[0], tolerance);
116+
Assert.assertEquals(expected[1], actual[1], tolerance);
117+
Assert.assertEquals(expected[2], actual[2], tolerance);
118+
}
119+
120+
private void assertEquals(Vector3f expected, Vector3f actual,
121+
float tolerance) {
122+
Assert.assertEquals(expected.x, actual.x, tolerance);
123+
Assert.assertEquals(expected.y, actual.y, tolerance);
124+
Assert.assertEquals(expected.z, actual.z, tolerance);
125+
}
126+
}

0 commit comments

Comments
 (0)