private SCNNode GetFillPlane() { var length = 1f - 2f * BorderSegment.Thickness; var plane = SCNPlane.Create(length, length * this.AspectRatio); var node = SCNNode.FromGeometry(plane); node.Name = "fillPlane"; node.Opacity = 0.6f; var material = plane.FirstMaterial; material.Diffuse.Contents = UIImage.FromBundle("art.scnassets/textures/grid.png"); var textureScale = SimdExtensions.CreateFromScale(new SCNVector3(40f, 40f * this.AspectRatio, 1f)); material.Diffuse.ContentsTransform = textureScale; material.Emission.Contents = UIImage.FromBundle("art.scnassets/textures/grid.png"); material.Emission.ContentsTransform = textureScale; material.Diffuse.WrapS = SCNWrapMode.Repeat; material.Diffuse.WrapT = SCNWrapMode.Repeat; material.DoubleSided = true; material.Ambient.Contents = UIColor.Black; material.LightingModelName = SCNLightingModel.Constant; return(node); }
/// <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)))); }
/// <summary> /// Returns the interpolated position at a given length (l from 0.0 to totalLength) /// </summary> public SCNVector3 Position(float l) { var s = this.FindIndex(l); return(SimdExtensions.Mix(this.Positions[this.lastIndex], this.Positions[this.lastIndex + 1], new SCNVector3(s, s, s))); }
private void UpdateColors() { var baseColor = SimdExtensions.CreateVector4(this.Team.GetColor()); for (var i = 0; i < this.colors.Count; i++) { var scale = (float)i / (float)this.colors.Count; this.colors[i] = baseColor * scale; } }
private void PerformPlaneCollision(List <SimulatedTransform> previousTransforms, float seconds) { for (var i = this.BoneInset; i < this.simulatedTransforms.Count - this.BoneInset; i++) { if (!this.simulatedTransforms[i].Dynamic) { continue; } var p = this.simulatedTransforms[i].Position; var v = this.simulatedTransforms[i].Velocity; // project into the space of the base var pM = SCNMatrix4.Identity.SetTranslation((OpenTK.Vector3)p); var pLocal = SCNMatrix4.Invert(this.restPoseSpace) * pM; if (pLocal.Column3.Z <= CollisionPlane) { pLocal.M34 = CollisionPlane; pM = this.restPoseSpace * pLocal; var pOnPlane = new SCNVector3(pM.Column3.X, pM.Column3.Y, pM.Column3.Z); var blend = new SCNVector3(0.3f, 0.3f, 0.3f); this.simulatedTransforms[i].Position = SimdExtensions.Mix(p, pOnPlane, blend); var correctedVelocity = (this.simulatedTransforms[i].Position - previousTransforms[i].Position) / seconds; correctedVelocity = SCNVector3.Multiply(correctedVelocity, new SCNVector3(0.7f, 0.1f, 0.7f)); // verlet integration this.simulatedTransforms[i].Velocity = SimdExtensions.Mix(v, correctedVelocity, blend); p = this.simulatedTransforms[i].Position; v = this.simulatedTransforms[i].Velocity; } if (pLocal.Column3.Y <= CollisionPlane + 0.3f) { pLocal.M24 = CollisionPlane + 0.3f; pM = this.restPoseSpace * pLocal; var pOnPlane = new SCNVector3(pM.Column3.X, pM.Column3.Y, pM.Column3.Z); var blend = new SCNVector3(0.3f, 0.3f, 0.3f); this.simulatedTransforms[i].Position = SimdExtensions.Mix(p, pOnPlane, blend); var correctedVelocity = (this.simulatedTransforms[i].Position - previousTransforms[i].Position) / seconds; // verlet integration this.simulatedTransforms[i].Velocity = SimdExtensions.Mix(v, correctedVelocity, blend); } } }
/// <summary> /// Disables the simulation on the slingshot and sets the rigid bodies to be driven by the input pose /// </summary> public void EnableInputPose() { for (var i = 0; i < this.simulatedTransforms.Count; i++) { var l = this.OriginalTotalLength * (float)i / (float)(this.simulatedTransforms.Count - 1); var transform = this.InputPoseTransform(l); var position = new SCNVector3(transform.Column3.X, transform.Column3.Y, transform.Column3.Z); this.simulatedTransforms[i].Position = position; this.simulatedTransforms[i].Orientation = SimdExtensions.CreateQuaternion(transform); this.simulatedTransforms[i].Velocity = SCNVector3.Zero; this.simulatedTransforms[i].Dynamic = false; } }
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); }); }
private void OrientToPlane(ARPlaneAnchor planeAnchor, ARCamera camera) { // Get board rotation about y this.Orientation = SimdExtensions.CreateQuaternion(planeAnchor.Transform.ToSCNMatrix4()); var boardAngle = this.EulerAngles.Y; // If plane is longer than deep, rotate 90 degrees if (planeAnchor.Extent.X > planeAnchor.Extent.Z) { boardAngle += (float)Math.PI / 2f; } // Normalize angle to closest 180 degrees to camera angle boardAngle = boardAngle.NormalizedAngle(camera.EulerAngles.Y, (float)Math.PI); this.Rotate(boardAngle); }
private void SlideInWorld(SCNVector3 start, SCNVector3 velocity) { var maxSlideIteration = 4; var iteration = 0; var stop = false; var replacementPoint = start; var options = new SCNPhysicsTest() { CollisionBitMask = (int)Bitmask.Collision, SearchMode = SCNPhysicsSearchMode.Closest, }; while (!stop) { var from = SCNMatrix4.Identity; SimdExtensions.SetPosition(ref from, start); var to = SCNMatrix4.Identity; SimdExtensions.SetPosition(ref to, start + velocity); var contacts = this.PhysicsWorld.ConvexSweepTest(this.characterCollisionShape, from, to, options.Dictionary); if (contacts.Any()) { (velocity, start) = this.HandleSlidingAtContact(contacts.FirstOrDefault(), start, velocity); iteration += 1; if (velocity.LengthSquared <= (10E-3 * 10E-3) || iteration >= maxSlideIteration) { replacementPoint = start; stop = true; } } else { replacementPoint = start + velocity; stop = true; } } this.characterNode.WorldPosition = replacementPoint - this.collisionShapeOffsetFromModel; }
/// <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); } }
private void AverageVelocities() { var currentTransforms = new List <SimulatedTransform>(); currentTransforms.AddRange(this.simulatedTransforms); for (var i = this.BoneInset; i < this.SimulatedTransformCount - this.BoneInset; i++) { if (!this.simulatedTransforms[i].Dynamic) { continue; } var a = currentTransforms[i - 1].Velocity; var b = currentTransforms[i].Velocity; var c = currentTransforms[i + 1].Velocity; var ab = SimdExtensions.Mix(a, b, new SCNVector3(0.5f, 0.5f, 0.5f)); var bc = SimdExtensions.Mix(b, c, new SCNVector3(0.5f, 0.5f, 0.5f)); this.simulatedTransforms[i].Velocity = SimdExtensions.Mix(ab, bc, t: new SCNVector3(0.5f, 0.5f, 0.5f)); var center = SimdExtensions.Mix(currentTransforms[i - 1].Position, currentTransforms[i + 1].Position, new SCNVector3(0.5f, 0.5f, 0.5f)); this.simulatedTransforms[i].Position = SimdExtensions.Mix(this.simulatedTransforms[i].Position, center, new SCNVector3(SmoothRope, SmoothRope, SmoothRope)); } }
private void UpdateBorderAspectRatio() { var borderSize = new CGSize(1f, this.AspectRatio); foreach (var segment in this.borderSegments) { segment.BorderSize = borderSize; } if (this.FillPlane.Geometry is SCNPlane plane) { var length = 1 - 2 * BorderSegment.Thickness; plane.Height = length * this.AspectRatio; var textureScale = SimdExtensions.CreateFromScale(new SCNVector3(40f, 40f * this.AspectRatio, 1f)); if (plane.FirstMaterial != null) { plane.FirstMaterial.Diffuse.ContentsTransform = textureScale; plane.FirstMaterial.Emission.ContentsTransform = textureScale; } } this.isBorderOpen = false; }
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); }