美文网首页
MR开发实战Hololens项目之智能建筑类沙盘展示

MR开发实战Hololens项目之智能建筑类沙盘展示

作者: TonyWan_AR | 来源:发表于2020-11-20 18:33 被阅读0次

    一、框架视图

    二、关键代码

    NearInteractionTouchableUnityUI

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using System;
    using System.Collections.Generic;
    using UnityEngine;
    
    namespace Microsoft.MixedReality.Toolkit.Input
    {
        /// <summary>
        /// Use a Unity UI RectTransform as touchable surface.
        /// </summary>
        [RequireComponent(typeof(RectTransform))]
        [AddComponentMenu("Scripts/MRTK/Services/NearInteractionTouchableUnityUI")]
        public class NearInteractionTouchableUnityUI : NearInteractionTouchableSurface
        {
            private Lazy<RectTransform> rectTransform;
    
            public static IReadOnlyList<NearInteractionTouchableUnityUI> Instances => instances;
    
            public override Vector3 LocalCenter => Vector3.zero;
            public override Vector3 LocalPressDirection => Vector3.forward;
            public override Vector2 Bounds => rectTransform.Value.rect.size;
    
            private static readonly List<NearInteractionTouchableUnityUI> instances = new List<NearInteractionTouchableUnityUI>();
    
            public NearInteractionTouchableUnityUI()
            {
                rectTransform = new Lazy<RectTransform>(GetComponent<RectTransform>);
            }
    
            /// <inheritdoc />
            public override float DistanceToTouchable(Vector3 samplePoint, out Vector3 normal)
            {
                normal = transform.TransformDirection(-LocalPressDirection);
    
                Vector3 localPoint = transform.InverseTransformPoint(samplePoint);
    
                // touchables currently can only be touched within the bounds of the rectangle.
                // We return infinity to ensure that any point outside the bounds does not get touched.
                if (!rectTransform.Value.rect.Contains(localPoint))
                {
                    return float.PositiveInfinity;
                }
    
                // Scale back to 3D space
                localPoint = transform.TransformSize(localPoint);
    
                return Math.Abs(localPoint.z);
            }
    
            protected void OnEnable()
            {
                instances.Add(this);
            }
    
            protected void OnDisable()
            {
                instances.Remove(this);
            }
        }
    }
    

    CanvasUtility

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using UnityEngine;
    using UnityEngine.EventSystems;
    
    namespace Microsoft.MixedReality.Toolkit.Input.Utilities
    {
        /// <summary>
        /// Helper class for setting up canvases for use in the MRTK.
        /// </summary>
        [DisallowMultipleComponent]
        [RequireComponent(typeof(Canvas))]
        [AddComponentMenu("Scripts/MRTK/Services/CanvasUtility")]
        public class CanvasUtility : MonoBehaviour, IMixedRealityPointerHandler
        {
            private bool oldIsTargetPositionLockedOnFocusLock = false;
            public void OnPointerClicked(MixedRealityPointerEventData eventData) {}
    
            public void OnPointerDown(MixedRealityPointerEventData eventData)
            {
                oldIsTargetPositionLockedOnFocusLock = eventData.Pointer.IsTargetPositionLockedOnFocusLock;
                if (!(eventData.Pointer is IMixedRealityNearPointer) && eventData.Pointer.Controller.IsRotationAvailable)
                {
                    eventData.Pointer.IsTargetPositionLockedOnFocusLock = false;
                }
            }
    
            public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
    
            public void OnPointerUp(MixedRealityPointerEventData eventData)
            {
                eventData.Pointer.IsTargetPositionLockedOnFocusLock = oldIsTargetPositionLockedOnFocusLock;
            }
    
            private void Start()
            {
                Canvas canvas = GetComponent<Canvas>();
                Debug.Assert(canvas != null);
    
                if (canvas.worldCamera == null)
                {
                    Debug.Assert(CoreServices.InputSystem?.FocusProvider?.UIRaycastCamera != null, this);
                    canvas.worldCamera = CoreServices.InputSystem?.FocusProvider?.UIRaycastCamera;
    
                    if (EventSystem.current == null)
                    {
                        Debug.LogError("No EventSystem detected. UI events will not be propagated to Unity UI.");
                    }
                }
                else
                {
                    Debug.LogError("World Space Canvas should have no camera set to work properly with Mixed Reality Toolkit. At runtime, they'll get their camera set automatically.");
                }
            }
        }
    }
    
    

    VideoScreen

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.UI;
    using UnityEngine.Video;
    public class VideoScreen : MonoBehaviour
    {
        public VideoPlayer videoPlayer;
        public RawImage videoImage;
        private bool isPlay=true;
        void Start()
        {
            videoPlayer.loopPointReached += EndReached;
        }
    
     
        void Update()
        {
            if (videoPlayer.texture == null) return;
            videoImage.texture = videoPlayer.texture;
        }
    
        public void PlayOrPauseVideo()
        {
    
            if (isPlay) {
                videoPlayer.transform.gameObject.SetActive(true);
                videoPlayer.Play();
            } else {
                videoPlayer.transform.gameObject.SetActive(false);
                videoPlayer.Pause();
            }
                isPlay = !isPlay;
           
    
            //  SendPause
    
        }
    
        private void EndReached(VideoPlayer source)
        {
            //  gameObject.SetActive(false);
        }
    }
    
    
     public void SetRotation() {
    
            transform.localEulerAngles = new Vector3(0,transform.localEulerAngles.y,0);
        }
    

    ObjectManipulator

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.MixedReality.Toolkit.Input;
    using Microsoft.MixedReality.Toolkit.Physics;
    using Microsoft.MixedReality.Toolkit.UI;
    using Microsoft.MixedReality.Toolkit.Utilities;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Assertions;
    using UnityEngine.Serialization;
    
    namespace Microsoft.MixedReality.Toolkit.Experimental.UI
    {
        /// <summary>
        /// This script allows for an object to be movable, scalable, and rotatable with one or two hands. 
        /// You may also configure the script on only enable certain manipulations. The script works with 
        /// both HoloLens' gesture input and immersive headset's motion controller input.
        /// </summary>
        [HelpURL("https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/README_ManipulationHandler.html")]
        public class ObjectManipulator : MonoBehaviour, IMixedRealityPointerHandler, IMixedRealityFocusChangedHandler
        {
            #region Public Enums
    
            /// <summary>
            /// Describes what pivot the manipulated object will rotate about when
            /// you rotate your hand. This is not a description of any limits or 
            /// additional rotation logic. If no other factors (such as constraints)
            /// are involved, rotating your hand by an amount should rotate the object
            /// by the same amount.
            /// For example a possible future value here is RotateAboutUserDefinedPoint
            /// where the user could specify a pivot that the object is to rotate
            /// around.
            /// An example of a value that should not be found here is MaintainRotationToUser
            /// as this restricts rotation of the object when we rotate the hand.
            /// </summary>
            public enum RotateInOneHandType
            {
                RotateAboutObjectCenter,
                RotateAboutGrabPoint
            };
            [System.Flags]
            public enum ReleaseBehaviorType
            {
                KeepVelocity = 1 << 0,
                KeepAngularVelocity = 1 << 1
            }
            #endregion Public Enums
    
            #region Serialized Fields
    
            [SerializeField]
            [Tooltip("Transform that will be dragged. Defaults to the object of the component.")]
            private Transform hostTransform = null;
    
            /// <summary>
            /// Transform that will be dragged. Defaults to the object of the component.
            /// </summary>
            public Transform HostTransform
            {
                get
                {
                    if (hostTransform == null)
                    {
                        hostTransform = gameObject.transform;
                    }
    
                    return hostTransform;
                }
                set => hostTransform = value;
            }
            
            [SerializeField]
            [EnumFlags]
            [Tooltip("Can manipulation be done only with one hand, only with two hands, or with both?")]
            private ManipulationHandFlags manipulationType = ManipulationHandFlags.OneHanded | ManipulationHandFlags.TwoHanded;
    
            /// <summary>
            /// Can manipulation be done only with one hand, only with two hands, or with both?
            /// </summary>
            public ManipulationHandFlags ManipulationType
            {
                get => manipulationType;
                set => manipulationType = value;
            }
    
            [SerializeField]
            [EnumFlags]
            [Tooltip("What manipulation will two hands perform?")]
            private TransformFlags twoHandedManipulationType = TransformFlags.Move | TransformFlags.Rotate | TransformFlags.Scale;
    
            /// <summary>
            /// What manipulation will two hands perform?
            /// </summary>
            public TransformFlags TwoHandedManipulationType
            {
                get => twoHandedManipulationType;
                set => twoHandedManipulationType = value;
            }
    
            [SerializeField]
            [Tooltip("Specifies whether manipulation can be done using far interaction with pointers.")]
            private bool allowFarManipulation = true;
    
            /// <summary>
            /// Specifies whether manipulation can be done using far interaction with pointers.
            /// </summary>
            public bool AllowFarManipulation
            {
                get => allowFarManipulation;
                set => allowFarManipulation = value;
            }
    
            [SerializeField]
            [Tooltip("Rotation behavior of object when using one hand near")]
            private RotateInOneHandType oneHandRotationModeNear = RotateInOneHandType.RotateAboutGrabPoint;
    
            /// <summary>
            /// Rotation behavior of object when using one hand near
            /// </summary>
            public RotateInOneHandType OneHandRotationModeNear
            {
                get => oneHandRotationModeNear;
                set => oneHandRotationModeNear = value;
            }
    
            [SerializeField]
            [Tooltip("Rotation behavior of object when using one hand at distance")]
            private RotateInOneHandType oneHandRotationModeFar = RotateInOneHandType.RotateAboutGrabPoint;
    
            /// <summary>
            /// Rotation behavior of object when using one hand at distance
            /// </summary>
            public RotateInOneHandType OneHandRotationModeFar
            {
                get => oneHandRotationModeFar;
                set => oneHandRotationModeFar = value;
            }
    
            [SerializeField]
            [EnumFlags]
            [Tooltip("Rigid body behavior of the dragged object when releasing it.")]
            private ReleaseBehaviorType releaseBehavior = ReleaseBehaviorType.KeepVelocity | ReleaseBehaviorType.KeepAngularVelocity;
    
            /// <summary>
            /// Rigid body behavior of the dragged object when releasing it.
            /// </summary>
            public ReleaseBehaviorType ReleaseBehavior
            {
                get => releaseBehavior;
                set => releaseBehavior = value;
            }
            
            [SerializeField]
            [Tooltip("Check to enable frame-rate independent smoothing.")]
            private bool smoothingActive = true;
    
            /// <summary>
            /// Check to enable frame-rate independent smoothing.
            /// </summary>
            public bool SmoothingActive
            {
                get => smoothingActive;
                set => smoothingActive = value;
            }
    
            [SerializeField]
            [Range(0, 1)]
            [Tooltip("Enter amount representing amount of smoothing to apply to the movement. Smoothing of 0 means no smoothing. Max value means no change to value.")]
            private float moveLerpTime = 0.001f;
    
            /// <summary>
            /// Enter amount representing amount of smoothing to apply to the movement. Smoothing of 0 means no smoothing. Max value means no change to value.
            /// </summary>
            public float MoveLerpTime
            {
                get => moveLerpTime;
                set => moveLerpTime = value;
            }
    
            [SerializeField]
            [Range(0, 1)]
            [Tooltip("Enter amount representing amount of smoothing to apply to the rotation. Smoothing of 0 means no smoothing. Max value means no change to value.")]
            private float rotateLerpTime = 0.001f;
    
            /// <summary>
            /// Enter amount representing amount of smoothing to apply to the rotation. Smoothing of 0 means no smoothing. Max value means no change to value.
            /// </summary>
            public float RotateLerpTime
            {
                get => rotateLerpTime;
                set => rotateLerpTime = value;
            }
    
            [SerializeField]
            [Range(0, 1)]
            [Tooltip("Enter amount representing amount of smoothing to apply to the scale. Smoothing of 0 means no smoothing. Max value means no change to value.")]
            private float scaleLerpTime = 0.001f;
    
            /// <summary>
            /// Enter amount representing amount of smoothing to apply to the scale. Smoothing of 0 means no smoothing. Max value means no change to value.
            /// </summary>
            public float ScaleLerpTime
            {
                get => scaleLerpTime;
                set => scaleLerpTime = value;
            }
    
            #endregion Serialized Fields
    
            #region Event handlers
            [Header("Manipulation Events")]
            [SerializeField]
            [FormerlySerializedAs("OnManipulationStarted")]
            private ManipulationEvent onManipulationStarted = new ManipulationEvent();
    
            /// <summary>
            /// Unity event raised on manipulation started
            /// </summary>
            public ManipulationEvent OnManipulationStarted
            {
                get => onManipulationStarted;
                set => onManipulationStarted = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnManipulationEnded")]
            private ManipulationEvent onManipulationEnded = new ManipulationEvent();
    
            /// <summary>
            /// Unity event raised on manipulation ended
            /// </summary>
            public ManipulationEvent OnManipulationEnded
            {
                get => onManipulationEnded;
                set => onManipulationEnded = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnHoverEntered")]
            private ManipulationEvent onHoverEntered = new ManipulationEvent();
    
            /// <summary>
            /// Unity event raised on hover started
            /// </summary>
            public ManipulationEvent OnHoverEntered
            {
                get => onHoverEntered;
                set => onHoverEntered = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnHoverExited")]
            private ManipulationEvent onHoverExited = new ManipulationEvent();
    
            /// <summary>
            /// Unity event raised on hover ended
            /// </summary>
            public ManipulationEvent OnHoverExited
            {
                get => onHoverExited;
                set => onHoverExited = value;
            }
            #endregion
    
            #region Private Properties
            
            private ManipulationMoveLogic moveLogic;
            private TwoHandScaleLogic scaleLogic;
            private TwoHandRotateLogic rotateLogic;
    
            /// <summary>
            /// Holds the pointer and the initial intersection point of the pointer ray 
            /// with the object on pointer down in pointer space
            /// </summary>
            private struct PointerData
            {
                public IMixedRealityPointer pointer;
                private Vector3 initialGrabPointInPointer;
    
                public PointerData(IMixedRealityPointer pointer, Vector3 worldGrabPoint) : this()
                {
                    this.pointer = pointer;
                    this.initialGrabPointInPointer = Quaternion.Inverse(pointer.Rotation) * (worldGrabPoint - pointer.Position);
                }
    
                public bool IsNearPointer => pointer is IMixedRealityNearPointer;
    
                /// Returns the grab point on the manipulated object in world space
                public Vector3 GrabPoint => (pointer.Rotation * initialGrabPointInPointer) + pointer.Position;
            }
    
            private Dictionary<uint, PointerData> pointerIdToPointerMap = new Dictionary<uint, PointerData>();
            private Quaternion objectToGripRotation;
            private bool isNearManipulation;
            private bool isManipulationStarted;
    
            private Rigidbody rigidBody;
            private bool wasKinematic = false;
    
            private ConstraintManager constraints;
    
            private bool IsOneHandedManipulationEnabled => manipulationType.HasFlag(ManipulationHandFlags.OneHanded) && pointerIdToPointerMap.Count == 1;
            private bool IsTwoHandedManipulationEnabled => manipulationType.HasFlag(ManipulationHandFlags.TwoHanded) && pointerIdToPointerMap.Count > 1;
    
            #endregion
    
            #region MonoBehaviour Functions
    
            private void Awake()
            {
                moveLogic = new ManipulationMoveLogic();
                rotateLogic = new TwoHandRotateLogic();
                scaleLogic = new TwoHandScaleLogic();
            }
            private void Start()
            {
                rigidBody = HostTransform.GetComponent<Rigidbody>();
                constraints = new ConstraintManager(gameObject);
            }
            #endregion MonoBehaviour Functions
    
            #region Private Methods
            private Vector3 GetPointersGrabPoint()
            {
                Vector3 sum = Vector3.zero;
                int count = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    sum += p.GrabPoint;
                    count++;
                }
                return sum / Math.Max(1, count);
            }
    
            private MixedRealityPose GetPointersPose()
            {
                Vector3 sumPos = Vector3.zero;
                Vector3 sumDir = Vector3.zero;
                int count = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    sumPos += p.pointer.Position;
                    sumDir += p.pointer.Rotation * Vector3.forward;
                    count++;
                }
    
                return new MixedRealityPose
                {
                    Position = sumPos / Math.Max(1, count),
                    Rotation = Quaternion.LookRotation(sumDir / Math.Max(1, count))
                };
            }
    
            private Vector3 GetPointersVelocity()
            {
                Vector3 sum = Vector3.zero;
                int numControllers = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    // Check pointer has a valid controller (e.g. gaze pointer doesn't)
                    if (p.pointer.Controller != null)
                    {
                        numControllers++;
                        sum += p.pointer.Controller.Velocity;
                    }
                }
                return sum / Math.Max(1, numControllers);
            }
    
            private Vector3 GetPointersAngularVelocity()
            {
                Vector3 sum = Vector3.zero;
                int numControllers = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    // Check pointer has a valid controller (e.g. gaze pointer doesn't)
                    if (p.pointer.Controller != null)
                    {
                        numControllers++;
                        sum += p.pointer.Controller.AngularVelocity;
                    }
                }
                return sum / Math.Max(1, numControllers);
            }
    
            private bool IsNearManipulation()
            {
                foreach (var item in pointerIdToPointerMap)
                {
                    if (item.Value.IsNearPointer)
                    {
                        return true;
                    }
                }
                return false;
            }
            #endregion Private Methods
    
            #region Public Methods
    
            /// <summary>
            /// Releases the object that is currently manipulated
            /// </summary>
            public void ForceEndManipulation()
            {
                // end manipulation
                if (isManipulationStarted)
                {
                    HandleManipulationEnded(GetPointersGrabPoint(), GetPointersVelocity(), GetPointersAngularVelocity());
                }
                pointerIdToPointerMap.Clear();
            }
    
            /// <summary>
            /// Gets the grab point for the given pointer id.
            /// Only use if you know that your given pointer id corresponds to a pointer that has grabbed
            /// this component.
            /// </summary>
            public Vector3 GetPointerGrabPoint(uint pointerId)
            {
                Assert.IsTrue(pointerIdToPointerMap.ContainsKey(pointerId));
                return pointerIdToPointerMap[pointerId].GrabPoint;
            }
    
            #endregion Public Methods
    
            #region Hand Event Handlers
    
            /// <inheritdoc />
            public void OnPointerDown(MixedRealityPointerEventData eventData)
            {
                if (eventData.used || 
                    (!allowFarManipulation && eventData.Pointer as IMixedRealityNearPointer == null))
                {
                    return;
                }
    
                // If we only allow one handed manipulations, check there is no hand interacting yet. 
                if (manipulationType != ManipulationHandFlags.OneHanded || pointerIdToPointerMap.Count == 0)
                {
                    uint id = eventData.Pointer.PointerId;
                    // Ignore poke pointer events
                    if (!pointerIdToPointerMap.ContainsKey(id))
                    {
                        // cache start ptr grab point
                        pointerIdToPointerMap.Add(id, new PointerData(eventData.Pointer, eventData.Pointer.Result.Details.Point));
    
                        // Call manipulation started handlers
                        if (IsTwoHandedManipulationEnabled)
                        {
                            if (!isManipulationStarted)
                            {
                                HandleManipulationStarted();
                            }
                            HandleTwoHandManipulationStarted();
                        }
                        else if (IsOneHandedManipulationEnabled)
                        {
                            if (!isManipulationStarted)
                            {
                                HandleManipulationStarted();
                            }
                            HandleOneHandMoveStarted();
                        }
                    }
                }
    
                if (pointerIdToPointerMap.Count > 0)
                {
                    // Always mark the pointer data as used to prevent any other behavior to handle pointer events
                    // as long as the ManipulationHandler is active.
                    // This is due to us reacting to both "Select" and "Grip" events.
                    eventData.Use();
                }
            }
    
            public void OnPointerDragged(MixedRealityPointerEventData eventData)
            {                    
                // Call manipulation updated handlers
                if (IsOneHandedManipulationEnabled)
                {
                    HandleOneHandMoveUpdated();
                }
                else if (IsTwoHandedManipulationEnabled)
                {
                    HandleTwoHandManipulationUpdated();
                }
            }
    
            /// <inheritdoc />
            public void OnPointerUp(MixedRealityPointerEventData eventData)
            {
                // Get pointer data before they are removed from the map
                Vector3 grabPoint = GetPointersGrabPoint();
                Vector3 velocity = GetPointersVelocity();
                Vector3 angularVelocity = GetPointersAngularVelocity();
    
                uint id = eventData.Pointer.PointerId;
                if (pointerIdToPointerMap.ContainsKey(id))
                {
                    pointerIdToPointerMap.Remove(id);
                }
    
                // Call manipulation ended handlers
                var handsPressedCount = pointerIdToPointerMap.Count;
                if (manipulationType.HasFlag(ManipulationHandFlags.TwoHanded) && handsPressedCount == 1)
                {
                    if (manipulationType.HasFlag(ManipulationHandFlags.OneHanded))
                    {
                        HandleOneHandMoveStarted();
                    }
                    else
                    {
                        HandleManipulationEnded(grabPoint, velocity, angularVelocity);
                    }
                }
                else if (isManipulationStarted && handsPressedCount == 0)
                {
                    HandleManipulationEnded(grabPoint, velocity, angularVelocity);
                }
    
                eventData.Use();
            }
    
            #endregion Hand Event Handlers
    
            #region Private Event Handlers
            private void HandleTwoHandManipulationStarted()
            {
                var handPositionArray = GetHandPositionArray();
    
                if (twoHandedManipulationType.HasFlag(TransformFlags.Rotate))
                {
                    rotateLogic.Setup(handPositionArray, HostTransform);
                }
                if (twoHandedManipulationType.HasFlag(TransformFlags.Move))
                {
                    MixedRealityPose pointerPose = GetPointersPose();
                    MixedRealityPose hostPose = new MixedRealityPose(HostTransform.position, HostTransform.rotation);
                    moveLogic.Setup(pointerPose, GetPointersGrabPoint(), hostPose, HostTransform.localScale);
                }
                if (twoHandedManipulationType.HasFlag(TransformFlags.Scale))
                {
                    scaleLogic.Setup(handPositionArray, HostTransform);
                }
            }
    
            private void HandleTwoHandManipulationUpdated()
            {
                var targetTransform = new MixedRealityTransform(HostTransform.position, HostTransform.rotation, HostTransform.localScale);
    
                var handPositionArray = GetHandPositionArray();
    
                if (twoHandedManipulationType.HasFlag(TransformFlags.Scale))
                {
                    targetTransform.Scale = scaleLogic.UpdateMap(handPositionArray);
                    constraints.ApplyScaleConstraints(ref targetTransform, false, IsNearManipulation());
                }
                if (twoHandedManipulationType.HasFlag(TransformFlags.Rotate))
                {
                    targetTransform.Rotation = rotateLogic.Update(handPositionArray, targetTransform.Rotation);
                    constraints.ApplyRotationConstraints(ref targetTransform, false, IsNearManipulation());
                }
                if (twoHandedManipulationType.HasFlag(TransformFlags.Move))
                {
                    MixedRealityPose pose = GetPointersPose();
                    targetTransform.Position = moveLogic.Update(pose, targetTransform.Rotation, targetTransform.Scale, true);
                    constraints.ApplyTranslationConstraints(ref targetTransform, false, IsNearManipulation());
                }
    
                ApplyTargetTransform(targetTransform);
            }
    
            private void HandleOneHandMoveStarted()
            {
                Assert.IsTrue(pointerIdToPointerMap.Count == 1);
                PointerData pointerData = GetFirstPointer();
                IMixedRealityPointer pointer = pointerData.pointer;
    
                // Calculate relative transform from object to grip.
                Quaternion gripRotation;
                TryGetGripRotation(pointer, out gripRotation);
                Quaternion worldToGripRotation = Quaternion.Inverse(gripRotation);
                objectToGripRotation = worldToGripRotation * HostTransform.rotation;
    
                MixedRealityPose pointerPose = new MixedRealityPose(pointer.Position, pointer.Rotation);
                MixedRealityPose hostPose = new MixedRealityPose(HostTransform.position, HostTransform.rotation);
                moveLogic.Setup(pointerPose, pointerData.GrabPoint, hostPose, HostTransform.localScale);            
            }
    
            private void HandleOneHandMoveUpdated()
            {
                Debug.Assert(pointerIdToPointerMap.Count == 1);
                PointerData pointerData = GetFirstPointer();
                IMixedRealityPointer pointer = pointerData.pointer;
    
                var targetTransform = new MixedRealityTransform(HostTransform.position, HostTransform.rotation, HostTransform.localScale);
    
                constraints.ApplyScaleConstraints(ref targetTransform, true, IsNearManipulation());
    
                Quaternion gripRotation;
                TryGetGripRotation(pointer, out gripRotation);
                targetTransform.Rotation = gripRotation * objectToGripRotation;
    
                constraints.ApplyRotationConstraints(ref targetTransform, true, IsNearManipulation());
    
                RotateInOneHandType rotateInOneHandType = isNearManipulation ? oneHandRotationModeNear : oneHandRotationModeFar;
                MixedRealityPose pointerPose = new MixedRealityPose(pointer.Position, pointer.Rotation);
                targetTransform.Position = moveLogic.Update(pointerPose, targetTransform.Rotation, targetTransform.Scale, rotateInOneHandType != RotateInOneHandType.RotateAboutObjectCenter);
    
                constraints.ApplyTranslationConstraints(ref targetTransform, true, IsNearManipulation());
    
                ApplyTargetTransform(targetTransform);
            }
    
            private void HandleManipulationStarted()
            {
                isManipulationStarted = true;
                isNearManipulation = IsNearManipulation();
                // TODO: If we are on HoloLens 1, push and pop modal input handler so that we can use old
                // gaze/gesture/voice manipulation. For HoloLens 2, we don't want to do this.
                if (OnManipulationStarted != null)
                {
                    OnManipulationStarted.Invoke(new ManipulationEventData
                    {
                        ManipulationSource = gameObject,
                        IsNearInteraction = isNearManipulation,
                        Pointer = GetFirstPointer().pointer,
                        PointerCentroid = GetPointersGrabPoint(),
                        PointerVelocity = GetPointersVelocity(),
                        PointerAngularVelocity = GetPointersAngularVelocity()
                    });
                }
    
                if (rigidBody != null)
                {
                    wasKinematic = rigidBody.isKinematic;
                    rigidBody.isKinematic = false;
                }
                
                constraints.Initialize(new MixedRealityPose(HostTransform.position, HostTransform.rotation));
            }
    
            private void HandleManipulationEnded(Vector3 pointerGrabPoint, Vector3 pointerVelocity, Vector3 pointerAnglularVelocity)
            {
                isManipulationStarted = false;
                // TODO: If we are on HoloLens 1, push and pop modal input handler so that we can use old
                // gaze/gesture/voice manipulation. For HoloLens 2, we don't want to do this.
                if (OnManipulationEnded != null)
                {
                    OnManipulationEnded.Invoke(new ManipulationEventData
                    {
                        ManipulationSource = gameObject,
                        IsNearInteraction = isNearManipulation,
                        PointerCentroid = pointerGrabPoint,
                        PointerVelocity = pointerVelocity,
                        PointerAngularVelocity = pointerAnglularVelocity
                    }); 
                }
                
                ReleaseRigidBody(pointerVelocity, pointerAnglularVelocity);
            }
    
            #endregion Private Event Handlers
    
            #region Unused Event Handlers
            /// <inheritdoc />
            public void OnPointerClicked(MixedRealityPointerEventData eventData) { }
            public void OnBeforeFocusChange(FocusEventData eventData) { }
    
            #endregion Unused Event Handlers
    
            #region Private methods
    
            private void ApplyTargetTransform(MixedRealityTransform targetTransform)
            {
                if (rigidBody == null)
                {
                    HostTransform.position = SmoothTo(HostTransform.position, targetTransform.Position, moveLerpTime);
                    HostTransform.rotation = SmoothTo(HostTransform.rotation, targetTransform.Rotation, rotateLerpTime);
                    HostTransform.localScale = SmoothTo(HostTransform.localScale, targetTransform.Scale, scaleLerpTime);
                }
                else
                {
                    rigidBody.velocity = ((1f - Mathf.Pow(moveLerpTime, Time.deltaTime)) / Time.deltaTime) * (targetTransform.Position - HostTransform.position);
    
                    var relativeRotation = targetTransform.Rotation * Quaternion.Inverse(HostTransform.rotation);
                    relativeRotation.ToAngleAxis(out float angle, out Vector3 axis);
                    if (angle > 180f)
                        angle -= 360f;
                    if (axis.IsValidVector())
                    {
                        rigidBody.angularVelocity = ((1f - Mathf.Pow(rotateLerpTime, Time.deltaTime)) / Time.deltaTime) * (axis.normalized * angle * Mathf.Deg2Rad);
                    }
    
                    HostTransform.localScale = SmoothTo(HostTransform.localScale, targetTransform.Scale, scaleLerpTime);
                }
            }
    
            private Vector3 SmoothTo(Vector3 source, Vector3 goal, float lerpTime)
            {
                return Vector3.Lerp(source, goal, (!smoothingActive || lerpTime == 0f) ? 1f : 1f - Mathf.Pow(lerpTime, Time.deltaTime));
            }
    
            private Quaternion SmoothTo(Quaternion source, Quaternion goal, float slerpTime)
            {
                return Quaternion.Slerp(source, goal, (!smoothingActive || slerpTime == 0f) ? 1f : 1f - Mathf.Pow(slerpTime, Time.deltaTime));
            }
    
            private Vector3[] GetHandPositionArray()
            {
                var handPositionMap = new Vector3[pointerIdToPointerMap.Count];
                int index = 0;
                foreach (var item in pointerIdToPointerMap)
                {
                    handPositionMap[index++] = item.Value.pointer.Position;
                }
                return handPositionMap;
            }
    
            public void OnFocusChanged(FocusEventData eventData)
            {
                bool isFar = !(eventData.Pointer is IMixedRealityNearPointer);
                if (isFar && !AllowFarManipulation)
                {
                    return;
                }
    
                if (eventData.OldFocusedObject == null ||
                    !eventData.OldFocusedObject.transform.IsChildOf(transform))
                {
                    if (OnHoverEntered != null)
                    {
                        OnHoverEntered.Invoke(new ManipulationEventData
                        {
                            ManipulationSource = gameObject,
                            Pointer = eventData.Pointer,
                            IsNearInteraction = !isFar
                        });
                    }
                }
                else if (eventData.NewFocusedObject == null ||
                        !eventData.NewFocusedObject.transform.IsChildOf(transform))
                {
                    if (OnHoverExited != null)
                    {
                        OnHoverExited.Invoke(new ManipulationEventData
                        {
                            ManipulationSource = gameObject,
                            Pointer = eventData.Pointer,
                            IsNearInteraction = !isFar
                        });
                    }
                }
            }
    
            private void ReleaseRigidBody(Vector3 velocity, Vector3 angularVelocity)
            {
                if (rigidBody != null)
                {
                    rigidBody.isKinematic = wasKinematic;
    
                    if (releaseBehavior.HasFlag(ReleaseBehaviorType.KeepVelocity))
                    {
                        rigidBody.velocity = velocity;
                    }
    
                    if (releaseBehavior.HasFlag(ReleaseBehaviorType.KeepAngularVelocity))
                    {
                        rigidBody.angularVelocity = angularVelocity;
                    }
                }
            }
    
            private PointerData GetFirstPointer()
            {
                // We may be able to do this without allocating memory.
                // Moving to a method for later investigation.
                return pointerIdToPointerMap.Values.First();
            }
    
            private bool TryGetGripRotation(IMixedRealityPointer pointer, out Quaternion rotation)
            {
                for (int i = 0; i < pointer.Controller.Interactions.Length; i++)
                {
                    if (pointer.Controller.Interactions[i].InputType == DeviceInputType.SpatialGrip)
                    {
                        rotation = pointer.Controller.Interactions[i].RotationData;
                        return true;
                    }
                }
                rotation = Quaternion.identity;
                return false;
            }
    
            #endregion
        }
    }
    
    

    NearInteractionGrabbable

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using System;
    using UnityEngine;
    
    namespace Microsoft.MixedReality.Toolkit.Input
    {
        /// <summary>
        /// Add a NearInteractionGrabbable component to any GameObject that has a collidable
        /// on it in order to make that collidable near grabbable.
        /// 
        /// Any IMixedRealityNearPointer will then dispatch pointer events
        /// to the closest near grabbable objects.
        ///
        /// Additionally, the near pointer will send focus enter and exit events when the 
        /// decorated object is the closest object to the near pointer
        /// </summary>
        [AddComponentMenu("Scripts/MRTK/Services/NearInteractionGrabbable")]
        public class NearInteractionGrabbable : MonoBehaviour
        {
            [Tooltip("Check to show a tether from the position where object was grabbed to the hand when manipulating. Useful for things like bounding boxes where resizing/rotating might be constrained.")]
            public bool ShowTetherWhenManipulating = false;
    
            void OnEnable()
            {
                // As of https://docs.unity3d.com/ScriptReference/Physics.ClosestPoint.html
                // ClosestPoint call will only work on specific types of colliders.
                // Using incorrect type of collider will emit warning from FocusProvider, 
                // but grab behavior will be broken at this point.
                // Emit exception on initialization, when we know grab interaction is used 
                // on this object to make an error clearly visible.
    
                var collider = gameObject.GetComponent<Collider>();
    
                if((collider as BoxCollider) == null && 
                    (collider as CapsuleCollider) == null &&
                    (collider as SphereCollider) == null &&
                    ((collider as MeshCollider) == null || (collider as MeshCollider).convex == false))
                {
                    Debug.LogException(new InvalidOperationException("NearInteractionGrabbable requires a " +
                        "BoxCollider, SphereCollider, CapsuleCollider or a convex MeshCollider on an object. " +
                        "Otherwise grab interaction will not work correctly."));
                }
            }
        }
    }
    

    LockRotation

    using UnityEngine;
    using System.Collections;
    
    public class LockRotation : MonoBehaviour {
    
        Quaternion rotation;
        
        void Awake()
        {
            rotation = transform.rotation;
        }
        
        void LateUpdate()
        {
            transform.rotation = rotation;
        }
    }
    

    ConstraintManager

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.MixedReality.Toolkit.Utilities;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    
    namespace Microsoft.MixedReality.Toolkit.UI
    {
        /// <summary>
        /// Manages constraints for a given object and ensures that Scale/Rotation/Translation 
        /// constraints are executed separately.
        /// </summary>
        internal class ConstraintManager
        {
            private List<TransformConstraint> constraints;
    
            public ConstraintManager(GameObject gameObject)
            {
                constraints = gameObject.GetComponents<TransformConstraint>().ToList();
            }
    
            public void ApplyScaleConstraints(ref MixedRealityTransform transform, bool isOneHanded, bool isNear)
            {
                ApplyConstraintsForType(ref transform, isOneHanded, isNear, TransformFlags.Scale);
            }
    
            public void ApplyRotationConstraints(ref MixedRealityTransform transform, bool isOneHanded, bool isNear)
            {
                ApplyConstraintsForType(ref transform, isOneHanded, isNear, TransformFlags.Rotate);
            }
    
            public void ApplyTranslationConstraints(ref MixedRealityTransform transform, bool isOneHanded, bool isNear)
            {
                ApplyConstraintsForType(ref transform, isOneHanded, isNear, TransformFlags.Move);
            }
    
            public void Initialize(MixedRealityPose worldPose)
            {
                foreach (var constraint in constraints)
                {
                    constraint.Initialize(worldPose);
                }
            }
    
            private void ApplyConstraintsForType(ref MixedRealityTransform transform, bool isOneHanded, bool isNear, TransformFlags transformType)
            {
                ManipulationHandFlags handMode = isOneHanded ? ManipulationHandFlags.OneHanded : ManipulationHandFlags.TwoHanded;
                ManipulationProximityFlags proximityMode = isNear ? ManipulationProximityFlags.Near : ManipulationProximityFlags.Far;
    
                foreach (var constraint in constraints)
                {
                    if (constraint.ConstraintType == transformType &&
                        constraint.HandType.HasFlag(handMode) &&
                        constraint.ProximityType.HasFlag(proximityMode))
                    {
                        constraint.ApplyConstraint(ref transform);
                    }
                }
            }
        }
    }
    

    ManipulationHandler

    // Copyright (c) Microsoft Corporation. All rights reserved.
    // Licensed under the MIT License. See LICENSE in the project root for license information.
    
    using Microsoft.MixedReality.Toolkit.Input;
    using Microsoft.MixedReality.Toolkit.Physics;
    using Microsoft.MixedReality.Toolkit.Utilities;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using UnityEngine;
    using UnityEngine.Assertions;
    using UnityEngine.Serialization;
    
    namespace Microsoft.MixedReality.Toolkit.UI
    {
        /// <summary>
        /// This script allows for an object to be movable, scalable, and rotatable with one or two hands. 
        /// You may also configure the script on only enable certain manipulations. The script works with 
        /// both HoloLens' gesture input and immersive headset's motion controller input.
        /// </summary>
        [HelpURL("https://microsoft.github.io/MixedRealityToolkit-Unity/Documentation/README_ManipulationHandler.html")]
        [AddComponentMenu("Scripts/MRTK/SDK/ManipulationHandler")]
        public class ManipulationHandler : MonoBehaviour, IMixedRealityPointerHandler, IMixedRealityFocusChangedHandler
        {
            #region Public Enums
            public enum HandMovementType
            {
                OneHandedOnly = 0,
                TwoHandedOnly,
                OneAndTwoHanded
            }
            public enum TwoHandedManipulation
            {
                Scale,
                Rotate,
                MoveScale,
                MoveRotate,
                RotateScale,
                MoveRotateScale
            };
            public enum RotateInOneHandType
            {
                MaintainRotationToUser,
                GravityAlignedMaintainRotationToUser,
                FaceUser,
                FaceAwayFromUser,
                MaintainOriginalRotation,
                RotateAboutObjectCenter,
                RotateAboutGrabPoint
            };
            [System.Flags]
            public enum ReleaseBehaviorType
            {
                KeepVelocity = 1 << 0,
                KeepAngularVelocity = 1 << 1
            }
            #endregion Public Enums
    
            #region Serialized Fields
    
            [SerializeField]
            [Tooltip("Transform that will be dragged. Defaults to the object of the component.")]
            private Transform hostTransform = null;
    
            public Transform HostTransform
            {
                get => hostTransform;
                set => hostTransform = value;
            }
            
            [SerializeField]
            [Tooltip("Can manipulation be done only with one hand, only with two hands, or with both?")]
            private HandMovementType manipulationType = HandMovementType.OneAndTwoHanded;
    
            public HandMovementType ManipulationType
            {
                get => manipulationType;
                set => manipulationType = value;
            }
    
            [SerializeField]
            [Tooltip("What manipulation will two hands perform?")]
            private TwoHandedManipulation twoHandedManipulationType = TwoHandedManipulation.MoveRotateScale;
    
            public TwoHandedManipulation TwoHandedManipulationType
            {
                get => twoHandedManipulationType;
                set => twoHandedManipulationType = value;
            }
    
            [SerializeField]
            [Tooltip("Specifies whether manipulation can be done using far interaction with pointers.")]
            private bool allowFarManipulation = true;
    
            public bool AllowFarManipulation
            {
                get => allowFarManipulation;
                set => allowFarManipulation = value;
            }
    
            [SerializeField]
            [Tooltip("Rotation behavior of object when using one hand near")]
            private RotateInOneHandType oneHandRotationModeNear = RotateInOneHandType.RotateAboutGrabPoint;
    
            public RotateInOneHandType OneHandRotationModeNear
            {
                get => oneHandRotationModeNear;
                set => oneHandRotationModeNear = value;
            }
    
            [SerializeField]
            [Tooltip("Rotation behavior of object when using one hand at distance")]
            private RotateInOneHandType oneHandRotationModeFar = RotateInOneHandType.RotateAboutGrabPoint;
    
            public RotateInOneHandType OneHandRotationModeFar
            {
                get => oneHandRotationModeFar;
                set => oneHandRotationModeFar = value;
            }
    
            [SerializeField]
            [EnumFlags]
            [Tooltip("Rigid body behavior of the dragged object when releasing it.")]
            private ReleaseBehaviorType releaseBehavior = ReleaseBehaviorType.KeepVelocity | ReleaseBehaviorType.KeepAngularVelocity;
    
            public ReleaseBehaviorType ReleaseBehavior
            {
                get => releaseBehavior;
                set => releaseBehavior = value;
            }
            
            [SerializeField]
            [Tooltip("Constrain rotation along an axis")]
            private RotationConstraintType constraintOnRotation = RotationConstraintType.None;
    
            public RotationConstraintType ConstraintOnRotation
            {
                get => constraintOnRotation;
                set
                {
                    constraintOnRotation = value;
                    rotateConstraint.ConstraintOnRotation = RotationConstraintHelper.ConvertToAxisFlags(constraintOnRotation);
                }
            }
    
            [SerializeField]
            [Tooltip("Check if object rotation should be in local space of object being manipulated instead of world space.")]
            private bool useLocalSpaceForConstraint = false;
    
            /// <summary>
            /// Gets or sets whether the constraints should be applied in local space of the object being manipulated or world space.
            /// </summary>
            public bool UseLocalSpaceForConstraint
            {
                get => rotateConstraint != null && rotateConstraint.UseLocalSpaceForConstraint;
                set
                {
                    if (rotateConstraint != null)
                    {
                        rotateConstraint.UseLocalSpaceForConstraint = value;
                    }
                }
            }
    
            [SerializeField]
            [Tooltip("Constrain movement")]
            private MovementConstraintType constraintOnMovement = MovementConstraintType.None;
    
            public MovementConstraintType ConstraintOnMovement
            {
                get => constraintOnMovement;
                set => constraintOnMovement = value;
            }
            
            [SerializeField]
            [Tooltip("Check to enable frame-rate independent smoothing. ")]
            private bool smoothingActive = true;
    
            public bool SmoothingActive
            {
                get => smoothingActive;
                set => smoothingActive = value;
            }
    
            [SerializeField]
            [Range(0, 1)]
            [Tooltip("Enter amount representing amount of smoothing to apply to the movement, scale, rotation.  Smoothing of 0 means no smoothing. Max value means no change to value.")]
            private float smoothingAmountOneHandManip = 0.001f;
    
            public float SmoothingAmoutOneHandManip
            {
                get => smoothingAmountOneHandManip;
                set => smoothingAmountOneHandManip = value;
            }
    
            #endregion Serialized Fields
    
            #region Event handlers
            [SerializeField]
            [FormerlySerializedAs("OnManipulationStarted")]
            private ManipulationEvent onManipulationStarted = new ManipulationEvent();
            public ManipulationEvent OnManipulationStarted
            {
                get => onManipulationStarted;
                set => onManipulationStarted = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnManipulationEnded")]
            private ManipulationEvent onManipulationEnded = new ManipulationEvent();
            public ManipulationEvent OnManipulationEnded
            {
                get => onManipulationEnded;
                set => onManipulationEnded = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnHoverEntered")]
            private ManipulationEvent onHoverEntered = new ManipulationEvent();
            public ManipulationEvent OnHoverEntered
            {
                get => onHoverEntered;
                set => onHoverEntered = value;
            }
    
            [SerializeField]
            [FormerlySerializedAs("OnHoverExited")]
            private ManipulationEvent onHoverExited = new ManipulationEvent();
            public ManipulationEvent OnHoverExited
            {
                get => onHoverExited;
                set => onHoverExited = value;
            }
            #endregion
    
            #region Private Properties
    
            [System.Flags]
            private enum State
            {
                Start = 0x000,
                Moving = 0x001,
                Scaling = 0x010,
                Rotating = 0x100,
                MovingRotating = Moving | Rotating,
                MovingScaling = Moving | Scaling,
                RotatingScaling = Rotating | Scaling,
                MovingRotatingScaling = Moving | Rotating | Scaling
            };
    
            private State currentState = State.Start;
            private ManipulationMoveLogic moveLogic;
            private TwoHandScaleLogic scaleLogic;
            private TwoHandRotateLogic rotateLogic;
            /// <summary>
            /// Holds the pointer and the initial intersection point of the pointer ray 
            /// with the object on pointer down in pointer space
            /// </summary>
            private struct PointerData
            {
                public IMixedRealityPointer pointer;
                private Vector3 initialGrabPointInPointer;
    
                public PointerData(IMixedRealityPointer pointer, Vector3 initialGrabPointInPointer) : this()
                {
                    this.pointer = pointer;
                    this.initialGrabPointInPointer = initialGrabPointInPointer;
                }
    
                public bool IsNearPointer()
                {
                    return (pointer is IMixedRealityNearPointer);
                }
    
                /// Returns the grab point on the manipulated object in world space
                public Vector3 GrabPoint
                {
                    get
                    {
                        return (pointer.Rotation * initialGrabPointInPointer) + pointer.Position;
                    }
                }
            }
            private Dictionary<uint, PointerData> pointerIdToPointerMap = new Dictionary<uint, PointerData>();
            private Quaternion objectToHandRotation;
            private Quaternion objectToGripRotation;
            private bool isNearManipulation;
    
            private Rigidbody rigidBody;
            private bool wasKinematic = false;
    
            private Quaternion startObjectRotationCameraSpace;
            private Quaternion startObjectRotationFlatCameraSpace;
            private Quaternion hostWorldRotationOnManipulationStart;
    
            private FixedDistanceConstraint moveConstraint;
            private RotationAxisConstraint rotateConstraint;
            private MinMaxScaleConstraint scaleHandler;
    
            #endregion
    
            #region MonoBehaviour Functions
    
            private void Awake()
            {
                moveLogic = new ManipulationMoveLogic();
                rotateLogic = new TwoHandRotateLogic();
                scaleLogic = new TwoHandScaleLogic();
            }
            private void Start()
            {
                if (hostTransform == null)
                {
                    hostTransform = transform;
                }
    
                moveConstraint = this.EnsureComponent<FixedDistanceConstraint>();
                moveConstraint.TargetTransform = hostTransform;
                moveConstraint.ConstraintTransform = CameraCache.Main.transform;
    
                rotateConstraint = this.EnsureComponent<RotationAxisConstraint>();
                rotateConstraint.TargetTransform = hostTransform;
                rotateConstraint.ConstraintOnRotation = RotationConstraintHelper.ConvertToAxisFlags(constraintOnRotation);
                rotateConstraint.UseLocalSpaceForConstraint = useLocalSpaceForConstraint;
    
                scaleHandler = this.GetComponent<MinMaxScaleConstraint>();
            }
            #endregion MonoBehaviour Functions
    
            #region Private Methods
            private Vector3 GetPointersCentroid()
            {
                Vector3 sum = Vector3.zero;
                int count = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    sum += p.GrabPoint;
                    count++;
                }
                return sum / Math.Max(1, count);
            }
    
            private MixedRealityPose GetAveragePointerPose()
            {
                Vector3 sumPos = Vector3.zero;
                Vector3 sumDir = Vector3.zero;
                int count = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    sumPos += p.pointer.Position;
                    sumDir += p.pointer.Rotation * Vector3.forward;
                    count++;
                }
    
                MixedRealityPose pose = new MixedRealityPose();
    
                if (count > 0)
                {
                    pose.Position = sumPos / count;
                    pose.Rotation = Quaternion.LookRotation(sumDir / count);
                }
    
                return pose;
            }
    
            private Vector3 GetPointersVelocity()
            {
                Vector3 sum = Vector3.zero;
                int numControllers = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    // Check pointer has a valid controller (e.g. gaze pointer doesn't)
                    if (p.pointer.Controller != null)
                    {
                        numControllers++;
                        sum += p.pointer.Controller.Velocity;
                    }
                }
                return sum / Math.Max(1, numControllers);
            }
    
            private Vector3 GetPointersAngularVelocity()
            {
                Vector3 sum = Vector3.zero;
                int numControllers = 0;
                foreach (var p in pointerIdToPointerMap.Values)
                {
                    // Check pointer has a valid controller (e.g. gaze pointer doesn't)
                    if (p.pointer.Controller != null)
                    {
                        numControllers++;
                        sum += p.pointer.Controller.AngularVelocity;
                    }
                }
                return sum / Math.Max(1, numControllers);
            }
    
            private bool IsNearManipulation()
            {
                foreach (var item in pointerIdToPointerMap)
                {
                    if (item.Value.IsNearPointer())
                    {
                        return true;
                    }
                }
                return false;
            }
    
            private void UpdateStateMachine()
            {
                var handsPressedCount = pointerIdToPointerMap.Count;
                State newState = currentState;
                // early out for no hands or one hand if TwoHandedOnly is active
                if (handsPressedCount == 0 || (handsPressedCount == 1 && manipulationType == HandMovementType.TwoHandedOnly))
                {
                    newState = State.Start;
                }
                else
                {
                    switch (currentState)
                    {
                        case State.Start:
                        case State.Moving:
                            if (handsPressedCount == 1)
                            {
                                newState = State.Moving;
                            }
                            else if (handsPressedCount > 1 && manipulationType != HandMovementType.OneHandedOnly)
                            {
                                switch (twoHandedManipulationType)
                                {
                                    case TwoHandedManipulation.Scale:
                                        newState = State.Scaling;
                                        break;
                                    case TwoHandedManipulation.Rotate:
                                        newState = State.Rotating;
                                        break;
                                    case TwoHandedManipulation.MoveRotate:
                                        newState = State.MovingRotating;
                                        break;
                                    case TwoHandedManipulation.MoveScale:
                                        newState = State.MovingScaling;
                                        break;
                                    case TwoHandedManipulation.RotateScale:
                                        newState = State.RotatingScaling;
                                        break;
                                    case TwoHandedManipulation.MoveRotateScale:
                                        newState = State.MovingRotatingScaling;
                                        break;
                                    default:
                                        throw new ArgumentOutOfRangeException();
                                }
                            }
                            break;
                        case State.Scaling:
                        case State.Rotating:
                        case State.MovingScaling:
                        case State.MovingRotating:
                        case State.RotatingScaling:
                        case State.MovingRotatingScaling:
                            // one hand only supports move for now
                            if (handsPressedCount == 1)
                            {
                                newState = State.Moving;
                            }
                            break;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                }
    
                InvokeStateUpdateFunctions(currentState, newState);
                currentState = newState;
            }
    
            private void InvokeStateUpdateFunctions(State oldState, State newState)
            {
                if (newState != oldState)
                {
                    switch (newState)
                    {
                        case State.Moving:
                            HandleOneHandMoveStarted();
                            break;
                        case State.Start:
                            HandleManipulationEnded();
                            break;
                        case State.RotatingScaling:
                        case State.MovingRotating:
                        case State.MovingRotatingScaling:
                        case State.Scaling:
                        case State.Rotating:
                        case State.MovingScaling:
                            HandleTwoHandManipulationStarted(newState);
                            break;
                    }
                    switch (oldState)
                    {
                        case State.Start:
                            HandleManipulationStarted();
                            break;
                        case State.Scaling:
                        case State.Rotating:
                        case State.RotatingScaling:
                        case State.MovingRotating:
                        case State.MovingRotatingScaling:
                        case State.MovingScaling:
                            HandleTwoHandManipulationEnded();
                            break;
                    }
                }
                else
                {
                    switch (newState)
                    {
                        case State.Moving:
                            HandleOneHandMoveUpdated();
                            break;
                        case State.Scaling:
                        case State.Rotating:
                        case State.RotatingScaling:
                        case State.MovingRotating:
                        case State.MovingRotatingScaling:
                        case State.MovingScaling:
                            HandleTwoHandManipulationUpdated();
                            break;
                        default:
                            break;
                    }
                }
            }
            #endregion Private Methods
    
            #region Public Methods
    
            /// <summary>
            /// Releases the object that is currently manipulated
            /// </summary>
            public void ForceEndManipulation()
            {
                // release rigidbody and clear pointers
                ReleaseRigidBody();
                pointerIdToPointerMap.Clear();
    
                // end manipulation
                State newState = State.Start;
                InvokeStateUpdateFunctions(currentState, newState);
                currentState = newState;
            }
    
            /// <summary>
            /// Gets the grab point for the given pointer id.
            /// Only use if you know that your given pointer id corresponds to a pointer that has grabbed
            /// this component.
            /// </summary>
            public Vector3 GetPointerGrabPoint(uint pointerId)
            {
                Assert.IsTrue(pointerIdToPointerMap.ContainsKey(pointerId));
                return pointerIdToPointerMap[pointerId].GrabPoint;
            }
    
            #endregion Public Methods
                
            #region Hand Event Handlers
    
            /// <inheritdoc />
            public void OnPointerDown(MixedRealityPointerEventData eventData)
            {
                if (eventData.used ||
                    (!allowFarManipulation && eventData.Pointer as IMixedRealityNearPointer == null))
                {
                    return;
                }
    
                // If we only allow one handed manipulations, check there is no hand interacting yet. 
                if (manipulationType != HandMovementType.OneHandedOnly || pointerIdToPointerMap.Count == 0)
                {
                    uint id = eventData.Pointer.PointerId;
                    // Ignore poke pointer events
                    if (!pointerIdToPointerMap.ContainsKey(eventData.Pointer.PointerId))
                    {
                        if (pointerIdToPointerMap.Count == 0)
                        {
                            rigidBody = GetComponent<Rigidbody>();
                            if (rigidBody != null)
                            {
                                wasKinematic = rigidBody.isKinematic;
                                rigidBody.isKinematic = true;
                            }
                        }
    
                        // cache start ptr grab point
                        Vector3 initialGrabPoint = Quaternion.Inverse(eventData.Pointer.Rotation) * (eventData.Pointer.Result.Details.Point - eventData.Pointer.Position);
                        pointerIdToPointerMap.Add(id, new PointerData(eventData.Pointer, initialGrabPoint));
    
                        UpdateStateMachine();
                    }
                }
    
                if (pointerIdToPointerMap.Count > 0)
                {
                    // Always mark the pointer data as used to prevent any other behavior to handle pointer events
                    // as long as the ManipulationHandler is active.
                    // This is due to us reacting to both "Select" and "Grip" events.
                    eventData.Use();
                }
            }
    
            public void OnPointerDragged(MixedRealityPointerEventData eventData)
            {
                if (currentState != State.Start)
                {
                    UpdateStateMachine();
                }
            }
    
            /// <inheritdoc />
            public void OnPointerUp(MixedRealityPointerEventData eventData)
            {
                uint id = eventData.Pointer.PointerId;
                if (pointerIdToPointerMap.ContainsKey(id))
                {
                    if (pointerIdToPointerMap.Count == 1 && rigidBody != null)
                    {
                        ReleaseRigidBody();
                    }
    
                    pointerIdToPointerMap.Remove(id);
                }
    
                UpdateStateMachine();
                eventData.Use();
            }
    
            #endregion Hand Event Handlers
    
            #region Private Event Handlers
            private void HandleTwoHandManipulationUpdated()
            {
                var targetTransform = new MixedRealityTransform(hostTransform.position, hostTransform.rotation, hostTransform.localScale);
    
                var handPositionArray = GetHandPositionArray();
    
                if ((currentState & State.Scaling) > 0)
                {
                    targetTransform.Scale = scaleLogic.UpdateMap(handPositionArray);
                    if (scaleHandler != null)
                    {
                        scaleHandler.ApplyConstraint(ref targetTransform);
                    }
                }
                if ((currentState & State.Rotating) > 0)
                {
                    targetTransform.Rotation = rotateLogic.Update(handPositionArray, targetTransform.Rotation);
                    if (rotateConstraint != null)
                    {
                        rotateConstraint.ApplyConstraint(ref targetTransform);
                    }
                }
                if ((currentState & State.Moving) > 0)
                {
                    MixedRealityPose pose = GetAveragePointerPose();
                    targetTransform.Position = moveLogic.Update(pose, targetTransform.Rotation, targetTransform.Scale, true);
                    if (constraintOnMovement == MovementConstraintType.FixDistanceFromHead && moveConstraint != null)
                    {
                        moveConstraint.ApplyConstraint(ref targetTransform);
                    }
                }
    
                float lerpAmount = GetLerpAmount();
    
                hostTransform.position = Vector3.Lerp(hostTransform.position, targetTransform.Position, lerpAmount);
                hostTransform.rotation = Quaternion.Lerp(hostTransform.rotation, targetTransform.Rotation, lerpAmount);
                hostTransform.localScale = Vector3.Lerp(hostTransform.localScale, targetTransform.Scale, lerpAmount);
            }
    
            private void HandleOneHandMoveUpdated()
            {
                Debug.Assert(pointerIdToPointerMap.Count == 1);
                PointerData pointerData = GetFirstPointer();
                IMixedRealityPointer pointer = pointerData.pointer;
    
                var targetTransform = new MixedRealityTransform(hostTransform.position, hostTransform.rotation, hostTransform.localScale);
    
                RotateInOneHandType rotateInOneHandType = isNearManipulation ? oneHandRotationModeNear : oneHandRotationModeFar;
                switch (rotateInOneHandType)
                {
                    case RotateInOneHandType.MaintainOriginalRotation:
                        targetTransform.Rotation = hostTransform.rotation;
                        break;
                    case RotateInOneHandType.MaintainRotationToUser:
                        Vector3 euler = CameraCache.Main.transform.rotation.eulerAngles;
                        // don't use roll (feels awkward) - just maintain yaw / pitch angle
                        targetTransform.Rotation = Quaternion.Euler(euler.x, euler.y, 0) * startObjectRotationCameraSpace;
                        break;
                    case RotateInOneHandType.GravityAlignedMaintainRotationToUser:
                        var cameraForwardFlat = CameraCache.Main.transform.forward;
                        cameraForwardFlat.y = 0;
                        targetTransform.Rotation = Quaternion.LookRotation(cameraForwardFlat, Vector3.up) * startObjectRotationFlatCameraSpace;
                        break;
                    case RotateInOneHandType.FaceUser:
                        {
                            Vector3 directionToTarget = pointerData.GrabPoint - CameraCache.Main.transform.position;
                            // Vector3 directionToTarget = hostTransform.position - CameraCache.Main.transform.position;
                            targetTransform.Rotation = Quaternion.LookRotation(-directionToTarget);
                            break;
                        }
                    case RotateInOneHandType.FaceAwayFromUser:
                        {
                            Vector3 directionToTarget = pointerData.GrabPoint - CameraCache.Main.transform.position;
                            targetTransform.Rotation = Quaternion.LookRotation(directionToTarget);
                            break;
                        }
                    case RotateInOneHandType.RotateAboutObjectCenter:
                    case RotateInOneHandType.RotateAboutGrabPoint:
                        Quaternion gripRotation;
                        TryGetGripRotation(pointer, out gripRotation);
                        targetTransform.Rotation = gripRotation * objectToGripRotation;
                        break;
                }
                if (rotateConstraint != null)
                {
                    rotateConstraint.ApplyConstraint(ref targetTransform);
                }
    
                MixedRealityPose pointerPose = new MixedRealityPose(pointer.Position, pointer.Rotation);
                targetTransform.Position = moveLogic.Update(pointerPose, targetTransform.Rotation, targetTransform.Scale, rotateInOneHandType != RotateInOneHandType.RotateAboutObjectCenter);
                if (constraintOnMovement == MovementConstraintType.FixDistanceFromHead && moveConstraint != null)
                {
                    moveConstraint.ApplyConstraint(ref targetTransform);
                }
    
                float lerpAmount = GetLerpAmount();
                Quaternion smoothedRotation = Quaternion.Lerp(hostTransform.rotation, targetTransform.Rotation, lerpAmount);
                Vector3 smoothedPosition = Vector3.Lerp(hostTransform.position, targetTransform.Position, lerpAmount);
    
                hostTransform.SetPositionAndRotation(smoothedPosition, smoothedRotation);
            }
    
            private void HandleTwoHandManipulationStarted(State newState)
            {
                var handPositionArray = GetHandPositionArray();
    
                if ((newState & State.Rotating) > 0)
                {
                    rotateLogic.Setup(handPositionArray, hostTransform);
                }
                if ((newState & State.Moving) > 0)
                {
                    MixedRealityPose pointerPose = GetAveragePointerPose();
                    MixedRealityPose hostPose = new MixedRealityPose(hostTransform.position, hostTransform.rotation);
                    moveLogic.Setup(pointerPose, GetPointersCentroid(), hostPose, hostTransform.localScale);
                }
                if ((newState & State.Scaling) > 0)
                {
                    scaleLogic.Setup(handPositionArray, hostTransform);
                }
            }
            private void HandleTwoHandManipulationEnded() { }
    
            private void HandleOneHandMoveStarted()
            {
                Assert.IsTrue(pointerIdToPointerMap.Count == 1);
                PointerData pointerData = GetFirstPointer();
                IMixedRealityPointer pointer = pointerData.pointer;
    
                // cache objects rotation on start to have a reference for constraint calculations
                // if we don't cache this on manipulation start the near rotation might drift off the hand
                // over time
                hostWorldRotationOnManipulationStart = hostTransform.rotation;
    
                // Calculate relative transform from object to hand.
                Quaternion worldToPalmRotation = Quaternion.Inverse(pointer.Rotation);
                objectToHandRotation = worldToPalmRotation * hostTransform.rotation;
    
                // Calculate relative transform from object to grip.
                Quaternion gripRotation;
                TryGetGripRotation(pointer, out gripRotation);
                Quaternion worldToGripRotation = Quaternion.Inverse(gripRotation);
                objectToGripRotation = worldToGripRotation * hostTransform.rotation;
    
                MixedRealityPose pointerPose = new MixedRealityPose(pointer.Position, pointer.Rotation);
                MixedRealityPose hostPose = new MixedRealityPose(hostTransform.position, hostTransform.rotation);
                moveLogic.Setup(pointerPose, pointerData.GrabPoint, hostPose, hostTransform.localScale);
    
                Vector3 worldGrabPoint = pointerData.GrabPoint;
    
                startObjectRotationCameraSpace = Quaternion.Inverse(CameraCache.Main.transform.rotation) * hostTransform.rotation;
                var cameraFlat = CameraCache.Main.transform.forward;
                cameraFlat.y = 0;
                var hostForwardFlat = hostTransform.forward;
                hostForwardFlat.y = 0;
                var hostRotFlat = Quaternion.LookRotation(hostForwardFlat, Vector3.up);
                startObjectRotationFlatCameraSpace = Quaternion.Inverse(Quaternion.LookRotation(cameraFlat, Vector3.up)) * hostRotFlat;
            }
    
            private void HandleManipulationStarted()
            {
                isNearManipulation = IsNearManipulation();
                // TODO: If we are on HoloLens 1, push and pop modal input handler so that we can use old
                // gaze/gesture/voice manipulation. For HoloLens 2, we don't want to do this.
                if (OnManipulationStarted != null)
                {
                    OnManipulationStarted.Invoke(new ManipulationEventData
                    {
                        ManipulationSource = gameObject,
                        IsNearInteraction = isNearManipulation,
                        Pointer = GetFirstPointer().pointer,
                        PointerCentroid = GetPointersCentroid(),
                        PointerVelocity = GetPointersVelocity(),
                        PointerAngularVelocity = GetPointersAngularVelocity()
                    });
                }
    
                var pose = new MixedRealityPose(hostTransform.position, hostTransform.rotation);
                if (constraintOnMovement == MovementConstraintType.FixDistanceFromHead && moveConstraint != null)
                {
                    moveConstraint.Initialize(pose);
                }
                if (rotateConstraint != null)
                {
                    rotateConstraint.Initialize(pose);
                }
                if (scaleHandler != null)
                {
                    scaleHandler.Initialize(pose);
                }
            }
    
            private void HandleManipulationEnded()
            {
                // TODO: If we are on HoloLens 1, push and pop modal input handler so that we can use old
                // gaze/gesture/voice manipulation. For HoloLens 2, we don't want to do this.
                if (OnManipulationEnded != null)
                {
                    OnManipulationEnded.Invoke(new ManipulationEventData
                    {
                        ManipulationSource = gameObject,
                        IsNearInteraction = isNearManipulation,
                        PointerCentroid = GetPointersCentroid(),
                        PointerVelocity = GetPointersVelocity(),
                        PointerAngularVelocity = GetPointersAngularVelocity()
                    });
                }
            }
    
            #endregion Private Event Handlers
    
            #region Unused Event Handlers
            /// <inheritdoc />
            public void OnPointerClicked(MixedRealityPointerEventData eventData) { }
            public void OnBeforeFocusChange(FocusEventData eventData) { }
    
            #endregion Unused Event Handlers
    
            #region Private methods
    
            private float GetLerpAmount()
            {
                if (smoothingActive == false || smoothingAmountOneHandManip == 0)
                {
                    return 1;
                }
                // Obtained from "Frame-rate independent smoothing"
                // www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
                // We divide by max value to give the slider a bit more sensitivity.
                return 1.0f - Mathf.Pow(smoothingAmountOneHandManip, Time.deltaTime);
            }
    
            private Vector3[] GetHandPositionArray()
            {
                var handPositionMap = new Vector3[pointerIdToPointerMap.Count];
                int index = 0;
                foreach (var item in pointerIdToPointerMap)
                {
                    handPositionMap[index++] = item.Value.pointer.Position;
                }
                return handPositionMap;
            }
    
            public void OnFocusChanged(FocusEventData eventData)
            {
                bool isFar = !(eventData.Pointer is IMixedRealityNearPointer);
                if (eventData.OldFocusedObject == null ||
                    !eventData.OldFocusedObject.transform.IsChildOf(transform))
                {
                    if (isFar && !AllowFarManipulation)
                    {
                        return;
                    }
                    if (OnHoverEntered != null)
                    {
                        OnHoverEntered.Invoke(new ManipulationEventData
                        {
                            ManipulationSource = gameObject,
                            Pointer = eventData.Pointer,
                            IsNearInteraction = !isFar
                        });
                    }
                }
                else if (eventData.NewFocusedObject == null ||
                        !eventData.NewFocusedObject.transform.IsChildOf(transform))
                {
                    if (isFar && !AllowFarManipulation)
                    {
                        return;
                    }
                    if (OnHoverExited != null)
                    {
                        OnHoverExited.Invoke(new ManipulationEventData
                        {
                            ManipulationSource = gameObject,
                            Pointer = eventData.Pointer,
                            IsNearInteraction = !isFar
                        });
                    }
                }
            }
    
            private void ReleaseRigidBody()
            {
                if (rigidBody != null)
                {
                    rigidBody.isKinematic = wasKinematic;
    
                    if (releaseBehavior.HasFlag(ReleaseBehaviorType.KeepVelocity))
                    {
                        rigidBody.velocity = GetPointersVelocity();
                    }
    
                    if (releaseBehavior.HasFlag(ReleaseBehaviorType.KeepAngularVelocity))
                    {
                        rigidBody.angularVelocity = GetPointersAngularVelocity();
                    }
    
                    rigidBody = null;
                }
            }
    
            private PointerData GetFirstPointer()
            {
                // We may be able to do this without allocating memory.
                // Moving to a method for later investigation.
                return pointerIdToPointerMap.Values.First();
            }
    
            private bool TryGetGripRotation(IMixedRealityPointer pointer, out Quaternion rotation)
            {
    
                for (int i = 0; i < pointer.Controller.Interactions.Length; i++)
                {
                    if (pointer.Controller.Interactions[i].InputType == DeviceInputType.SpatialGrip)
                    {
                        rotation = pointer.Controller.Interactions[i].RotationData;
                        return true;
                    }
                }
                rotation = Quaternion.identity;
                return false;
            }
    
            #endregion
        }
    }
    
    

    三、效果展示




    相关文章

      网友评论

          本文标题:MR开发实战Hololens项目之智能建筑类沙盘展示

          本文链接:https://www.haomeiwen.com/subject/mxldiktx.html