From 28961dd1cfc21a90510faa33af6d3c1c6f8bc0af Mon Sep 17 00:00:00 2001
From: Micheil Merlin
Date: Sun, 4 Sep 2011 12:21:29 -0500
Subject: llSetPrimitiveParams Prim type params precision errors

---
 .../Shared/Api/Implementation/LSL_Api.cs           | 76 ++++++++++++++++------
 .../ScriptEngine/Shared/Tests/LSL_ApiTest.cs       | 58 ++++++++++-------
 2 files changed, 90 insertions(+), 44 deletions(-)

diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
index 88e884d..cf8517d 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs
@@ -6570,6 +6570,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
 
         protected ObjectShapePacket.ObjectDataBlock SetPrimitiveBlockShapeParams(SceneObjectPart part, int holeshape, LSL_Vector cut, float hollow, LSL_Vector twist, byte profileshape, byte pathcurve)
         {
+            float tempFloat;                                    // Use in float expressions below to avoid byte cast precision issues.
             ObjectShapePacket.ObjectDataBlock shapeBlock = new ObjectShapePacket.ObjectDataBlock();
 
             if (holeshape != (int)ScriptBaseClass.PRIM_HOLE_DEFAULT &&
@@ -6651,8 +6652,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 twist.y = 1.0f;
             }
-            shapeBlock.PathTwistBegin = (sbyte)(100 * twist.x);
-            shapeBlock.PathTwist = (sbyte)(100 * twist.y);
+            // A fairly large precision error occurs for some calculations,
+            // if a float or double is directly cast to a byte or sbyte
+            // variable, in both .Net and Mono. In .Net, coding
+            // "(sbyte)(float)(some expression)" corrects the precision
+            // errors. But this does not work for Mono. This longer coding
+            // form of creating a tempoary float variable from the
+            // expression first, then casting that variable to a byte or
+            // sbyte, works for both .Net and Mono. These types of
+            // assignments occur in SetPrimtiveBlockShapeParams and
+            // SetPrimitiveShapeParams in support of llSetPrimitiveParams.
+            tempFloat = (float)(100.0d * twist.x);
+            shapeBlock.PathTwistBegin = (sbyte)tempFloat;
+            tempFloat = (float)(100.0d * twist.y);
+            shapeBlock.PathTwist = (sbyte)tempFloat;
 
             shapeBlock.ObjectLocalID = part.LocalId;
 
@@ -6663,6 +6676,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
         // Prim type box, cylinder and prism.
         protected void SetPrimitiveShapeParams(SceneObjectPart part, int holeshape, LSL_Vector cut, float hollow, LSL_Vector twist, LSL_Vector taper_b, LSL_Vector topshear, byte profileshape, byte pathcurve)
         {
+            float tempFloat;                                    // Use in float expressions below to avoid byte cast precision issues.
             ObjectShapePacket.ObjectDataBlock shapeBlock;
 
             shapeBlock = SetPrimitiveBlockShapeParams(part, holeshape, cut, hollow, twist, profileshape, pathcurve);
@@ -6683,8 +6697,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 taper_b.y = 2f;
             }
-            shapeBlock.PathScaleX = (byte)(100 * (2.0 - taper_b.x));
-            shapeBlock.PathScaleY = (byte)(100 * (2.0 - taper_b.y));
+            tempFloat = (float)(100.0d * (2.0d - taper_b.x));
+            shapeBlock.PathScaleX = (byte)tempFloat;
+            tempFloat = (float)(100.0d * (2.0d - taper_b.y));
+            shapeBlock.PathScaleY = (byte)tempFloat;
             if (topshear.x < -0.5f)
             {
                 topshear.x = -0.5f;
@@ -6701,8 +6717,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 topshear.y = 0.5f;
             }
-            shapeBlock.PathShearX = (byte)(100 * topshear.x);
-            shapeBlock.PathShearY = (byte)(100 * topshear.y);
+            tempFloat = (float)(100.0d * topshear.x);
+            shapeBlock.PathShearX = (byte)tempFloat;
+            tempFloat = (float)(100.0d * topshear.y);
+            shapeBlock.PathShearY = (byte)tempFloat;
 
             part.Shape.SculptEntry = false;
             part.UpdateShape(shapeBlock);
@@ -6752,6 +6770,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
         // Prim type torus, tube and ring.
         protected void SetPrimitiveShapeParams(SceneObjectPart part, int holeshape, LSL_Vector cut, float hollow, LSL_Vector twist, LSL_Vector holesize, LSL_Vector topshear, LSL_Vector profilecut, LSL_Vector taper_a, float revolutions, float radiusoffset, float skew, byte profileshape, byte pathcurve)
         {
+            float tempFloat;                                    // Use in float expressions below to avoid byte cast precision issues.
             ObjectShapePacket.ObjectDataBlock shapeBlock;
 
             shapeBlock = SetPrimitiveBlockShapeParams(part, holeshape, cut, hollow, twist, profileshape, pathcurve);
@@ -6776,8 +6795,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 holesize.y = 0.5f;
             }
-            shapeBlock.PathScaleX = (byte)(100 * (2 - holesize.x));
-            shapeBlock.PathScaleY = (byte)(100 * (2 - holesize.y));
+            tempFloat = (float)(100.0d * (2.0d - holesize.x));
+            shapeBlock.PathScaleX = (byte)tempFloat;
+            tempFloat = (float)(100.0d * (2.0d - holesize.y));
+            shapeBlock.PathScaleY = (byte)tempFloat;
             if (topshear.x < -0.5f)
             {
                 topshear.x = -0.5f;
@@ -6794,8 +6815,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 topshear.y = 0.5f;
             }
-            shapeBlock.PathShearX = (byte)(100 * topshear.x);
-            shapeBlock.PathShearY = (byte)(100 * topshear.y);
+            tempFloat = (float)(100.0d * topshear.x);
+            shapeBlock.PathShearX = (byte)tempFloat;
+            tempFloat = (float)(100.0d * topshear.y);
+            shapeBlock.PathShearY = (byte)tempFloat;
             if (profilecut.x < 0f)
             {
                 profilecut.x = 0f;
@@ -6839,8 +6862,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 taper_a.y = 1f;
             }
-            shapeBlock.PathTaperX = (sbyte)(100 * taper_a.x);
-            shapeBlock.PathTaperY = (sbyte)(100 * taper_a.y);
+            tempFloat = (float)(100.0d * taper_a.x);
+            shapeBlock.PathTaperX = (sbyte)tempFloat;
+            tempFloat = (float)(100.0d * taper_a.y);
+            shapeBlock.PathTaperY = (sbyte)tempFloat;
             if (revolutions < 1f)
             {
                 revolutions = 1f;
@@ -6849,7 +6874,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 revolutions = 4f;
             }
-            shapeBlock.PathRevolutions = (byte)(66.666667 * (revolutions - 1.0));
+            tempFloat = 66.66667f * (revolutions - 1.0f);
+            shapeBlock.PathRevolutions = (byte)tempFloat;
             // limits on radiusoffset depend on revolutions and hole size (how?) seems like the maximum range is 0 to 1
             if (radiusoffset < 0f)
             {
@@ -6859,7 +6885,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 radiusoffset = 1f;
             }
-            shapeBlock.PathRadiusOffset = (sbyte)(100 * radiusoffset);
+            tempFloat = 100.0f * radiusoffset;
+            shapeBlock.PathRadiusOffset = (sbyte)tempFloat;
             if (skew < -0.95f)
             {
                 skew = -0.95f;
@@ -6868,7 +6895,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
             {
                 skew = 0.95f;
             }
-            shapeBlock.PathSkew = (sbyte)(100 * skew);
+            tempFloat = 100.0f * skew;
+            shapeBlock.PathSkew = (sbyte)tempFloat;
 
             part.Shape.SculptEntry = false;
             part.UpdateShape(shapeBlock);
@@ -7681,10 +7709,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api
                                 res.Add(new LSL_Vector(Shape.PathTaperX / 100.0, Shape.PathTaperY / 100.0, 0));
 
                                 // float revolutions
-                                res.Add(new LSL_Float((Shape.PathRevolutions * 0.015) + 1.0)); // Slightly inaccurate, because an unsigned
-                                                                                               // byte is being used to represent the entire
-                                                                                               // range of floating-point values from 1.0
-                                                                                               // through 4.0 (which is how SL does it).
+                                res.Add(new LSL_Float(Math.Round(Shape.PathRevolutions * 0.015d, 2, MidpointRounding.AwayFromZero)) + 1.0d); 
+                                // Slightly inaccurate, because an unsigned byte is being used to represent
+                                // the entire range of floating-point values from 1.0 through 4.0 (which is how 
+                                // SL does it).
+                                //
+                                // Using these formulas to store and retrieve PathRevolutions, it is not 
+                                // possible to use all values between 1.00 and 4.00. For instance, you can't 
+                                // represent 1.10. You can represent 1.09 and 1.11, but not 1.10. So, if you
+                                // use llSetPrimitiveParams to set revolutions to 1.10 and then retreive them
+                                // with llGetPrimitiveParams, you'll retrieve 1.09. You can also see a similar 
+                                // behavior in the viewer as you cannot set 1.10. The viewer jumps to 1.11.
+                                // In SL, llSetPrimitveParams and llGetPrimitiveParams can set and get a value
+                                // such as 1.10. So, SL must store and retreive the actual user input rather
+                                // than only storing the encoded value.
 
                                 // float radiusoffset
                                 res.Add(new LSL_Float(Shape.PathRadiusOffset / 100.0));
diff --git a/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
index 8cd1e84..0cbad41 100644
--- a/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
+++ b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiTest.cs
@@ -49,7 +49,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
 
         private const double ANGLE_ACCURACY_IN_RADIANS = 1E-6;
         private const double VECTOR_COMPONENT_ACCURACY = 0.0000005d;
-        private const double FLOAT_ACCURACY = 0.00005d;
+        private const float FLOAT_ACCURACY = 0.00005f;
         private LSL_Api m_lslApi;
 
         [SetUp]
@@ -194,10 +194,10 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
                 ScriptBaseClass.PRIM_TYPE_SPHERE,           // Prim type
                 ScriptBaseClass.PRIM_HOLE_DEFAULT,          // Prim hole type
                 new LSL_Types.Vector3(0.0d, 0.075d, 0.0d),  // Prim cut
-                0.80d,                                      // Prim hollow
+                0.80f,                                      // Prim hollow
                 new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim twist
                 new LSL_Types.Vector3(0.32d, 0.76d, 0.0d),  // Prim dimple
-                0.80d);                                     // Prim hollow check
+                0.80f);                                     // Prim hollow check
 
             // Test a prism.
             CheckllSetPrimitiveParams(
@@ -206,11 +206,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
                 ScriptBaseClass.PRIM_TYPE_PRISM,            // Prim type
                 ScriptBaseClass.PRIM_HOLE_CIRCLE,           // Prim hole type
                 new LSL_Types.Vector3(0.0d, 1.0d, 0.0d),    // Prim cut
-                0.90d,                                      // Prim hollow
+                0.90f,                                      // Prim hollow
                 new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim twist
                 new LSL_Types.Vector3(2.0d, 1.0d, 0.0d),    // Prim taper 
                 new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim shear
-                0.90d);                                     // Prim hollow check
+                0.90f);                                     // Prim hollow check
 
             // Test a box.
             CheckllSetPrimitiveParams(
@@ -219,11 +219,11 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
                 ScriptBaseClass.PRIM_TYPE_BOX,              // Prim type
                 ScriptBaseClass.PRIM_HOLE_TRIANGLE,         // Prim hole type
                 new LSL_Types.Vector3(0.0d, 1.0d, 0.0d),    // Prim cut
-                0.95d,                                      // Prim hollow
+                0.95f,                                      // Prim hollow
                 new LSL_Types.Vector3(1.0d, 0.0d, 0.0d),    // Prim twist
                 new LSL_Types.Vector3(1.0d, 1.0d, 0.0d),    // Prim taper 
                 new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim shear
-                0.95d);                                     // Prim hollow check
+                0.95f);                                     // Prim hollow check
 
             // Test a tube.
             CheckllSetPrimitiveParams(
@@ -232,16 +232,20 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
                 ScriptBaseClass.PRIM_TYPE_TUBE,             // Prim type
                 ScriptBaseClass.PRIM_HOLE_SQUARE,           // Prim hole type
                 new LSL_Types.Vector3(0.0d, 1.0d, 0.0d),    // Prim cut
-                0.00d,                                      // Prim hollow
+                0.00f,                                      // Prim hollow
                 new LSL_Types.Vector3(1.0d, -1.0d, 0.0d),   // Prim twist
-                new LSL_Types.Vector3(1.0d, 0.5d, 0.0d),    // Prim hole size
-                new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim shear
+                new LSL_Types.Vector3(1.0d, 0.05d, 0.0d),   // Prim hole size
+                // Expression for y selected to test precision problems during byte
+                // cast in SetPrimitiveShapeParams.
+                new LSL_Types.Vector3(0.0d, 0.35d + 0.1d, 0.0d),    // Prim shear
                 new LSL_Types.Vector3(0.0d, 1.0d, 0.0d),    // Prim profile cut
-                new LSL_Types.Vector3(-1.0d, 1.0d, 0.0d),   // Prim taper
-                1.0d,                                       // Prim revolutions
-                1.0d,                                       // Prim radius
-                0.0d,                                       // Prim skew
-                0.00d);                                     // Prim hollow check
+                // Expression for y selected to test precision problems during sbyte
+                // cast in SetPrimitiveShapeParams.
+                new LSL_Types.Vector3(-1.0d, 0.70d + 0.1d + 0.1d, 0.0d),    // Prim taper
+                1.11f,                                      // Prim revolutions
+                0.88f,                                      // Prim radius
+                0.95f,                                      // Prim skew
+                0.00f);                                     // Prim hollow check
 
             // Test a prism.
             CheckllSetPrimitiveParams(
@@ -250,11 +254,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
                 ScriptBaseClass.PRIM_TYPE_PRISM,            // Prim type
                 ScriptBaseClass.PRIM_HOLE_SQUARE,           // Prim hole type
                 new LSL_Types.Vector3(0.0d, 1.0d, 0.0d),    // Prim cut
-                0.95d,                                      // Prim hollow
-                new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim twist
-                new LSL_Types.Vector3(2.0d, 1.0d, 0.0d),    // Prim taper 
+                0.95f,                                      // Prim hollow
+                // Expression for x selected to test precision problems during sbyte
+                // cast in SetPrimitiveShapeBlockParams.
+                new LSL_Types.Vector3(0.7d + 0.2d, 0.0d, 0.0d),     // Prim twist
+                // Expression for y selected to test precision problems during sbyte
+                // cast in SetPrimitiveShapeParams.
+                new LSL_Types.Vector3(2.0d, (1.3d + 0.1d), 0.0d),   // Prim taper 
                 new LSL_Types.Vector3(0.0d, 0.0d, 0.0d),    // Prim shear
-                0.70d);                                     // Prim hollow check
+                0.70f);                                     // Prim hollow check
 
             // Test a sculpted prim.
             CheckllSetPrimitiveParams(
@@ -268,8 +276,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
         // Set prim params for a box, cylinder or prism and check results.
         public void CheckllSetPrimitiveParams(string primTest,
             LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut,
-            double primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primTaper, LSL_Types.Vector3 primShear,
-            double primHollowCheck)
+            float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primTaper, LSL_Types.Vector3 primShear,
+            float primHollowCheck)
         {
             // Set the prim params.
             m_lslApi.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize,
@@ -297,7 +305,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
         // Set prim params for a sphere and check results.
         public void CheckllSetPrimitiveParams(string primTest,
             LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut,
-            double primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primDimple, double primHollowCheck)
+            float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primDimple, float primHollowCheck)
         {
             // Set the prim params.
             m_lslApi.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize,
@@ -324,9 +332,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
         // Set prim params for a torus, tube or ring and check results.
         public void CheckllSetPrimitiveParams(string primTest,
             LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut,
-            double primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primHoleSize,
+            float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primHoleSize,
             LSL_Types.Vector3 primShear, LSL_Types.Vector3 primProfCut, LSL_Types.Vector3 primTaper,
-            double primRev, double primRadius, double primSkew, double primHollowCheck)
+            float primRev, float primRadius, float primSkew, float primHollowCheck)
         {
             // Set the prim params.
             m_lslApi.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize,
@@ -353,7 +361,7 @@ namespace OpenSim.Region.ScriptEngine.Shared.Tests
             CheckllSetPrimitiveParamsVector(primProfCut, m_lslApi.llList2Vector(primParams, 8), primTest + " prim profile cut");
             CheckllSetPrimitiveParamsVector(primTaper, m_lslApi.llList2Vector(primParams, 9), primTest + " prim taper");
             Assert.AreEqual(primRev, m_lslApi.llList2Float(primParams, 10), FLOAT_ACCURACY,
-                "TestllSetPrimitiveParams " + primTest + " prim revolution fail");
+                "TestllSetPrimitiveParams " + primTest + " prim revolutions fail");
             Assert.AreEqual(primRadius, m_lslApi.llList2Float(primParams, 11), FLOAT_ACCURACY,
                 "TestllSetPrimitiveParams " + primTest + " prim radius fail");
             Assert.AreEqual(primSkew, m_lslApi.llList2Float(primParams, 12), FLOAT_ACCURACY,
-- 
cgit v1.1