public void UpdateTranslation(VirtualObject virtualObject, CGPoint midpoint) { if (!TranslationThresholdPassed) { var initialLocationToCurrentLocation = midpoint.Subtract(InitialMidpoint); var distanceFromStartLocation = initialLocationToCurrentLocation.Length(); // Check if the translate gesture has crossed the threshold. // If the user is already rotating and or scaling we use a bigger threshold. var threshold = TranslationThreshold; if (RotationThresholdPassed || TranslationThresholdPassed) { threshold = TranslationThresholdHarder; } if (distanceFromStartLocation >= threshold) { TranslationThresholdPassed = true; var currentObjectLocation = CGPointExtensions.FromVector(SceneView.ProjectPoint(virtualObject.Position)); DragOffset = midpoint.Subtract(currentObjectLocation); } } if (TranslationThresholdPassed) { var offsetPos = midpoint.Subtract(DragOffset); manager.Translate(virtualObject, SceneView, offsetPos, false, true); LastUsedObject = virtualObject; } }
public void UpdateVirtualObjectPosition(VirtualObject virtualObject, SCNVector3 position, bool filterPosition, NMatrix4 cameraTransform) { var cameraWorldPos = cameraTransform.Translation(); var cameraToPosition = position.Subtract(cameraWorldPos); // Limit the distance of the object from the camera to a maximum of 10m if (cameraToPosition.LengthFast > 10) { cameraToPosition = cameraToPosition.Normalized() * 10; } // Compute the average distance of the object from the camera over the last ten // updates. If filterPosition is true, compute a new position for the object // with this average. Notice that the distance is applied to the vector from // the camera to the content, so it only affects the percieved distance of the // object - the averaging does _not_ make the content "lag". var hitTestResultDistance = cameraToPosition.LengthFast; virtualObject.RecentVirtualObjectDistances.Add(hitTestResultDistance); virtualObject.RecentVirtualObjectDistances.KeepLast(10); if (filterPosition) { var averageDistance = virtualObject.RecentVirtualObjectDistances.Average(); var averagedDistancePos = cameraWorldPos + cameraToPosition.Normalized() * averageDistance; virtualObject.Position = averagedDistancePos; } else { virtualObject.Position = cameraWorldPos + cameraToPosition; } }
public void DidLoad(VirtualObjectManager manager, VirtualObject virtualObject) { IsLoadingObject = false; //Remove progress indicator Spinner.RemoveFromSuperview(); AddObjectButton.SetImage(UIImage.FromBundle("add"), UIControlState.Normal); AddObjectButton.SetImage(UIImage.FromBundle("addPressed"), UIControlState.Highlighted); }
public SingleFingerGesture(NSSet touches, ARSCNView scnView, VirtualObject lastUsedObject, VirtualObjectManager vom) : base(touches, scnView, lastUsedObject, vom) { var firstTouch = (UITouch)touches.First(); initialTouchPosition = firstTouch.LocationInView(scnView); latestTouchPosition = initialTouchPosition; firstTouchedObject = this.VirtualObjectAt(initialTouchPosition); }
public void WillLoad(VirtualObjectManager manager, VirtualObject virtualObject) { // Show progress indicator Spinner = new UIActivityIndicatorView(); Spinner.Center = AddObjectButton.Center; Spinner.Bounds = new CGRect(0, 0, AddObjectButton.Bounds.Width - 5, AddObjectButton.Bounds.Height - 5); AddObjectButton.SetImage(UIImage.FromBundle("buttonring"), UIControlState.Normal); SceneView.AddSubview(Spinner); Spinner.StartAnimating(); IsLoadingObject = true; }
public Gesture(NSSet currentTouches, ARSCNView sceneView, VirtualObject lastUsedObject, VirtualObjectManager manager) { this.currentTouches = currentTouches; this.SceneView = sceneView; this.LastUsedObject = lastUsedObject; this.manager = manager; // Refresh the current gesture at 60 Hz - This ensures smooth updates even when no // new touch events are incoming (but the camera might have moved). this.refreshTimer = new System.Threading.Timer(CheckedUpdateGesture, null, 0, 17); }
private void SetPosition(VirtualObject virtualObject, SCNVector3 position, bool instantly, bool filterPosition, NMatrix4 cameraTransform) { if (instantly) { SetNewVirtualObjectPosition(virtualObject, position, cameraTransform); } else { UpdateVirtualObjectPosition(virtualObject, position, filterPosition, cameraTransform); } }
public TwoFingerGesture(NSSet touches, ARSCNView view, VirtualObject parentObject, VirtualObjectManager manager) : base(touches, view, parentObject, manager) { var tArray = touches.ToArray <UITouch>(); FirstTouch = tArray[0]; SecondTouch = tArray[1]; var firstTouchPoint = FirstTouch.LocationInView(SceneView); var secondTouchPoint = SecondTouch.LocationInView(SceneView); InitialMidpoint = firstTouchPoint.Add(secondTouchPoint).Divide(2f); // Compute the two other corners of the rectangle defined by the two fingers // and compute the points in between. var thirdCorner = new CGPoint(firstTouchPoint.X, secondTouchPoint.Y); var fourthCorner = new CGPoint(secondTouchPoint.X, firstTouchPoint.Y); // Compute points in between. var midpoints = new[] { thirdCorner.Add(firstTouchPoint).Divide(2f), thirdCorner.Add(secondTouchPoint).Divide(2f), fourthCorner.Add(firstTouchPoint).Divide(2f), fourthCorner.Add(secondTouchPoint).Divide(2f), InitialMidpoint.Add(firstTouchPoint).Divide(2f), InitialMidpoint.Add(secondTouchPoint).Divide(2f), InitialMidpoint.Add(thirdCorner).Divide(2f), InitialMidpoint.Add(fourthCorner).Divide(2f) }; // Check if any of the two fingers or their midpoint is touching the object. // Based on that, translation, rotation and scale will be enabled or disabled. var allPoints = new List <CGPoint>(new[] { firstTouchPoint, secondTouchPoint, thirdCorner, fourthCorner, InitialMidpoint }); allPoints.AddRange(midpoints); FirstTouchedObject = allPoints.Select(pt => this.VirtualObjectAt(pt)).Where(vo => vo != null).FirstOrDefault(); if (FirstTouchedObject != null) { ObjectBaseScale = FirstTouchedObject.Scale.X; AllowTranslation = true; AllowRotation = true; InitialDistanceBetweenFingers = (firstTouchPoint.Subtract(secondTouchPoint)).Length(); InitialFingerAngle = Math.Atan2(InitialMidpoint.X, InitialMidpoint.Y); InitialObjectAngle = FirstTouchedObject.EulerAngles.Y; } else { AllowTranslation = false; AllowRotation = false; } }
private void UnloadVirtualObject(VirtualObject vo) { vo.Unload(); vo.RemoveFromParentNode(); if (lastUsedObject == vo) { lastUsedObject = null; if (VirtualObjects.Count() > 1) { lastUsedObject = VirtualObjects[0]; } } }
private void SetNewVirtualObjectPosition(VirtualObject virtualObject, SCNVector3 position, NMatrix4 cameraTransform) { var cameraWorldPos = cameraTransform.Translation(); var cameraToPosition = position.Subtract(cameraWorldPos); // Limit the distance of the object from the camera to a maximum of 10m if (cameraToPosition.LengthFast > 10) { cameraToPosition = cameraToPosition.Normalized() * 10; } virtualObject.Position = cameraWorldPos + cameraToPosition; virtualObject.RecentVirtualObjectDistances.Clear(); }
// Hit tests against the `sceneView` to find an object at the provided point. protected VirtualObject VirtualObjectAt(CGPoint loc) { var hitTestOptions = new SCNHitTestOptions { BoundingBoxOnly = true }; var hitTestResults = SceneView.HitTest(loc, hitTestOptions); if (hitTestResults.Count() == 0) { return(null); } var firstNodeHit = hitTestResults.First((result) => VirtualObject.IsNodePartOfVirtualObject(result.Node)); var voNode = VirtualObject.ForChildNode(firstNodeHit.Node); return(voNode); }
public void LoadVirtualObject(VirtualObject vo, SCNVector3 position, NMatrix4 cameraTransform) { VirtualObjects.Add(vo); if (Delegate != null) { Delegate.WillLoad(this, vo); } vo.Load(); // Immediately place the object in 3D space SetNewVirtualObjectPosition(vo, position, cameraTransform); lastUsedObject = vo; if (Delegate != null) { Delegate.DidLoad(this, vo); } }
public void UpdateRotation(VirtualObject virtualObject, CGPoint span) { var midPointToFirstTouch = span.Divide(2f); var currentAngle = Math.Atan2(midPointToFirstTouch.X, midPointToFirstTouch.Y); var currentAngleToInitialFingerAngle = InitialFingerAngle - currentAngle; if (!RotationThresholdPassed) { var threshold = RotationThreshold; if (TranslationThresholdPassed || ScaleThresholdPassed) { threshold = RotationThresholdHarder; } if ((float)Math.Abs(currentAngleToInitialFingerAngle) > threshold) { RotationThresholdPassed = true; // Change the initial object angle to prevent a sudden jump after crossing the threshold. if (currentAngleToInitialFingerAngle > 0f) { InitialObjectAngle += threshold; } else { InitialObjectAngle -= threshold; } } } if (RotationThresholdPassed) { // Note: // For looking down on the object (99% of all use cases), we need to subtract the angle. // To make rotation also work correctly when looking from below the object one would have to // flip the sign of the angle depending on whether the object is above or below the camera... var x = virtualObject.EulerAngles.X; var y = InitialObjectAngle - currentAngleToInitialFingerAngle; var z = virtualObject.EulerAngles.Z; virtualObject.EulerAngles = new SCNVector3(x, (float)y, z); LastUsedObject = virtualObject; } }
public void DidSelectObjectAt(int index) { if (Session == null || ViewController.CurrentFrame == null) { return; } var cameraTransform = ViewController.CurrentFrame.Camera.Transform; var definition = VirtualObjectManager.AvailableObjects[index]; var vo = new VirtualObject(definition); var position = FocusSquare != null ? FocusSquare.LastPosition : new SCNVector3(0, 0, -1.0f); virtualObjectManager.LoadVirtualObject(vo, position, cameraTransform); if (vo.ParentNode == null) { SceneView.Scene.RootNode.AddChildNode(vo); } }
public void ReactToTouchesBegan(NSSet touches, UIEvent evt, ARSCNView scnView) { if (!VirtualObjects.Any()) { return; } if (currentGesture == null) { currentGesture = Gesture.StartGestureFromTouches(touches, scnView, lastUsedObject, this); } else { currentGesture = currentGesture.UpdateGestureFromTouches(touches, TouchEventType.TouchBegan); } if (currentGesture != null && currentGesture.LastUsedObject != null) { lastUsedObject = currentGesture.LastUsedObject; } }
private void MoveIfNecessary(NSSet touches, UIEvent evt, TouchEventType evtType) { if (VirtualObjects.Count() == 0) { return; } currentGesture = currentGesture?.UpdateGestureFromTouches(touches, evtType); var newObj = currentGesture?.LastUsedObject; if (newObj != null) { lastUsedObject = newObj; } var gesture = currentGesture; var touchedObj = gesture?.LastUsedObject; if (gesture != null && touchedObj != null) { Delegate?.TransformDidChangeFor(this, touchedObj); } }
public void Translate(VirtualObject vObject, ARSCNView sceneView, CGPoint screenPos, bool instantly, bool infinitePlane) { DispatchQueue.MainQueue.DispatchAsync(() => { var result = WorldPositionFromScreenPosition(screenPos, sceneView, vObject.Position, infinitePlane); var newPosition = result.Item1; if (newPosition == null) { if (this.Delegate != null) { this.Delegate.CouldNotPlace(this, vObject); return; } } var currentFrame = ViewController.CurrentFrame; if (currentFrame == null || currentFrame.Camera == null) { return; } var cameraTransform = currentFrame.Camera.Transform; queue.DispatchAsync(() => SetPosition(vObject, newPosition.Value, instantly, result.Item3, cameraTransform)); }); }
public void CouldNotPlace(VirtualObjectManager manager, VirtualObject virtualObject) { UserFeedback.ShowMessage("Cannot place object\nTry moving left or right."); }
// Static factory method public static Gesture StartGestureFromTouches(NSSet currentTouches, ARSCNView sceneView, VirtualObject lastUsedObject, VirtualObjectManager manager) { switch (currentTouches.Count) { case 1: return(new SingleFingerGesture(currentTouches, sceneView, lastUsedObject, manager)); case 2: return(new TwoFingerGesture(currentTouches, sceneView, lastUsedObject, manager)); default: return(null); } }