aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim
diff options
context:
space:
mode:
authorJohn Cochran2012-01-03 11:38:38 -0600
committerJustin Clark-Casey (justincc)2012-01-06 21:08:54 +0000
commiteb9bf717264083ad76022dff6a2284ad9393ac38 (patch)
tree378d82e25d21439be3d548b9fad5117fbf9207a1 /OpenSim
parentImplement the FetchInventory2 capability. This accompanies the existing Fetc... (diff)
downloadopensim-SC-eb9bf717264083ad76022dff6a2284ad9393ac38.zip
opensim-SC-eb9bf717264083ad76022dff6a2284ad9393ac38.tar.gz
opensim-SC-eb9bf717264083ad76022dff6a2284ad9393ac38.tar.bz2
opensim-SC-eb9bf717264083ad76022dff6a2284ad9393ac38.tar.xz
Replaced llRot2Euler function.
The original function suffered from unexpected results due to rounding errors. An error of only 1 or 2 ulps would cause the code to not detect a singularity at Y rotation +/- PI/2 and take the non-singularity code path. The replacement code does not suffer from wildly inaccurate results at the +/- PI/2 singularity. The check in the code for the singularity isn't strictly needed, but gives more consistent results At the singularity, the X and Z rotations add. The if check simply forces the X rotation to be zero so the entirety of the X+Z rotation is carried by Z. Additionally, the test code has been updated to include test cases that caused the old code to fail. The test algorithm is also updated to perform a more meaningful test. The original code checked if the values against expected values. This could fail at +/- PI rotations since a rotation around an axis by PI causes the identical effect as a rotation by -PI. The new test code checks that the returned angles can be used to recreate a quaternion that causes the same rotation.
Diffstat (limited to '')
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs31
-rw-r--r--OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs97
2 files changed, 92 insertions, 36 deletions
diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index 443e7a5..d6316b2 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -468,26 +468,21 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
468 468
469 //Now we start getting into quaternions which means sin/cos, matrices and vectors. ckrinke 469 //Now we start getting into quaternions which means sin/cos, matrices and vectors. ckrinke
470 470
471 // Old implementation of llRot2Euler. Normalization not required as Atan2 function will 471 // Using algorithm based off http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/quat_2_euler_paper_ver2-1.pdf
472 // only return values >= -PI (-180 degrees) and <= PI (180 degrees). 472 // to avoid issues with singularity and rounding with Y rotation of +/- PI/2
473
474 public LSL_Vector llRot2Euler(LSL_Rotation r) 473 public LSL_Vector llRot2Euler(LSL_Rotation r)
475 { 474 {
476 m_host.AddScriptLPS(1); 475 LSL_Vector v = new LSL_Vector(0.0, 0.0, 1.0) * r; // Z axis unit vector unaffected by Z rotation component of r.
477 //This implementation is from http://lslwiki.net/lslwiki/wakka.php?wakka=LibraryRotationFunctions. ckrinke 476 double m = LSL_Vector.Mag(v); // Just in case v isn't normalized, need magnitude for Asin() operation later.
478 LSL_Rotation t = new LSL_Rotation(r.x * r.x, r.y * r.y, r.z * r.z, r.s * r.s); 477 if (m == 0.0) return new LSL_Vector();
479 double m = (t.x + t.y + t.z + t.s); 478 double x = Math.Atan2(-v.y, v.z);
480 if (m == 0) return new LSL_Vector(); 479 double sin = v.x / m;
481 double n = 2 * (r.y * r.s + r.x * r.z); 480 if (sin < -0.999999 || sin > 0.999999) x = 0.0; // Force X rotation to 0 at the singularities.
482 double p = m * m - n * n; 481 double y = Math.Asin(sin);
483 if (p > 0) 482 // Rotate X axis unit vector by r and unwind the X and Y rotations leaving only the Z rotation
484 return new LSL_Vector(Math.Atan2(2.0 * (r.x * r.s - r.y * r.z), (-t.x - t.y + t.z + t.s)), 483 v = new LSL_Vector(1.0, 0.0, 0.0) * ((r * new LSL_Rotation(Math.Sin(-x / 2.0), 0.0, 0.0, Math.Cos(-x / 2.0))) * new LSL_Rotation(0.0, Math.Sin(-y / 2.0), 0.0, Math.Cos(-y / 2.0)));
485 Math.Atan2(n, Math.Sqrt(p)), 484 double z = Math.Atan2(v.y, v.x);
486 Math.Atan2(2.0 * (r.z * r.s - r.x * r.y), (t.x - t.y - t.z + t.s))); 485 return new LSL_Vector(x, y, z);
487 else if (n > 0)
488 return new LSL_Vector(0.0, Math.PI * 0.5, Math.Atan2((r.z * r.s + r.x * r.y), 0.5 - t.x - t.z));
489 else
490 return new LSL_Vector(0.0, -Math.PI * 0.5, Math.Atan2((r.z * r.s + r.x * r.y), 0.5 - t.x - t.z));
491 } 486 }
492 487
493 /* From wiki: 488 /* From wiki:
diff --git a/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
index 0cbad41..7594691 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
@@ -142,30 +142,91 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
142 public void TestllRot2Euler() 142 public void TestllRot2Euler()
143 { 143 {
144 // 180, 90 and zero degree rotations. 144 // 180, 90 and zero degree rotations.
145 CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 0.0f, 0.0f, 0.0f), new LSL_Types.Vector3(Math.PI, 0.0f, 0.0f)); 145 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
146 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 1.0f, 0.0f, 0.0f), new LSL_Types.Vector3(Math.PI, 0.0f, Math.PI)); 146 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.707107f, 0.707107f));
147 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 1.0f, 0.0f), new LSL_Types.Vector3(0.0f, 0.0f, Math.PI)); 147 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 1.0f, 0.0f));
148 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.0f, 1.0f), new LSL_Types.Vector3(0.0f, 0.0f, 0.0f)); 148 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.707107f, -0.707107f));
149 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, 0.5f, 0.5f), new LSL_Types.Vector3(0, -Math.PI / 2.0f, Math.PI / 2.0f)); 149 CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, 0.0f, 0.0f, 0.707107f));
150 CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, 0.0f, 0.0f, -0.707107f), new LSL_Types.Vector3(Math.PI / 2.0f, 0.0f, 0.0f)); 150 CheckllRot2Euler(new LSL_Types.Quaternion(0.5f, -0.5f, 0.5f, 0.5f));
151 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.707107f, 0.0f));
152 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, 0.5f, -0.5f));
153 CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 0.0f, 0.0f, 0.0f));
154 CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, -0.707107f, 0.0f, 0.0f));
155 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -1.0f, 0.0f, 0.0f));
156 CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, -0.707107f, 0.0f, 0.0f));
157 CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, 0.0f, 0.0f, -0.707107f));
158 CheckllRot2Euler(new LSL_Types.Quaternion(0.5f, -0.5f, -0.5f, -0.5f));
159 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, -0.707107f, 0.0f));
160 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, -0.5f, 0.5f));
161 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.0f, 0.707107f));
162 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, 0.5f, 0.5f));
163 CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, 0.0f, 0.707107f, 0.0f));
164 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, 0.5f, 0.5f, -0.5f));
165 CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.0f, -0.707107f));
166 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, -0.5f, -0.5f));
167 CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, 0.0f, -0.707107f, 0.0f));
168 CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, 0.5f, -0.5f, 0.5f));
169
151 // A couple of messy rotations. 170 // A couple of messy rotations.
152 CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 5.651f, -3.1f, 67.023f), new LSL_Types.Vector3(0.037818f, 0.166447f, -0.095595f)); 171 CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 5.651f, -3.1f, 67.023f));
153 CheckllRot2Euler(new LSL_Types.Quaternion(0.719188f, -0.408934f, -0.363998f, -0.427841f), new LSL_Types.Vector3(-1.954769f, -0.174533f, 1.151917f)); 172 CheckllRot2Euler(new LSL_Types.Quaternion(0.719188f, -0.408934f, -0.363998f, -0.427841f));
173
174 // Some deliberately malicious rotations (intended on provoking singularity errors)
175 // The "f" suffexes are deliberately omitted.
176 CheckllRot2Euler(new LSL_Types.Quaternion(0.50001f, 0.50001f, 0.50001f, 0.50001f));
177 // More malice. The "f" suffixes are deliberately omitted.
178 CheckllRot2Euler(new LSL_Types.Quaternion(-0.701055, 0.092296, 0.701055, -0.092296));
179 CheckllRot2Euler(new LSL_Types.Quaternion(-0.183005, -0.683010, 0.183005, 0.683010));
180 CheckllRot2Euler(new LSL_Types.Quaternion(-0.430460, -0.560982, 0.430460, 0.560982));
181 CheckllRot2Euler(new LSL_Types.Quaternion(-0.701066, 0.092301, -0.701066, 0.092301));
182 CheckllRot2Euler(new LSL_Types.Quaternion(-0.183013, -0.683010, 0.183013, 0.683010));
183 CheckllRot2Euler(new LSL_Types.Quaternion(-0.183005, -0.683014, -0.183005, -0.683014));
184 CheckllRot2Euler(new LSL_Types.Quaternion(-0.353556, 0.612375, 0.353556, -0.612375));
185 CheckllRot2Euler(new LSL_Types.Quaternion(0.353554, -0.612385, -0.353554, 0.612385));
186 CheckllRot2Euler(new LSL_Types.Quaternion(-0.560989, 0.430450, 0.560989, -0.430450));
187 CheckllRot2Euler(new LSL_Types.Quaternion(-0.183013, 0.683009, -0.183013, 0.683009));
188 CheckllRot2Euler(new LSL_Types.Quaternion(0.430457, -0.560985, -0.430457, 0.560985));
189 CheckllRot2Euler(new LSL_Types.Quaternion(0.353552, 0.612360, -0.353552, -0.612360));
190 CheckllRot2Euler(new LSL_Types.Quaternion(-0.499991, 0.500003, 0.499991, -0.500003));
191 CheckllRot2Euler(new LSL_Types.Quaternion(-0.353555, -0.612385, -0.353555, -0.612385));
192 CheckllRot2Euler(new LSL_Types.Quaternion(0.701066, -0.092301, -0.701066, 0.092301));
193 CheckllRot2Euler(new LSL_Types.Quaternion(-0.499991, 0.500007, 0.499991, -0.500007));
194 CheckllRot2Euler(new LSL_Types.Quaternion(-0.683002, 0.183016, -0.683002, 0.183016));
195 CheckllRot2Euler(new LSL_Types.Quaternion(0.430458, 0.560982, 0.430458, 0.560982));
196 CheckllRot2Euler(new LSL_Types.Quaternion(0.499991, -0.500003, -0.499991, 0.500003));
197 CheckllRot2Euler(new LSL_Types.Quaternion(-0.183009, 0.683011, -0.183009, 0.683011));
198 CheckllRot2Euler(new LSL_Types.Quaternion(0.560975, -0.430457, 0.560975, -0.430457));
199 CheckllRot2Euler(new LSL_Types.Quaternion(0.701055, 0.092300, 0.701055, 0.092300));
200 CheckllRot2Euler(new LSL_Types.Quaternion(-0.560990, 0.430459, -0.560990, 0.430459));
201 CheckllRot2Euler(new LSL_Types.Quaternion(-0.092302, -0.701059, -0.092302, -0.701059));
154 } 202 }
155 203
156 private void CheckllRot2Euler(LSL_Types.Quaternion rot, LSL_Types.Vector3 eulerCheck) 204 // Testing Rot2Euler this way instead of comparing against expected angles because
205 // 1. There are several ways to get to the original Quaternion. For example a rotation
206 // of PI and -PI will give the same result. But PI and -PI aren't equal.
207 // 2. This method checks to see if the calculated angles from a quaternion can be used
208 // to create a new quaternion to produce the same rotation.
209 // However, can't compare the newly calculated quaternion against the original because
210 // once again, there are multiple quaternions that give the same result. For instance
211 // <X, Y, Z, S> == <-X, -Y, -Z, -S>. Additionally, the magnitude of S can be changed
212 // and will still result in the same rotation if the values for X, Y, Z are also changed
213 // to compensate.
214 // However, if two quaternions represent the same rotation, then multiplying the first
215 // quaternion by the conjugate of the second, will give a third quaternion representing
216 // a zero rotation. This can be tested for by looking at the X, Y, Z values which should
217 // be zero.
218 private void CheckllRot2Euler(LSL_Types.Quaternion rot)
157 { 219 {
158 // Call LSL function to convert quaternion rotaion to euler radians. 220 // Call LSL function to convert quaternion rotaion to euler radians.
159 LSL_Types.Vector3 eulerCalc = m_lslApi.llRot2Euler(rot); 221 LSL_Types.Vector3 eulerCalc = m_lslApi.llRot2Euler(rot);
160 // Check upper and lower bounds of x, y and z. 222 // Now use the euler radians to recalculate a new quaternion rotation
161 // This type of check is performed as opposed to comparing for equal numbers, in order to allow slight 223 LSL_Types.Quaternion newRot = m_lslApi.llEuler2Rot(eulerCalc);
162 // differences in accuracy. 224 // Multiple original quaternion by conjugate of quaternion calculated with angles.
163 Assert.Greater(eulerCalc.x, eulerCheck.x - ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler X lower bounds check fail"); 225 LSL_Types.Quaternion check = rot * new LSL_Types.Quaternion(-newRot.x, -newRot.y, -newRot.z, newRot.s);
164 Assert.Less(eulerCalc.x, eulerCheck.x + ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler X upper bounds check fail"); 226
165 Assert.Greater(eulerCalc.y, eulerCheck.y - ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler Y lower bounds check fail"); 227 Assert.AreEqual(0.0, check.x, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler X bounds check fail");
166 Assert.Less(eulerCalc.y, eulerCheck.y + ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler Y upper bounds check fail"); 228 Assert.AreEqual(0.0, check.y, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler Y bounds check fail");
167 Assert.Greater(eulerCalc.z, eulerCheck.z - ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler Z lower bounds check fail"); 229 Assert.AreEqual(0.0, check.z, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler Z bounds check fail");
168 Assert.Less(eulerCalc.z, eulerCheck.z + ANGLE_ACCURACY_IN_RADIANS, "TestllRot2Euler Z upper bounds check fail");
169 } 230 }
170 231
171 [Test] 232 [Test]