diff options
author | John Cochran | 2012-01-03 11:38:38 -0600 |
---|---|---|
committer | Justin Clark-Casey (justincc) | 2012-01-06 21:08:54 +0000 |
commit | eb9bf717264083ad76022dff6a2284ad9393ac38 (patch) | |
tree | 378d82e25d21439be3d548b9fad5117fbf9207a1 /OpenSim/Region/ScriptEngine/Shared | |
parent | Implement the FetchInventory2 capability. This accompanies the existing Fetc... (diff) | |
download | opensim-SC_OLD-eb9bf717264083ad76022dff6a2284ad9393ac38.zip opensim-SC_OLD-eb9bf717264083ad76022dff6a2284ad9393ac38.tar.gz opensim-SC_OLD-eb9bf717264083ad76022dff6a2284ad9393ac38.tar.bz2 opensim-SC_OLD-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 'OpenSim/Region/ScriptEngine/Shared')
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs | 31 | ||||
-rw-r--r-- | OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs | 97 |
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] |