3 * $Date: 2007-04-05 09:07:28 -0500 (Thu, 05 Apr 2007) $
\r
6 * Copyright (C) 2003-2005 The Jmol Development Team
\r
8 * Contact: jmol-developers@lists.sf.net
\r
10 * This library is free software; you can redistribute it and/or
\r
11 * modify it under the terms of the GNU Lesser General Public
\r
12 * License as published by the Free Software Foundation; either
\r
13 * version 2.1 of the License, or (at your option) any later version.
\r
15 * This library is distributed in the hope that it will be useful,
\r
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
18 * Lesser General Public License for more details.
\r
20 * You should have received a copy of the GNU Lesser General Public
\r
21 * License along with this library; if not, write to the Free Software
\r
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
\r
24 package javajs.util;
\r
27 * Standard UNIT quaternion math -- for rotation.
\r
29 * All rotations can be represented as two identical quaternions.
\r
30 * This is because any rotation can be considered from either end of the
\r
31 * rotational axis -- either as a + rotation or a - rotation. This code
\r
32 * is designed to always maintain the quaternion with a rotation in the
\r
35 * This ensures that the reported theta is always positive, and the normal
\r
36 * reported is always associated with a positive theta.
\r
38 * @author Bob Hanson, hansonr@stolaf.edu 6/2008
\r
43 public float q0, q1, q2, q3;
\r
46 private final static P4 qZero = new P4();
\r
47 private static final double RAD_PER_DEG = Math.PI / 180;
\r
53 public static Quat newQ(Quat q) {
\r
54 Quat q1 = new Quat();
\r
59 public static Quat newVA(T3 v, float theta) {
\r
60 Quat q = new Quat();
\r
65 public static Quat newM(M3 mat) {
\r
66 Quat q = new Quat();
\r
67 q.setM(M3.newM3(mat));
\r
71 public static Quat newAA(A4 a) {
\r
72 Quat q = new Quat();
\r
77 public static Quat newP4(P4 pt) {
\r
78 Quat q = new Quat();
\r
84 * Note that q0 is the last parameter here
\r
90 * @return {q1 q2 q3 q0}
\r
92 public static Quat new4(float q1, float q2, float q3, float q0) {
\r
93 Quat q = new Quat();
\r
109 public void set(Quat q) {
\r
117 * {x y z w} --> {q1 q2 q3 q0} and factored
\r
121 private void setP4(P4 pt) {
\r
122 float factor = (pt == null ? 0 : pt.distance4(qZero));
\r
127 q0 = pt.w / factor;
\r
128 q1 = pt.x / factor;
\r
129 q2 = pt.y / factor;
\r
130 q3 = pt.z / factor;
\r
134 * q = (cos(theta/2), sin(theta/2) * n)
\r
139 public void setTA(T3 pt, float theta) {
\r
140 if (pt.x == 0 && pt.y == 0 && pt.z == 0) {
\r
144 double fact = (Math.sin(theta / 2 * RAD_PER_DEG) / Math.sqrt(pt.x
\r
145 * pt.x + pt.y * pt.y + pt.z * pt.z));
\r
146 q0 = (float) (Math.cos(theta / 2 * RAD_PER_DEG));
\r
147 q1 = (float) (pt.x * fact);
\r
148 q2 = (float) (pt.y * fact);
\r
149 q3 = (float) (pt.z * fact);
\r
152 public void setAA(A4 a) {
\r
153 A4 aa = A4.newAA(a);
\r
156 setM(new M3().setAA(aa));
\r
159 private void setM(M3 mat) {
\r
162 * Changed 7/16/2008 to double precision for 11.5.48.
\r
166 * RayTrace Software Package, release 3.0. May 3, 2006.
\r
168 * Mathematics Subpackage (VrMath)
\r
170 * Author: Samuel R. Buss
\r
172 * Software is "as-is" and carries no warranty. It may be used without
\r
173 * restriction, but if you modify it, please change the filenames to
\r
174 * prevent confusion between different versions. Please acknowledge
\r
175 * all use of the software in any publications or products based on it.
\r
177 * Bug reports: Sam Buss, sbuss@ucsd.edu.
\r
178 * Web page: http://math.ucsd.edu/~sbuss/MathCG
\r
180 // Use Shepperd's algorithm, which is stable, does not lose
\r
181 // significant precision and uses only one sqrt.
\r
182 // J. Guidance and Control, 1 (1978) 223-224.
\r
186 * Except, that code has errors.
\r
188 * CORRECTIONS (as noted below) of Quaternion.cpp. I have reported the bug.
\r
193 * cos(theta/2)^2 = (cos(theta) + 1)/2
\r
195 * trace = (1-x^2)ct + (1-y^2)ct + (1-z^2)ct + 1 = 2cos(theta) + 1
\r
197 * cos(theta) = (trace - 1)/2
\r
201 * w = cos(theta/2)
\r
202 * = sqrt((cos(theta)+1)/2)
\r
203 * = sqrt((trace-1)/4+1/2)
\r
204 * = sqrt((trace+1)/4)
\r
205 * = sqrt(trace+1)/2
\r
207 * but there are precision issues, so we allow for other situations.
\r
208 * note -- trace >= 0.5 when cos(theta) >= -0.25 (-104.48 <= theta <= 104.48).
\r
209 * this code cleverly matches the precision in all four options.
\r
215 double trace = mat.m00 + mat.m11 + mat.m22;
\r
218 if (trace >= 0.5) {
\r
219 w = Math.sqrt(1.0 + trace);
\r
220 x = (mat.m21 - mat.m12) / w;
\r
221 y = (mat.m02 - mat.m20) / w;
\r
222 z = (mat.m10 - mat.m01) / w;
\r
223 } else if ((temp = mat.m00 + mat.m00 - trace) >= 0.5) {
\r
224 x = Math.sqrt(1.0 + temp);
\r
225 w = (mat.m21 - mat.m12) / x;
\r
226 y = (mat.m10 + mat.m01) / x;
\r
227 z = (mat.m20 + mat.m02) / x;
\r
228 } else if ((temp = mat.m11 + mat.m11 - trace) >= 0.5
\r
229 || mat.m11 > mat.m22) {
\r
230 y = Math.sqrt(1.0 + temp);
\r
231 w = (mat.m02 - mat.m20) / y;
\r
232 x = (mat.m10 + mat.m01) / y;
\r
233 z = (mat.m21 + mat.m12) / y;
\r
235 z = Math.sqrt(1.0 + mat.m22 + mat.m22 - trace);
\r
236 w = (mat.m10 - mat.m01) / z;
\r
237 x = (mat.m20 + mat.m02) / z; // was -
\r
238 y = (mat.m21 + mat.m12) / z; // was -
\r
241 q0 = (float) (w * 0.5);
\r
242 q1 = (float) (x * 0.5);
\r
243 q2 = (float) (y * 0.5);
\r
244 q3 = (float) (z * 0.5);
\r
247 * Originally from http://www.gamedev.net/community/forums/topic.asp?topic_id=448380
\r
248 * later algorithm was adapted from Visualizing Quaternions, by Andrew J. Hanson
\r
249 * (Morgan Kaufmann, 2006), page 446
\r
251 * HOWEVER, checking with AxisAngle4f and Quat4f equivalents, it was found that
\r
252 * BOTH of these sources produce inverted quaternions. So here we do an inversion.
\r
254 * This correction was made in 11.5.42 6/19/2008 -- Bob Hanson
\r
256 * former algorithm used:
\r
259 double tr = mat.m00 + mat.m11 + mat.m22; //Matrix trace
\r
261 double[] q = new double[4];
\r
263 s = Math.sqrt(tr + 1);
\r
264 q0 = (float) (0.5 * s);
\r
265 s = 0.5 / s; // = 1/q0
\r
266 q1 = (float) ((mat.m21 - mat.m12) * s);
\r
267 q2 = (float) ((mat.m02 - mat.m20) * s);
\r
268 q3 = (float) ((mat.m10 - mat.m01) * s);
\r
270 float[][] m = new float[][] { new float[3], new float[3], new float[3] };
\r
271 mat.getRow(0, m[0]);
\r
272 mat.getRow(1, m[1]);
\r
273 mat.getRow(2, m[2]);
\r
275 //Find out the biggest element along the diagonal
\r
276 float max = Math.max(mat.m11, mat.m00);
\r
277 int i = (mat.m22 > max ? 2 : max == mat.m11 ? 1 : 0);
\r
278 int j = (i + 1) % 3;
\r
279 int k = (j + 1) % 3;
\r
280 s = -Math.sqrt(1 + m[i][i] - m[j][j] - m[k][k]);
\r
281 // 0 = 1 + (1-x^2)ct + x^2 -(1-y^2)ct - y^2 - (1-z^2)ct - z^2
\r
282 // 0 = 1 - ct + (x^2 - y^2 - z^2) - (x^2 - y^2 - z^2)ct
\r
283 // 0 = 1 - ct + 2x^2 - 1 - (2x^2)ct + ct
\r
284 // 0 = 2x^2(1 - ct)
\r
285 // theta = 0 (but then trace = 1 + 1 + 1 = 3)
\r
289 s = 0.5 / s; // = 1/q[i]
\r
290 q[j] = (m[i][j] + m[j][i]) * s;
\r
291 q[k] = (m[i][k] + m[k][i]) * s;
\r
292 q0 = (float) ((m[k][j] - m[j][k]) * s);
\r
293 q1 = (float) q[0]; // x
\r
294 q2 = (float) q[1]; // y
\r
295 q3 = (float) q[2]; // z
\r
302 * if qref is null, "fix" this quaternion
\r
303 * otherwise, return a quaternion that is CLOSEST to the given quaternion
\r
304 * that is, one that gives a positive dot product
\r
307 public void setRef(Quat qref) {
\r
308 if (qref == null) {
\r
309 mul(getFixFactor());
\r
312 if (dot(qref) >= 0)
\r
321 * returns a quaternion frame based on three points (center, x, and any point in xy plane)
\r
322 * or two vectors (vA, vB).
\r
324 * @param center (null for vA/vB option)
\r
327 * @return quaternion for frame
\r
329 public static final Quat getQuaternionFrame(P3 center, T3 x,
\r
331 V3 vA = V3.newV(x);
\r
332 V3 vB = V3.newV(xy);
\r
333 if (center != null) {
\r
337 return getQuaternionFrameV(vA, vB, null, false);
\r
341 * Create a quaternion based on a frame
\r
346 * @return quaternion
\r
348 public static final Quat getQuaternionFrameV(V3 vA, V3 vB,
\r
349 V3 vC, boolean yBased) {
\r
356 V3 vBprime = new V3();
\r
357 vBprime.cross(vC, vA);
\r
359 vBprime.normalize();
\r
362 mat.setColumnV(0, vA);
\r
363 mat.setColumnV(1, vBprime);
\r
364 mat.setColumnV(2, vC);
\r
368 * Verification tests using Quat4f and AngleAxis4f:
\r
370 System.out.println("quaternion frame matrix: " + mat);
\r
372 Point3f pt2 = new Point3f();
\r
373 mat.transform(Point3f.new3(1, 0, 0), pt2);
\r
374 System.out.println("vA=" + vA + " M(100)=" + pt2);
\r
375 mat.transform(Point3f.new3(0, 1, 0), pt2);
\r
376 System.out.println("vB'=" + vBprime + " M(010)=" + pt2);
\r
377 mat.transform(Point3f.new3(0, 0, 1), pt2);
\r
378 System.out.println("vC=" + vC + " M(001)=" + pt2);
\r
379 Quat4f q4 = new Quat4f();
\r
381 System.out.println("----");
\r
382 System.out.println("Quat4f: {" + q4.w + " " + q4.x + " " + q4.y + " " + q4.z + "}");
\r
383 System.out.println("Quat4f: 2xy + 2wz = m10: " + (2 * q4.x * q4.y + 2 * q4.w * q4.z) + " = " + mat.m10);
\r
387 Quat q = newM(mat);
\r
390 System.out.println("Quaternion mat from q \n" + q.getMatrix());
\r
391 System.out.println("Quaternion: " + q.getNormal() + " " + q.getTheta());
\r
392 AxisAngle4f a = new AxisAngle4f();
\r
394 Vector3f v = Vector3f.new3(a.x, a.y, a.z);
\r
396 System.out.println("angleAxis: " + v + " "+(a.angle/Math.PI * 180));
\r
402 public M3 getMatrix() {
\r
408 private void setMatrix() {
\r
410 // q0 = w, q1 = x, q2 = y, q3 = z
\r
411 mat.m00 = q0 * q0 + q1 * q1 - q2 * q2 - q3 * q3;
\r
412 mat.m01 = 2 * q1 * q2 - 2 * q0 * q3;
\r
413 mat.m02 = 2 * q1 * q3 + 2 * q0 * q2;
\r
414 mat.m10 = 2 * q1 * q2 + 2 * q0 * q3;
\r
415 mat.m11 = q0 * q0 - q1 * q1 + q2 * q2 - q3 * q3;
\r
416 mat.m12 = 2 * q2 * q3 - 2 * q0 * q1;
\r
417 mat.m20 = 2 * q1 * q3 - 2 * q0 * q2;
\r
418 mat.m21 = 2 * q2 * q3 + 2 * q0 * q1;
\r
419 mat.m22 = q0 * q0 - q1 * q1 - q2 * q2 + q3 * q3;
\r
422 public Quat add(float x) {
\r
423 // scalar theta addition (degrees)
\r
424 return newVA(getNormal(), getTheta() + x);
\r
427 public Quat mul(float x) {
\r
428 // scalar theta multiplication
\r
429 return (x == 1 ? new4(q1, q2, q3, q0) :
\r
430 newVA(getNormal(), getTheta() * x));
\r
433 public Quat mulQ(Quat p) {
\r
435 q0 * p.q1 + q1 * p.q0 + q2 * p.q3 - q3 * p.q2,
\r
436 q0 * p.q2 + q2 * p.q0 + q3 * p.q1 - q1 * p.q3,
\r
437 q0 * p.q3 + q3 * p.q0 + q1 * p.q2 - q2 * p.q1,
\r
438 q0 * p.q0 - q1 * p.q1 - q2 * p.q2 - q3 * p.q3);
\r
441 public Quat div(Quat p) {
\r
442 // unit quaternions assumed -- otherwise would scale by 1/p.dot(p)
\r
443 return mulQ(p.inv());
\r
446 public Quat divLeft(Quat p) {
\r
447 // unit quaternions assumed -- otherwise would scale by 1/p.dot(p)
\r
448 return this.inv().mulQ(p);
\r
451 public float dot(Quat q) {
\r
452 return this.q0 * q.q0 + this.q1 * q.q1 + this.q2 * q.q2 + this.q3 * q.q3;
\r
455 public Quat inv() {
\r
456 return new4(-q1, -q2, -q3, q0);
\r
459 public Quat negate() {
\r
460 return new4(-q1, -q2, -q3, -q0);
\r
468 * 2) q0 = 0 and q1 > 0
\r
470 * 3) q0 = 0 and q1 = 0 and q2 > 0
\r
472 * 4) q0 = 0 and q1 = 0 and q2 = 0 and q3 > 0
\r
478 private float getFixFactor() {
\r
480 q0 == 0 && (q1 < 0 || q1 == 0 && (q2 < 0 || q2 == 0 && q3 < 0)) ? -1 : 1);
\r
483 public V3 getVector(int i) {
\r
484 return getVectorScaled(i, 1f);
\r
487 public V3 getVectorScaled(int i, float scale) {
\r
489 scale *= getFixFactor();
\r
490 return V3.new3(q1 * scale, q2 * scale, q3 * scale);
\r
495 mat.getColumnV(i, v);
\r
503 * @return vector such that 0 <= angle <= 180
\r
505 public V3 getNormal() {
\r
506 V3 v = getRawNormal(this);
\r
507 v.scale(getFixFactor());
\r
511 private static V3 getRawNormal(Quat q) {
\r
512 V3 v = V3.new3(q.q1, q.q2, q.q3);
\r
513 if (v.length() == 0)
\r
514 return V3.new3(0, 0, 1);
\r
521 * @return 0 <= angle <= 180 in degrees
\r
523 public float getTheta() {
\r
524 return (float) (Math.acos(Math.abs(q0)) * 2 * 180 / Math.PI);
\r
527 public float getThetaRadians() {
\r
528 return (float) (Math.acos(Math.abs(q0)) * 2);
\r
534 * @return vector option closest to v0
\r
537 public V3 getNormalDirected(V3 v0) {
\r
538 V3 v = getNormal();
\r
539 if (v.x * v0.x + v.y * v0.y + v.z * v0.z < 0) {
\r
545 public V3 get3dProjection(V3 v3d) {
\r
546 v3d.set(q1, q2, q3);
\r
553 * @return fill in theta of axisAngle such that
\r
555 public P4 getThetaDirected(P4 axisAngle) {
\r
557 float theta = getTheta();
\r
558 V3 v = getNormal();
\r
559 if (axisAngle.x * q1 + axisAngle.y * q2 + axisAngle.z * q3 < 0) {
\r
563 axisAngle.set4(v.x, v.y, v.z, theta);
\r
569 * @param vector a vector, same as for getNormalDirected
\r
570 * @return return theta
\r
572 public float getThetaDirectedV(V3 vector) {
\r
574 float theta = getTheta();
\r
575 V3 v = getNormal();
\r
576 if (vector.x * q1 + vector.y * q2 + vector.z * q3 < 0) {
\r
584 * Quaternions are saved as {q1, q2, q3, q0}
\r
586 * While this may seem odd, it is so that for any point4 --
\r
587 * planes, axisangles, and quaternions -- we can use the
\r
588 * first three coordinates to determine the relavent axis
\r
589 * the fourth then gives us offset to {0,0,0} (plane),
\r
590 * rotation angle (axisangle), and cos(theta/2) (quaternion).
\r
591 * @return {x y z w} (unnormalized)
\r
593 public P4 toPoint4f() {
\r
594 return P4.new4(q1, q2, q3, q0); // x,y,z,w
\r
597 public A4 toAxisAngle4f() {
\r
598 double theta = 2 * Math.acos(Math.abs(q0));
\r
599 double sinTheta2 = Math.sin(theta/2);
\r
600 V3 v = getNormal();
\r
601 if (sinTheta2 < 0) {
\r
603 theta = Math.PI - theta;
\r
605 return A4.newVA(v, (float) theta);
\r
608 public T3 transform2(T3 pt, T3 ptNew) {
\r
611 mat.rotate2(pt, ptNew);
\r
615 public Quat leftDifference(Quat q2) {
\r
616 //dq = q.leftDifference(qnext);//q.inv().mul(qnext);
\r
617 Quat q2adjusted = (this.dot(q2) < 0 ? q2.negate() : q2);
\r
618 return inv().mulQ(q2adjusted);
\r
621 public Quat rightDifference(Quat q2) {
\r
622 //dq = qnext.rightDifference(q);//qnext.mul(q.inv());
\r
623 Quat q2adjusted = (this.dot(q2) < 0 ? q2.negate() : q2);
\r
624 return mulQ(q2adjusted.inv());
\r
629 * Java axisAngle / plane / Point4f format
\r
630 * all have the format {x y z w}
\r
631 * so we go with that here as well
\r
633 * @return "{q1 q2 q3 q0}"
\r
636 public String toString() {
\r
637 return "{" + q1 + " " + q2 + " " + q3 + " " + q0 + "}";
\r
644 * @param nMax > 0 --> limit to this number
\r
645 * @param isRelative
\r
647 * @return pairwise array of data1 / data2 or data1 \ data2
\r
649 public static Quat[] div(Quat[] data1, Quat[] data2, int nMax, boolean isRelative) {
\r
651 if (data1 == null || data2 == null || (n = Math.min(data1.length, data2.length)) == 0)
\r
653 if (nMax > 0 && n > nMax)
\r
655 Quat[] dqs = new Quat[n];
\r
656 for (int i = 0; i < n; i++) {
\r
657 if (data1[i] == null || data2[i] == null)
\r
659 dqs[i] = (isRelative ? data1[i].divLeft(data2[i]) : data1[i].div(data2[i]));
\r
664 public static Quat sphereMean(Quat[] data, float[] retStddev, float criterion) {
\r
665 // Samuel R. Buss, Jay P. Fillmore:
\r
666 // Spherical averages and applications to spherical splines and interpolation.
\r
667 // ACM Trans. Graph. 20(2): 95-126 (2001)
\r
668 if (data == null || data.length == 0)
\r
670 if (retStddev == null)
\r
671 retStddev = new float[1];
\r
672 if (data.length == 1) {
\r
674 return newQ(data[0]);
\r
676 float diff = Float.MAX_VALUE;
\r
677 float lastStddev = Float.MAX_VALUE;
\r
678 Quat qMean = simpleAverage(data);
\r
679 int maxIter = 100; // typically goes about 5 iterations
\r
681 while (diff > criterion && lastStddev != 0 && iter < maxIter) {
\r
682 qMean = newMean(data, qMean);
\r
683 retStddev[0] = stdDev(data, qMean);
\r
684 diff = Math.abs(retStddev[0] - lastStddev);
\r
685 lastStddev = retStddev[0];
\r
686 //Logger.info(++iter + " sphereMean " + qMean + " stddev=" + lastStddev + " diff=" + diff);
\r
692 * Just a starting point.
\r
693 * get average normal vector
\r
694 * scale normal by average projection of vectors onto it
\r
695 * create quaternion from this 3D projection
\r
698 * @return approximate average
\r
700 private static Quat simpleAverage(Quat[] ndata) {
\r
701 V3 mean = V3.new3(0, 0, 1);
\r
702 // using the directed normal ensures that the mean is
\r
703 // continually added to and never subtracted from
\r
704 V3 v = ndata[0].getNormal();
\r
706 for (int i = ndata.length; --i >= 0;)
\r
707 mean.add(ndata[i].getNormalDirected(mean));
\r
711 // the 3D projection of the quaternion is [sin(theta/2)]*n
\r
712 // so dotted with the normalized mean gets us an approximate average for sin(theta/2)
\r
713 for (int i = ndata.length; --i >= 0;)
\r
714 f += Math.abs(ndata[i].get3dProjection(v).dot(mean));
\r
716 mean.scale(f / ndata.length);
\r
717 // now convert f to the corresponding cosine instead of sine
\r
718 f = (float) Math.sqrt(1 - mean.lengthSquared());
\r
719 if (Float.isNaN(f))
\r
721 return newP4(P4.new4(mean.x, mean.y, mean.z, f));
\r
724 private static Quat newMean(Quat[] data, Quat mean) {
\r
725 /* quaternion derivatives nicely take care of producing the necessary
\r
726 * metric. Since dq gives us the normal with the smallest POSITIVE angle,
\r
727 * we just scale by that -- using degrees.
\r
728 * No special normalization is required.
\r
730 * The key is that the mean has been set up already, and dq.getTheta()
\r
731 * will always return a value between 0 and 180. True, for groupings
\r
732 * where dq swings wildly -- 178, 182, 178, for example -- there will
\r
733 * be problems, but the presumption here is that there is a REASONABLE
\r
734 * set of data. Clearly there are spherical data sets that simply cannot
\r
735 * be assigned a mean. (For example, where the three projected points
\r
736 * are equally distant on the sphere. We just can't worry about those
\r
737 * cases here. Rather, if there is any significance to the data,
\r
738 * there will be clusters of projected points, and the analysis will
\r
741 * Note that the hemisphere problem drops out because dq.getNormal() and
\r
742 * dq.getTheta() will never return (n, 182 degrees) but will
\r
743 * instead return (-n, 2 degrees). That's just what we want in that case.
\r
745 * Note that the projection in this case is to 3D -- a set of vectors
\r
746 * in space with lengths proportional to theta (not the sin(theta/2)
\r
747 * that is associated with a quaternion map).
\r
749 * This is officially an "exponential" or "hyperbolic" projection.
\r
755 //System.out.println("newMean mean " + mean);
\r
756 for (int i = data.length; --i >= 0;) {
\r
759 v = dq.getNormal();
\r
760 v.scale(dq.getTheta());
\r
763 sum.scale(1f/data.length);
\r
764 Quat dqMean = newVA(sum, sum.length());
\r
765 //System.out.println("newMean dqMean " + dqMean + " " + dqMean.getNormal() + " " + dqMean.getTheta());
\r
766 return dqMean.mulQ(mean);
\r
772 * @return standard deviation in units of degrees
\r
774 private static float stdDev(Quat[] data, Quat mean) {
\r
775 // the quaternion dot product gives q0 for dq (i.e. q / mean)
\r
776 // that is, cos(theta/2) for theta between them
\r
778 int n = data.length;
\r
779 for (int i = n; --i >= 0;) {
\r
780 float theta = data[i].div(mean).getTheta();
\r
781 sum2 += theta * theta;
\r
783 return (float) Math.sqrt(sum2 / n);
\r
786 public float[] getEulerZYZ() {
\r
787 // http://www.swarthmore.edu/NatSci/mzucker1/e27/diebel2006attitude.pdf
\r
789 if (q1 == 0 && q2 == 0) {
\r
790 float theta = getTheta();
\r
791 // pure Z rotation - ambiguous
\r
792 return new float[] { q3 < 0 ? -theta : theta , 0, 0 };
\r
794 rA = Math.atan2(2 * (q2 * q3 + q0 * q1), 2 * (-q1 * q3 + q0 * q2 ));
\r
795 rB = Math.acos(q3 * q3 - q2 * q2 - q1 * q1 + q0 * q0);
\r
796 rG = Math.atan2( 2 * (q2 * q3 - q0 * q1), 2 * (q0 * q2 + q1 * q3));
\r
797 return new float[] {(float) (rA / RAD_PER_DEG), (float) (rB / RAD_PER_DEG), (float) (rG / RAD_PER_DEG)};
\r
800 public float[] getEulerZXZ() {
\r
801 // NOT http://en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
\r
802 // http://www.swarthmore.edu/NatSci/mzucker1/e27/diebel2006attitude.pdf
\r
804 if (q1 == 0 && q2 == 0) {
\r
805 float theta = getTheta();
\r
806 // pure Z rotation - ambiguous
\r
807 return new float[] { q3 < 0 ? -theta : theta , 0, 0 };
\r
809 rA = Math.atan2(2 * (q1 * q3 - q0 * q2), 2 * (q0 * q1 + q2 * q3 ));
\r
810 rB = Math.acos(q3 * q3 - q2 * q2 - q1 * q1 + q0 * q0);
\r
811 rG = Math.atan2( 2 * (q1 * q3 + q0 * q2), 2 * (-q2 * q3 + q0 * q1));
\r
812 return new float[] {(float) (rA / RAD_PER_DEG), (float) (rB / RAD_PER_DEG), (float) (rG / RAD_PER_DEG)};
\r