// 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);
}
}
}
}
}