private bool HitTestPull(Ray cameraRay) { // Careful not to make this too large or you'll pick a neighboring slingshot or one across the table. // We're not sorting hits across all of the slignshots and picking the smallest, but we visit them all. // Within radius behind the ball/pull allow the slingshot to be picked. var playerDistanceFromPull = cameraRay.Position - this.pullOrigin.WorldPosition; // This is a linear distance along the current firing direction of the catapult // Just using it here to make sure you're behind the pull (the pull is visible). var stretchDistance = SCNVector3.Dot(playerDistanceFromPull, -this.FiringDirection()); // make sure player is on positive side of pull + some fudge factor to make sure we can see it // to avoid flickering highlight on and off, we add a buffer when highlighted if (stretchDistance <= 0.01f && this.HighlightObject != null && this.HighlightObject.Hidden) { return(false); } else if (stretchDistance < -0.03f) { // slack during highlight mode return(false); } // player can be inside a highlight radius or the pick radius // one approach is to auto grab when within radius (but facing the catapult) if (playerDistanceFromPull.Length > properties.PickRadius) { return(false); } return(true); }
private void ScaleToPlane(ARPlaneAnchor planeAnchor) { // Determine if extent should be flipped (plane is 90 degrees rotated) var planeXAxis = planeAnchor.Transform.Column0.Xyz; var axisFlipped = Math.Abs(SCNVector3.Dot(planeXAxis, this.WorldRight)) < 0.5f; // Flip dimensions if necessary var planeExtent = planeAnchor.Extent; if (axisFlipped) { planeExtent = new OpenTK.NVector3(planeExtent.Z, 0f, planeExtent.X); } // Scale board to the max extent that fits in the plane var width = Math.Min(planeExtent.X, GameBoard.MaximumScale); var depth = Math.Min(planeExtent.Z, width * this.AspectRatio); width = depth / this.AspectRatio; this.Scale = new SCNVector3(width, width, width); // Adjust position of board within plane's bounds var planeLocalExtent = new SCNVector3(width, 0f, depth); if (axisFlipped) { planeLocalExtent = new SCNVector3(planeLocalExtent.Z, 0f, planeLocalExtent.X); } this.AdjustPosition(planeAnchor, planeLocalExtent); }
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); }
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); }
public static FeatureHitTestResult?HitTestFromOrigin(this ARSCNView view, SCNVector3 origin, SCNVector3 direction) { FeatureHitTestResult?result = null; ARPointCloud features = null; using (var frame = view.Session.CurrentFrame) { features = frame?.RawFeaturePoints; } if (features != null) { var points = features.Points; // Determine the point from the whole point cloud which is closest to the hit test ray. var closestFeaturePoint = origin; var minDistance = float.MaxValue; // Float.greatestFiniteMagnitude for (nuint i = 0; i < features.Count; i++) { var feature = points[i]; var featurePosition = new SCNVector3((Vector3)feature); var originVector = origin - featurePosition; var crossProduct = SCNVector3.Cross(originVector, direction); var featureDistanceFromResult = crossProduct.Length; if (featureDistanceFromResult < minDistance) { closestFeaturePoint = featurePosition; minDistance = featureDistanceFromResult; } } // Compute the point along the ray that is closest to the selected feature. var originToFeature = closestFeaturePoint - origin; var hitTestResult = origin + (direction * SCNVector3.Dot(direction, originToFeature)); var hitTestResultDistance = (hitTestResult - origin).Length; result = new FeatureHitTestResult { Position = hitTestResult, DistanceToRayOrigin = hitTestResultDistance, FeatureHit = closestFeaturePoint, FeatureDistanceToHitResult = minDistance }; } return(result); }
public static FeatureHitTestResult HitTestFromOrigin(this ARSCNView self, SCNVector3 origin, SCNVector3 direction) { if (self.Session == null || ViewController.CurrentFrame == null) { return(null); } var currentFrame = ViewController.CurrentFrame; var features = currentFrame.RawFeaturePoints; if (features == null) { return(null); } var points = features.Points; // Determine the point from the whole point cloud which is closest to the hit test ray. var closestFeaturePoint = origin; var minDistance = float.MaxValue; for (int n = 0; n < (int)features.Count; ++n) { var feature = points[n]; var featurePos = new SCNVector3(feature.X, feature.Y, feature.Z); var originVector = origin.Subtract(featurePos); var crossProduct = originVector.Cross(direction); var featureDistanceFromResult = crossProduct.Length; if (featureDistanceFromResult < minDistance) { closestFeaturePoint = featurePos; minDistance = featureDistanceFromResult; } } // Compute the point along the ray that is closest to the selected feature. var originToFeature = closestFeaturePoint.Subtract(origin); var hitTestResult = origin.Add(direction * direction.Dot(originToFeature)); var hitTestResultDistance = hitTestResult.Subtract(origin).LengthFast; // Return result return(new FeatureHitTestResult(hitTestResult, hitTestResultDistance, closestFeaturePoint, minDistance)); }
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(); } } }
public static System.nfloat PlaneIntersect(SCNVector3 planeNormal, System.nfloat planeDist, SCNVector3 rayOrigin, SCNVector3 rayDirection) #endif { return((planeDist - SCNVector3.Dot(planeNormal, rayOrigin)) / SCNVector3.Dot(planeNormal, rayDirection)); }
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); }