internal static SCNVector3 Normalized(this SCNVector3 self)
        {
            var copy = new SCNVector3(self);

            copy.Normalize();
            return(copy);
        }
Beispiel #2
0
        public GameVelocity TryGetLaunchVelocity(CameraInfo cameraInfo)
        {
            GameVelocity result = null;

            if (this.Projectile == null)
            {
                throw new Exception("Trying to launch without a ball");
            }

            // Move the catapult to make sure that it is moved at least once before launch (prevent NaN in launch direction)
            this.Move(cameraInfo);

            var stretchNormalized = DigitExtensions.Clamp((this.stretch - this.properties.MinStretch) / (this.properties.MaxStretch - this.properties.MinStretch), 0.0, 1.0);

            // this is a lerp
            var velocity = (float)(this.properties.MinVelocity * (1d - stretchNormalized) + this.properties.MaxVelocity * stretchNormalized);

            var launchDir = SCNVector3.Normalize(this.pullOrigin.WorldPosition - this.Projectile.WorldPosition);

            if (!launchDir.HasNaN())
            {
                var liftFactor = 0.05f * Math.Abs(1f - SCNVector3.Dot(launchDir, SCNVector3.UnitY)); // used to keep ball in air longer
                var lift       = SCNVector3.UnitY * velocity * liftFactor;

                result = new GameVelocity(this.Projectile.WorldPosition, launchDir * velocity + lift);
            }

            return(result);
        }
        /// <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;
            }
        }
Beispiel #4
0
        private SCNVector3 CharacterDirection(SCNNode pointOfView)
        {
            SCNVector3 result;

            var controllerDir = this.Direction;

            if (controllerDir.AllZero())
            {
                result = SCNVector3.Zero;
            }
            else
            {
                var directionWorld = SCNVector3.Zero;
                if (pointOfView != null)
                {
                    var p1 = pointOfView.PresentationNode.ConvertPositionToNode(new SCNVector3(controllerDir.X, 0f, controllerDir.Y), null);
                    var p0 = pointOfView.PresentationNode.ConvertPositionToNode(SCNVector3.Zero, null);
                    directionWorld   = p1 - p0;
                    directionWorld.Y = 0f;

                    if (directionWorld != SCNVector3.Zero)
                    {
                        var minControllerSpeedFactor = 0.2f;
                        var maxControllerSpeedFactor = 1f;
                        var speed = controllerDir.Length * (maxControllerSpeedFactor - minControllerSpeedFactor) + minControllerSpeedFactor;
                        directionWorld = speed * SCNVector3.Normalize(directionWorld);
                    }
                }

                result = directionWorld;
            }

            return(result);
        }
Beispiel #5
0
        /// <summary>
        /// Aligns the rigid bodies by correcting their orienation
        /// This should be called after the simulation step
        /// </summary>
        private void AlignBones()
        {
            // orient the bodies accordingly
            for (var i = this.BoneInset; i < this.simulatedTransforms.Count - this.BoneInset; i++)
            {
                if (!this.simulatedTransforms[i].Dynamic)
                {
                    continue;
                }

                var a = this.simulatedTransforms[i - 1].Position;
                var b = this.simulatedTransforms[i + 1].Position;

                // this is the upVector computed for each bone of the rest pose
                var transform = this.restPoseSpace * this.restPoseTransforms[i]; // todo: direction of multiply?
                var y         = SimdExtensions.CreateQuaternion(transform).Act(new SCNVector3(0f, 1f, 0f));

                var x = SCNVector3.Normalize(b - a);
                var z = SCNVector3.Normalize(SCNVector3.Cross(x, y));
                y = SCNVector3.Normalize(SCNVector3.Cross(z, x));

                var rot = new OpenTK.Matrix3(x.X, y.X, z.X, x.Y, y.Y, z.Y, x.Z, y.Z, z.Z);
                this.simulatedTransforms[i].Orientation = new SCNQuaternion(rot.ToQuaternion());
            }
        }
        /// <summary>
        /// Returns the interpolated tangent at a given length (l from 0.0 to totalLength)
        /// </summary>
        public SCNVector3 Tangent(float l)
        {
            var s = this.FindIndex(l);

            return(SCNVector3.Normalize(SimdExtensions.Mix(this.Tangents[this.lastIndex],
                                                           this.Tangents[this.lastIndex + 1],
                                                           new SCNVector3(s, s, s))));
        }
        private void ApplyRandomTorque(SCNPhysicsBody physicsBody, float maxTorque)
        {
            var randomAxis = new SCNVector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble());

            randomAxis = SCNVector3.Normalize(randomAxis);

            var randomTorque = new SCNVector4(randomAxis, (float)(random.NextDouble() * 2d - 1d) * maxTorque);

            physicsBody.ApplyTorque(randomTorque, true);
        }
Beispiel #8
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);
        }
        /// <summary>
        /// Returns the interpolated transform at a given length (l from 0.0 to totalLength)
        /// </summary>
        public SCNMatrix4 Transform(float l)
        {
            var position = this.Position(l);
            var x        = this.Tangent(l);
            var y        = SCNVector3.Normalize(this.UpVector);
            var z        = SCNVector3.Normalize(SCNVector3.Cross(x, y));

            y = SCNVector3.Normalize(SCNVector3.Cross(z, x));

            var rot    = new OpenTK.Matrix3(x.X, y.X, z.X, x.Y, y.Y, z.Y, x.Z, y.Z, z.Z);
            var matrix = SCNMatrix4.Rotate(rot.ToQuaternion());

            return(matrix.SetTranslation((OpenTK.Vector3)position));
        }
Beispiel #10
0
        private void InitializeHelpers()
        {
            this.computedInputPose        = new ComputedValue <SlingShotPose>(() => this.ComputeInputPose());
            this.computedTangentPositionR = new ComputedValue <SCNVector3>(() => this.TangentPosition(this.FixturePositionR));
            this.computedTangentPositionL = new ComputedValue <SCNVector3>(() => this.TangentPosition(this.FixturePositionL));

            this.computedBetaAngle = new ComputedValue <float>(() =>
            {
                var d = SCNVector3.Normalize(this.ballPosition - this.CenterPosition);
                var t = SCNVector3.Normalize(this.TangentPositionL - this.ballPosition);

                var quaternion = SimdExtensions.CreateQuaternion(d, t);
                quaternion.ToAxisAngle(out SCNVector3 _, out float angle);
                return(2f * angle);
            });

            this.computedCenterPosition = new ComputedValue <SCNVector3>(() =>
            {
                var direction = SCNVector3.Cross(this.UpVector, this.TangentPositionR - this.TangentPositionL);
                return(this.ballPosition - SCNVector3.Normalize(direction) * 1.25f * this.ballRadius);
            });

            this.computedRestPose = new ComputedValue <SlingShotPose>(() =>
            {
                var data = new SlingShotPose();

                for (var i = 0; i < this.restPoseTransforms.Count; i++)
                {
                    var transform = this.restPoseSpace * this.restPoseTransforms[i];
                    var p         = new SCNVector3(transform.Column3.X, transform.Column3.Y, transform.Column3.Z);
                    var t         = SimdExtensions.CreateQuaternion(transform).Act(new SCNVector3(1f, 0f, 0f));

                    var l = 0f;
                    if (i > 0)
                    {
                        l = data.Lengths[i - 1] + (p - data.Positions[i - 1]).Length;
                    }

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

                return(data);
            });
        }
Beispiel #11
0
        protected void PerformEnemyDieWithExplosion(SCNNode enemy, SCNVector3 direction)
        {
            var explositionScene = SCNScene.FromFile("art.scnassets/enemy/enemy_explosion.scn");

            if (explositionScene != null)
            {
                SCNTransaction.Begin();
                SCNTransaction.AnimationDuration       = 0.4f;
                SCNTransaction.AnimationTimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseOut);

                SCNTransaction.SetCompletionBlock(() =>
                {
                    explositionScene.RootNode.EnumerateHierarchy((SCNNode node, out bool stop) =>
                    {
                        stop = false;
                        if (node.ParticleSystems != null)
                        {
                            foreach (var particle in node.ParticleSystems)
                            {
                                enemy.AddParticleSystem(particle);
                            }
                        }
                    });

                    // Hide
                    if (enemy.ChildNodes.Length > 0)
                    {
                        enemy.ChildNodes[0].Opacity = 0f;
                    }
                });

                direction.Y = 0;
                enemy.RemoveAllAnimations();
                enemy.EulerAngles = new SCNVector3(enemy.EulerAngles.X, enemy.EulerAngles.X + (float)Math.PI * 4.0f, enemy.EulerAngles.Z);

                enemy.WorldPosition += SCNVector3.Normalize(direction) * 1.5f;
                this.PositionAgentFromNode();

                SCNTransaction.Commit();
            }
            else
            {
                Console.WriteLine("Missing enemy_explosion.scn");
            }
        }
Beispiel #12
0
        SCNVector3 ComputeDirection()
        {
            CGPoint p   = DirectionFromPressedKeys();
            var     dir = new SCNVector3((float)p.X, 0f, (float)p.Y);
            var     p0  = new SCNVector3(0f, 0f, 0f);

            dir = PointOfView.PresentationNode.ConvertPositionToNode(dir, null);
            p0  = PointOfView.PresentationNode.ConvertPositionToNode(p0, null);

            dir = new SCNVector3(dir.X - p0.X, 0f, dir.Z - p0.Z);

            if (dir.X != 0 || dir.Z != 0)
            {
                dir.Normalize();
            }

            return(dir);
        }
Beispiel #13
0
        /// <summary>
        /// Computes and applies the custom forces for the slingshot rope.
        /// This should be called every frame.
        /// </summary>
        private void ApplyForces()
        {
            var b = new SCNVector3(SimBlend, SimBlend, SimBlend);

            for (var i = this.BoneInset; i < this.simulatedTransforms.Count - this.BoneInset; i++)
            {
                if (!this.simulatedTransforms[i].Dynamic)
                {
                    continue;
                }

                var force = SCNVector3.Zero;

                if (i > 0)
                {
                    var restA           = this.RestPose.Positions[i - 1];
                    var restB           = this.RestPose.Positions[i];
                    var currentA        = this.simulatedTransforms[i - 1].Position;
                    var currentB        = this.simulatedTransforms[i].Position;
                    var restDistance    = (restA - restB).Length;
                    var currentDistance = (currentA - currentB).Length;
                    force += SCNVector3.Normalize(currentA - currentB) * (currentDistance - restDistance) * SimNeighborStrength;
                }

                if (i < this.simulatedTransforms.Count - 1)
                {
                    var restA           = this.RestPose.Positions[i + 1];
                    var restB           = this.RestPose.Positions[i];
                    var currentA        = this.simulatedTransforms[i + 1].Position;
                    var currentB        = this.simulatedTransforms[i].Position;
                    var restDistance    = (restA - restB).Length;
                    var currentDistance = (currentA - currentB).Length;
                    force += SCNVector3.Normalize(currentA - currentB) * (currentDistance - restDistance) * SimNeighborStrength;
                }

                force += (this.RestPose.Positions[i] - this.simulatedTransforms[i].Position) * SimRestPoseStrength;


                var vel = this.simulatedTransforms[i].Velocity;
                this.simulatedTransforms[i].Velocity = SimdExtensions.Mix(vel, force, b);
            }
        }
Beispiel #14
0
        public static SCNVector3?RayIntersectionWithHorizontalPlane(SCNVector3 rayOrigin, SCNVector3 direction, float planeY)
        {
            direction = SCNVector3.Normalize(direction);

            // Special case handling: Check if the ray is horizontal as well.
            if (direction.Y == 0)
            {
                if (rayOrigin.Y == planeY)
                {
                    // The ray is horizontal and on the plane, thus all points on the ray intersect with the plane.
                    // Therefore we simply return the ray origin.
                    return(rayOrigin);
                }
                else
                {
                    // The ray is parallel to the plane and never intersects.
                    return(null);
                }
            }

            // The distance from the ray's origin to the intersection point on the plane is:
            //   (pointOnPlane - rayOrigin) dot planeNormal
            //  --------------------------------------------
            //          direction dot planeNormal

            // Since we know that horizontal planes have normal (0, 1, 0), we can simplify this to:
            var dist = (planeY - rayOrigin.Y) / direction.Y;

            // Do not return intersections behind the ray's origin.
            if (dist < 0)
            {
                return(null);
            }

            // Return the intersection point.
            return(rayOrigin + (direction * dist));
        }
Beispiel #15
0
        public static HitTestRay?HitTestRayFromScreenPosition(this ARSCNView view, CGPoint point)
        {
            HitTestRay?result = null;

            using (var frame = view.Session.CurrentFrame)
            {
                if (frame != null)
                {
                    var cameraPosition = frame.Camera.Transform.GetTranslation();

                    // Note: z: 1.0 will unproject() the screen position to the far clipping plane.
                    var positionVector = new SCNVector3((float)point.X, (float)point.Y, 1f);

                    var screenPositionOnFarClippingPlane = view.UnprojectPoint(positionVector);
                    var rayDirection = SCNVector3.Normalize(screenPositionOnFarClippingPlane - cameraPosition);

                    result = new HitTestRay {
                        Origin = cameraPosition, Direction = rayDirection
                    };
                }
            }

            return(result);
        }
Beispiel #16
0
 public static void SetLength(this SCNVector3 vector3, float length)
 {
     vector3.Normalize();
     vector3 *= length;
 }
Beispiel #17
0
        private SCNVector3 ComputeBallPosition(CameraInfo cameraInfo)
        {
            var cameraRay = cameraInfo.Ray;

            // These should be based on the projectile radius.
            // This affects centering of ball, and can hit near plane of camera
            // This is always centering to one edge of screen independent of screen orient
            // We always want the ball at the bottom of the screen.
            var distancePullToCamera = 0.21f;
            var ballShiftDown        = 0.2f;

            var targetBallPosition = cameraRay.Position + cameraRay.Direction * distancePullToCamera;

            var cameraDown = -SCNVector4.Normalize(cameraInfo.Transform.Column1).Xyz;

            targetBallPosition += cameraDown * ballShiftDown;

            // Clamp to only the valid side
            var pullWorldPosition = this.pullOrigin.WorldPosition;

            if (pullWorldPosition.Z < 0f)
            {
                targetBallPosition.Z = Math.Min(targetBallPosition.Z, pullWorldPosition.Z);
            }
            else
            {
                targetBallPosition.Z = Math.Max(targetBallPosition.Z, pullWorldPosition.Z);
            }

            // Clamp to cone/circular core
            var yDistanceFromPull       = Math.Max(0f, pullWorldPosition.Y - targetBallPosition.Y);
            var minBallDistanceFromPull = 0.5f;
            var pullBlockConeSlope      = 1.0f;
            var pullBlockConeRadius     = yDistanceFromPull / pullBlockConeSlope;
            var pullBlockCoreRadius     = Math.Max(minBallDistanceFromPull, pullBlockConeRadius);

            // if pull is in the core, move it out.
            var pullWorldPositionGrounded  = new SCNVector3(pullWorldPosition.X, 0f, pullWorldPosition.Z);
            var targetPullPositionGrounded = new SCNVector3(targetBallPosition.X, 0f, targetBallPosition.Z);
            var targetInitialToTargetPull  = targetPullPositionGrounded - pullWorldPositionGrounded;

            if (pullBlockCoreRadius > targetInitialToTargetPull.Length)
            {
                var moveOutDirection = SCNVector3.Normalize(targetInitialToTargetPull);
                var newTargetPullPositionGrounded = pullWorldPositionGrounded + moveOutDirection * pullBlockCoreRadius;
                targetBallPosition = new SCNVector3(newTargetPullPositionGrounded.X, targetBallPosition.Y, newTargetPullPositionGrounded.Z);
            }

            // only use the 2d distance, so that user can gauage stretch indepdent of mtch
            var distance2D = targetBallPosition - pullWorldPosition;
            var stretchY   = Math.Abs(distance2D.Y);

            distance2D.Y = 0f;

            var stretchDistance = distance2D.Length;

            this.stretch = DigitExtensions.Clamp((double)stretchDistance, this.properties.MinStretch, this.properties.MaxStretch);

            // clamp a little bit farther than maxStretch
            // can't let the strap move back too far right now
            var clampedStretchDistance = (float)(1.1d * this.properties.MaxStretch);

            if (stretchDistance > clampedStretchDistance)
            {
                targetBallPosition = (clampedStretchDistance / stretchDistance) * (targetBallPosition - pullWorldPosition) + pullWorldPosition;
                stretchDistance    = clampedStretchDistance;
            }

            // Make this optional, not required.  You're often at max stretch.
            // Also have a timer for auto-launch.  This makes it very difficuilt to test
            // storing state in member data
            this.IsPulledTooFar = stretchDistance > (float)(this.properties.MaxStretch) || stretchY > (float)(this.properties.MaxStretch);

            return(targetBallPosition);
        }
Beispiel #18
0
        public static IList <FeatureHitTestResult> HitTestWithFeatures(this ARSCNView view,
                                                                       CGPoint point,
                                                                       float coneOpeningAngleInDegrees,
                                                                       float minDistance = 0,
                                                                       float maxDistance = float.MaxValue,
                                                                       int maxResults    = 1)
        {
            var results = new List <FeatureHitTestResult>();

            ARPointCloud features = null;

            using (var frame = view.Session.CurrentFrame)
            {
                features = frame?.RawFeaturePoints;
            }

            if (features != null)
            {
                var ray = view.HitTestRayFromScreenPosition(point);
                if (ray.HasValue)
                {
                    var maxAngleInDegrees = Math.Min(coneOpeningAngleInDegrees, 360f) / 2f;
                    var maxAngle          = (maxAngleInDegrees / 180f) * Math.PI;

                    var points = features.Points;
                    for (nuint j = 0; j < features.Count; j++)
                    {
                        var feature = points[j];

                        var featurePosition = new SCNVector3((Vector3)feature);
                        var originToFeature = featurePosition - ray.Value.Origin;

                        var crossProduct = SCNVector3.Cross(originToFeature, ray.Value.Direction);
                        var featureDistanceFromResult = crossProduct.Length;

                        var hitTestResult         = ray.Value.Origin + (ray.Value.Direction * SCNVector3.Dot(ray.Value.Direction, originToFeature));
                        var hitTestResultDistance = (hitTestResult - ray.Value.Origin).Length;

                        if (hitTestResultDistance < minDistance || hitTestResultDistance > maxDistance)
                        {
                            // Skip this feature - it is too close or too far away.
                            continue;
                        }

                        var originToFeatureNormalized = SCNVector3.Normalize(originToFeature);
                        var angleBetweenRayAndFeature = Math.Acos(SCNVector3.Dot(ray.Value.Direction, originToFeatureNormalized));

                        if (angleBetweenRayAndFeature > maxAngle)
                        {
                            // Skip this feature - is outside of the hit test cone.
                            continue;
                        }

                        // All tests passed: Add the hit against this feature to the results.
                        results.Add(new FeatureHitTestResult
                        {
                            Position                   = hitTestResult,
                            DistanceToRayOrigin        = hitTestResultDistance,
                            FeatureHit                 = featurePosition,
                            FeatureDistanceToHitResult = featureDistanceFromResult
                        });
                    }

                    // Sort the results by feature distance to the ray.
                    results = results.OrderBy(result => result.DistanceToRayOrigin).ToList();

                    // Cap the list to maxResults.
                    var cappedResults = new List <FeatureHitTestResult>();
                    var i             = 0;

                    while (i < maxResults && i < results.Count)
                    {
                        cappedResults.Add(results[i]);
                        i += 1;
                    }

                    results = cappedResults;
                }
            }

            return(results);
        }
Beispiel #19
0
        public override void OnDidApplyConstraints(ISCNSceneRenderer renderer)
        {
            var frameSkips = 3;

            if ((GameTime.FrameCount + this.Index) % frameSkips == 0)
            {
                if (this.PhysicsNode != null)
                {
                    if (this.worldPositions.Count > (this.MaxTrailPositions / frameSkips))
                    {
                        this.RemoveVerticesPair();
                    }

                    var position = this.PhysicsNode.PresentationNode.WorldPosition;

                    SCNVector3 trailDir;
                    if (this.worldPositions.Any())
                    {
                        var previousPosition = this.worldPositions.LastOrDefault();
                        trailDir = position - previousPosition;

                        var lengthSquared = trailDir.LengthSquared;
                        if (lengthSquared < Epsilon)
                        {
                            this.RemoveVerticesPair();
                            this.UpdateColors();
                            var tempPositions = this.tempWorldPositions.Select(tempPosition => this.trailNode.PresentationNode.ConvertPositionFromNode(tempPosition, null)).ToList();
                            this.trailNode.PresentationNode.Geometry = this.CreateTrailMesh(tempPositions, this.colors);
                            return;
                        }

                        trailDir = SCNVector3.Normalize(trailDir);
                    }
                    else
                    {
                        trailDir = this.ObjectRootNode.WorldFront;
                    }

                    var right = SCNVector3.Cross(SCNVector3.UnitY, trailDir);
                    right = SCNVector3.Normalize(right);
                    var scale     = 1f; //Float(i - 1) / worldPositions.count
                    var halfWidth = this.TrailHalfWidth;
                    if (this.TrailShouldNarrow)
                    {
                        halfWidth *= scale;
                    }

                    var u = position + right * halfWidth;
                    var v = position - right * halfWidth;

                    this.worldPositions.Add(position);
                    this.tempWorldPositions.Add(u);
                    this.tempWorldPositions.Add(v);

                    this.colors.Add(SCNVector4.Zero);
                    this.colors.Add(SCNVector4.Zero);

                    this.UpdateColors();
                    var localPositions = this.tempWorldPositions.Select(tempPosition => this.trailNode.PresentationNode.ConvertPositionFromNode(tempPosition, null)).ToList();
                    this.trailNode.PresentationNode.Geometry = this.CreateTrailMesh(localPositions, this.colors);
                }
            }
        }
Beispiel #20
0
		SCNVector3 ComputeDirection ()
		{
			CGPoint p = DirectionFromPressedKeys ();
			var dir = new SCNVector3 ((float)p.X, 0f, (float)p.Y);
			var p0 = new SCNVector3 (0f, 0f, 0f);

			dir = PointOfView.PresentationNode.ConvertPositionToNode (dir, null);
			p0 = PointOfView.PresentationNode.ConvertPositionToNode (p0, null);

			dir = new SCNVector3 (dir.X - p0.X, 0f, dir.Z - p0.Z);

			if (dir.X != 0 || dir.Z != 0)
				dir.Normalize ();

			return dir;
		}
Beispiel #21
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);
        }
Beispiel #22
0
        private Tuple <SCNVector3, SCNVector3> HandleSlidingAtContact(SCNPhysicsContact closestContact, SCNVector3 start, SCNVector3 velocity)
        {
            var originalDistance = velocity.Length;

            var colliderPositionAtContact = start + (float)closestContact.SweepTestFraction * velocity;

            // Compute the sliding plane.
            var slidePlaneNormal = new SCNVector3(closestContact.ContactNormal);
            var slidePlaneOrigin = new SCNVector3(closestContact.ContactPoint);
            var centerOffset     = slidePlaneOrigin - colliderPositionAtContact;

            // Compute destination relative to the point of contact.
            var destinationPoint = slidePlaneOrigin + velocity;

            // We now project the destination point onto the sliding plane.
            var distPlane = SCNVector3.Dot(slidePlaneOrigin, slidePlaneNormal);

            // Project on plane.
            var t = Utils.PlaneIntersect(slidePlaneNormal, distPlane, destinationPoint, slidePlaneNormal);

            var normalizedVelocity = velocity * (1f / originalDistance);
            var angle = SCNVector3.Dot(slidePlaneNormal, normalizedVelocity);

            var frictionCoeff = 0.3f;

            if (Math.Abs(angle) < 0.9f)
            {
                t            += (float)10E-3;
                frictionCoeff = 1.0f;
            }

            var newDestinationPoint = (destinationPoint + t * slidePlaneNormal) - centerOffset;
            // Advance start position to nearest point without collision.
            var computedVelocity = frictionCoeff * (float)(1f - closestContact.SweepTestFraction) * originalDistance * SCNVector3.Normalize(newDestinationPoint - start);

            return(new Tuple <SCNVector3, SCNVector3>(computedVelocity, colliderPositionAtContact));
        }
        public void AnimateVortex()
        {
            if (this.Delegate == null)
            {
                throw new Exception("No delegate");
            }

            if (this.vortexCylinder == null)
            {
                throw new Exception("Vortex animation cylinder not set");
            }

            // Vortex shape from animation
            var vortexShape       = this.vortexCylinder.PresentationNode.Scale;
            var vortexHeightDelta = vortexShape.Y - this.lastVortexHeight;

            this.lastVortexHeight = vortexShape.Y;

            var vortexCenterY      = this.vortexCylinder.PresentationNode.WorldPosition.Y;
            var vortexCenterYDelta = vortexCenterY - this.lastVortexCenterY;

            this.lastVortexCenterY = vortexCenterY;

            // Deform shape over time
            var maxOuterRadius      = vortexShape.X;
            var maxInnerRadius      = maxOuterRadius * 0.2f; // 20 % from experiment
            var maxOuterRadiusDelta = maxOuterRadius - this.lastOuterRadius;

            this.lastOuterRadius = maxOuterRadius;

            // Orbital velocity
            var currentFront     = this.vortexCylinder.PresentationNode.WorldFront;
            var orbitalMoveDelta = (currentFront - this.lastFront).Length * maxInnerRadius;

            this.lastFront = currentFront;

            var orbitalVelocityFactor = 5f;
            var orbitalVelocity       = (orbitalMoveDelta / (float)GameTime.DeltaTime) * orbitalVelocityFactor;

            var topBound    = vortexCenterY + vortexShape.Y * 0.5f;
            var bottomBound = vortexCenterY - vortexShape.Y * 0.5f;

            var blockObjects = this.Delegate.AllBlockObjects;
            var up           = SCNVector3.UnitY;

            foreach (var block in blockObjects)
            {
                if (block.PhysicsNode?.PhysicsBody != null)
                {
                    var position           = block.PhysicsNode.PresentationNode.WorldPosition;
                    var positionWithoutY   = new SCNVector3(position.X, 0f, position.Z);
                    var distanceFromCenter = positionWithoutY.Length;
                    var directionToCenter  = -SCNVector3.Normalize(positionWithoutY);

                    // Adjust radius into curve
                    // Equation representing a half radius chord of circle equation
                    var normalizedY  = DigitExtensions.Clamp(position.Y / topBound, 0f, 1f);
                    var radiusFactor = (float)Math.Sqrt(4f - 3f * normalizedY * normalizedY) - 1f;
                    radiusFactor = radiusFactor * 0.8f + 0.2f;
                    var innerRadius = maxInnerRadius * radiusFactor;
                    var outerRadius = maxOuterRadius * radiusFactor;

                    // Cap velocity
                    var maxVelocity = 30f;
                    if (block.PhysicsNode.PhysicsBody.Velocity.Length > maxVelocity)
                    {
                        block.PhysicsNode.PhysicsBody.Velocity = SCNVector3.Normalize(block.PhysicsNode.PhysicsBody.Velocity) * maxVelocity;
                    }

                    var force = SCNVector3.Zero;

                    // Stage specific manipulation
                    var vortexDirection        = SCNVector3.Cross(directionToCenter, up);
                    var speedInVortexDirection = SCNVector3.Dot(block.PhysicsNode.PhysicsBody.Velocity, vortexDirection);

                    // Stable vortex pull
                    var pullForceMagnitude = (speedInVortexDirection * speedInVortexDirection) * (float)(block.PhysicsNode.PhysicsBody.Mass) / distanceFromCenter;
                    force += pullForceMagnitude * directionToCenter;

                    // Pull into outer radius
                    var radialInwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, distanceFromCenter - outerRadius);
                    force += radialInwardForceMagnitude * directionToCenter;

                    // Pull away from inner radius
                    var radialOutwardForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, innerRadius - distanceFromCenter);
                    force += -radialOutwardForceMagnitude * directionToCenter;

                    // Vortex velocity adjustment
                    if (distanceFromCenter > innerRadius)
                    {
                        var tangentForceMagnitude = TangentVelocitySpringContant * (speedInVortexDirection - orbitalVelocity);
                        force += -tangentForceMagnitude * vortexDirection * (0.5f + (float)(random.NextDouble() * 1d));
                    }

                    // Random forces/torque
                    force += force.Length * (float)((random.NextDouble() * 2d - 1d) * MaxRandomVortexForce) * up;
                    this.ApplyRandomTorque(block.PhysicsNode.PhysicsBody, MaxRandomVortexTorque);

                    // Top bound pull down
                    var topBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, position.Y - topBound);
                    force += topBoundForceMagnitude * -up;

                    // Bottom bound pull up
                    var bottomBoundForceMagnitude = RadialSpringConstant * (float)Math.Max(0d, bottomBound - position.Y);
                    force += bottomBoundForceMagnitude * up;

                    block.PhysicsNode.PhysicsBody.ApplyForce(force, false);

                    // Scale the vortex
                    // The higher position in the bound, more it should move upward to scale the vortex
                    var normalizedPositionInBoundY = DigitExtensions.Clamp((position.Y - bottomBound) / vortexShape.Y, 0f, 1f);
                    var heightMoveFactor           = Math.Abs(normalizedPositionInBoundY - 0.5f);
                    var newPositionY = position.Y + vortexCenterYDelta + vortexHeightDelta * heightMoveFactor;

                    var positionXZ       = new SCNVector3(position.X, 0f, position.Z);
                    var radialMoveFactor = DigitExtensions.Clamp(distanceFromCenter / outerRadius, 0f, 1f);
                    var newPositionXZ    = positionXZ + maxOuterRadiusDelta * radialMoveFactor * -directionToCenter;

                    block.PhysicsNode.WorldPosition    = new SCNVector3(newPositionXZ.X, newPositionY, newPositionXZ.Z);
                    block.PhysicsNode.WorldOrientation = block.PhysicsNode.PresentationNode.WorldOrientation;
                    block.PhysicsNode.PhysicsBody.ResetTransform();
                }
            }
        }