Gestures
Description
Gesture Recognisers give us the ability to interact with our virtual objects in different ways such as moving them (pan), rotating them or scaling them (pinch).
There are a number of built in gestures we can make use of:
- UITapGestureRecognizer
- UIPinchGestureRecognizer
- UIRotationGestureRecognizer
- UISwipeGestureRecognizer
- UIPanGestureRecognizer
- UIScreenEdgePanGestureRecognizer
- UILongPressGestureRecognizer
In this lesson we will only use UIPanGestureRecognizer
, UIPinchGestureRecognizer
and UIRotationGestureRecognizer
.
I have really struggled to find Xamarin C# implementations using these in ARKit so I am pleased to show you the code below that I have translated from swift.
One caveat with these gestures is that they can only detect movement in 2 dimensions (as your device screen is only 2D so therefore you can only move your finger across the screen in 2 dimensions).
Therefore whilst it is possible to pan an object up and down (y axis) and left and right (x axis) we cannot detect a pan gesture forwards and backwards (z axis).
However there may be times when we may want this restricted movement in 2 dimensions, for example moving something on a plane/surface like a table or a wall.
Whilst the gestures/interactions in the video don't seem remarkable, they allow us to do some more advanced things later.
Video
Code
using ARKit;
using SceneKit;
using System;
using System.Linq;
using UIKit;
namespace XamarinArkitSample
{
public partial class ViewController : UIViewController
{
private readonly ARSCNView sceneView;
public ViewController(IntPtr handle) : base(handle)
{
this.sceneView = new ARSCNView
{
AutoenablesDefaultLighting = true
};
this.View.AddSubview(this.sceneView);
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
this.sceneView.Frame = this.View.Frame;
}
public override void ViewDidAppear(bool animated)
{
base.ViewDidAppear(animated);
this.sceneView.Session.Run(new ARWorldTrackingConfiguration
{
AutoFocusEnabled = true,
PlaneDetection = ARPlaneDetection.None,
LightEstimationEnabled = true,
WorldAlignment = ARWorldAlignment.GravityAndHeading
}, ARSessionRunOptions.ResetTracking | ARSessionRunOptions.RemoveExistingAnchors);
var panGesture = new UIPanGestureRecognizer(HandlePanGesture);
this.sceneView.AddGestureRecognizer(panGesture);
var rotateGesture = new UIRotationGestureRecognizer(HandleRotateGesture);
this.sceneView.AddGestureRecognizer(rotateGesture);
var pinchGesture = new UIPinchGestureRecognizer(HandlePinchGesture);
this.sceneView.AddGestureRecognizer(pinchGesture);
var tapGesture = new UITapGestureRecognizer(HandleTapGesture);
this.sceneView.AddGestureRecognizer(tapGesture);
var swipeGesture = new UISwipeGestureRecognizer(HandleSwipeGesture);
this.sceneView.AddGestureRecognizer(swipeGesture);
var longPressGesture = new UILongPressGestureRecognizer(HandleLongPressGesture);
this.sceneView.AddGestureRecognizer(longPressGesture);
this.sceneView.Scene.RootNode.AddChildNode(CreateModelNodeFromFile("art.scnassets/mam-tor-1-2.dae"));
}
float currentAngleZ;
float newAngleZ;
private void HandleTapGesture(UITapGestureRecognizer sender)
{
var areaTapped = sender.View as SCNView;
var location = sender.LocationInView(areaTapped);
var hitTestResults = areaTapped.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Black;
node.Geometry.FirstMaterial = material;
}
private void HandleSwipeGesture(UISwipeGestureRecognizer sender)
{
var areaSwiped = sender.View as SCNView;
var location = sender.LocationInView(areaSwiped);
var hitTestResults = areaSwiped.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.SystemPinkColor;
node.Geometry.FirstMaterial = material;
}
private void HandleLongPressGesture(UILongPressGestureRecognizer sender)
{
var areaPressed = sender.View as SCNView;
var location = sender.LocationInView(areaPressed);
var hitTestResults = areaPressed.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Orange;
node.Geometry.FirstMaterial = material;
}
private void HandlePinchGesture(UIPinchGestureRecognizer sender)
{
var areaPinched = sender.View as SCNView;
var location = sender.LocationInView(areaPinched);
var hitTestResults = areaPinched.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
var scaleX = (float)sender.Scale * node.Scale.X;
var scaleY = (float)sender.Scale * node.Scale.Y;
var scaleZ = (float)sender.Scale * node.Scale.Z;
node.Scale = new SCNVector3(scaleX, scaleY, scaleZ);
sender.Scale = 1;
}
private void HandleRotateGesture(UIRotationGestureRecognizer sender)
{
var areaTouched = sender.View as SCNView;
var location = sender.LocationInView(areaTouched);
var hitTestResults = areaTouched.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
newAngleZ = (float)(-sender.Rotation);
newAngleZ += currentAngleZ;
node.EulerAngles = new SCNVector3(node.EulerAngles.X, newAngleZ , node.EulerAngles.Z);
}
private void HandlePanGesture(UIPanGestureRecognizer sender)
{
var areaPanned = sender.View as SCNView;
var location = sender.LocationInView(areaPanned);
var hitTestResults = areaPanned.HitTest(location, new SCNHitTestOptions());
var hitTest = hitTestResults.FirstOrDefault();
if (hitTest == null)
return;
var node = hitTest.Node;
if (sender.State == UIGestureRecognizerState.Changed)
{
var translate = sender.TranslationInView(areaPanned);
// Only allow movement vertically or horizontally
node.LocalTranslate(new SCNVector3((float)translate.X / 10000f, (float)-translate.Y / 10000, 0.0f));
}
}
public static SCNNode CreateModelNodeFromFile(string filePath)
{
var sceneFromFile = SCNScene.FromFile(filePath);
var material = new SCNMaterial();
material.Diffuse.Contents = UIColor.Red;
var model = sceneFromFile.RootNode.FindChildNode("EXPORT_GOOGLE_SAT_WM", true);
model.Scale = new SCNVector3(0.1f, 0.1f, 0.1f);
model.Position = new SCNVector3(0, -0.2f, 0);
return model;
}
public override void ViewDidDisappear(bool animated)
{
base.ViewDidDisappear(animated);
this.sceneView.Session.Pause();
}
public override void DidReceiveMemoryWarning()
{
base.DidReceiveMemoryWarning();
}
}
}
Next Step : Virtual table items
After you have mastered this you should try Virtual table items