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

140 lines
5.9 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Runtime.CompilerServices;
using UnityEngine;
[assembly: InternalsVisibleTo("Microsoft.MixedReality.Toolkit.Tests.PlayModeTests")]
namespace Microsoft.MixedReality.Toolkit.Experimental.Physics
{
public class QuaternionElasticSystem : IElasticSystem<Quaternion>
{
private Quaternion currentValue;
private Quaternion currentVelocity;
private QuaternionElasticExtent extent;
private ElasticProperties elasticProperties;
/// <summary>
/// Default constructor; initializes the elastic system with the specified
/// initial value, velocity, extent, and elastic properties.
/// </summary>
public QuaternionElasticSystem(Quaternion initialValue,
Quaternion initialVelocity,
QuaternionElasticExtent extentInfo,
ElasticProperties elasticProperties)
{
currentValue = initialValue;
currentVelocity = initialVelocity;
this.extent = extentInfo;
this.elasticProperties = elasticProperties;
}
/// <inheritdoc/>
public Quaternion ComputeIteration(Quaternion forcingValue, float deltaTime)
{
// If the dot product is negative, we need to negate the forcing
// quaternion so that the force is coherent/wraps correctly.
if (Quaternion.Dot(forcingValue, currentValue) < 0)
{
forcingValue = new Quaternion(-forcingValue.x, -forcingValue.y, -forcingValue.z, -forcingValue.w);
}
// Displacement of the forcing value compared to the current state's value.
Quaternion displacement = Quaternion.Inverse(currentValue) * forcingValue;
// The force applied is an approximation of F=(kx - vd) in 4-space
Quaternion force = Add(Scale(displacement, elasticProperties.HandK), Scale(currentVelocity, -elasticProperties.Drag));
// Euler representation of the current elastic quaternion state.
Vector3 eulers = currentValue.eulerAngles;
foreach (var snapPoint in extent.SnapPoints)
{
// If we are set to repeat the snap points (i.e., tiling them across the sphere),
// we use the nearest integer multiple of the snap point. If it is not repeated,
// we use the snap point directly.
Vector3 nearest = extent.RepeatSnapPoints ? GetNearest(eulers, snapPoint) : snapPoint;
// Convert the nearest point to a quaternion representation.
Quaternion nearestQuat = Quaternion.Euler(nearest.x, nearest.y, nearest.z);
// If the dot product is negative, we need to negate the snap
// quaternion so that the snap force is coherent/wraps correctly.
if (Quaternion.Dot(nearestQuat, currentValue) < 0)
{
nearestQuat = Scale(nearestQuat, -1.0f);
}
// Displacement from the current value to the snap quaternion.
Quaternion snapDisplacement = Quaternion.Inverse(currentValue) * nearestQuat;
// Angle of the snapping displacement, used to calculate the snapping magnitude.
float snapAngle = Quaternion.Angle(currentValue, nearestQuat);
// Strength of the snapping force, function of the snap angle and the configured
// snapping radius.
float snapFactor = ComputeSnapFactor(snapAngle, extent.SnapRadius);
// Accumulate the force from every snap point.
force = Add(force, Scale(snapDisplacement, elasticProperties.SnapK * snapFactor));
}
// Performing Euler integration in 4-space.
// Acceleration = F/m
Quaternion accel = Scale(force, (1 / elasticProperties.Mass));
// v' = v + a * deltaT
currentVelocity = Add(currentVelocity, Scale(accel, deltaTime));
// x' = x + v' * deltaT
currentValue *= Scale(currentVelocity, deltaTime).normalized;
// As the current value is a quaternion, we must renormalize the quaternion
// before using it as a representation of a rotation.
currentValue = currentValue.normalized;
return currentValue;
}
/// <inheritdoc/>
public Quaternion GetCurrentValue() => currentValue;
/// <inheritdoc/>
public Quaternion GetCurrentVelocity() => currentVelocity;
// Find the nearest integer multiple of the specified interval
private Vector3 GetNearest(Vector3 target, Vector3 interval)
{
return new Vector3(GetNearest(target.x, interval.x), GetNearest(target.y, interval.y), GetNearest(target.z, interval.z));
}
// Find the nearest integer multiple of the specified interval
private float GetNearest(float target, float interval)
{
return Mathf.Round(target / interval) * interval;
}
private float ComputeSnapFactor(float angleFromPoint, float radius)
{
// Snap force is calculated by multiplying the "-kx" factor by
// a clamped distance factor. This results in an overall
// hyperbolic profile to the force imparted by the snap point.
return (1.0f - Mathf.Clamp01(Mathf.Abs(angleFromPoint / radius)));
}
// Elementwise quaternion addition.
private Quaternion Add(Quaternion p, Quaternion q)
{
return new Quaternion(p.x + q.x, p.y + q.y, p.z + q.z, p.w + q.w);
}
// Elementwise quaternion scale.
private Quaternion Scale(Quaternion p, float t)
{
return new Quaternion(p.x * t, p.y * t, p.z * t, p.w * t);
}
}
}