Gestures

Share on Twitter Share on Facebook Share on LinkedIn

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