239 lines
11 KiB
C#
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
|
|
}
|
|
} |