internal static SCNVector3 Normalized(this SCNVector3 self) { var copy = new SCNVector3(self); copy.Normalize(); return(copy); }
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; } }
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); }
/// <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); }
/// <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)); }
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); }); }
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"); } }
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); }
/// <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); } }
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)); }
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); }
public static void SetLength(this SCNVector3 vector3, float length) { vector3.Normalize(); vector3 *= length; }
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); }
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); }
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); } } }
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; }
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); }
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(); } } }