예제 #1
0
 public SimulatedTransform(SCNVector3 position, SCNQuaternion orientation, SCNVector3 velocity, bool dynamic)
 {
     this.Position    = position;
     this.Orientation = orientation;
     this.Velocity    = velocity;
     this.Dynamic     = dynamic;
 }
예제 #2
0
        public static SCNMatrix4 ToMatrix4(this SCNQuaternion quaternion)
        {
            quaternion.Normalize();

            nfloat x = quaternion.X;
            nfloat y = quaternion.Y;
            nfloat z = quaternion.Z;
            nfloat w = quaternion.W;

            nfloat doubleX = x + x;
            nfloat doubleY = y + y;
            nfloat doubleZ = z + z;
            nfloat doubleW = w + w;

            return(new SCNMatrix4(
                       1.0f - doubleY * y - doubleZ * z,
                       doubleX * y + doubleW * z,
                       doubleX * z - doubleW * y,
                       0.0f,
                       doubleX * y - doubleW * z,
                       1.0f - doubleX * x - doubleZ * z,
                       doubleY * z + doubleW * x,
                       0.0f,
                       doubleX * z + doubleW * y,
                       doubleY * z - doubleW * x,
                       1.0f - doubleX * x - doubleY * y,
                       0.0f,
                       0.0f,
                       0.0f,
                       0.0f,
                       1.0f
                       ));
        }
        /// <summary>
        /// Inch geometry back to original offset from rigid body
        /// </summary>
        private void UpdateSmooth(double deltaTime)
        {
            //  allow some motion up to a maximum offset
            var posDelta = this.parentOffPos - this.sourceOffPos;

            if (posDelta.Length > MaxCorrection)
            {
                posDelta = MaxCorrection * SCNVector3.Normalize(posDelta);
            }

            // lerp pos
            var newPos = this.sourceOffPos + posDelta;

            this.geometryNode.Position = newPos;

            // cap the max rotation that can show through
            var quatDelta = this.parentOffRot.Divide(this.sourceOffRot);

            quatDelta.ToAxisAngle(out SCNVector3 _, out float angle);

            if (angle > MaxRotation)
            {
                this.geometryNode.Orientation = SCNQuaternion.Slerp(this.sourceOffRot, this.parentOffRot, MaxRotation / angle);
            }
            else
            {
                this.geometryNode.Orientation = this.parentOffRot;
            }
        }
예제 #4
0
        public void Apply(PhysicsNodeData nodeData, bool isHalfway)
        {
            if (this.PhysicsNode != null)
            {
                // if we're not alive, avoid applying physics updates.
                // this will allow objects on clients to get culled properly
                if (this.IsAlive)
                {
                    if (isHalfway)
                    {
                        this.PhysicsNode.WorldPosition = (nodeData.Position + this.PhysicsNode.WorldPosition) * 0.5f;
                        this.PhysicsNode.Orientation   = SCNQuaternion.Slerp(this.PhysicsNode.Orientation, nodeData.Orientation, 0.5f);
                    }
                    else
                    {
                        this.PhysicsNode.WorldPosition = nodeData.Position;
                        this.PhysicsNode.Orientation   = nodeData.Orientation;
                    }

                    if (this.PhysicsNode.PhysicsBody != null)
                    {
                        this.PhysicsNode.PhysicsBody.ResetTransform();
                        this.PhysicsNode.PhysicsBody.Velocity        = nodeData.Velocity;
                        this.PhysicsNode.PhysicsBody.AngularVelocity = nodeData.AngularVelocity;
                    }
                }
            }
        }
        public override void Update(double deltaTimeInSeconds)
        {
            this.currentTime += deltaTimeInSeconds;
            this.CalcCurrentFrameIndex();

            var alpha = (float)((this.currentTime - this.wayPoints[currentFrame].Time) /
                                (this.wayPoints[currentFrame + 1].Time - this.wayPoints[currentFrame].Time));

            alpha = DigitExtensions.Clamp(alpha, 0f, 1f);

            var curPos  = this.wayPoints[currentFrame].Pos;
            var curTan  = this.wayPoints[currentFrame].Tangent;
            var curRot  = this.wayPoints[currentFrame].Rot;
            var nextPos = this.wayPoints[currentFrame + 1].Pos;
            var nextTan = this.wayPoints[currentFrame + 1].Tangent;
            var nextRot = this.wayPoints[currentFrame + 1].Rot;


            var newPosX = this.HermiteCurve(curPos.X, nextPos.X, curTan.X, nextTan.X, alpha);
            var newPosY = this.HermiteCurve(curPos.Y, nextPos.Y, curTan.Y, nextTan.Y, alpha);
            var newPosZ = this.HermiteCurve(curPos.Z, nextPos.Z, curTan.Z, nextTan.Z, alpha);
            var newQuat = SCNQuaternion.Slerp(curRot, nextRot, alpha);

            node.WorldPosition    = new SCNVector3(newPosX, newPosY, newPosZ);
            node.WorldOrientation = newQuat;

            // update child rigid bodies to percolate into physics
            if (this.Entity is GameObject entity)
            {
                if (entity.PhysicsNode?.PhysicsBody != null)
                {
                    entity.PhysicsNode.PhysicsBody.ResetTransform();
                }
            }
        }
        // we can make this call when the physics changes to smooth it
        // make sure its called BEFORE the physics value is changed in the rigid body
        // it works by separating the actual geometry slightly from the physics to correct for the visual pop when position is changed
        private void CorrectPhysics(SCNNode node, SCNVector3 pos, SCNQuaternion rot)
        {
            // find old value
            var oldTransform = this.geometryNode.WorldTransform;

            // change position of object
            node.Position    = pos;
            node.Orientation = rot;

            this.physicsNode.PhysicsBody.ResetTransform();

            // restore offset
            if (node != this.geometryNode)
            {
                this.geometryNode.WorldTransform = oldTransform;
                this.sourceOffPos = this.geometryNode.Position;
                this.sourceOffRot = geometryNode.Orientation;
            }
            else
            {
                this.sourceOffPos = parentOffPos;
                this.sourceOffRot = parentOffRot;
            }

            // cap the maximum deltas we allow in rotation and position space
            this.UpdateSmooth(1d / 60d);
        }
 public Waypoint(SCNVector3 pos, SCNVector3 tangent, SCNQuaternion rot, double time)
 {
     this.Pos     = pos;
     this.Tangent = tangent;
     this.Rot     = rot;
     this.Time    = time;
 }
예제 #8
0
        public override void RotateWithEvent(NSEvent theEvent)
        {
            base.RotateWithEvent(theEvent);
            var r = SCNQuaternion.FromAxisAngle(new SCNVector3(0, 0, 1), -theEvent.Rotation / 180);

            r.Normalize();
            Trackball.Rotate(r);
        }
        public GamePhysicsSmoothComponent(SCNNode physicsNode, SCNNode geometryNode) : base()
        {
            this.physicsNode  = physicsNode;
            this.geometryNode = geometryNode;

            // get initial offset
            this.parentOffPos = this.geometryNode.Position;
            this.parentOffRot = this.geometryNode.Orientation;
        }
예제 #10
0
        public static SCNQuaternion CreateQuaternion(SCNVector3 v1, SCNVector3 v2)
        {
            var a      = SCNVector3.Cross(v1, v2);
            var w      = (float)Math.Sqrt(v1.LengthSquared * v2.LengthSquared) + SCNVector3.Dot(v1, v2);
            var result = new SCNQuaternion(a.X, a.Y, a.Z, w);

            result.Normalize();

            return(result);
        }
예제 #11
0
        /// <summary>
        /// Computes the tangent position of the rope based on a given fixture
        /// </summary>
        private SCNVector3 TangentPosition(SCNVector3 fixture)
        {
            var r     = this.ballRadius;
            var d     = fixture - this.ballPosition;
            var alpha = (float)Math.Acos(r / d.Length);

            d = this.ballRadius * SCNVector3.Normalize(d);
            var rot       = SCNQuaternion.FromAxisAngle(this.UpVector, fixture == this.FixturePositionL ? -alpha : alpha);
            var d_rotated = rot.Act(d);

            return(d_rotated + this.ballPosition);
        }
예제 #12
0
        public SlingShotSimulation(SCNNode rootNode, int count, float segmentRadius) : base()
        {
            this.InitializeHelpers();

            for (var i = 0; i < count; i++)
            {
                var isStatic   = (i < this.BoneInset) || (i >= count - this.BoneInset);
                var quaternion = SCNQuaternion.FromAxisAngle(SCNVector3.UnitX, 0);

                var transform = new SimulatedTransform(SCNVector3.Zero, quaternion, SCNVector3.Zero, !isStatic);
                this.simulatedTransforms.Add(transform);
            }
        }
예제 #13
0
        public void Quaternion()
        {
            var id = SCNQuaternion.Identity;

#if !XAMCORE_2_0
            var q = new SCNQuaternion();
            // that was possible in classic - but will now trigger a CS0198
            SCNQuaternion.Identity = q;
#else
            id.W = 2;
#endif
            Assert.That(SCNQuaternion.Identity, Is.Not.EqualTo(id), "Identity");
        }
예제 #14
0
        public static SCNVector3 Act(this SCNQuaternion self, SCNVector3 vector)
        {
            // Calculate the resulting vector using the Hamilton product
            // P' = RPR'
            // P  = [0, p1, p2, p2] < -- vector
            // R  = [w, x, y, z] < -- rotation
            // R' = [w, -x, -y, -z]

            var p  = new SCNQuaternion(vector, 0f);
            var r  = self;
            var rt = r;

            rt.Conjugate();

            return(SCNQuaternion.Multiply(SCNQuaternion.Multiply(r, p), rt).Xyz);
        }
예제 #15
0
        private void Setup(NVector3 extent)
        {
            // Translate and rotate line nodes to the correct transform
            var halfX = extent.X / 2;
            var halfY = extent.Y / 2;
            var halfZ = extent.Z / 2;

            // Two helper functions to isolate the allocations and casts
            Action <SCNNode, float, float, float> xlat = (node, extentX, extentY, extentZ) => node.LocalTranslate(new SCNVector3(extentX, extentY, extentZ));

            Action <SCNNode, float, float, float, Axis> xlatAndRot = (node, extentX, extentY, extentZ, axis) =>
            {
                xlat(node, extentX, extentY, extentZ);
                node.LocalRotate(SCNQuaternion.FromAxisAngle(axis.Normal().ToSCNVector3(), (float)-Math.PI / 2));
            };

            var halfWidth  = extent.X / 2;
            var halfHeight = extent.Y / 2;
            var halfDepth  = extent.Z / 2;

            xlatAndRot(lineNodes[0], 0, -halfHeight, -halfDepth, Axis.Z);
            xlatAndRot(lineNodes[1], -halfWidth, -halfHeight, 0, Axis.X);
            xlatAndRot(lineNodes[2], 0, -halfHeight, halfDepth, Axis.Z);
            xlatAndRot(lineNodes[3], halfWidth, -halfHeight, 0, Axis.X);
            xlat(lineNodes[4], -extent.X, 0, -halfDepth);
            xlat(lineNodes[5], -halfWidth, 0, halfDepth);
            xlat(lineNodes[6], halfWidth, 0, -halfDepth);
            xlat(lineNodes[7], halfWidth, 0, halfDepth);
            xlatAndRot(lineNodes[8], 0, halfHeight, -halfDepth, Axis.Z);
            xlatAndRot(lineNodes[9], -halfWidth, halfHeight, 0, Axis.X);
            xlatAndRot(lineNodes[10], 0, halfHeight, halfDepth, Axis.Z);
            xlatAndRot(lineNodes[11], halfWidth, halfHeight, 0, Axis.X);

            // Assign geometries
            lineNodes[0].Geometry  = Cylinder(extent.X);
            lineNodes[1].Geometry  = Cylinder(extent.Z);
            lineNodes[2].Geometry  = Cylinder(extent.X);
            lineNodes[3].Geometry  = Cylinder(extent.Z);
            lineNodes[4].Geometry  = Cylinder(extent.Y);
            lineNodes[5].Geometry  = Cylinder(extent.Y);
            lineNodes[6].Geometry  = Cylinder(extent.Y);
            lineNodes[7].Geometry  = Cylinder(extent.Y);
            lineNodes[8].Geometry  = Cylinder(extent.X);
            lineNodes[9].Geometry  = Cylinder(extent.Z);
            lineNodes[10].Geometry = Cylinder(extent.X);
            lineNodes[11].Geometry = Cylinder(extent.Z);
        }
예제 #16
0
        SCNVector4 GetDirectionFromPosition(SCNVector3 currentPosition)
        {
            SCNVector3    target = LocationAlongPath(TimeAlongPath - 0.05f);
            SCNMatrix4    lookAt = SCNMatrix4.LookAt(currentPosition, target, new SCNVector3(0f, 1f, 0f));
            SCNQuaternion q      = lookAt.ToQuaternion();

            nfloat angle = 0;

            q.ToAxisAngle(out target, out angle);

            if (PlayerCharacter.PlayerWalkDirection == WalkDirection.Left)
            {
                angle -= (nfloat)Math.PI;
            }

            return(new SCNVector4(0f, 1f, 0f, angle));
        }
예제 #17
0
        public static void SetTransform(this GKAgent2D agent, SCNMatrix4 newTransform)
        {
            var quatf = new SCNQuaternion(newTransform.Column3.Xyz, newTransform.Column3.W);

            SCNVector3 axis;

#if !__OSX__
            float angle;
            quatf.ToAxisAngle(out axis, out angle);
            agent.Rotation = -(angle + ((float)Math.PI / 2f));
            agent.Position = new Vector2(newTransform.M41, newTransform.M43);
#else
            nfloat angle;
            quatf.ToAxisAngle(out axis, out angle);
            agent.Rotation = -((float)angle + ((float)Math.PI / 2f));
            agent.Position = new Vector2((float)newTransform.M41, (float)newTransform.M43);
#endif
        }
예제 #18
0
        public void CreateFlagSimulationFromNode(SCNNode node)
        {
            var meshData  = new ClothSimMetalNode(this.device, Width, Height);
            var clothNode = SCNNode.FromGeometry(meshData.Geometry);

            var flag = node.FindChildNode("flagStaticWave", true);

            if (flag != null)
            {
                var boundingBoxMax = SCNVector3.Zero;
                var boundingBoxMin = SCNVector3.Zero;
                flag.GetBoundingBox(ref boundingBoxMin, ref boundingBoxMax);
                var existingFlagBV = boundingBoxMax - boundingBoxMin;

                var rescaleToMatchSizeMatrix = SCNMatrix4.Scale(existingFlagBV.X / (float)Width);

                var rotation       = SCNQuaternion.FromAxisAngle(SCNVector3.UnitX, (float)Math.PI / 2f);
                var localTransform = rescaleToMatchSizeMatrix * SCNMatrix4.Rotate(rotation.ToQuaternion());

                localTransform.Transpose();
                var currentTransform = SCNMatrix4.Transpose(flag.Transform);
                var newTransform     = currentTransform * localTransform;

                clothNode.Transform = SCNMatrix4.Transpose(newTransform);// flag.Transform * localTransform;
                if (clothNode.Geometry != null)
                {
                    clothNode.Geometry.FirstMaterial = flag.Geometry?.FirstMaterial;
                    if (clothNode.Geometry.FirstMaterial != null)
                    {
                        clothNode.Geometry.FirstMaterial.DoubleSided = true;
                    }
                }

                flag.ParentNode.ReplaceChildNode(flag, clothNode);
                clothNode.Geometry.SetupPaintColorMask("flag_flagA");
                clothNode.SetPaintColors();
                clothNode.FixNormalMaps();

                this.clothData.Add(new ClothData(clothNode, meshData));
            }
        }
예제 #19
0
        public static SCNMatrix4 Interpolate(SCNMatrix4 scnm0, SCNMatrix4 scnmf, nfloat factor)
        {
            SCNVector4 p0 = scnm0.Row3;
            SCNVector4 pf = scnmf.Row3;

            var q0 = scnm0.ToQuaternion();
            var qf = scnmf.ToQuaternion();

            SCNVector4    pTmp = Lerp(p0, pf, factor);
            SCNQuaternion qTmp = SCNQuaternion.Slerp(q0, qf, (float)factor);
            SCNMatrix4    rTmp = qTmp.ToMatrix4();

            SCNMatrix4 transform = new SCNMatrix4(
                rTmp.M11, rTmp.M12, rTmp.M13, 0.0f,
                rTmp.M21, rTmp.M22, rTmp.M23, 0.0f,
                rTmp.M31, rTmp.M32, rTmp.M33, 0.0f,
                pTmp.X, pTmp.Y, pTmp.Z, 1.0f
                );

            return(transform);
        }
예제 #20
0
 public static SCNVector4 GetVector(this SCNQuaternion self)
 {
     return(new SCNVector4(self.Xyz, self.W));
 }
예제 #21
0
 public static OpenTK.Quaternion ToQuaternion(this SCNQuaternion self)
 {
     return(new OpenTK.Quaternion(self.X, self.Y, self.Z, self.W));
 }
예제 #22
0
 public static SCNQuaternion Divide(this SCNQuaternion left, SCNQuaternion right)
 {
     return(SCNQuaternion.Multiply(left, SCNQuaternion.Invert(right)));
 }
예제 #23
0
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            // Perform any additional setup after loading the view, typically from a nib.
            var scene = new SCNScene();

            var rnd = new Random();

            Func <int, int, bool, float> random = (min, max, clamp) => {
                float num = (float)((double)rnd.Next(min, max) * rnd.NextDouble());
                if (!clamp)
                {
                    return(num);
                }
                else if (num < 1.0f)
                {
                    return(1.0f);
                }
                else
                {
                    return(num);
                }
            };

            Enumerable.Range(0, 200).Select <int, int> ((i) => Building(
                                                            random(2, 5, true),
                                                            random(2, 5, true),
                                                            random(2, 10, true),
                                                            random(-20, 20, false),
                                                            random(-20, 20, false),
                                                            scene,
                                                            rnd
                                                            )).ToArray();

            //Lights!
            var lightNode = new SCNNode()
            {
                Light    = new SCNLight(),
                Position = new SCNVector3(30.0F, 20.0F, 60.0F)
            };

            lightNode.Light.LightType = SCNLightType.Omni;
            scene.RootNode.AddChildNode(lightNode);

            var ambientLightNode = new SCNNode()
            {
                Light = new SCNLight()
            };

            ambientLightNode.Light.LightType = SCNLightType.Ambient;
            ambientLightNode.Light.Color     = UIColor.DarkGray;
            scene.RootNode.AddChildNode(ambientLightNode);

            //Camera!
            var cameraNode = new SCNNode()
            {
                Camera = new SCNCamera()
            };

            scene.RootNode.AddChildNode(cameraNode);
            cameraNode.Position = new SCNVector3(0.0F, 10.0F, 20.0F);

            var targetNode = new SCNNode()
            {
                Position = new SCNVector3(00.0F, 1.5F, 0.0F)
            };

            scene.RootNode.AddChildNode(targetNode);

            var lc = SCNLookAtConstraint.Create(targetNode);

            cameraNode.Constraints = new[] { lc };

            var scnView = new SCNView(UIScreen.MainScreen.Bounds)
            {
                Scene = scene,
                AllowsCameraControl = true,
                ShowsStatistics     = true,
                BackgroundColor     = UIColor.FromRGB(52, 152, 219)
            };

            var floorNode = new SCNNode {
                Geometry = new SCNPlane {
                    Height = 40.0F,
                    Width  = 40.0F
                },
                Position = SCNVector3.Zero
            };

            var pi2 = Math.PI / 2.0;

            floorNode.Orientation = SCNQuaternion.FromAxisAngle(SCNVector3.UnitX, (float)(0.0 - pi2));

            scene.RootNode.AddChildNode(floorNode);

            var material = new SCNMaterial();

            material.Diffuse.Contents            = UIImage.FromFile("Content/road.jpg");
            material.Diffuse.ContentsTransform   = SCNMatrix4.Scale(new SCNVector3(10.0f, 10.0f, 1.0f));
            material.Diffuse.MinificationFilter  = SCNFilterMode.Linear;
            material.Diffuse.MagnificationFilter = SCNFilterMode.Linear;
            material.Diffuse.MipFilter           = SCNFilterMode.Linear;
            material.Diffuse.WrapS     = SCNWrapMode.Repeat;
            material.Diffuse.WrapT     = SCNWrapMode.Repeat;
            material.Specular.Contents = UIColor.Gray;

            floorNode.Geometry.FirstMaterial = material;

            this.View = scnView;
        }
예제 #24
0
        internal BoundingBoxSide(PositionName position, NVector3 extent, UIColor color = null) : base()
        {
            if (color == null)
            {
                color = Utilities.AppYellow;
            }
            Tiles      = new List <Tile>();
            this.color = color;
            this.face  = position;

            // inline Swift setup() and setupExtensions() functions
            size = Size(extent);

            var   yAxis       = Axis.Y.Normal().ToSCNVector3();
            var   xAxis       = Axis.X.Normal().ToSCNVector3();
            var   zAxis       = Axis.Z.Normal().ToSCNVector3();
            float halfTurn    = (float)Math.PI;
            float quarterTurn = (float)Math.PI / 2;

            switch (face)
            {
            case PositionName.Front:
                LocalTranslate(new SCNVector3(0, 0, extent.Z / 2));
                break;

            case PositionName.Back:
                LocalTranslate(new SCNVector3(0, 0, -extent.Z / 2));
                LocalRotate(SCNQuaternion.FromAxisAngle(yAxis, halfTurn));
                break;

            case PositionName.Left:
                LocalTranslate(new SCNVector3(-extent.X / 2, 0, 0));
                LocalRotate(SCNQuaternion.FromAxisAngle(yAxis, -quarterTurn));
                break;

            case PositionName.Right:
                LocalTranslate(new SCNVector3(extent.X / 2, 0, 0));
                LocalRotate(SCNQuaternion.FromAxisAngle(yAxis, quarterTurn));
                break;

            case PositionName.Bottom:
                LocalTranslate(new SCNVector3(0, -extent.Y / 2, 0));
                LocalRotate(SCNQuaternion.FromAxisAngle(xAxis, halfTurn));
                break;

            case PositionName.Top:
                LocalTranslate(new SCNVector3(0, extent.Y / 2, 0));
                LocalRotate(SCNQuaternion.FromAxisAngle(xAxis, -quarterTurn));
                break;
            }

            for (int index = 0; index < 12; index++)
            {
                var line = new SCNNode();
                line.Geometry = Cylinder(lineThickness, extensionLength);
                if (index < 4)
                {
                    xAxisExtLines.Add(line);
                    line.LocalRotate(SCNQuaternion.FromAxisAngle(zAxis, -quarterTurn));
                    if (index == 2 || index == 3)
                    {
                        line.LocalRotate(SCNQuaternion.FromAxisAngle(xAxis, halfTurn));
                    }
                    xAxisExtNode.AddChildNode(line);
                }
                else if (index < 8)
                {
                    yAxisExtLines.Add(line);
                    if (index == 5 || index == 7)
                    {
                        line.LocalRotate(SCNQuaternion.FromAxisAngle(xAxis, halfTurn));
                    }
                    yAxisExtNode.AddChildNode(line);
                }
                else
                {
                    zAxisExtLines.Add(line);
                    line.LocalRotate(SCNQuaternion.FromAxisAngle(xAxis, -quarterTurn));
                    zAxisExtNode.AddChildNode(line);
                }
            }

            UpdateExtensions();
            HideXAxisExtensions();
            HideYAxisExtensions();
            HideZAxisExtensions();

            AddChildNode(xAxisExtNode);
            AddChildNode(yAxisExtNode);
            AddChildNode(zAxisExtNode);
        }
예제 #25
0
        public SlingShotPose ComputeInputPose()
        {
            // note the -1 here differs from other usage
            var data = new SlingShotPose {
                UpVector = -this.UpVector                            /* negated because the strap Y-axis points down */
            };

            var startBend            = this.CurrentLengthL / this.CurrentTotalLength;
            var endBend              = 1f - this.CurrentLengthR / this.CurrentTotalLength;
            var leatherOnStraights   = this.OriginalLeatherLength - this.CurrentLengthOnBall;
            var segmentAStart        = 0f;
            var segmentAEnd          = this.CurrentLengthL - leatherOnStraights * 0.5f;
            var segmentCStart        = segmentAEnd + this.OriginalLeatherLength;
            var segmentCEnd          = this.CurrentTotalLength;
            var originalLeatherRange = this.OriginalLeatherLength / this.OriginalTotalLength;
            var currentLeatherRange  = this.OriginalLeatherLength / this.CurrentTotalLength;

            for (var i = 0; i < this.SimulatedTransformCount; i++)
            {
                var l = this.OriginalTotalLength * (float)i / (float)(this.SimulatedTransformCount - 1f);
                var u = l / this.OriginalTotalLength;

                // remap the u value depending on the material (rubber vs leather)
                var isRubber = Math.Abs(0.5f - u) > originalLeatherRange * 0.5f;
                if (isRubber)
                {
                    if (u < 0.5f)
                    {
                        u = u / (0.5f - originalLeatherRange * 0.5f);
                        u = (segmentAStart + (segmentAEnd - segmentAStart) * u) / this.CurrentTotalLength;
                    }
                    else
                    {
                        u = 1f - (1f - u) / (0.5f - originalLeatherRange * 0.5f);
                        u = (segmentCStart + (segmentCEnd - segmentCStart) * u) / this.CurrentTotalLength;
                    }
                }
                else
                {
                    u = (startBend + endBend) * 0.5f - (0.5f - u) * (currentLeatherRange / originalLeatherRange);
                }

                var p = SCNVector3.Zero;
                var t = SCNVector3.UnitX;
                if (u < startBend)
                {
                    // left straight
                    var value = u / startBend;
                    p = SimdExtensions.Mix(this.FixturePositionL,
                                           this.TangentPositionL,
                                           new SCNVector3(value, value, value)); // left rubber band
                    t = SCNVector3.Normalize(this.TangentPositionL - this.FixturePositionL);
                }
                else if (u > endBend)
                {
                    // right straight
                    var value = (1f - u) / (1f - endBend);
                    p = SimdExtensions.Mix(this.FixturePositionR,
                                           this.TangentPositionR,
                                           new SCNVector3(value, value, value)); // right rubber band
                    t = SCNVector3.Normalize(this.FixturePositionR - this.TangentPositionR);
                }
                else
                {
                    // on the ball
                    var upv = this.UpVector;
                    var rot = SCNQuaternion.FromAxisAngle(upv, -this.BetaAngle * (u - startBend) / (endBend - startBend));
                    p = this.ballPosition + rot.Act(this.TangentPositionL - this.ballPosition);
                    t = SCNVector3.Cross(upv, SCNVector3.Normalize(this.ballPosition - p));
                }

                data.Positions.Add(p);
                data.Tangents.Add(t);
                data.Lengths.Add(l);
            }

            return(data);
        }