mixedreality/com.microsoft.mixedreality..../SDK/Experimental/Elastic/Scripts/ElasticsManager.cs

239 lines
11 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Utilities;
using UnityEngine;
namespace Microsoft.MixedReality.Toolkit.Experimental.Physics
{
/// <summary>
/// ElasticsManager can be used to add elastics simulation to supporting components.
/// Call Initialize on manipulation start.
/// Call ApplyHostTransform to apply elastics calculation to target transform.
/// Elastics will continue simulating once manipulation ends through its update function -
/// to block the elastics auto update set EnableElasticsUpdate to false.
/// </summary>
[HelpURL("https://docs.microsoft.com/windows/mixed-reality/mrtk-unity/features/experimental/elastic-system")]
[AddComponentMenu("Scripts/MRTK/SDK/Experimental/Elastics Manager")]
public class ElasticsManager : MonoBehaviour
{
[SerializeField]
[Tooltip("Reference to the ScriptableObject which holds the elastic system configuration for translation manipulation.")]
private ElasticConfiguration translationElasticConfigurationObject = null;
/// <summary>
/// Reference to the ScriptableObject which holds the elastic system configuration for translation manipulation.
/// </summary>
public ElasticConfiguration TranslationElasticConfigurationObject
{
get => translationElasticConfigurationObject;
set => translationElasticConfigurationObject = value;
}
[SerializeField]
[Tooltip("Reference to the ScriptableObject which holds the elastic system configuration for rotation manipulation.")]
private ElasticConfiguration rotationElasticConfigurationObject = null;
/// <summary>
/// Reference to the ScriptableObject which holds the elastic system configuration for rotation manipulation.
/// </summary>
public ElasticConfiguration RotationElasticConfigurationObject
{
get => rotationElasticConfigurationObject;
set => rotationElasticConfigurationObject = value;
}
[SerializeField]
[Tooltip("Reference to the ScriptableObject which holds the elastic system configuration for scale manipulation.")]
private ElasticConfiguration scaleElasticConfigurationObject = null;
/// <summary>
/// Reference to the ScriptableObject which holds the elastic system configuration for scale manipulation.
/// </summary>
public ElasticConfiguration ScaleElasticConfigurationObject
{
get => scaleElasticConfigurationObject;
set => scaleElasticConfigurationObject = value;
}
[SerializeField]
[Tooltip("Extent of the translation elastic.")]
private VolumeElasticExtent translationElasticExtent;
/// <summary>
/// Extent of the translation elastic.
/// </summary>
public VolumeElasticExtent TranslationElasticExtent
{
get => translationElasticExtent;
set => translationElasticExtent = value;
}
[SerializeField]
[Tooltip("Extent of the rotation elastic.")]
private QuaternionElasticExtent rotationElasticExtent;
/// <summary>
/// Extent of the rotation elastic.
/// </summary>
public QuaternionElasticExtent RotationElasticExtent
{
get => rotationElasticExtent;
set => rotationElasticExtent = value;
}
[SerializeField]
[Tooltip("Extent of the scale elastic.")]
private VolumeElasticExtent scaleElasticExtent;
/// <summary>
/// Extent of the scale elastic.
/// </summary>
public VolumeElasticExtent ScaleElasticExtent
{
get => scaleElasticExtent;
set => scaleElasticExtent = value;
}
[SerializeField]
[Tooltip("Indication of which manipulation types use elastic feedback.")]
private TransformFlags elasticTypes = 0; // Default to none enabled.
/// <summary>
/// Indication of which manipulation types use elastic feedback.
/// </summary>
public TransformFlags ElasticTypes
{
get => elasticTypes;
set => elasticTypes = value;
}
/// <summary>
/// Enables elastics simulation in the update method.
/// </summary>
public bool EnableElasticsUpdate
{
get;
set;
}
#region private properties
// Magnitude of the velocity at which the elastic systems will
// cease being simulated (if enabled) and the object will stop updating/moving.
private const float elasticVelocityThreshold = 0.001f;
private IElasticSystem<Vector3> translationElastic;
private IElasticSystem<Quaternion> rotationElastic;
private IElasticSystem<Vector3> scaleElastic;
private Transform hostTransform = null;
private TransformFlags elasticTypesSimulating = 0;
#endregion
/// <summary>
/// Applies elastics calculation to the passed targetTransform and applies to the host transform.
/// </summary>
/// <param name="targetTransform">Precalculated target transform that's influenced by elastics</param>
/// <param name="transformsToApply">Indicates which types of transforms are going to be applied. Default is Move, Rotate and Scale.</param>
/// <returns>Modified transform types.</returns>
public TransformFlags ApplyTargetTransform(MixedRealityTransform targetTransform, TransformFlags transformsToApply = TransformFlags.Move | TransformFlags.Rotate | TransformFlags.Scale)
{
Debug.Assert(hostTransform != null, "Can't apply target before calling Initialize with a valid transform reference.");
if (hostTransform != null)
{
TransformFlags enabledTransformTypes = transformsToApply & elasticTypes;
if (enabledTransformTypes.IsMaskSet(TransformFlags.Move))
{
hostTransform.position = translationElastic.ComputeIteration(targetTransform.Position, Time.deltaTime);
}
if (enabledTransformTypes.IsMaskSet(TransformFlags.Rotate))
{
hostTransform.rotation = rotationElastic.ComputeIteration(targetTransform.Rotation, Time.deltaTime);
}
if (enabledTransformTypes.IsMaskSet(TransformFlags.Scale))
{
hostTransform.localScale = scaleElastic.ComputeIteration(targetTransform.Scale, Time.deltaTime);
}
elasticTypesSimulating = enabledTransformTypes;
return elasticTypes;
}
else
{
return 0;
}
}
/// <summary>
/// Initialize elastics system with the given host transform.
/// Caches a reference to the host transform to be able to keep updating elastics after manipulation.
/// </summary>
/// <param name="elasticsTransform">host transform the elastics are applied to.</param>
public void InitializeElastics(Transform elasticsTransform)
{
hostTransform = elasticsTransform;
if (elasticTypes.IsMaskSet(TransformFlags.Move))
{
translationElastic = new VolumeElasticSystem(hostTransform.position,
translationElastic?.GetCurrentVelocity() ?? Vector3.zero,
translationElasticExtent,
translationElasticConfigurationObject.ElasticProperties);
}
if (elasticTypes.IsMaskSet(TransformFlags.Rotate))
{
rotationElastic = new QuaternionElasticSystem(hostTransform.rotation,
rotationElastic?.GetCurrentVelocity() ?? Quaternion.identity,
rotationElasticExtent,
rotationElasticConfigurationObject.ElasticProperties);
}
if (elasticTypes.IsMaskSet(TransformFlags.Scale))
{
scaleElastic = new VolumeElasticSystem(hostTransform.localScale,
scaleElastic?.GetCurrentVelocity() ?? Vector3.zero,
scaleElasticExtent,
scaleElasticConfigurationObject.ElasticProperties);
}
}
#region MonoBehaviour Functions
private void Update()
{
// If the user is not actively interacting with the object,
// we let the elastic systems continue simulating, to allow
// the object to naturally come to rest.
if (EnableElasticsUpdate && hostTransform != null && elasticTypesSimulating != 0)
{
TransformFlags currentlySimulatedStates = 0;
float squaredVelocityThreshold = elasticVelocityThreshold * elasticVelocityThreshold;
if (ShouldUpdateElastics(TransformFlags.Move, translationElastic) && translationElastic.GetCurrentVelocity().sqrMagnitude > squaredVelocityThreshold)
{
hostTransform.position = translationElastic.ComputeIteration(hostTransform.position, Time.deltaTime);
currentlySimulatedStates |= TransformFlags.Move;
}
if (ShouldUpdateElastics(TransformFlags.Rotate, rotationElastic) && rotationElastic.GetCurrentVelocity().eulerAngles.sqrMagnitude > squaredVelocityThreshold)
{
hostTransform.rotation = rotationElastic.ComputeIteration(hostTransform.rotation, Time.deltaTime);
currentlySimulatedStates |= TransformFlags.Rotate;
}
if (ShouldUpdateElastics(TransformFlags.Scale, scaleElastic) && scaleElastic.GetCurrentVelocity().sqrMagnitude > squaredVelocityThreshold)
{
hostTransform.localScale = scaleElastic.ComputeIteration(hostTransform.localScale, Time.deltaTime);
currentlySimulatedStates |= TransformFlags.Scale;
}
elasticTypesSimulating = currentlySimulatedStates;
}
}
private bool ShouldUpdateElastics<T>(TransformFlags elasticType, IElasticSystem<T> elasticSystem)
{
return (elasticTypes.IsMaskSet(elasticType) &&
elasticTypesSimulating.IsMaskSet(elasticType) &&
elasticSystem != null);
}
#endregion MonoBehaviour Functions
}
}