// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using Microsoft.MixedReality.Toolkit.Utilities; using System.Collections.Generic; using UnityEngine; namespace Microsoft.MixedReality.Toolkit.UI { /// /// Manages constraints for a given object and ensures that Scale/Rotation/Translation /// constraints are executed separately. /// [HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/ux-building-blocks/constraint-manager")] public class ConstraintManager : MonoBehaviour { [SerializeField] [Tooltip("Per default, constraint manager will apply all to this gameobject attached constraint components." + "If this flag is enabled only the selected constraint list will be applied.")] private bool autoConstraintSelection = true; /// /// Per default, constraint manager will apply all to this gameobject attached constraint components automatically. /// If this flag is enabled, only the selected constraint list will be applied. /// public bool AutoConstraintSelection { get => autoConstraintSelection; set => autoConstraintSelection = value; } [SerializeField] [Tooltip("Manually selected list of transform constraints. Note that this list will only be processed by the" + "manager if AutoConstraintSelection is disabled.")] private List selectedConstraints = new List(); /// /// Manually selected list of transform constraints. Note that this list will only be processed by the /// manager if AutoConstraintSelection is disabled. /// Note that this is a read only property. To add new constraints to the list call RegisterConstraint. /// public List SelectedConstraints { get => selectedConstraints; } private List constraints = new List(); private MixedRealityTransform initialWorldPose; /// /// Adds a constraint to the manual selection list. /// Note that only unique components will be added to the list. /// /// Constraint to add to the managers manual constraint list. /// Returns true if insertion was successful. If the component was already in the list the insertion will fail. public bool AddConstraintToManualSelection(TransformConstraint constraint) { var existingConstraint = selectedConstraints.Find(t => t == constraint); if (existingConstraint == null) { ConstraintUtils.AddWithPriority(ref selectedConstraints, constraint, new ConstraintExecOrderComparer()); } return existingConstraint == null; } /// /// Removes the given component from the manually selected constraint list. /// /// Constraint to remove. public void RemoveConstraintFromManualSelection(TransformConstraint constraint) { selectedConstraints.Remove(constraint); } 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(MixedRealityTransform worldPose) { initialWorldPose = worldPose; foreach (var constraint in constraints) { constraint.Initialize(worldPose); } } /// /// Re-sort list of constraints. Triggered by constraints /// when their execution order is modified at runtime. /// internal void RefreshPriorities() { constraints.Sort(new ConstraintExecOrderComparer()); } /// /// Registering of a constraint during runtime. This method gets called by the constraint /// components to auto register in their OnEnable method. /// /// Constraint to add to the manager. internal void AutoRegisterConstraint(TransformConstraint constraint) { // add to auto component list if (constraint.isActiveAndEnabled) { ConstraintUtils.AddWithPriority(ref constraints, constraint, new ConstraintExecOrderComparer()); constraint.Initialize(initialWorldPose); } } /// /// Unregister a constraint from the manager. /// Removes the constraint from the manual list if auto mode is disabled. /// /// Constraint to remove from the manager. internal void AutoUnregisterConstraint(TransformConstraint constraint) { constraints.Remove(constraint); } protected void Awake() { var constraintComponents = gameObject.GetComponents(); foreach (var constraint in constraintComponents) { if (constraint.isActiveAndEnabled) { ConstraintUtils.AddWithPriority(ref constraints, constraint, new ConstraintExecOrderComparer()); } } } 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 on manual mode, filter executed constraints by which have been manually selected if (!autoConstraintSelection && !selectedConstraints.Contains(constraint)) { continue; } if (constraint.isActiveAndEnabled && constraint.ConstraintType == transformType && constraint.HandType.IsMaskSet(handMode) && constraint.ProximityType.IsMaskSet(proximityMode)) { constraint.ApplyConstraint(ref transform); } } } } }