Move third party assets to ThirdParty folder

This commit is contained in:
2024-08-08 11:26:28 +02:00
parent 386f303057
commit bd91af6f98
10340 changed files with 100 additions and 175 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f1d90ebea4cf4029834b50cc6c8b968a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 569325838cef427db2a7071d0b396e63
timeCreated: 1643803757

View File

@@ -0,0 +1,391 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFaceGestures.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections;
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Animation.Avatars
{
/// <summary>
/// Allows to simulate facial gestures like eyes movement/blinking and mouth using
/// the microphone input.
/// </summary>
public class UxrFaceGestures : UxrComponent
{
#region Inspector Properties/Serialized Fields
[Header("Blinking")] [SerializeField] private bool _blinkEyes = true;
[SerializeField] private Transform _eyeLidTopLeft;
[SerializeField] private Transform _eyeLidTopRight;
[SerializeField] private Transform _eyeLidBottomLeft;
[SerializeField] private Transform _eyeLidBottomRight;
[SerializeField] private Vector3 _eyeBlinkTopLocalAxis = Vector3.right;
[SerializeField] private float _eyeBlinkTopClosedAngle = 30.0f;
[SerializeField] private Vector3 _eyeBlinkBottomLocalAxis = Vector3.right;
[SerializeField] private float _eyeBlinkBottomClosedAngle;
[SerializeField] private float _eyeBlinkDurationMin = 0.05f;
[SerializeField] private float _eyeBlinkDurationMax = 0.1f;
[SerializeField] private float _eyeBlinkIntervalMin = 1.0f;
[SerializeField] private float _eyeBlinkIntervalMax = 5.0f;
[Header("Eye movement")] [SerializeField] private bool _moveEyes = true;
[SerializeField] private Transform _eyeLeft;
[SerializeField] private Transform _eyeRight;
[SerializeField] [Range(0.0f, 90.0f)] private float _eyeLookStraightAngleRange = 3.0f;
[SerializeField] [Range(0.0f, 90.0f)] private float _eyeLookEdgeAngleMin = 15.0f;
[SerializeField] [Range(0.0f, 90.0f)] private float _eyeLookEdgeAngleMax = 45.0f;
[SerializeField] [Range(0.0f, 1.0f)] private float _eyeLookEdgeProbability = 0.3f;
[SerializeField] private float _eyeSwitchLookDurationMin = 0.05f;
[SerializeField] private float _eyeSwitchLookDurationMax = 0.1f;
[SerializeField] private float _eyeSwitchLookIntervalMin = 0.25f;
[SerializeField] private float _eyeSwitchLookIntervalMax = 2.0f;
[Header("Mouth movement")] [SerializeField] private bool _moveMouthUsingMic = true;
[SerializeField] private float _microphoneAmplification = 1.0f;
[SerializeField] private Transform _mouthOpenTransform;
[SerializeField] private Vector3 _mouthOpenLocalAxis = Vector3.right;
[SerializeField] private float _mouthClosedAngle;
[SerializeField] private float _mouthMaxOpenAngle = 5.0f;
[SerializeField] [Range(0.0f, 0.2f)] private float _mouthRotationDamp = 0.05f;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
StartCoroutine(BlinkCoroutine());
StartCoroutine(SwitchLookCoroutine());
if (_moveMouthUsingMic && Microphone.devices.Length > 0)
{
_microphoneClipRecord = Microphone.Start(null, true, 10, 44100);
}
}
/// <summary>
/// Releases the microphone resource if it's in use.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
if (Microphone.IsRecording(null) && _moveMouthUsingMic)
{
Microphone.End(null);
}
}
/// <summary>
/// Additional initialization.
/// </summary>
protected override void Start()
{
base.Start();
if (_eyeLidTopLeft)
{
_localRotEyeLidTopLeft = _eyeLidTopLeft.localRotation;
}
if (_eyeLidTopRight)
{
_localRotEyeLidTopRight = _eyeLidTopRight.localRotation;
}
if (_eyeLidBottomLeft)
{
_localRotEyeLidBottomLeft = _eyeLidBottomLeft.localRotation;
}
if (_eyeLidBottomLeft)
{
_localRotEyeLidBottomRight = _eyeLidBottomRight.localRotation;
}
if (_eyeLeft)
{
_localRotEyeLeft = _eyeLeft.localRotation;
}
if (_eyeRight)
{
_localRotEyeRight = _eyeRight.localRotation;
}
if (_mouthOpenTransform)
{
_localRotMouth = _mouthOpenTransform.localRotation;
}
}
/// <summary>
/// Updates the mouth if the microphone is being used.
/// </summary>
private void Update()
{
if (Microphone.IsRecording(null) && _mouthOpenTransform && _moveMouthUsingMic)
{
_mouthAngle = Mathf.SmoothDampAngle(_mouthAngle, Mathf.LerpAngle(_mouthClosedAngle, _mouthMaxOpenAngle, GetMicrophoneMaxLevel()), ref _mouthAngleDampSpeed, _mouthRotationDamp);
_mouthOpenTransform.localRotation = _localRotMouth * Quaternion.AngleAxis(_mouthAngle, _mouthOpenLocalAxis);
}
else if (_mouthOpenTransform)
{
_mouthOpenTransform.localRotation = _localRotMouth;
}
}
#endregion
#region Coroutines
/// <summary>
/// Blinking coroutine.
/// </summary>
/// <returns>Coroutine enumerator</returns>
private IEnumerator BlinkCoroutine()
{
while (true)
{
// Wait until next blink
yield return new WaitForSeconds(Random.Range(_eyeBlinkIntervalMin, _eyeBlinkIntervalMax));
if (_blinkEyes == false)
{
continue;
}
// Start blink with random duration
float blinkDuration = Random.Range(_eyeBlinkDurationMin, _eyeBlinkDurationMax);
float startTime = Time.time;
// Close
while (Time.time - startTime < blinkDuration * 0.5f)
{
float t = Mathf.Clamp01((Time.time - startTime) / (blinkDuration * 0.5f));
Quaternion rotationTop = Quaternion.AngleAxis(_eyeBlinkTopClosedAngle * t, _eyeBlinkTopLocalAxis);
Quaternion rotationBottom = Quaternion.AngleAxis(_eyeBlinkBottomClosedAngle * t, _eyeBlinkBottomLocalAxis);
if (_eyeLidTopLeft)
{
_eyeLidTopLeft.localRotation = _localRotEyeLidTopLeft * rotationTop;
}
if (_eyeLidTopRight)
{
_eyeLidTopRight.localRotation = _localRotEyeLidTopRight * rotationTop;
}
if (_eyeLidBottomLeft)
{
_eyeLidBottomLeft.localRotation = _localRotEyeLidBottomLeft * rotationBottom;
}
if (_eyeLidBottomRight)
{
_eyeLidBottomRight.localRotation = _localRotEyeLidBottomRight * rotationBottom;
}
yield return null;
}
Quaternion rotationClosedTop = Quaternion.AngleAxis(_eyeBlinkTopClosedAngle, _eyeBlinkTopLocalAxis);
Quaternion rotationClosedBottom = Quaternion.AngleAxis(_eyeBlinkBottomClosedAngle, _eyeBlinkBottomLocalAxis);
if (_eyeLidTopLeft)
{
_eyeLidTopLeft.localRotation = _localRotEyeLidTopLeft * rotationClosedTop;
}
if (_eyeLidTopRight)
{
_eyeLidTopRight.localRotation = _localRotEyeLidTopRight * rotationClosedTop;
}
if (_eyeLidBottomLeft)
{
_eyeLidBottomLeft.localRotation = _localRotEyeLidBottomLeft * rotationClosedBottom;
}
if (_eyeLidBottomRight)
{
_eyeLidBottomRight.localRotation = _localRotEyeLidBottomRight * rotationClosedBottom;
}
yield return null;
// Open
startTime = Time.time;
while (Time.time - startTime < blinkDuration * 0.5f)
{
float t = 1.0f - Mathf.Clamp01((Time.time - startTime) / (blinkDuration * 0.5f));
Quaternion rotationTop = Quaternion.AngleAxis(_eyeBlinkTopClosedAngle * t, _eyeBlinkTopLocalAxis);
Quaternion rotationBottom = Quaternion.AngleAxis(_eyeBlinkBottomClosedAngle * t, _eyeBlinkBottomLocalAxis);
if (_eyeLidTopLeft)
{
_eyeLidTopLeft.localRotation = _localRotEyeLidTopLeft * rotationTop;
}
if (_eyeLidTopRight)
{
_eyeLidTopRight.localRotation = _localRotEyeLidTopRight * rotationTop;
}
if (_eyeLidBottomLeft)
{
_eyeLidBottomLeft.localRotation = _localRotEyeLidBottomLeft * rotationBottom;
}
if (_eyeLidBottomRight)
{
_eyeLidBottomRight.localRotation = _localRotEyeLidBottomRight * rotationBottom;
}
yield return null;
}
if (_eyeLidTopLeft)
{
_eyeLidTopLeft.localRotation = _localRotEyeLidTopLeft;
}
if (_eyeLidTopRight)
{
_eyeLidTopRight.localRotation = _localRotEyeLidTopRight;
}
if (_eyeLidBottomLeft)
{
_eyeLidBottomLeft.localRotation = _localRotEyeLidBottomLeft;
}
if (_eyeLidBottomRight)
{
_eyeLidBottomRight.localRotation = _localRotEyeLidBottomRight;
}
}
}
/// <summary>
/// Coroutine that randomly switches where the eyes are looking.
/// </summary>
/// <returns>Coroutine enumerator</returns>
private IEnumerator SwitchLookCoroutine()
{
while (true)
{
// Wait until next switch
yield return new WaitForSeconds(Random.Range(_eyeSwitchLookIntervalMin, _eyeSwitchLookIntervalMax));
if (_moveEyes == false)
{
if (_eyeLeft)
{
_eyeLeft.localRotation = _localRotEyeLeft;
}
if (_eyeRight)
{
_eyeRight.localRotation = _localRotEyeRight;
}
continue;
}
// Start switch with random duration
float switchDuration = Random.Range(_eyeSwitchLookDurationMin, _eyeSwitchLookDurationMax);
float startTime = Time.time;
// Rotate
Quaternion rotation = Quaternion.identity;
if (Random.value < _eyeLookEdgeProbability)
{
rotation = Quaternion.RotateTowards(Quaternion.identity, Random.rotation, Random.Range(_eyeLookEdgeAngleMin, _eyeLookEdgeAngleMax));
}
else
{
rotation = Quaternion.RotateTowards(Quaternion.identity, Random.rotation, Random.Range(0.0f, _eyeLookStraightAngleRange));
}
Quaternion rotLeft = _eyeLeft.localRotation;
Quaternion rotRight = _eyeRight.localRotation;
while (Time.time - startTime < switchDuration)
{
float t = Mathf.Clamp01((Time.time - startTime) / switchDuration);
if (_eyeLeft)
{
_eyeLeft.localRotation = Quaternion.Slerp(rotLeft, _localRotEyeLeft * rotation, t);
}
if (_eyeRight)
{
_eyeRight.localRotation = Quaternion.Slerp(rotRight, _localRotEyeRight * rotation, t);
}
yield return null;
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Tries to get the current microphone output level.
/// </summary>
/// <returns>
/// Microphone output level, approximately in the [0.0, 1.0] range but it's not clamped and the actual range is
/// undefined.
/// </returns>
private float GetMicrophoneMaxLevel()
{
float maxLevel = 0;
float[] waveData = new float[MicrophoneSampleWindow];
int micPosition = Microphone.GetPosition(null) - (MicrophoneSampleWindow + 1);
if (micPosition < 0)
{
return 0.0f;
}
_microphoneClipRecord.GetData(waveData, micPosition);
for (int i = 0; i < MicrophoneSampleWindow; ++i)
{
float wavePeak = waveData[i] * waveData[i];
if (maxLevel < wavePeak)
{
maxLevel = wavePeak;
}
}
return maxLevel * 1024.0f * _microphoneAmplification;
}
#endregion
#region Private Types & Data
private const int MicrophoneSampleWindow = 128;
private Quaternion _localRotEyeLidTopLeft;
private Quaternion _localRotEyeLidTopRight;
private Quaternion _localRotEyeLidBottomLeft;
private Quaternion _localRotEyeLidBottomRight;
private Quaternion _localRotEyeLeft;
private Quaternion _localRotEyeRight;
private Quaternion _localRotMouth;
private float _mouthAngle;
private float _mouthAngleDampSpeed;
private AudioClip _microphoneClipRecord;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: c4580b2065b1d39428ae4bb5e6840419
timeCreated: 1549387631
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d938f776bf314dd0b238ec5ea2d84058
timeCreated: 1643801512

View File

@@ -0,0 +1,90 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleComponent.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Animation.Components
{
/// <summary>
/// Component that allows to toggle components enabled state back and forth at random times.
/// </summary>
public class UxrToggleComponent : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private MonoBehaviour[] _components;
[SerializeField] private float _enabledDurationMin;
[SerializeField] private float _enabledDurationMax;
[SerializeField] private float _disabledDurationMin;
[SerializeField] private float _disabledDurationMax;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Unity
/// <summary>
/// Called each time the component is enabled. Sets up the next toggle time.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_nextToggleTime = GetNextRelativeToggleTime();
}
/// <summary>
/// Called on each update. Checks if it is time to toggle the components.
/// </summary>
private void Update()
{
float time = (_useUnscaledTime ? Time.unscaledTime : Time.time) - _startTime;
if (time > _nextToggleTime)
{
foreach (MonoBehaviour component in _components)
{
component.enabled = !component.enabled;
}
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_nextToggleTime = GetNextRelativeToggleTime();
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets the next time the components will be toggled
/// </summary>
/// <returns>Next toggle time in seconds relative to the current time</returns>
private float GetNextRelativeToggleTime()
{
if (_components.Length > 0 && _components[0].enabled)
{
return Random.Range(_enabledDurationMin, _enabledDurationMax);
}
if (_components.Length > 0 && !_components[0].enabled)
{
return Random.Range(_disabledDurationMin, _disabledDurationMax);
}
return 0.0f;
}
#endregion
#region Private Types & Data
private float _startTime;
private float _nextToggleTime;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 7a67b8fc64f10ba439788082a67721ae
timeCreated: 1522850574
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9c55e0d258b8ace4cba63217ad6fa2f0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrDelayedDestroy.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to destroy the <see cref="GameObject" /> it is attached to after a variable amount of seconds
/// </summary>
public class UxrDelayedDestroy : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _seconds;
#endregion
#region Unity
/// <summary>
/// Programs the object destruction.
/// </summary>
protected override void Start()
{
base.Start();
Destroy(gameObject, _seconds);
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: af3a08495f6a01846a24adb624144bb7
timeCreated: 1534242279
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,313 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrObjectBlink.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to make objects blink using their material's emission channel.
/// </summary>
public class UxrObjectBlink : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private MeshRenderer _renderer;
[SerializeField] private int _materialSlot = -1;
[SerializeField] private Color _colorNormal = Color.black;
[SerializeField] private Color _colorHighlight = Color.white;
[SerializeField] private float _blinksPerSec = 4.0f;
[SerializeField] private float _durationSeconds = -1.0f;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the object is currently blinking.
/// </summary>
public bool IsBlinking { get; private set; } = true;
#endregion
#region Public Methods
/// <summary>
/// Starts a blinking animation using the emission material of an object.
/// </summary>
/// <param name="gameObject">The GameObject to blink</param>
/// <param name="emissionColor">The emission color</param>
/// <param name="blinksPerSec">The blink frequency</param>
/// <param name="durationSeconds">Total duration of the blinking animation</param>
/// <param name="materialSlot">
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
/// </param>
/// <param name="useUnscaledTime">
/// Whether to use unscaled time (<see cref="Time.unscaledTime" />) or not (
/// <see cref="Time.time" />).
/// </param>
/// <returns>Animation component</returns>
public static UxrObjectBlink StartBlinking(GameObject gameObject, Color emissionColor, float blinksPerSec, float durationSeconds, int materialSlot = -1, bool useUnscaledTime = false)
{
if (gameObject == null)
{
return null;
}
UxrObjectBlink blinkComponent = gameObject.GetOrAddComponent<UxrObjectBlink>();
blinkComponent.CheckInitialize();
blinkComponent.StartBlinkingInternal(emissionColor, blinksPerSec, durationSeconds, materialSlot, useUnscaledTime);
return blinkComponent;
}
/// <summary>
/// Stops a blinking animation on an object if it has any.
/// </summary>
/// <param name="gameObject">GameObject to stop the animation from</param>
public static void StopBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return;
}
if (gameObject.TryGetComponent<UxrObjectBlink>(out var blinkComponent))
{
blinkComponent.enabled = false;
}
}
/// <summary>
/// Checks whether the given GameObject has any blinking animation running.
/// </summary>
/// <param name="gameObject">GameObject to check</param>
/// <returns>Whether the given GameObject has any blinking animation running</returns>
public static bool CheckBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return false;
}
UxrObjectBlink blinkComponent = gameObject.GetComponent<UxrObjectBlink>();
return blinkComponent != null && blinkComponent.IsBlinking;
}
/// <summary>
/// Sets up the blinking animation parameters.
/// </summary>
/// <param name="renderer">Renderer whose material will be animated</param>
/// <param name="colorNormal">The emission color when it is not blinking</param>
/// <param name="colorHighlight">The fully blinking color</param>
/// <param name="blinksPerSec">The blinking frequency</param>
/// <param name="durationSeconds">The total duration of the animation in seconds</param>
/// <param name="materialSlot">
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
/// </param>
/// <param name="useUnscaledTime">
/// Whether to use unscaled time (<see cref="Time.unscaledTime" />) or not (
/// <see cref="Time.time" />).
/// </param>
public void Setup(MeshRenderer renderer, Color colorNormal, Color colorHighlight, float blinksPerSec = 4.0f, float durationSeconds = -1.0f, int materialSlot = -1, bool useUnscaledTime = false)
{
_renderer = renderer;
_colorNormal = colorNormal;
_colorHighlight = colorHighlight;
_blinksPerSec = blinksPerSec;
_durationSeconds = durationSeconds;
_materialSlot = materialSlot;
_useUnscaledTime = useUnscaledTime;
IsBlinking = false;
IsInitialized = false;
CheckInitialize();
}
/// <summary>
/// Starts or restarts the blinking animation using the current parameters.
/// </summary>
public void StartBlinkingWithCurrentParameters()
{
CheckInitialize();
StartBlinkingInternal(_colorHighlight, _blinksPerSec, _durationSeconds, _materialSlot, _useUnscaledTime);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
/// <summary>
/// When re-enabled, starts blinking again with the current parameters.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
StartBlinkingWithCurrentParameters();
}
/// <summary>
/// Stops blinking.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
StopBlinkingInternal();
}
/// <summary>
/// Updates the blinking animation if active.
/// </summary>
private void Update()
{
if (IsBlinking && _renderer != null)
{
float timer = CurrentTime - _blinkStartTime;
if (_durationSeconds >= 0.0f && timer >= _durationSeconds)
{
StopBlinkingInternal();
}
else
{
float blend = (Mathf.Sin(timer * Mathf.PI * _blinksPerSec * 2.0f) + 1.0f) * 0.5f;
Material[] materials = _renderer.materials;
for (int i = 0; i < materials.Length; i++)
{
if (i == _materialSlot || _materialSlot < 0)
{
materials[i].SetColor(UxrConstants.Shaders.EmissionColorVarName, Color.Lerp(_colorNormal, _colorHighlight, blend));
}
}
_renderer.materials = materials;
}
}
}
#endregion
#region Private Methods
/// <summary>
/// Initializes the component if necessary.
/// </summary>
private void CheckInitialize()
{
if (!IsInitialized)
{
if (_renderer == null)
{
_renderer = GetComponent<MeshRenderer>();
}
if (_renderer != null)
{
_originalMaterials = _renderer.sharedMaterials;
}
IsInitialized = true;
}
}
/// <summary>
/// Starts blinking.
/// </summary>
/// <param name="emissionColor">Emission color</param>
/// <param name="blinksPerSec">Blinking frequency</param>
/// <param name="durationSeconds">Total duration in seconds</param>
/// <param name="materialSlot">The material(s) to target</param>
/// <param name="useUnscaledTime">Whether to use unscaled time or not</param>
private void StartBlinkingInternal(Color emissionColor, float blinksPerSec, float durationSeconds, int materialSlot, bool useUnscaledTime)
{
if (_renderer == null)
{
return;
}
_useUnscaledTime = useUnscaledTime;
_blinkStartTime = CurrentTime;
_colorHighlight = emissionColor;
_blinksPerSec = blinksPerSec;
_durationSeconds = durationSeconds;
_materialSlot = materialSlot;
Material[] materials = _renderer.materials;
for (int i = 0; i < materials.Length; i++)
{
if (i == _materialSlot || _materialSlot < 0)
{
materials[i].EnableKeyword(UxrConstants.Shaders.EmissionKeyword);
}
}
_renderer.materials = materials;
IsBlinking = true;
enabled = true;
}
/// <summary>
/// Stops blinking.
/// </summary>
private void StopBlinkingInternal()
{
if (_renderer == null)
{
return;
}
IsBlinking = false;
RestoreOriginalSharedMaterial();
}
/// <summary>
/// Restores the original (shared) material.
/// </summary>
private void RestoreOriginalSharedMaterial()
{
if (_renderer)
{
_renderer.sharedMaterials = _originalMaterials;
}
}
#endregion
#region Private Types & Data
private float CurrentTime => _useUnscaledTime ? Time.unscaledTime : Time.time;
private bool IsInitialized { get; set; }
private float _blinkStartTime;
private Material[] _originalMaterials;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a6cff78ce55139e48a3e08257187ea94
timeCreated: 1504781678
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrObjectFade.ObjectEntry.MaterialEntry.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
public partial class UxrObjectFade
{
#region Private Types & Data
private partial class ObjectEntry
{
#region Private Types & Data
/// <summary>
/// Stores information of a material in a fade animation.
/// </summary>
private struct MaterialEntry
{
#region Public Types & Data
/// <summary>
/// Gets or sets the initial material color.
/// </summary>
public Color StartColor { get; set; }
/// <summary>
/// Gets or sets whether the shader transparency was enabled.
/// </summary>
public bool ShaderChanged { get; set; }
#endregion
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e51e06febf0347f899cb77675622506d
timeCreated: 1643744815

View File

@@ -0,0 +1,123 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrObjectFade.ObjectEntry.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
namespace UltimateXR.Animation.GameObjects
{
public partial class UxrObjectFade
{
#region Private Types & Data
/// <summary>
/// Stores information about an object in a fade animation.
/// </summary>
private partial class ObjectEntry
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="renderer">Renderer component</param>
public ObjectEntry(Renderer renderer)
{
Renderer = renderer;
SharedMaterials = renderer.sharedMaterials;
Materials = renderer.materials;
MaterialEntries = new MaterialEntry[Materials.Length];
for (int i = 0; i < Materials.Length; ++i)
{
MaterialEntries[i].StartColor = Materials[i].color;
MaterialEntries[i].ShaderChanged = false;
}
}
#endregion
#region Public Methods
/// <summary>
/// Changes the material transparency.
/// </summary>
/// <param name="startQuantity">Start alpha</param>
/// <param name="endQuantity">End alpha</param>
/// <param name="fadeT">Interpolation factor [0.0, 1.0]</param>
public void Fade(float startQuantity, float endQuantity, float fadeT)
{
for (int i = 0; i < Materials.Length; ++i)
{
if (!MaterialEntries[i].ShaderChanged)
{
ChangeStandardMaterialRenderMode(Materials[i]);
MaterialEntries[i].ShaderChanged = true;
}
Color color = MaterialEntries[i].StartColor;
color.a *= Mathf.Lerp(startQuantity, endQuantity, fadeT);
Materials[i].color = color;
}
Renderer.materials = Materials;
}
/// <summary>
/// Restores the original material(s).
/// </summary>
public void Restore()
{
Renderer.sharedMaterials = SharedMaterials;
MaterialEntries.ForEach(m => m.ShaderChanged = false);
}
#endregion
#region Private Methods
/// <summary>
/// Enables transparency on a material.
/// </summary>
/// <param name="material">Material to enable transparency on</param>
private void ChangeStandardMaterialRenderMode(Material material)
{
if (material.HasProperty(UxrConstants.Shaders.SurfaceModeVarName))
{
// Universal render pipeline
material.SetInt(UxrConstants.Shaders.SurfaceModeVarName, UxrConstants.Shaders.SurfaceModeTransparent);
material.SetInt(UxrConstants.Shaders.BlendModeVarName, UxrConstants.Shaders.BlendModeAlpha);
material.renderQueue = (int)RenderQueue.Transparent;
}
else if (material.IsKeywordEnabled(UxrConstants.Shaders.AlphaBlendOnKeyword) == false)
{
// Built-in render pipeline
material.SetInt(UxrConstants.Shaders.SrcBlendVarName, (int)BlendMode.SrcAlpha);
material.SetInt(UxrConstants.Shaders.DstBlendVarName, (int)BlendMode.OneMinusSrcAlpha);
material.SetInt(UxrConstants.Shaders.ZWriteVarName, 0);
material.DisableKeyword(UxrConstants.Shaders.AlphaTestOnKeyword);
material.EnableKeyword(UxrConstants.Shaders.AlphaBlendOnKeyword);
material.DisableKeyword(UxrConstants.Shaders.AlphaPremultiplyOnKeyword);
material.renderQueue = (int)RenderQueue.Transparent;
}
}
#endregion
#region Private Types & Data
private MaterialEntry[] MaterialEntries { get; }
private Renderer Renderer { get; }
private Material[] Materials { get; }
private Material[] SharedMaterials { get; }
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: edc5622f479147cd8ef96832732a9ff0
timeCreated: 1644846685

View File

@@ -0,0 +1,175 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrObjectFade.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to fade an object out by making the material progressively more transparent.
/// </summary>
public partial class UxrObjectFade : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _recursively = true;
[SerializeField] private float _delaySeconds;
[SerializeField] private float _duration = 1.0f;
[SerializeField] private float _startQuantity = 1.0f;
[SerializeField] private float _endQuantity;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Public Methods
/// <summary>
/// Starts a fade animation.
/// </summary>
/// <param name="gameObject">GameObject whose material transparency will be enabled and animated.</param>
/// <param name="startAlphaQuantity">Start alpha</param>
/// <param name="endFadeQuantity">End alpha</param>
/// <param name="delaySeconds">Seconds to wait before the animation starts</param>
/// <param name="durationSeconds">Fade duration in seconds</param>
/// <param name="recursively">Whether to also process all other child objects in the hierarchy</param>
/// <param name="useUnscaledTime">
/// Whether to use unscaled time (<see cref="Time.unscaledTime" />) or not (
/// <see cref="Time.time" />)
/// </param>
/// <param name="finishedCallback">Optional callback executed when the animation finished</param>
/// <returns>Animation component</returns>
public static UxrObjectFade Fade(GameObject gameObject,
float startAlphaQuantity,
float endFadeQuantity,
float delaySeconds,
float durationSeconds,
bool recursively = true,
bool useUnscaledTime = false,
Action finishedCallback = null)
{
UxrObjectFade objectFade = gameObject.GetOrAddComponent<UxrObjectFade>();
objectFade._startQuantity = startAlphaQuantity;
objectFade._endQuantity = endFadeQuantity;
objectFade._delaySeconds = delaySeconds;
objectFade._duration = durationSeconds;
objectFade._recursively = recursively;
objectFade._useUnscaledTime = useUnscaledTime;
objectFade._finishedCallback = finishedCallback;
objectFade.CheckInitialize(true);
return objectFade;
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
/// <summary>
/// Starts or re-starts the animation.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_fadeStartTime = CurrentTime;
_finished = false;
}
/// <summary>
/// Stops the animation and restores the material.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
foreach (ObjectEntry objectEntry in _objects)
{
objectEntry.Restore();
}
}
/// <summary>
/// Updates the animation.
/// </summary>
private void Update()
{
if (_finished)
{
return;
}
float fadeTime = CurrentTime - _fadeStartTime - _delaySeconds;
if (fadeTime <= 0)
{
return;
}
float fadeT = Mathf.Clamp01(fadeTime / _duration);
foreach (ObjectEntry entry in _objects)
{
entry.Fade(_startQuantity, _endQuantity, fadeT);
}
if (fadeTime > _duration)
{
_finishedCallback?.Invoke();
_finished = true;
}
}
#endregion
#region Private Methods
/// <summary>
/// Initializes the component if necessary.
/// </summary>
/// <param name="forceInitialize">Forces initializing the component even if it already may have been initialized</param>
private void CheckInitialize(bool forceInitialize = false)
{
if (_objects.Count == 0 || forceInitialize)
{
Renderer[] objectRenderers = _recursively ? gameObject.GetComponentsInChildren<Renderer>() : new[] { gameObject.GetComponent<Renderer>() };
foreach (Renderer renderer in objectRenderers)
{
_objects.Add(new ObjectEntry(renderer));
}
}
}
#endregion
#region Private Types & Data
private float CurrentTime => _useUnscaledTime ? Time.unscaledTime : Time.time;
private readonly List<ObjectEntry> _objects = new List<ObjectEntry>();
private float _fadeStartTime;
private bool _finished;
private Action _finishedCallback;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 832e898c6d705a84692abd8a162a48e0
timeCreated: 1505585796
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,87 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleObject.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to toggle <see cref="GameObject" /> active state back and forth at random times.
/// </summary>
public class UxrToggleObject : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private GameObject _gameObject;
[SerializeField] private float _enabledDurationMin;
[SerializeField] private float _enabledDurationMax;
[SerializeField] private float _disabledDurationMin;
[SerializeField] private float _disabledDurationMax;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Unity
/// <summary>
/// Called each time the component is enabled. Sets up the next toggle time.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_nextToggleTime = GetNextRelativeToggleTime();
}
/// <summary>
/// Called on each update. Checks if it is time to toggle the GameObjects.
/// </summary>
private void Update()
{
float time = (_useUnscaledTime ? Time.unscaledTime : Time.time) - _startTime;
if (time > _nextToggleTime)
{
_gameObject.SetActive(!_gameObject.activeSelf);
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_nextToggleTime = GetNextRelativeToggleTime();
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets the next time the objects will be toggled.
/// </summary>
/// <returns>Next toggle time in seconds relative to the current time</returns>
private float GetNextRelativeToggleTime()
{
if (_gameObject && _gameObject.activeSelf)
{
return Random.Range(_enabledDurationMin, _enabledDurationMax);
}
if (_gameObject && !_gameObject.activeSelf)
{
return Random.Range(_disabledDurationMin, _disabledDurationMax);
}
return 0.0f;
}
#endregion
#region Private Types & Data
private float _startTime;
private float _nextToggleTime;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 161147edff113284a819edef7ac1d57c
timeCreated: 1522850574
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,149 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleObjectsUsingButtons.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Devices;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to enable/disable GameObjects based on input from the VR controller buttons.
/// </summary>
public class UxrToggleObjectsUsingButtons : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private List<GameObject> _objectList;
[SerializeField] private bool _startEnabled = true;
[SerializeField] private UxrHandSide _controllerHand = UxrHandSide.Left;
[SerializeField] private UxrInputButtons _buttonsEnable = UxrInputButtons.Button1;
[SerializeField] private UxrButtonEventType _buttonsEventEnable = UxrButtonEventType.PressDown;
[SerializeField] private UxrInputButtons _buttonsDisable = UxrInputButtons.Button1;
[SerializeField] private UxrButtonEventType _buttonsEventsDisable = UxrButtonEventType.TouchDown;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the object list to enable/disable.
/// </summary>
public List<GameObject> ObjectList
{
get => _objectList;
set => _objectList = value;
}
/// <summary>
/// Gets or sets which controller hand is responsible for the input.
/// </summary>
public UxrHandSide ControllerHand
{
get => _controllerHand;
set => _controllerHand = value;
}
/// <summary>
/// Gets or sets the button(s) used to enable.
/// </summary>
public UxrInputButtons ButtonsToEnable
{
get => _buttonsEnable;
set => _buttonsEnable = value;
}
/// <summary>
/// Gets or sets the button event to enable.
/// </summary>
public UxrButtonEventType EnableButtonEvent
{
get => _buttonsEventEnable;
set => _buttonsEventEnable = value;
}
/// <summary>
/// Gets or sets the button(s) to disable.
/// </summary>
public UxrInputButtons ButtonsToDisable
{
get => _buttonsDisable;
set => _buttonsDisable = value;
}
/// <summary>
/// Gets or sets the button event to disable.
/// </summary>
public UxrButtonEventType DisableButtonEvent
{
get => _buttonsEventsDisable;
set => _buttonsEventsDisable = value;
}
#endregion
#region Unity
/// <summary>
/// Called at the beginning. Sets the object initial state.
/// </summary>
protected override void Awake()
{
base.Awake();
_state = _startEnabled;
SetObjectsState(_startEnabled);
}
/// <summary>
/// Called each frame. Checks for VR controller button events and toggles states.
/// </summary>
private void Update()
{
if (!UxrAvatar.LocalAvatar)
{
return;
}
if (_state == false && UxrAvatar.LocalAvatarInput.GetButtonsEvent(ControllerHand, ButtonsToEnable, EnableButtonEvent))
{
SetObjectsState(true);
}
else if (_state && UxrAvatar.LocalAvatarInput.GetButtonsEvent(ControllerHand, ButtonsToDisable, DisableButtonEvent))
{
SetObjectsState(false);
}
}
#endregion
#region Private Methods
/// <summary>
/// Sets the object state of the list of objects in this component.
/// </summary>
/// <param name="value">State they should be changed to</param>
private void SetObjectsState(bool value)
{
_state = value;
foreach (GameObject obj in ObjectList)
{
obj.SetActive(value);
}
}
#endregion
#region Private Types & Data
private bool _state;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 29eb44ed5f72f6d4f8341d2397b451a1
timeCreated: 1522133718
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bad62e3862af46c8a36599131b4b2118
timeCreated: 1643803875

View File

@@ -0,0 +1,562 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrArmIKSolver.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Math;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// IK component that implements basic Inverse Kinematics for an arm.
/// </summary>
public class UxrArmIKSolver : UxrIKSolver
{
#region Inspector Properties/Serialized Fields
[Header("General")] [SerializeField] private UxrArmOverExtendMode _overExtendMode = UxrArmOverExtendMode.LimitHandReach;
[Header("Clavicle")] [SerializeField] [Range(0, 1)] private float _clavicleDeformation = DefaultClavicleDeformation;
[SerializeField] private float _clavicleRangeOfMotionAngle = DefaultClavicleRangeOfMotionAngle;
[SerializeField] private bool _clavicleAutoComputeBias = true;
[SerializeField] private Vector3 _clavicleDeformationAxesBias = Vector3.zero;
[SerializeField] private Vector3 _clavicleDeformationAxesScale = new Vector3(1.0f, 0.8f, 1.0f);
[Header("Arm (shoulder), forearm & hand")] [SerializeField] private float _armRangeOfMotionAngle = DefaultArmRangeOfMotionAngle;
[SerializeField] [Range(0, 1)] private float _relaxedElbowAperture = DefaultElbowAperture;
[SerializeField] [Range(0, 1)] private float _elbowApertureRotation = DefaultElbowApertureRotation;
[SerializeField] private bool _smooth = true;
#endregion
#region Public Types & Data
public const float DefaultClavicleDeformation = 0.4f;
public const float DefaultClavicleRangeOfMotionAngle = 30.0f;
public const float DefaultArmRangeOfMotionAngle = 100.0f;
public const float DefaultElbowAperture = 0.5f;
public const float DefaultElbowApertureRotation = 0.3f;
/// <summary>
/// Gets the clavicle bone.
/// </summary>
public Transform Clavicle { get; private set; }
/// <summary>
/// Gets the arm bone.
/// </summary>
public Transform Arm { get; private set; }
/// <summary>
/// Gets the forearm bone.
/// </summary>
public Transform Forearm { get; private set; }
/// <summary>
/// Gets the hand bone.
/// </summary>
public Transform Hand { get; private set; }
/// <summary>
/// Gets whether it is the left or right arm.
/// </summary>
public UxrHandSide Side => _side;
/// <summary>
/// Gets or sets how far [0.0, 1.0] the elbow will from the body when solving the IK. Lower values will bring the elbow
/// closer to the body.
/// </summary>
public float RelaxedElbowAperture
{
get => _relaxedElbowAperture;
set => _relaxedElbowAperture = value;
}
/// <summary>
/// Gets or sets what happens when the real hand makes the VR arm to over-extend. This may happen if the user has a
/// longer arm than the VR model, if the controller is placed far away or if the avatar is grabbing an object with
/// constraints that lock the hand position.
/// </summary>
public UxrArmOverExtendMode OverExtendMode
{
get => _overExtendMode;
set => _overExtendMode = value;
}
#endregion
#region Public Overrides UxrIKSolver
/// <inheritdoc />
public override bool Initialized => _initialized;
#endregion
#region Public Methods
/// <summary>
/// Solves a pass in the Inverse Kinematics.
/// </summary>
/// <param name="armSolveOptions">Arm solving options</param>
/// <param name="armOverExtendMode">What happens when the hand moves farther than the actual arm length</param>
public void SolveIKPass(UxrArmSolveOptions armSolveOptions, UxrArmOverExtendMode armOverExtendMode)
{
if (Hand == null || Forearm == null || Arm == null)
{
return;
}
Vector3 localClaviclePos = ToLocalAvatarPos(Clavicle.position);
Vector3 localForearmPos = ToLocalAvatarPos(Forearm.position);
Vector3 localHandPos = ToLocalAvatarPos(Hand.position);
if (Clavicle != null)
{
if (armSolveOptions.HasFlag(UxrArmSolveOptions.ResetClavicle))
{
Clavicle.transform.localRotation = _clavicleUniversalLocalAxes.InitialLocalRotation;
}
if (armSolveOptions.HasFlag(UxrArmSolveOptions.SolveClavicle))
{
// Compute the rotation to make the clavicle look at the elbow.
// Computations are performed in local avatar space to allow avatars with pitch/roll and improve precision.
Vector3 avatarClavicleLookAt = (localForearmPos - localClaviclePos).normalized;
avatarClavicleLookAt = Vector3.Scale(avatarClavicleLookAt, _clavicleDeformationAxesScale) + _clavicleDeformationAxesBias;
Quaternion avatarClavicleRotation = ToLocalAvatarRot(Clavicle.rotation);
Quaternion avatarClavicleRotationLookAt = Quaternion.Slerp(avatarClavicleRotation,
Quaternion.LookRotation(avatarClavicleLookAt) * _clavicleUniversalLocalAxes.UniversalToActualAxesRotation,
_clavicleDeformation);
float deformationAngle = Quaternion.Angle(avatarClavicleRotationLookAt, avatarClavicleRotation);
if (deformationAngle > _clavicleRangeOfMotionAngle)
{
avatarClavicleRotationLookAt = Quaternion.Slerp(avatarClavicleRotation, avatarClavicleRotationLookAt, _clavicleRangeOfMotionAngle / deformationAngle);
}
// Smooth out:
float totalDegrees = Quaternion.Angle(_lastClavicleLocalRotation, avatarClavicleRotationLookAt);
float degreesRot = ClavicleMaxDegreesPerSecond * Time.deltaTime;
if (_smooth == false)
{
_lastClavicleRotationInitialized = false;
}
if (_lastClavicleRotationInitialized == false || totalDegrees < 0.001f)
{
Clavicle.rotation = ToWorldRot(avatarClavicleRotationLookAt);
}
else
{
Clavicle.rotation = Quaternion.Slerp(ToWorldRot(_lastClavicleLocalRotation),
ToWorldRot(avatarClavicleRotationLookAt),
Mathf.Clamp01(degreesRot / totalDegrees));
}
}
Hand.position = ToWorldPos(localHandPos);
}
// Find the plane of intersection between 2 spheres (sphere with "upper arm" radius and sphere with "forearm" radius).
// Computations are performed in local avatar space to allow avatars with pitch/roll and improve precision.
localForearmPos = ToLocalAvatarPos(Forearm.position);
Vector3 localArmPos = ToLocalAvatarPos(Arm.position);
float a = 2.0f * (localHandPos.x - localArmPos.x);
float b = 2.0f * (localHandPos.y - localArmPos.y);
float c = 2.0f * (localHandPos.z - localArmPos.z);
float d = localArmPos.x * localArmPos.x - localHandPos.x * localHandPos.x + localArmPos.y * localArmPos.y - localHandPos.y * localHandPos.y +
localArmPos.z * localArmPos.z - localHandPos.z * localHandPos.z - _upperArmLocalLength * _upperArmLocalLength + _forearmLocalLength * _forearmLocalLength;
// Find the center of the circle intersecting the 2 spheres. Check if the intersection exists (hand may be stretched over the limits)
float t = (localArmPos.x * a + localArmPos.y * b + localArmPos.z * c + d) / (a * (localArmPos.x - localHandPos.x) + b * (localArmPos.y - localHandPos.y) + c * (localArmPos.z - localHandPos.z));
Vector3 localArmToCenter = (localHandPos - localArmPos) * t;
Vector3 localCenter = localForearmPos;
float safeDistance = 0.001f;
float maxHandDistance = _upperArmLocalLength + _forearmLocalLength - safeDistance;
float circleRadius = 0.0f;
if (localArmToCenter.magnitude + _forearmLocalLength > maxHandDistance)
{
// Too far from shoulder and arm is over-extending. Solve depending on selected mode, but some are applied at the end of this method.
localArmToCenter = localArmToCenter.normalized * (_upperArmLocalLength - safeDistance * 0.5f);
localCenter = localArmPos + localArmToCenter;
if (armOverExtendMode == UxrArmOverExtendMode.LimitHandReach)
{
// Clamp hand distance
Hand.position = ToWorldPos(localArmPos + localArmToCenter.normalized * maxHandDistance);
}
float angleRadians = Mathf.Acos((localCenter - localArmPos).magnitude / _upperArmLocalLength);
circleRadius = Mathf.Sin(angleRadians) * _upperArmLocalLength;
}
else if (localArmToCenter.magnitude < 0.04f)
{
// Too close to shoulder: keep current elbow position.
localArmToCenter = localForearmPos - localArmPos;
localCenter = localForearmPos;
}
else
{
localCenter = localArmPos + localArmToCenter;
// Find the circle radius
float angleRadians = Mathf.Acos((localCenter - localArmPos).magnitude / _upperArmLocalLength);
circleRadius = Mathf.Sin(angleRadians) * _upperArmLocalLength;
}
Vector3 finalLocalHandPosition = ToLocalAvatarPos(Hand.position);
Quaternion finalHandRotation = Hand.rotation;
// Compute the point inside this circle using the elbowAperture parameter.
// Possible range is from bottom to exterior (far left or far right for left arm and right arm respectively).
Vector3 planeNormal = -new Vector3(a, b, c);
Transform otherArm = _side == UxrHandSide.Left ? Avatar.AvatarRig.RightArm.UpperArm : Avatar.AvatarRig.LeftArm.UpperArm;
Vector3 otherLocalArmPos = otherArm != null ? ToLocalAvatarPos(otherArm.position) : Vector3.zero;
if (otherArm == null)
{
otherLocalArmPos = ToLocalAvatarPos(Arm.position);
otherLocalArmPos.x = -otherLocalArmPos.x;
}
Quaternion rotToShoulder = Quaternion.LookRotation(Vector3.Cross((localArmPos - otherLocalArmPos) * (_side == UxrHandSide.Left ? -1.0f : 1.0f), Vector3.up).normalized, Vector3.up);
Vector3 armToHand = (finalLocalHandPosition - localArmPos).normalized;
Quaternion rotArmForward = rotToShoulder * Quaternion.LookRotation(Quaternion.Inverse(rotToShoulder) * localArmToCenter, Quaternion.Inverse(rotToShoulder) * armToHand);
Vector3 vectorFromCenterSide = Vector3.Cross(_side == UxrHandSide.Left ? rotArmForward * Vector3.up : rotArmForward * -Vector3.up, planeNormal);
if (otherArm != null)
{
bool isBack = Vector3.Cross(localArmPos - otherLocalArmPos, localCenter - localArmPos).y * (_side == UxrHandSide.Left ? -1.0f : 1.0f) > 0.0f;
/*
* Do stuff with isBack
*/
}
// Compute elbow aperture value [0.0, 1.0] depending on the relaxedElbowAperture parameter and the current wrist torsion
float wristDegrees = _side == UxrHandSide.Left ? -Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Left).WristTorsionInfo.WristTorsionAngle : Avatar.AvatarRigInfo.GetArmInfo(UxrHandSide.Right).WristTorsionInfo.WristTorsionAngle;
float elbowApertureBiasDueToWrist = wristDegrees / WristTorsionDegreesFactor * _elbowApertureRotation;
float elbowAperture = Mathf.Clamp01(_relaxedElbowAperture + elbowApertureBiasDueToWrist);
_elbowAperture = _elbowAperture < 0.0f ? elbowAperture : Mathf.SmoothDampAngle(_elbowAperture, elbowAperture, ref _elbowApertureVelocity, ElbowApertureRotationSmoothTime);
// Now compute the elbow position using it
Vector3 vectorFromCenterBottom = _side == UxrHandSide.Left ? Vector3.Cross(vectorFromCenterSide, planeNormal) : Vector3.Cross(planeNormal, vectorFromCenterSide);
Vector3 elbowPosition = localCenter + Vector3.Lerp(vectorFromCenterBottom, vectorFromCenterSide, _elbowAperture).normalized * circleRadius;
// Compute the desired rotation
Vector3 armForward = (elbowPosition - localArmPos).normalized;
// Check range of motion of the arm
if (Arm.parent != null)
{
Vector3 armNeutralForward = ToLocalAvatarDir(Arm.parent.TransformDirection(_armNeutralForwardInParent));
if (Vector3.Angle(armForward, armNeutralForward) > _armRangeOfMotionAngle)
{
armForward = Vector3.RotateTowards(armNeutralForward, armForward, _armRangeOfMotionAngle * Mathf.Deg2Rad, 0.0f);
elbowPosition = localArmPos + armForward * _upperArmLocalLength;
}
}
// Compute the position and rotation of the rest
Vector3 forearmForward = (ToLocalAvatarPos(Hand.position) - elbowPosition).normalized;
float elbowAngle = Vector3.Angle(armForward, forearmForward);
Vector3 elbowAxis = elbowAngle > ElbowMinAngleThreshold ? Vector3.Cross(forearmForward, armForward).normalized : Vector3.up;
elbowAxis = _side == UxrHandSide.Left ? -elbowAxis : elbowAxis;
Quaternion armRotationTarget = Quaternion.LookRotation(armForward, elbowAxis);
Quaternion forearmRotationTarget = Quaternion.LookRotation(forearmForward, elbowAxis);
// Transform from top hierarchy to bottom to avoid jitter. Since we consider Z forward and Y the elbow rotation axis, we also
// need to transform from this "universal" space to the actual axes the model uses.
Arm.rotation = ToWorldRot(armRotationTarget * _armUniversalLocalAxes.UniversalToActualAxesRotation);
if (Vector3.Distance(finalLocalHandPosition, localArmPos) > maxHandDistance)
{
// Arm over extended: solve if the current mode is one of the remaining 2 to handle:
if (armOverExtendMode == UxrArmOverExtendMode.ExtendUpperArm)
{
// Move the elbow away to reach the hand. This will stretch the arm.
elbowPosition = finalLocalHandPosition - (finalLocalHandPosition - elbowPosition).normalized * _forearmLocalLength;
}
else if (armOverExtendMode == UxrArmOverExtendMode.ExtendArm)
{
// Stretch both the arm and forearm
Vector3 elbowPosition2 = finalLocalHandPosition - (finalLocalHandPosition - elbowPosition).normalized * _forearmLocalLength;
elbowPosition = (elbowPosition + elbowPosition2) * 0.5f;
}
}
Forearm.SetPositionAndRotation(ToWorldPos(elbowPosition), ToWorldRot(forearmRotationTarget * _forearmUniversalLocalAxes.UniversalToActualAxesRotation));
Hand.SetPositionAndRotation(ToWorldPos(finalLocalHandPosition), finalHandRotation);
}
#endregion
#region Unity
/// <summary>
/// Computes internal IK parameters.
/// </summary>
protected override void Awake()
{
base.Awake();
ComputeParameters();
_initialized = true;
}
/// <summary>
/// Subscribe to events.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.AvatarsUpdated += UxrManager_AvatarsUpdated;
}
/// <summary>
/// Unsubscribes from events.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.AvatarsUpdated -= UxrManager_AvatarsUpdated;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Stores the clavicle orientation to smooth it out the next frame.
/// </summary>
private void UxrManager_AvatarsUpdated()
{
if (Clavicle != null)
{
_lastClavicleLocalRotation = Quaternion.Inverse(Avatar.transform.rotation) * Clavicle.rotation;
_lastClavicleRotationInitialized = true;
}
}
#endregion
#region Protected Overrides UxrIKSolver
/// <summary>
/// Solves the IK for the current frame.
/// </summary>
protected override void InternalSolveIK()
{
if (Avatar == null || Avatar.AvatarController == null || !Avatar.AvatarController.Initialized)
{
return;
}
if (Clavicle != null)
{
// If we have a clavicle, perform another pass this time taking it into account.
// The first pass won't clamp the hand distance because thanks to the clavicle rotation there is a little more reach.
SolveIKPass(UxrArmSolveOptions.ResetClavicle, UxrArmOverExtendMode.ExtendForearm);
SolveIKPass(UxrArmSolveOptions.ResetClavicle | UxrArmSolveOptions.SolveClavicle, _overExtendMode);
}
else
{
SolveIKPass(UxrArmSolveOptions.None, _overExtendMode);
}
}
#endregion
#region Private Methods
/// <summary>
/// Transforms a point from world space to local avatar space.
/// </summary>
/// <param name="pos">World space position</param>
/// <returns>Avatar space position</returns>
private Vector3 ToLocalAvatarPos(Vector3 pos)
{
return Avatar.transform.InverseTransformPoint(pos);
}
/// <summary>
/// Transforms a point from local avatar space to world space.
/// </summary>
/// <param name="pos">Avatar space position</param>
/// <returns>World space position</returns>
private Vector3 ToWorldPos(Vector3 pos)
{
return Avatar.transform.TransformPoint(pos);
}
/// <summary>
/// Transforms a direction from world space to local avatar space.
/// </summary>
/// <param name="dir">World space direction</param>
/// <returns>Avatar space direction</returns>
private Vector3 ToLocalAvatarDir(Vector3 dir)
{
return Avatar.transform.InverseTransformDirection(dir);
}
/// <summary>
/// Transforms a rotation from world space to local avatar space.
/// </summary>
/// <param name="rot">World space rotation</param>
/// <returns>Avatar space rotation</returns>
private Quaternion ToLocalAvatarRot(Quaternion rot)
{
return Quaternion.Inverse(Avatar.transform.rotation) * rot;
}
/// <summary>
/// Transforms a rotation from local avatar space to world space.
/// </summary>
/// <param name="rot">Avatar space rotation</param>
/// <returns>World space rotation</returns>
private Quaternion ToWorldRot(Quaternion rot)
{
return Avatar.transform.rotation * rot;
}
/// <summary>
/// Computes the internal parameters for the IK.
/// </summary>
private void ComputeParameters()
{
if (Avatar == null)
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(UxrArmIKSolver)} can't find {nameof(UxrAvatar)} component upwards in the hierarchy. Component is located in {this.GetPathUnderScene()}");
}
return;
}
_side = transform.HasParent(Avatar.AvatarRig.LeftArm.Clavicle ?? Avatar.AvatarRig.LeftArm.UpperArm) ? UxrHandSide.Left : UxrHandSide.Right;
// Set up references
if (Clavicle == null)
{
Clavicle = _side == UxrHandSide.Left ? Avatar.AvatarRig.LeftArm.Clavicle : Avatar.AvatarRig.RightArm.Clavicle;
}
if (Arm == null)
{
Arm = _side == UxrHandSide.Left ? Avatar.AvatarRig.LeftArm.UpperArm : Avatar.AvatarRig.RightArm.UpperArm;
}
if (Forearm == null)
{
Forearm = _side == UxrHandSide.Left ? Avatar.AvatarRig.LeftArm.Forearm : Avatar.AvatarRig.RightArm.Forearm;
}
if (Hand == null)
{
Hand = Avatar.GetHandBone(_side);
}
UxrAvatarArm arm = Avatar.GetArm(_side);
if (arm != null && arm.UpperArm && arm.Forearm && arm.Hand.Wrist)
{
// Compute lengths in local avatar coordinates in case avatar has scaling.
// We use special treatment for the local hand in case that the component is being added at runtime and the hand is driven by a multiplayer NetworkTransform component that already moved it.
Vector3 localUpperArm = ToLocalAvatarPos(arm.UpperArm.position);
Vector3 localForearm = ToLocalAvatarPos(arm.Forearm.position);
Vector3 localHand = ToLocalAvatarPos(arm.Hand.Wrist.transform.parent.TransformPoint(Avatar.AvatarRigInfo.GetArmInfo(_side).HandUniversalLocalAxes.InitialLocalPosition));
_upperArmLocalLength = Vector3.Distance(localUpperArm, localForearm);
_forearmLocalLength = Vector3.Distance(localForearm, localHand);
}
else
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(UxrArmIKSolver)} can't find one or more of the following bones: upper arm, forearm, wrist. Component is located in {this.GetPathUnderScene()}");
}
return;
}
_clavicleUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ClavicleUniversalLocalAxes;
_armUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ArmUniversalLocalAxes;
_forearmUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_side).ForearmUniversalLocalAxes;
// Compute arm range of motion neutral direction
_armNeutralForwardInParent = Vector3.forward;
_armNeutralForwardInParent = Quaternion.AngleAxis(30.0f * (_side == UxrHandSide.Left ? -1.0f : 1.0f), Vector3.up) * _armNeutralForwardInParent;
_armNeutralForwardInParent = Quaternion.AngleAxis(30.0f, Vector3.right) * _armNeutralForwardInParent;
if (Arm.parent != null)
{
_armNeutralForwardInParent = Arm.parent.InverseTransformDirection(Avatar.transform.TransformDirection(_armNeutralForwardInParent));
}
if (Clavicle && Avatar)
{
// If we have a clavicle, set it up too
if (_clavicleAutoComputeBias)
{
Vector3 clavicleLookAt = (Forearm.position - Clavicle.position).normalized;
Avatar.transform.InverseTransformDirection(clavicleLookAt);
_clavicleDeformationAxesBias = new Vector3(0.0f, -clavicleLookAt.y + 0.25f, -clavicleLookAt.z);
}
}
_elbowAperture = -1.0f;
_elbowApertureVelocity = 0.0f;
}
#endregion
#region Private Types & Data
private const float ClavicleMaxDegreesPerSecond = 360.0f;
private const float WristTorsionDegreesFactor = 150.0f;
private const float ElbowApertureRotationSmoothTime = 0.1f;
private const float ElbowMinAngleThreshold = 3.0f;
private UxrHandSide _side;
private bool _initialized;
private UxrUniversalLocalAxes _clavicleUniversalLocalAxes;
private UxrUniversalLocalAxes _armUniversalLocalAxes;
private UxrUniversalLocalAxes _forearmUniversalLocalAxes;
private float _upperArmLocalLength;
private float _forearmLocalLength;
private float _elbowAperture = -1.0f;
private float _elbowApertureVelocity;
private Vector3 _armNeutralForwardInParent = Vector3.zero;
private Quaternion _lastClavicleLocalRotation = Quaternion.identity;
private bool _lastClavicleRotationInitialized;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 61a25f8a356cfe1489a26b996a2d81de
timeCreated: 1510133937
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,34 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrArmOverExtendMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Enumerates the different solutions that can be used when an avatar with visible arms moves a hand farther than the
/// actual length of the arm.
/// </summary>
public enum UxrArmOverExtendMode
{
/// <summary>
/// Hand reach will be limited to what the actual arm length permits.
/// </summary>
LimitHandReach,
/// <summary>
/// Stretch the forearm.
/// </summary>
ExtendForearm,
/// <summary>
/// Stretch the upper arm.
/// </summary>
ExtendUpperArm,
/// <summary>
/// Stretch both the upper arm and forearm.
/// </summary>
ExtendArm
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3d2ff8c20c3e488b83833ab43415e40a
timeCreated: 1643116515

View File

@@ -0,0 +1,33 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrArmSolveOptions.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Different clavicle options supported by <see cref="UxrArmIKSolver.SolveIKPass" /> when clavicle data is present in
/// the rig.
/// </summary>
[Flags]
public enum UxrArmSolveOptions
{
/// <summary>
/// No options.
/// </summary>
None = 0,
/// <summary>
/// Reset the clavicle position.
/// </summary>
ResetClavicle = 1,
/// <summary>
/// Solve the clavicle position. Can be used together with <see cref="ResetClavicle" /> so that the clavicle is solved
/// without using the current position data.
/// </summary>
SolveClavicle = 1 << 1
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9663654228ad49fcb30c5657e7c2ec76
timeCreated: 1645360837

View File

@@ -0,0 +1,59 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrBodyIK.IndependentBoneInfo.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.IK
{
public sealed partial class UxrBodyIK
{
#region Private Types & Data
/// <summary>
/// Independent bones are bones that are driven externally and not using IK, such as the hands (wrist bones), which are
/// driven by the tracked input controllers. They need to be kept track of when parent bones are modified by Inverse
/// Kinematics to make sure that they are kept in the same place afterwards. Otherwise due to parenting the position
/// they should have would change.
/// </summary>
private class IndependentBoneInfo
{
#region Public Types & Data
/// <summary>
/// Gets the bone transform.
/// </summary>
public Transform Transform { get; }
/// <summary>
/// Gets or sets the correct current position.
/// </summary>
public Vector3 Position { get; set; }
/// <summary>
/// Gets or sets the correct current position.
/// </summary>
public Quaternion Rotation { get; set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="transform">Bone transform</param>
public IndependentBoneInfo(Transform transform)
{
Transform = transform;
Position = transform.position;
Rotation = transform.rotation;
}
#endregion
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 67c356fd5432455fa75bee27571d5a2a
timeCreated: 1643734039

View File

@@ -0,0 +1,592 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrBodyIK.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UltimateXR.Avatar;
using UltimateXR.Avatar.Rig;
using UltimateXR.Core;
using UltimateXR.Core.Math;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Class that provides functionality to compute Inverse Kinematics for a humanoid body.
/// </summary>
public sealed partial class UxrBodyIK
{
#region Public Types & Data
/// <summary>
/// Gets whether the object was initialized.
/// </summary>
public bool Initialized { get; private set; }
#endregion
#region Public Methods
/// <summary>
/// Initializes the IK object.
/// </summary>
/// <param name="avatar">Avatar that the IK will be computed for</param>
/// <param name="settings">IK settings to use</param>
/// <param name="usesExternalArmIK">Whether the avatar uses an arm IK</param>
/// <param name="usesExternalLegIK">Whether the avatar uses leg IK</param>
public void Initialize(UxrAvatar avatar, UxrBodyIKSettings settings, bool usesExternalArmIK, bool usesExternalLegIK)
{
Initialized = true;
_avatar = avatar;
_avatarTransform = avatar.transform;
_settings = settings;
// Get body root
if (avatar.AvatarRig.Head.Head == null)
{
if (UxrGlobalSettings.Instance.LogLevelAvatar >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AvatarModule} Avatar {avatar.name} has no head setup in the {nameof(UxrAvatar)}'s Rig field");
}
return;
}
_independentBones = new List<IndependentBoneInfo>();
List<Transform> bodyTransforms = new List<Transform>();
if (avatar.AvatarRig.UpperChest)
{
bodyTransforms.Add(avatar.AvatarRig.UpperChest);
_upperChestUniversalLocalAxes = new UxrUniversalLocalAxes(avatar.AvatarRig.UpperChest, _avatarTransform);
}
if (avatar.AvatarRig.Chest)
{
bodyTransforms.Add(avatar.AvatarRig.Chest);
_chestUniversalLocalAxes = new UxrUniversalLocalAxes(avatar.AvatarRig.Chest, _avatarTransform);
}
if (avatar.AvatarRig.Spine)
{
bodyTransforms.Add(avatar.AvatarRig.Spine);
_spineUniversalLocalAxes = new UxrUniversalLocalAxes(avatar.AvatarRig.Spine, _avatarTransform);
}
if (avatar.AvatarRig.Hips)
{
bodyTransforms.Add(avatar.AvatarRig.Hips);
}
if (avatar.AvatarRig.Head.Neck && avatar.AvatarRig.Head.Neck != null && avatar.AvatarRig.Head.Neck != _avatarTransform && !bodyTransforms.Contains(avatar.AvatarRig.Head.Neck.parent))
{
bodyTransforms.Add(avatar.AvatarRig.Head.Neck.parent);
}
if (avatar.AvatarRig.UpperChest && avatar.AvatarRig.UpperChest.parent != null && avatar.AvatarRig.UpperChest.parent != _avatarTransform && !bodyTransforms.Contains(avatar.AvatarRig.UpperChest.parent))
{
bodyTransforms.Add(avatar.AvatarRig.UpperChest.parent);
}
if (avatar.AvatarRig.Chest && avatar.AvatarRig.Chest.parent != null && avatar.AvatarRig.Chest.parent != _avatarTransform && !bodyTransforms.Contains(avatar.AvatarRig.Chest.parent))
{
bodyTransforms.Add(avatar.AvatarRig.Chest.parent);
}
if (avatar.AvatarRig.Spine && avatar.AvatarRig.Spine.parent != null && avatar.AvatarRig.Spine.parent != _avatarTransform && !bodyTransforms.Contains(avatar.AvatarRig.Spine.parent))
{
bodyTransforms.Add(avatar.AvatarRig.Spine.parent);
}
if (avatar.AvatarRig.Hips && avatar.AvatarRig.Hips.parent != null && avatar.AvatarRig.Hips.parent != _avatarTransform && !bodyTransforms.Contains(avatar.AvatarRig.Hips.parent))
{
bodyTransforms.Add(avatar.AvatarRig.Hips.parent);
}
_avatarBodyRoot = TransformExt.GetCommonRootTransformFromSet(bodyTransforms.ToArray());
if (_avatarBodyRoot == null)
{
if (UxrGlobalSettings.Instance.LogLevelAvatar >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.AvatarModule} No common avatar body root found. If there is an avatar body it will not follow the head position.");
}
_avatarBodyRoot = new GameObject("Dummy Root").transform;
_avatarBodyRoot.SetParent(_avatarTransform);
_avatarBodyRoot.SetPositionAndRotation(_avatarTransform.position, _avatarTransform.rotation);
}
// Neck
_avatarNeck = avatar.AvatarRig.Head.Neck;
if (_avatarNeck == null)
{
_avatarNeck = new GameObject("Dummy Neck").transform;
_avatarNeck.SetParent(_avatarBodyRoot);
_avatarNeck.SetPositionAndRotation(_avatarTransform.position + _avatarTransform.up * _settings.NeckBaseHeight + _avatarTransform.forward * _settings.NeckForwardOffset, _avatarTransform.rotation);
if (avatar.AvatarRig.Head.Head != null)
{
avatar.AvatarRig.Head.Head.SetParent(_avatarNeck);
}
}
_neckUniversalLocalAxes = new UxrUniversalLocalAxes(_avatarNeck, _avatarTransform);
// Head
_headUniversalLocalAxes = new UxrUniversalLocalAxes(avatar.AvatarRig.Head.Head, _avatarTransform);
_avatarHead = avatar.AvatarRig.Head.Head;
// Eyes
_avatarEyes = new GameObject("Dummy Eyes").transform;
_avatarEyes.SetParent(_avatarHead);
_avatarEyes.SetPositionAndRotation(_avatarTransform.position + _avatarTransform.up * _settings.EyesBaseHeight + _avatarTransform.forward * _settings.EyesForwardOffset, _avatarTransform.rotation);
// Avatar Forward
_avatarForward = new GameObject("Dummy Forward").transform;
_avatarForward.SetParent(_avatarTransform);
_avatarForward.SetPositionAndRotation(_avatarHead.position - avatar.transform.up * _avatarHead.position.y, _avatarTransform.rotation);
_avatarBodyRoot.SetParent(_avatarForward);
// Initialize
_neckPosRelativeToEyes = Quaternion.Inverse(_avatarEyes.rotation) * (_avatarNeck.position - _avatarEyes.position);
_neckRotRelativeToEyes = Quaternion.Inverse(_avatarEyes.rotation) * _avatarNeck.rotation;
_avatarForwardPosRelativeToNeck = _avatarForward.position - _avatarNeck.transform.position;
_avatarForwardTarget = _avatarForward.forward;
_straightSpineForward = _avatarForward.forward;
if (usesExternalArmIK)
{
if (avatar.LeftHandBone)
{
_independentBones.Add(new IndependentBoneInfo(avatar.LeftHandBone));
}
if (avatar.RightHandBone)
{
_independentBones.Add(new IndependentBoneInfo(avatar.RightHandBone));
}
}
}
/// <summary>
/// Computes the Pre-pass in the IK solving.
/// </summary>
public void PreSolveAvatarIK()
{
if (_avatarHead == null)
{
return;
}
// Push transforms. These transforms hang from the body and need to be kept in place because
// due to parenting their absolute position/orientation will be altered
foreach (IndependentBoneInfo boneInfo in _independentBones)
{
boneInfo.Rotation = boneInfo.Transform.rotation;
boneInfo.Position = boneInfo.Transform.position;
}
// Reset upper body
_avatarNeck.localPosition = _neckUniversalLocalAxes.InitialLocalPosition;
_avatarHead.localPosition = _headUniversalLocalAxes.InitialLocalPosition;
_avatarNeck.localRotation = _neckUniversalLocalAxes.InitialLocalRotation;
_avatarHead.localRotation = _headUniversalLocalAxes.InitialLocalRotation;
if (_avatar.AvatarRig.UpperChest)
{
_avatar.AvatarRig.UpperChest.localRotation = _upperChestUniversalLocalAxes.InitialLocalRotation;
}
if (_avatar.AvatarRig.Chest)
{
_avatar.AvatarRig.Chest.localRotation = _chestUniversalLocalAxes.InitialLocalRotation;
}
if (_avatar.AvatarRig.Spine)
{
_avatar.AvatarRig.Spine.localRotation = _spineUniversalLocalAxes.InitialLocalRotation;
}
// Compute neck position/rotation to make the avatar eyes match the camera
Transform cameraTransform = _avatar.CameraComponent.transform;
Vector3 localAvatarPivotPos = _avatar.transform.InverseTransformPoint(_avatarForward.position);
Vector3 neckPosition = GetWorldPosFromOffset(cameraTransform, cameraTransform, _neckPosRelativeToEyes);
Quaternion neckRotation = cameraTransform.rotation * _neckRotRelativeToEyes;
_avatarNeck.SetPositionAndRotation(neckPosition, neckRotation);
// Update avatar pivot
_avatarForward.position = GetWorldPosFromOffset(_avatarForward, _avatarNeck, _avatarForwardPosRelativeToNeck);
bool smoothForwardRotation = true;
if (Vector3.Angle(cameraTransform.forward, _avatar.transform.up) > CameraUpsideDownAngleThreshold && Vector3.Angle(cameraTransform.forward, -_avatar.transform.up) > CameraUpsideDownAngleThreshold)
{
// _straightSpineForward contains the forward direction where the avatar looks (vector.y is 0).
// This is different from _avatarForward.forward because avatarForward allows the head to rotate
// some degrees without rotating the whole body along with it.
_straightSpineForward = Vector3.ProjectOnPlane(cameraTransform.forward, _avatar.transform.up);
if (Vector3.Angle(cameraTransform.forward, _avatarForward.forward) > 90.0f)
{
// Bad orientation, fix instantly.
smoothForwardRotation = false;
}
}
float bodyRotationAngle = Vector3.Angle(_straightSpineForward, _avatarForward.forward);
if (bodyRotationAngle > _settings.HeadFreeRangeTorsion)
{
float radians = (bodyRotationAngle - _settings.HeadFreeRangeTorsion) * Mathf.Deg2Rad;
_avatarForwardTarget = Vector3.RotateTowards(_avatarForwardTarget, _straightSpineForward, radians, 0.0f);
}
// Update avatar forward direction
float rotationSpeedMultiplier = Vector3.Angle(_avatarForward.forward, _avatarForwardTarget) / 30.0f;
float maxForwardDegreesDelta = AvatarRotationDegreesPerSecond * rotationSpeedMultiplier * _settings.BodyPivotRotationSpeed * Time.deltaTime;
_avatarForward.rotation = Quaternion.RotateTowards(Quaternion.LookRotation(_avatarForward.forward, _avatar.transform.up),
Quaternion.LookRotation(_avatarForwardTarget, _avatar.transform.up),
smoothForwardRotation ? maxForwardDegreesDelta : 180.0f);
// Since the avatar pivot is parent of all body nodes, move the neck back to its position
_avatarNeck.SetPositionAndRotation(neckPosition, neckRotation);
if (_settings.LockBodyPivot)
{
_avatarForward.position = _avatar.transform.TransformPoint(localAvatarPivotPos);
}
// We've computed the avatar head orientation using only the neck. Now redistribute it using the _neckHeadBalance parameter so that
// we can get a smoother head rotation by using both the neck and the head.
if (_settings.NeckHeadBalance > 0.0f)
{
Quaternion neckUniversalRotation = Quaternion.Inverse(_avatarForward.rotation) * _neckUniversalLocalAxes.UniversalRotation * Quaternion.Inverse(_neckUniversalLocalAxes.InitialUniversalLocalReferenceRotation);
Vector3 headPosition = _avatarHead.position;
Quaternion headRotation = _avatarHead.rotation;
// Compute partial neck rotation
neckRotation = _avatarForward.rotation * (Quaternion.Slerp(Quaternion.identity, neckUniversalRotation, 1.0f - _settings.NeckHeadBalance) *
_neckUniversalLocalAxes.InitialUniversalLocalReferenceRotation *
_neckUniversalLocalAxes.UniversalToActualAxesRotation);
_avatarNeck.rotation = neckRotation;
_avatarHead.rotation = headRotation;
_avatarForward.position -= _avatarHead.position - headPosition;
neckPosition = _avatarNeck.position;
}
// Check influence of head rotation on chest/spine transforms
if (!_settings.LockBodyPivot)
{
// Compute head rotation without the rotation around the Y axis:
Quaternion headPropagateRotation = Quaternion.Inverse(Quaternion.LookRotation(_straightSpineForward, _avatar.transform.up)) * _headUniversalLocalAxes.UniversalRotation;
// Remove the rotation that the head can do without propagation to chest/spine:
headPropagateRotation = Quaternion.RotateTowards(headPropagateRotation, Quaternion.identity, _settings.HeadFreeRangeBend);
// Compute influence on chest/spine elements:
float totalWeight = 0.0f;
if (_avatar.AvatarRig.UpperChest)
{
totalWeight += _settings.UpperChestBend;
}
if (_avatar.AvatarRig.Chest)
{
totalWeight += _settings.ChestBend;
}
if (_avatar.AvatarRig.Spine)
{
totalWeight += _settings.SpineBend;
}
// Propagate head rotation:
if (totalWeight > 0.0f)
{
if (totalWeight < 1.0f)
{
totalWeight = 1.0f;
}
if (_avatar.AvatarRig.UpperChest)
{
_avatar.AvatarRig.UpperChest.rotation = _avatarForward.rotation *
(Quaternion.Slerp(Quaternion.identity, headPropagateRotation, _settings.UpperChestBend / totalWeight) *
_upperChestUniversalLocalAxes.InitialUniversalLocalReferenceRotation * _upperChestUniversalLocalAxes.UniversalToActualAxesRotation);
}
if (_avatar.AvatarRig.Chest)
{
_avatar.AvatarRig.Chest.rotation = _avatarForward.rotation *
(Quaternion.Slerp(Quaternion.identity, headPropagateRotation, _settings.ChestBend / totalWeight) *
_chestUniversalLocalAxes.InitialUniversalLocalReferenceRotation * _chestUniversalLocalAxes.UniversalToActualAxesRotation);
}
if (_avatar.AvatarRig.Spine)
{
_avatar.AvatarRig.Spine.rotation = _avatarForward.rotation *
(Quaternion.Slerp(Quaternion.identity, headPropagateRotation, _settings.SpineBend / totalWeight) *
_spineUniversalLocalAxes.InitialUniversalLocalReferenceRotation * _spineUniversalLocalAxes.UniversalToActualAxesRotation);
}
}
// Make whole avatar move back so that head remains with the same position/orientation
_avatarNeck.rotation = neckRotation;
_avatarForward.position -= _avatarNeck.position - neckPosition;
_avatarNeck.position = neckPosition;
}
// If the avatar moves, straighten the forward direction
float avatarMovedDistance = Vector3.Distance(localAvatarPivotPos, _avatarForward.position);
if (avatarMovedDistance / Time.deltaTime > AvatarStraighteningMinSpeed)
{
float degreesToStraighten = avatarMovedDistance * DegreesStraightenedPerMeterMoved;
_avatarForwardTarget = Vector3.RotateTowards(_avatarForwardTarget, _straightSpineForward, degreesToStraighten * Mathf.Deg2Rad, 0.0f);
}
// Pop independent transforms (usually hands and other transforms with their own sensors)
foreach (IndependentBoneInfo boneInfo in _independentBones)
{
boneInfo.Transform.SetPositionAndRotation(boneInfo.Position, boneInfo.Rotation);
}
if (_settings.LockBodyPivot)
{
_avatarForward.position = _avatar.transform.TransformPoint(localAvatarPivotPos);
}
}
/// <summary>
/// Computes the post-pass in the IK solving.
/// </summary>
public void PostSolveAvatarIK()
{
if (_avatarHead == null)
{
return;
}
// Push transforms. These transforms hang from the body and need to be kept in place because
// due to parenting their absolute position/orientation will be altered
foreach (IndependentBoneInfo boneInfo in _independentBones)
{
boneInfo.Rotation = boneInfo.Transform.rotation;
boneInfo.Position = boneInfo.Transform.position;
}
Vector3 neckPosition = _avatarNeck.position;
Quaternion neckRotation = _avatarNeck.rotation;
// Compute torso rotation
float torsoRotation = 0.0f; // No degrees, just an abstract quantity we use later to multiply by a factor and get degrees
float totalWeight = 0.0f;
if (_avatar.AvatarRig.UpperChest)
{
torsoRotation = GetArmTorsoRotationWeight(_avatar.AvatarRig.UpperChest, _upperChestUniversalLocalAxes, _avatar.AvatarRig.LeftArm, UxrHandSide.Left) +
GetArmTorsoRotationWeight(_avatar.AvatarRig.UpperChest, _upperChestUniversalLocalAxes, _avatar.AvatarRig.RightArm, UxrHandSide.Right);
totalWeight += _settings.UpperChestTorsion;
}
if (_avatar.AvatarRig.Chest)
{
torsoRotation = GetArmTorsoRotationWeight(_avatar.AvatarRig.Chest, _chestUniversalLocalAxes, _avatar.AvatarRig.LeftArm, UxrHandSide.Left) +
GetArmTorsoRotationWeight(_avatar.AvatarRig.Chest, _chestUniversalLocalAxes, _avatar.AvatarRig.RightArm, UxrHandSide.Right);
totalWeight += _settings.ChestTorsion;
}
if (_avatar.AvatarRig.Spine)
{
torsoRotation = GetArmTorsoRotationWeight(_avatar.AvatarRig.Spine, _spineUniversalLocalAxes, _avatar.AvatarRig.LeftArm, UxrHandSide.Left) +
GetArmTorsoRotationWeight(_avatar.AvatarRig.Spine, _spineUniversalLocalAxes, _avatar.AvatarRig.RightArm, UxrHandSide.Right);
totalWeight += _settings.SpineTorsion;
}
torsoRotation *= 0.5f; // Because range is [-2, +2] since both hands have [-1, 1] range.
// Rotate upper body elements
float maxRotationDegrees = 70.0f;
if (totalWeight > 0.0f)
{
if (totalWeight < 1.0f)
{
totalWeight = 1.0f;
}
float upperChestTorsionAngle = maxRotationDegrees * torsoRotation * (_settings.UpperChestTorsion / totalWeight);
float chestTorsionAngle = maxRotationDegrees * torsoRotation * (_settings.ChestTorsion / totalWeight);
float spineTorsionAngle = maxRotationDegrees * torsoRotation * (_settings.SpineTorsion / totalWeight);
_upperChestTorsionAngle = Mathf.SmoothDampAngle(_upperChestTorsionAngle, upperChestTorsionAngle, ref _upperChestTorsionSpeed, BodyTorsionSmoothTime);
_chestTorsionAngle = Mathf.SmoothDampAngle(_chestTorsionAngle, chestTorsionAngle, ref _chestTorsionSpeed, BodyTorsionSmoothTime);
_spineTorsionAngle = Mathf.SmoothDampAngle(_spineTorsionAngle, spineTorsionAngle, ref _spineTorsionSpeed, BodyTorsionSmoothTime);
if (_avatar.AvatarRig.UpperChest)
{
_avatar.AvatarRig.UpperChest.localRotation *= Quaternion.AngleAxis(_spineTorsionAngle, _upperChestUniversalLocalAxes.LocalUp);
}
if (_avatar.AvatarRig.Chest)
{
_avatar.AvatarRig.Chest.localRotation *= Quaternion.AngleAxis(_chestTorsionAngle, _chestUniversalLocalAxes.LocalUp);
}
if (_avatar.AvatarRig.Spine)
{
_avatar.AvatarRig.Spine.localRotation *= Quaternion.AngleAxis(_spineTorsionAngle, _spineUniversalLocalAxes.LocalUp);
}
}
// Pop independent transforms (usually hands and other transforms with their own sensors)
foreach (IndependentBoneInfo boneInfo in _independentBones)
{
boneInfo.Transform.SetPositionAndRotation(boneInfo.Position, boneInfo.Rotation);
}
_avatarNeck.SetPositionAndRotation(neckPosition, neckRotation);
}
/// <summary>
/// Notifies whenever the avatar was moved in order to update the internal forward looking vector.
/// </summary>
/// <param name="e">Move event parameters</param>
public void NotifyAvatarMoved(UxrAvatarMoveEventArgs e)
{
if (!Initialized)
{
return;
}
float angle = Vector3.SignedAngle(e.OldForward, e.NewForward, _avatar.transform.up);
_avatarForwardTarget = Quaternion.AngleAxis(angle, _avatar.transform.up) * _avatarForwardTarget;
_straightSpineForward = Quaternion.AngleAxis(angle, _avatar.transform.up) * _straightSpineForward;
}
#endregion
#region Private Methods
/// <summary>
/// Computes an world position based on an offset from an object.
/// </summary>
/// <param name="axes">The axes <paramref name="offset" /> refer to</param>
/// <param name="transform">The object origin</param>
/// <param name="offset">The offset components</param>
/// <returns>Offset vector</returns>
private Vector3 GetWorldPosFromOffset(Transform axes, Transform transform, Vector3 offset)
{
return transform.position + axes.right * offset.x + axes.up * offset.y + axes.forward * offset.z;
}
/// <summary>
/// Computes how much of an influence an arm has on the upper body to make it rotate left or right.
/// </summary>
/// <param name="upperBodyTransform">Any upper body node (spine, chest...)</param>
/// <param name="upperBodyUniversalLocalAxes">
/// The upper body nodes's universal local axes.
/// These will be the upper body transform local axes that map to the avatar right, up and
/// forward vectors.
/// </param>
/// <param name="arm">The arm</param>
/// <param name="handSide">Is it a left arm or right arm?</param>
/// <returns>
/// Value [-1.0, 1.0] that tells how much the upper body should rotate to the left or
/// right due to the arm position.
/// </returns>
private float GetArmTorsoRotationWeight(Transform upperBodyTransform, UxrUniversalLocalAxes upperBodyUniversalLocalAxes, UxrAvatarArm arm, UxrHandSide handSide)
{
if (arm.UpperArm && arm.Forearm)
{
Vector3 shoulderToElbowVector = arm.Forearm.position - arm.UpperArm.position;
Vector3 projectedShoulderToElbowVector = Vector3.ProjectOnPlane(shoulderToElbowVector, upperBodyUniversalLocalAxes.WorldUp);
float straightFactor = projectedShoulderToElbowVector.magnitude / shoulderToElbowVector.magnitude;
float armAngle = Vector3.SignedAngle(upperBodyUniversalLocalAxes.WorldRight * (handSide == UxrHandSide.Left ? -1.0f : 1.0f), projectedShoulderToElbowVector, upperBodyUniversalLocalAxes.WorldUp);
return armAngle / 180.0f * straightFactor;
}
return 0.0f;
}
#endregion
#region Private Types & Data
private const float CameraUpsideDownAngleThreshold = 15.0f; // To avoid gimbal errors we will not update the avatar rotation if the avatar is looking up or down with this angle respect to the vertical axis.
private const float AvatarRotationDegreesPerSecond = 1080.0f; // Degrees per second we will move the avatar to compensate for the head torsion
private const float DegreesStraightenedPerMeterMoved = 120.0f; // Amount of degrees to straighten the avatar direction with respect to the head direction when the avatar moves.
private const float AvatarStraighteningMinSpeed = 0.3f; // To straighten the avatar direction it will need to be moving at least this amount of units per second
private const float BodyTorsionSmoothTime = 0.1f; // Body torsion smoothening time
private UxrAvatar _avatar;
private Transform _avatarTransform;
private UxrBodyIKSettings _settings;
private List<IndependentBoneInfo> _independentBones;
private Transform _avatarBodyRoot;
private Transform _avatarForward;
private Transform _avatarNeck;
private Transform _avatarHead;
private Transform _avatarEyes;
private Vector3 _neckPosRelativeToEyes;
private Quaternion _neckRotRelativeToEyes;
private Vector3 _avatarForwardPosRelativeToNeck;
private Vector3 _avatarForwardTarget;
private Vector3 _straightSpineForward;
private UxrUniversalLocalAxes _spineUniversalLocalAxes;
private UxrUniversalLocalAxes _chestUniversalLocalAxes;
private UxrUniversalLocalAxes _upperChestUniversalLocalAxes;
private UxrUniversalLocalAxes _neckUniversalLocalAxes;
private UxrUniversalLocalAxes _headUniversalLocalAxes;
private float _spineTorsionAngle;
private float _chestTorsionAngle;
private float _upperChestTorsionAngle;
private float _spineTorsionSpeed;
private float _chestTorsionSpeed;
private float _upperChestTorsionSpeed;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 618804a1df753f84194da553f01a2025
timeCreated: 1519294533
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,124 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrBodyIKSettings.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Stores parameters that drive Inverse Kinematics for full-body avatars.
/// </summary>
/// <remarks>
/// For now only half-body Inverse Kinematics is supported. Full-body will be implemented at some point.
/// </remarks>
[Serializable]
public class UxrBodyIKSettings
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _lockBodyPivot;
[SerializeField] private float _bodyPivotRotationSpeed = 0.2f;
[SerializeField] private float _headFreeRangeBend = 20.0f;
[SerializeField] private float _headFreeRangeTorsion = 30.0f;
[SerializeField] private float _neckHeadBalance = 0.5f;
[SerializeField] private float _spineBend = 0.05f;
[SerializeField] private float _spineTorsion = 0.4f;
[SerializeField] private float _chestBend = 0.3f;
[SerializeField] private float _chestTorsion = 0.8f;
[SerializeField] private float _upperChestBend = 0.4f;
[SerializeField] private float _upperChestTorsion = 0.2f;
[SerializeField] private float _neckBaseHeight = 1.6f;
[SerializeField] private float _neckForwardOffset;
[SerializeField] private float _eyesBaseHeight = 1.75f;
[SerializeField] private float _eyesForwardOffset = 0.1f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets whether the avatar pivot will be kept in place so that it will only rotate around.
/// </summary>
public bool LockBodyPivot => _lockBodyPivot;
/// <summary>
/// Gets the speed the body will turn around with. This is used to smooth out rotation.
/// </summary>
public float BodyPivotRotationSpeed => _bodyPivotRotationSpeed;
/// <summary>
/// Gets the amount of degrees the head can bend before requiring rotation of other bones down the spine.
/// </summary>
public float HeadFreeRangeBend => _headFreeRangeBend;
/// <summary>
/// Gets the amount of degrees the head can turn before requiring rotation of other bones down the spine.
/// </summary>
public float HeadFreeRangeTorsion => _headFreeRangeTorsion;
/// <summary>
/// Gets a value in [0.0, 1.0] range that tells how rotation will be distributed between the head and the neck. 0.0
/// will fully use the neck and 1.0 will fully use the head. Values in between will distribute it among the two.
/// </summary>
public float NeckHeadBalance => _neckHeadBalance;
/// <summary>
/// Gets the amount the spine will bend when the head bends.
/// </summary>
public float SpineBend => _spineBend;
/// <summary>
/// Gets the amount the spine will turn when the head turns.
/// </summary>
public float SpineTorsion => _spineTorsion;
/// <summary>
/// Gets the amount the chest will bend when the head bends.
/// </summary>
public float ChestBend => _chestBend;
/// <summary>
/// Gets the amount the chest will turn when the head turns.
/// </summary>
public float ChestTorsion => _chestTorsion;
/// <summary>
/// Gets the amount the upper chest will bend when the head bends.
/// </summary>
public float UpperChestBend => _upperChestBend;
/// <summary>
/// Gets the amount the upper chest will turn when the head turns.
/// </summary>
public float UpperChestTorsion => _upperChestTorsion;
/// <summary>
/// Gets the height of the base of the neck starting from the avatar root Y. This is used to create a dummy neck when
/// the avatar is lacking a neck bone.
/// </summary>
public float NeckBaseHeight => _neckBaseHeight;
/// <summary>
/// Gets the forward offset of the neck starting from the avatar root Z. This is used to create a dummy neck when the
/// avatar is lacking a neck bone.
/// </summary>
public float NeckForwardOffset => _neckForwardOffset;
/// <summary>
/// Gets the height of the eyes starting from the avatar root Y. This is used to know where to place the avatar head
/// knowing the camera will be positioned on the eyes.
/// </summary>
public float EyesBaseHeight => _eyesBaseHeight;
/// <summary>
/// Gets the forward offset of the eyes starting from the avatar root Z. This is used to know where to place the avatar
/// head knowing the camera will be positioned on the eyes.
/// </summary>
public float EyesForwardOffset => _eyesForwardOffset;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a8d0b000596d4a8c9294cb3450d0fdfe
timeCreated: 1642860814

View File

@@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCcdConstraintType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Enumerates the different constraints of a CCD link.
/// </summary>
public enum UxrCcdConstraintType
{
/// <summary>
/// Constrained to one axis.
/// </summary>
SingleAxis,
/// <summary>
/// Constrained to two axes.
/// </summary>
TwoAxes
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46ce404cf07f4b73a03b51e6675b8766
timeCreated: 1643125910

View File

@@ -0,0 +1,35 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCcdIKSolver.IterationResult.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.IK
{
public partial class UxrCcdIKSolver
{
#region Private Types & Data
/// <summary>
/// Result of the CCD algorithm iteration
/// </summary>
private enum IterationResult
{
/// <summary>
/// The effector has reached the goal.
/// </summary>
GoalReached,
/// <summary>
/// The effector is still trying to reach the goal.
/// </summary>
ReachingGoal,
/// <summary>
/// There was an error and no links were rotated in order to reach the goal.
/// </summary>
Error,
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ffa1891b73bb4713833bc72dc78e06c6
timeCreated: 1643742218

View File

@@ -0,0 +1,421 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCcdIKSolver.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using System.Linq;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Component that we use to solve IK chains using CCD (Cyclic Coordinate Descent). A chain is defined
/// by a set of links, an effector and a goal.
/// The links are bones that will try to make the effector reach the same exact point, or the closest to, the goal.
/// Usually the effector is on the tip of the last bone.
/// Each link can have different rotation constraints to simulate different behaviours and systems.
/// </summary>
public partial class UxrCcdIKSolver : UxrIKSolver
{
#region Inspector Properties/Serialized Fields
[SerializeField] private int _maxIterations = 10;
[SerializeField] private float _minDistanceToGoal = 0.001f;
[SerializeField] private List<UxrCcdLink> _links = new List<UxrCcdLink>();
[SerializeField] private Transform _endEffector;
[SerializeField] private Transform _goal;
[SerializeField] private bool _constrainGoalToEffector;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the list of links in the CCD.
/// </summary>
public IReadOnlyList<UxrCcdLink> Links => _links.AsReadOnly();
/// <summary>
/// Gets the end effector, which is the point that is part of the chain that will try to match the goal position.
/// </summary>
public Transform EndEffector => _endEffector;
/// <summary>
/// Gets the goal, which is the goal that the chain will try to match with the <see cref="EndEffector" />.
/// </summary>
public Transform Goal => _goal;
#endregion
#region Public Overrides UxrIKSolver
/// <inheritdoc />
public override bool Initialized => _initialized;
#endregion
#region Public Methods
/// <summary>
/// Initializes the internal data for the IK chain. This will only need to be called once during Awake(), but inside
/// the Unity editor we can call it also for drawing some gizmos that need it.
/// </summary>
public void ComputeLinkData()
{
if (_links != null && _endEffector != null)
{
for (int i = 0; i < _links.Count; ++i)
{
if (_links[i].Bone != null && !(i < _links.Count - 1 && _links[i + 1].Bone == null))
{
_links[i].MtxToLocalParent = Matrix4x4.identity;
if (_links[i].Bone.parent != null)
{
_links[i].MtxToLocalParent = _links[i].Bone.parent.worldToLocalMatrix;
}
_links[i].Initialized = true;
_links[i].InitialLocalRotation = _links[i].Bone.localRotation;
_links[i].LocalSpaceAxis1ZeroAngleVector = _links[i].RotationAxis1.GetPerpendicularVector();
_links[i].LocalSpaceAxis2ZeroAngleVector = _links[i].RotationAxis2.GetPerpendicularVector();
_links[i].ParentSpaceAxis1 = _links[i].MtxToLocalParent.MultiplyVector(_links[i].Bone.TransformDirection(_links[i].RotationAxis1));
_links[i].ParentSpaceAxis2 = _links[i].MtxToLocalParent.MultiplyVector(_links[i].Bone.TransformDirection(_links[i].RotationAxis2));
_links[i].ParentSpaceAxis1ZeroAngleVector = _links[i].MtxToLocalParent.MultiplyVector(_links[i].Bone.TransformDirection(_links[i].LocalSpaceAxis1ZeroAngleVector));
_links[i].ParentSpaceAxis2ZeroAngleVector = _links[i].MtxToLocalParent.MultiplyVector(_links[i].Bone.TransformDirection(_links[i].LocalSpaceAxis2ZeroAngleVector));
_links[i].LinkLength = i == _links.Count - 1 ? Vector3.Distance(_links[i].Bone.position, _endEffector.position) : Vector3.Distance(_links[i].Bone.position, _links[i + 1].Bone.position);
}
}
}
}
/// <summary>
/// Restores the initial link rotations.
/// </summary>
public void RestoreInitialRotations()
{
if (_links != null)
{
foreach (UxrCcdLink link in _links)
{
if (link.Bone != null && link.Initialized)
{
link.Bone.transform.localRotation = link.InitialLocalRotation;
}
}
}
}
/// <summary>
/// Sets the weight of the given link.
/// </summary>
/// <param name="link">Link index</param>
/// <param name="weight">Link weight [0.0f, 1.0f]</param>
public void SetLinkWeight(int link, float weight)
{
if (link >= 0 && link < _links.Count)
{
_links[link].Weight = weight;
}
}
/// <summary>
/// Sets the default values for the given link.
/// </summary>
/// <param name="link">Link index</param>
public void SetLinkDefaultValues(int link)
{
if (link >= 0 && link < _links.Count)
{
_links[link] = new UxrCcdLink();
}
}
#endregion
#region Unity
/// <summary>
/// Initializes the link data.
/// </summary>
protected override void Awake()
{
base.Awake();
ComputeLinkData();
}
/// <summary>
/// Checks if the goal needs to be parented so that the IK computation doesn't affect the goal itself.
/// </summary>
protected override void Start()
{
base.Start();
if (_goal.HasParent(_endEffector) || _links.Any(l => _goal.HasParent(l.Bone)))
{
_goal.SetParent(transform);
}
_initialized = true;
}
#endregion
#region Protected Overrides UxrIKSolver
/// <summary>
/// IK solver implementation. Will try to make the end effector in the link chain to match the goal.
/// </summary>
protected override void InternalSolveIK()
{
Vector3 goalPosition = _goal.position;
Vector3 goalForward = _goal.forward;
for (int i = 0; i < _maxIterations; ++i)
{
IterationResult result = ComputeSingleIterationCcd(_links, _endEffector, goalPosition, goalForward, _minDistanceToGoal);
if (result != IterationResult.ReachingGoal)
{
break;
}
}
if (_constrainGoalToEffector && Vector3.Distance(goalPosition, _endEffector.position) > _minDistanceToGoal)
{
_goal.position = _endEffector.position;
}
}
#endregion
#region Private Methods
/// <summary>
/// Fixes an angle so that it is always in the -180, 180 degrees range.
/// </summary>
/// <param name="angle">Angle in degrees</param>
/// <returns>Angle in the -180, 180 degrees range</returns>
private static float FixAngle(float angle)
{
angle = angle % 360.0f;
if (angle > 180.0f)
{
angle -= 360.0f;
}
else if (angle < -180.0f)
{
angle += 360.0f;
}
return angle;
}
/// <summary>
/// Computes a single iteration of the CCD algorithm on our link chain.
/// </summary>
/// <param name="links">List of links (bones) of the chain</param>
/// <param name="endEffector">The point on the chain that will try to reach the goal</param>
/// <param name="goalPosition">The goal that the end effector will try to reach</param>
/// <param name="goalForward">The goal forward vector that the end effector will try to reach if alignment is enabled</param>
/// <param name="minDistanceToGoal">Minimum distance to the goal that is considered success</param>
/// <returns>Result of the iteration</returns>
private static IterationResult ComputeSingleIterationCcd(List<UxrCcdLink> links, Transform endEffector, Vector3 goalPosition, Vector3 goalForward, float minDistanceToGoal)
{
if (Vector3.Distance(goalPosition, endEffector.position) <= minDistanceToGoal)
{
return IterationResult.GoalReached;
}
// Iterate from tip to base
bool linksRotated = false;
for (var i = links.Count - 1; i >= 0; i--)
{
UxrCcdLink link = links[i];
if (Vector3.Distance(goalPosition, endEffector.position) <= minDistanceToGoal)
{
return IterationResult.GoalReached;
}
// Compute the matrix that transforms from world space to the parent bone's local space
link.MtxToLocalParent = Matrix4x4.identity;
if (link.Bone.parent != null)
{
link.MtxToLocalParent = link.Bone.parent.worldToLocalMatrix;
}
// Compute the vector that rotates around axis1 corresponding to 0 degrees. It will be computed in local space of the parent link.
Vector3 parentSpaceAngle1Vector = link.MtxToLocalParent.MultiplyVector(link.Bone.TransformDirection(link.LocalSpaceAxis1ZeroAngleVector));
if (link.Constraint == UxrCcdConstraintType.TwoAxes)
{
// When dealing with 2 axis constraint mode we need to recompute the rotation axis in parent space
link.ParentSpaceAxis1 = link.MtxToLocalParent.MultiplyVector(link.Bone.TransformDirection(link.RotationAxis1));
}
// Using the computations above, calculate the angle1 value. This is the value of rotation in degrees corresponding to the first constraint axis
link.Angle1 = Vector3.SignedAngle(Vector3.ProjectOnPlane(link.ParentSpaceAxis1ZeroAngleVector, link.ParentSpaceAxis1),
Vector3.ProjectOnPlane(parentSpaceAngle1Vector, link.ParentSpaceAxis1),
link.ParentSpaceAxis1);
// Now let's rotate around axis1 if needed. We will compute the current vector from this node to the effector and also the current vector from this node
// to the target. Our goal is to make the first vector match the second vector but we may only rotate around axis1. So what we do is project the goal vector
// onto the plane with axis1 as its normal and this will be the result of our "valid" rotation due to the constraint.
Vector3 currentDirection = endEffector.position - link.Bone.position;
Vector3 desiredDirection = goalPosition - link.Bone.position;
if (link.AlignToGoal)
{
currentDirection = endEffector.forward;
desiredDirection = goalForward;
}
Vector3 worldAxis1 = link.Bone.TransformDirection(link.RotationAxis1);
Vector3 closestVectorAxis1Rotation = Vector3.ProjectOnPlane(desiredDirection, worldAxis1);
float newAxis1AngleIncrement = link.Weight * Vector3.SignedAngle(Vector3.ProjectOnPlane(currentDirection, worldAxis1), closestVectorAxis1Rotation, worldAxis1);
float totalAngleAxis1 = FixAngle(link.Angle1 + newAxis1AngleIncrement);
// Now that we have computed our increment, let's see if we need to clamp it between the limits
if (link.Axis1HasLimits)
{
if (totalAngleAxis1 > link.Axis1AngleMax)
{
newAxis1AngleIncrement -= totalAngleAxis1 - link.Axis1AngleMax;
}
else if (totalAngleAxis1 < link.Axis1AngleMin)
{
newAxis1AngleIncrement += link.Axis1AngleMin - totalAngleAxis1;
}
totalAngleAxis1 = FixAngle(link.Angle1 + newAxis1AngleIncrement);
}
// Do we need to rotate?
if (Mathf.Approximately(newAxis1AngleIncrement, 0.0f) == false)
{
link.Angle1 = totalAngleAxis1;
link.Bone.localRotation = link.InitialLocalRotation * Quaternion.AngleAxis(link.Angle1, link.RotationAxis1);
if (link.Constraint == UxrCcdConstraintType.TwoAxes)
{
link.Bone.localRotation = link.Bone.localRotation * Quaternion.AngleAxis(link.Angle2, link.RotationAxis2);
}
linksRotated = true;
}
if (link.Constraint == UxrCcdConstraintType.TwoAxes)
{
// Axis 2. Axis 2 works exactly like axis 1 but we operate on another plane
Vector3 parentSpaceAngle2Vector = link.MtxToLocalParent.MultiplyVector(link.Bone.TransformDirection(link.LocalSpaceAxis2ZeroAngleVector));
link.ParentSpaceAxis2 = link.MtxToLocalParent.MultiplyVector(link.Bone.TransformDirection(link.RotationAxis2));
link.Angle2 = Vector3.SignedAngle(Vector3.ProjectOnPlane(link.ParentSpaceAxis2ZeroAngleVector, link.ParentSpaceAxis2),
Vector3.ProjectOnPlane(parentSpaceAngle2Vector, link.ParentSpaceAxis2),
link.ParentSpaceAxis2);
currentDirection = endEffector.position - link.Bone.position;
desiredDirection = goalPosition - link.Bone.position;
if (link.AlignToGoal)
{
currentDirection = endEffector.forward;
desiredDirection = goalForward;
}
Vector3 worldAxis2 = link.Bone.TransformDirection(link.RotationAxis2);
Vector3 closestVectorAxis2Rotation = Vector3.ProjectOnPlane(desiredDirection, worldAxis2);
float newAxis2AngleIncrement = link.Weight * Vector3.SignedAngle(Vector3.ProjectOnPlane(currentDirection, worldAxis2), closestVectorAxis2Rotation, worldAxis2);
float totalAngleAxis2 = FixAngle(link.Angle2 + newAxis2AngleIncrement);
if (link.Axis2HasLimits)
{
if (totalAngleAxis2 > link.Axis2AngleMax)
{
newAxis2AngleIncrement -= totalAngleAxis2 - link.Axis2AngleMax;
}
else if (totalAngleAxis2 < link.Axis2AngleMin)
{
newAxis2AngleIncrement += link.Axis2AngleMin - totalAngleAxis2;
}
totalAngleAxis2 = FixAngle(link.Angle2 + newAxis2AngleIncrement);
}
if (Mathf.Approximately(newAxis2AngleIncrement, 0.0f) == false)
{
// Rotation order is first angle2 then angle1 because previously we have rotated in this order already
link.Angle2 = totalAngleAxis2;
link.Bone.localRotation = link.InitialLocalRotation * Quaternion.AngleAxis(link.Angle1, link.RotationAxis1) * Quaternion.AngleAxis(link.Angle2, link.RotationAxis2);
linksRotated = true;
}
}
}
return linksRotated ? Vector3.Distance(goalPosition, endEffector.position) <= minDistanceToGoal ? IterationResult.GoalReached : IterationResult.ReachingGoal : IterationResult.Error;
}
/// <summary>
/// Gets the transform that should be used to restore the goal position every time an IK link
/// is reoriented.
/// We use this in cases where we manipulate an object that the goal is part of, and the IK chain
/// is in a hierarchy above the object/goal. This is needed because when computing the different
/// IK steps, the goal and the object may be repositioned as a consequence, being below in the chain.
/// As a double measure, what we try to reposition is the topmost parent that is below the IK chain,
/// since the goal may be a dummy at the end of the chain and repositioning the goal alone would
/// not be enough.
/// </summary>
/// <param name="links">List of links (bones) of the chain</param>
/// <param name="goal">The goal that the end effector will try to reach</param>
/// <returns>Transform that should be stored</returns>
private static Transform GetGoalSafeRestoreTransform(List<UxrCcdLink> links, Transform goal)
{
Transform current = goal;
Transform previous = goal;
while (current != null)
{
for (int i = links.Count - 1; i >= 0; --i)
{
if (current == links[i].Bone && current != previous)
{
// Found a bone. previous here is the child that we should move/rotate in order to
// preserve the original goal position/orientation.
return previous;
}
}
previous = current;
current = current.parent;
}
return goal;
}
#endregion
#region Private Types & Data
private bool _initialized;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fba00edd9cbdb6b43915c325836d87c8
timeCreated: 1510133937
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,185 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCcdLink.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Defines a link -bone- in an IK chain solved using CCD.
/// </summary>
/// <seealso cref="UxrCcdIKSolver" />
[Serializable]
public class UxrCcdLink
{
#region Inspector Properties/Serialized Fields
// Setup in the editor
[SerializeField] private Transform _bone;
[SerializeField] private float _weight;
[SerializeField] private UxrCcdConstraintType _constraint;
[SerializeField] private Vector3 _rotationAxis1;
[SerializeField] private Vector3 _rotationAxis2;
[SerializeField] private bool _axis1HasLimits;
[SerializeField] private float _axis1AngleMin;
[SerializeField] private float _axis1AngleMax;
[SerializeField] private bool _axis2HasLimits;
[SerializeField] private float _axis2AngleMin;
[SerializeField] private float _axis2AngleMax;
[SerializeField] private bool _alignToGoal;
#endregion
#region Public Types & Data
/// <summary>
/// Gets the link transform.
/// </summary>
public Transform Bone => _bone;
/// <summary>
/// Gets the link constraint type.
/// </summary>
public UxrCcdConstraintType Constraint => _constraint;
/// <summary>
/// Gets the first rotation axis.
/// </summary>
public Vector3 RotationAxis1 => _rotationAxis1;
/// <summary>
/// Gets the second rotation axis when there are two constraints.
/// </summary>
public Vector3 RotationAxis2 => _rotationAxis2;
/// <summary>
/// Gets whether the first axis has rotational limits.
/// </summary>
public bool Axis1HasLimits => _axis1HasLimits;
/// <summary>
/// Gets the lower angle limits of the first axis.
/// </summary>
public float Axis1AngleMin => _axis1AngleMin;
/// <summary>
/// Gets the higher angle limits of the first axis.
/// </summary>
public float Axis1AngleMax => _axis1AngleMax;
/// <summary>
/// Gets whether the second axis has rotational limits when there are two constraints.
/// </summary>
public bool Axis2HasLimits => _axis2HasLimits;
/// <summary>
/// Gets the lower angle limits of the second axis when there are two constraints.
/// </summary>
public float Axis2AngleMin => _axis2AngleMin;
/// <summary>
/// Gets the higher angle limits of the second axis when there are two constraints.
/// </summary>
public float Axis2AngleMax => _axis2AngleMax;
/// <summary>
/// Gets whether the effector should not only try to position itself on the goal but also use the same orientation.
/// </summary>
public bool AlignToGoal => _alignToGoal;
/// <summary>
/// The weight among all the CCD links in the chain.
/// </summary>
public float Weight
{
get => _weight;
set => _weight = value;
}
/// <summary>
/// Gets whether the data has been initialized.
/// </summary>
public bool Initialized { get; internal set; }
/// <summary>
/// Gets the local rotation at the beginning.
/// </summary>
public Quaternion InitialLocalRotation { get; internal set; }
/// <summary>
/// Gets a reference perpendicular to axis1 that is considered as the reference of having 0 degrees around axis1.
/// </summary>
public Vector3 LocalSpaceAxis1ZeroAngleVector { get; internal set; }
/// <summary>
/// Gets a reference perpendicular to axis2 that is considered as the reference of having 0 degrees around axis2.
/// </summary>
public Vector3 LocalSpaceAxis2ZeroAngleVector { get; internal set; }
/// <summary>
/// Gets the length of the link.
/// </summary>
public float LinkLength { get; internal set; }
/// <summary>
/// Gets <see cref="RotationAxis1" /> in local space of the parent object.
/// </summary>
public Vector3 ParentSpaceAxis1 { get; internal set; }
/// <summary>
/// Gets <see cref="RotationAxis2" /> in local space of the parent object.
/// </summary>
public Vector3 ParentSpaceAxis2 { get; internal set; }
/// <summary>
/// Gets <see cref="LocalSpaceAxis1ZeroAngleVector" /> in local space of the parent object.
/// </summary>
public Vector3 ParentSpaceAxis1ZeroAngleVector { get; internal set; }
/// <summary>
/// Gets <see cref="LocalSpaceAxis2ZeroAngleVector" /> in local space of the parent object.
/// </summary>
public Vector3 ParentSpaceAxis2ZeroAngleVector { get; internal set; }
/// <summary>
/// Gets the transformation matrix that gets from world-space to local space in the parent transform.
/// </summary>
public Matrix4x4 MtxToLocalParent { get; internal set; }
/// <summary>
/// Gets rotation degrees around <see cref="RotationAxis1" />.
/// </summary>
public float Angle1 { get; internal set; }
/// <summary>
/// Gets rotation degrees around <see cref="RotationAxis2" />.
/// </summary>
public float Angle2 { get; internal set; }
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor.
/// </summary>
public UxrCcdLink()
{
_weight = 1.0f;
_constraint = UxrCcdConstraintType.SingleAxis;
_rotationAxis1 = Vector3.right;
_rotationAxis2 = Vector3.up;
_axis1HasLimits = true;
_axis1AngleMin = -45.0f;
_axis1AngleMax = 45.0f;
_axis2HasLimits = false;
_axis2AngleMin = -45.0f;
_axis2AngleMax = 45.0f;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34bdb75b6a3c4a7cb567e7f471b643a1
timeCreated: 1643125714

View File

@@ -0,0 +1,131 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrIKSolver.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core;
using UltimateXR.Core.Components.Composite;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// Base IK Solver class. IK solvers should inherit from it and override the <see cref="InternalSolveIK" /> method.
/// Not all solvers need to be part of an avatar, but the <see cref="UxrAvatarComponent{T}" /> inheritance is used to
/// be able to enumerate all the solvers that are part of an avatar.
/// </summary>
public abstract class UxrIKSolver : UxrAvatarComponent<UxrIKSolver>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _enabled = true;
[SerializeField] private bool _manualUpdate;
#endregion
#region Public Types & Data
/// <summary>
/// Called right before the IK is about to be solved during the current frame
/// </summary>
public event Action Solving;
/// <summary>
/// Called right after the IK was solved during the current frame
/// </summary>
public event Action Solved;
/// <summary>
/// Gets whether the component is initialized.
/// </summary>
public abstract bool Initialized { get; }
/// <summary>
/// Gets if the solver needs to be updated automatically.
/// </summary>
public bool NeedsAutoUpdate => gameObject.activeInHierarchy && enabled && SolverEnabled && !ManualUpdate;
/// <summary>
/// Gets or sets the IK solver enabled state?
/// </summary>
public bool SolverEnabled
{
get => _enabled;
set => _enabled = value;
}
/// <summary>
/// Gets if the IK solver will update itself. Otherwise the user will be responsible of calling <see cref="SolveIK" />.
/// </summary>
public bool ManualUpdate
{
get => _manualUpdate;
set => _manualUpdate = value;
}
#endregion
#region Public Methods
/// <summary>
/// Solves the IK. Calls <see cref="InternalSolveIK" />,which is implemented in child classes, but calls the
/// appropriate <see cref="Solving" /> and <see cref="Solved" /> events.
/// </summary>
public void SolveIK()
{
Solving?.Invoke();
InternalSolveIK();
Solved?.Invoke();
}
#endregion
#region Unity
/// <summary>
/// Subscribes to events
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
UxrManager.StageUpdating += UxrManager_StageUpdating;
}
/// <summary>
/// Unsubscribes from events
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
UxrManager.StageUpdating -= UxrManager_StageUpdating;
}
#endregion
#region Event Handling Methods
/// <summary>
/// Will solve the IK chain in case it is not part of an avatar. If it is part of a VR avatar, the VR avatar will take
/// care of calling the SolveIK method so that it is processed in the correct order, after the hands are updated.
/// </summary>
private void UxrManager_StageUpdating(UxrUpdateStage stage)
{
if (stage == UxrUpdateStage.PostProcess && Avatar == null && NeedsAutoUpdate)
{
SolveIK();
}
}
#endregion
#region Protected Methods
/// <summary>
/// To be implemented in child classes to execute the actual IK solving algorithm for the current frame
/// </summary>
protected abstract void InternalSolveIK();
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 0c24e6ba752cd9d47b3c68e991363e16
timeCreated: 1519111756
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,88 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrWristTorsionIKSolver.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Math;
using UltimateXR.Extensions.Unity;
using UltimateXR.Extensions.Unity.Math;
using UnityEngine;
namespace UltimateXR.Animation.IK
{
/// <summary>
/// IK solver that distributes a wrist torsion among different bones in a forearm in order to smooth it out.
/// </summary>
public class UxrWristTorsionIKSolver : UxrIKSolver
{
#region Inspector Properties/Serialized Fields
[SerializeField] [Range(0.0f, 1.0f)] private float _amount = 1.0f;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the amount of torsion to apply on this Transform from the source. 0 = no torsion, 1 = full torsion,
/// etc.
/// </summary>
public float Amount
{
get => _amount;
set => _amount = value;
}
#endregion
#region Public Overrides UxrIKSolver
/// <inheritdoc />
public override bool Initialized => _initialized;
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
_handSide = transform.HasParent(Avatar.AvatarRig.LeftArm.UpperArm) ? UxrHandSide.Left : UxrHandSide.Right;
_startLocalRotation = transform.localRotation;
UxrUniversalLocalAxes handUniversalLocalAxes = Avatar.AvatarRigInfo.GetArmInfo(_handSide).HandUniversalLocalAxes;
_torsionLocalAxis = transform.InverseTransformDirection(handUniversalLocalAxes.WorldForward).GetClosestAxis();
_initialized = true;
}
#endregion
#region Protected Overrides UxrIKSolver
/// <summary>
/// Solves the Inverse Kinematics.
/// </summary>
protected override void InternalSolveIK()
{
float angle = Avatar.AvatarRigInfo.GetArmInfo(_handSide).WristTorsionInfo.WristTorsionAngle;
transform.localRotation = _startLocalRotation * Quaternion.AngleAxis(angle * Amount, _torsionLocalAxis);
}
#endregion
#region Private Types & Data
private bool _initialized;
private UxrHandSide _handSide;
private Quaternion _startLocalRotation;
private Vector3 _torsionLocalAxis;
#endregion
}
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a41528363b03e1c4a8b730dee5b434ab
timeCreated: 1510043257
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 55c027c769a848d3bcca74385df23e35
timeCreated: 1643801906

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrColor32Interpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Color32" />.
/// </summary>
public class UxrColor32Interpolator : UxrVarInterpolator<Color32>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrColor32Interpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Color32>
/// <inheritdoc />
protected override Color32 GetInterpolatedValue(Color32 a, Color32 b, float t)
{
return Color32.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrColor32Interpolator DefaultInterpolator = new UxrColor32Interpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c25c810b663cfb9479248e749fcd8f68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrColorInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Color" />.
/// </summary>
public class UxrColorInterpolator : UxrVarInterpolator<Color>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrColorInterpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Color>
/// <inheritdoc />
protected override Color GetInterpolatedValue(Color a, Color b, float t)
{
return Color.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrColorInterpolator DefaultInterpolator = new UxrColorInterpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ee74b3fb73e96684e950de6e4b348181
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,80 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrEasing.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Animation.Lights;
using UltimateXR.Animation.Materials;
using UltimateXR.Animation.Transforms;
using UltimateXR.Animation.UI;
using UltimateXR.Manipulation.Helpers;
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// <para>
/// Type of interpolation curves.
/// </para>
/// <para>
/// References:
/// </para>
/// <list type="bullet">
/// <item>http://easings.net</item>
/// <item>http://gillcup.readthedocs.org/en/latest/_images/easings.png</item>
/// </list>
/// Examples of some classes that use interpolation:
/// <list type="bullet">
/// <item><see cref="UxrInterpolator" />: Access to interpolation calculations</item>
/// <item><see cref="UxrTween" /> and all derived classes (UI tweening)</item>
/// <item><see cref="UxrAnimatedTransform" /> (<see cref="Transform" /> animation)</item>
/// <item><see cref="UxrAnimatedLightIntensity" /> (<see cref="Light" /> intensity parameter animation)</item>
/// <item><see cref="UxrAnimatedMaterial" /> (<see cref="Material" /> parameter animation)</item>
/// <item><see cref="UxrRestoreOnRelease" /> (Restores original grabbable object position/rotation when released)</item>
/// </list>
/// </summary>
public enum UxrEasing
{
Linear,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseOutInSine,
EaseInQuad,
EaseOutQuad,
EaseInOutQuad,
EaseOutInQuad,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseOutInCubic,
EaseInQuart,
EaseOutQuart,
EaseInOutQuart,
EaseOutInQuart,
EaseInQuint,
EaseOutQuint,
EaseInOutQuint,
EaseOutInQuint,
EaseInExpo,
EaseOutExpo,
EaseInOutExpo,
EaseOutInExpo,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseOutInCirc,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseOutInBack,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseOutInElastic,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
EaseOutInBounce
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b117473a846147e7a03b9ae6af20c21a
timeCreated: 1642849506

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFloatInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for float values.
/// </summary>
public class UxrFloatInterpolator : UxrVarInterpolator<float>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrFloatInterpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<float>
/// <inheritdoc />
protected override float GetInterpolatedValue(float a, float b, float t)
{
return Mathf.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrFloatInterpolator DefaultInterpolator = new UxrFloatInterpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0fb9477ff396cd3409bf24e9e9466ce7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrIntInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for int values.
/// </summary>
public class UxrIntInterpolator : UxrVarInterpolator<int>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrIntInterpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<int>
/// <inheritdoc />
protected override int GetInterpolatedValue(int a, int b, float t)
{
return Mathf.RoundToInt(Mathf.Lerp(a, b, t));
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrIntInterpolator DefaultInterpolator = new UxrIntInterpolator();
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a825634ab74f53042ab27d06218b3c68
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,197 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInterpolationSettings.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Describes the different parameters of an interpolation.
/// </summary>
[Serializable]
public class UxrInterpolationSettings
{
#region Inspector Properties/Serialized Fields
[SerializeField] private float _durationSeconds;
[SerializeField] private float _delaySeconds;
[SerializeField] private UxrEasing _easing;
[SerializeField] private UxrLoopMode _loopMode;
[SerializeField] private float _loopedDurationSeconds;
[SerializeField] private bool _useUnscaledTime;
[SerializeField] private bool _delayUsingEndValue;
#endregion
#region Public Types & Data
/// <summary>
/// Gets or sets the interpolation duration in seconds. In looped interpolations it tells the duration of a single
/// loop.
/// </summary>
public float DurationSeconds
{
get => _durationSeconds;
set => _durationSeconds = value;
}
/// <summary>
/// Gets or sets the interpolation delay in seconds. The delay is usually relative to the time the object that it uses
/// was enabled and specifies an initial waiting time before the actual interpolation will start.
/// </summary>
public float DelaySeconds
{
get => _delaySeconds;
set => _delaySeconds = value;
}
/// <summary>
/// Gets or sets the easing function to use by the interpolation.
/// </summary>
public UxrEasing Easing
{
get => _easing;
set => _easing = value;
}
/// <summary>
/// Gets or sets if and how to loop the interpolation.
/// </summary>
public UxrLoopMode LoopMode
{
get => _loopMode;
set => _loopMode = value;
}
/// <summary>
/// Gets or sets the total animation duration in interpolations that use looping. The duration of a single loop is
/// described by <see cref="DurationSeconds" />. A negative value tells to loop indefinitely.
/// </summary>
public float LoopedDurationSeconds
{
get => _loopedDurationSeconds;
set => _loopedDurationSeconds = value;
}
/// <summary>
/// Gets or sets whether to use unscaled time (<see cref="Time.unscaledTime" />) or regular time
/// <see cref="Time.time" /> when interpolating.
/// Regular time is affected by <see cref="Time.timeScale" />, which is normally used to pause the application or
/// simulate slow motion effects.
/// </summary>
public bool UseUnscaledTime
{
get => _useUnscaledTime;
set => _useUnscaledTime = value;
}
/// <summary>
/// Gets or sets whether to use the interpolation end value during the initial delay, if there is a
/// <see cref="DelaySeconds" /> value specified.
/// By default the interpolation uses the start value during the initial delay.
/// </summary>
public bool DelayUsingEndValue
{
get => _delayUsingEndValue;
set => _delayUsingEndValue = value;
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Default constructor
/// </summary>
public UxrInterpolationSettings()
{
_durationSeconds = 0.0f;
_delaySeconds = 0.0f;
_easing = UxrEasing.Linear;
_loopMode = UxrLoopMode.None;
_loopedDurationSeconds = -1.0f;
_useUnscaledTime = false;
_delayUsingEndValue = false;
}
/// <summary>
/// UxrInterpolationSettings constructor.
/// </summary>
/// <param name="durationSeconds">
/// The duration in seconds the interpolation will be applied. If a loopMode was specified, it tells the duration of a
/// single loop.
/// </param>
/// <param name="delaySeconds">The delay in seconds before the interpolation</param>
/// <param name="easing">The type of interpolation used.</param>
/// <param name="loopMode">The type of looping used.</param>
/// <param name="loopedDurationSeconds">
/// If loopMode is not LoopMode.None this parameter will tell how many seconds the total duration of
/// the interpolation will last and durationSeconds will tell the duration of each loop. A negative value means it will
/// loop forever.
/// </param>
/// <param name="useUnscaledTime">
/// Tells whether to use the real timer value <see cref="Time.unscaledTime" /> (true) or the scaled
/// <see cref="Time.time" /> value (false) which is affected by <see cref="Time.timeScale" />.
/// </param>
/// <param name="delayUsingEndValue">
/// Tells whether to use the interpolation end value during the delay, if there is a
/// <paramref name="delaySeconds" /> specified. By default it's false, which means the interpolation start value is
/// used during the delay.
/// </param>
public UxrInterpolationSettings(float durationSeconds,
float delaySeconds = 0.0f,
UxrEasing easing = UxrEasing.Linear,
UxrLoopMode loopMode = UxrLoopMode.None,
float loopedDurationSeconds = -1.0f,
bool useUnscaledTime = false,
bool delayUsingEndValue = false)
{
_durationSeconds = durationSeconds;
_delaySeconds = delaySeconds;
_easing = easing;
_loopMode = loopMode;
_loopedDurationSeconds = loopedDurationSeconds;
_useUnscaledTime = useUnscaledTime;
_delayUsingEndValue = delayUsingEndValue;
}
#endregion
#region Public Methods
/// <summary>
/// Gets the T value used for linear interpolations given a time value.
/// </summary>
/// <param name="time">Time value</param>
/// <returns>The interpolation t value required to linearly interpolate using the current parameters</returns>
public float GetInterpolationFactor(float time)
{
return UxrInterpolator.Interpolate(0.0f, 1.0f, _durationSeconds, _delaySeconds, time, _easing, _loopMode, _loopedDurationSeconds, _delayUsingEndValue);
}
/// <summary>
/// Checks if the given time has surpassed the interpolation duration.
/// </summary>
/// <param name="time">Time value</param>
/// <returns>Boolean telling whether the interpolation has finished</returns>
public bool CheckInterpolationHasFinished(float time)
{
if (LoopMode == UxrLoopMode.None && time > DelaySeconds + DurationSeconds)
{
return true;
}
if (LoopMode != UxrLoopMode.None && time > DelaySeconds + LoopedDurationSeconds && LoopedDurationSeconds >= 0.0f)
{
return true;
}
return false;
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 712731616a3841a4c8822c33b6a6aa87
timeCreated: 1521533433
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,798 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.System;
using UltimateXR.UI.UnityInputModule.Utils;
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Provides functionality to interpolate between values using a wide range of interpolation modes.
/// This class also provides functionality to interpolate between 2 strings using a typewriter effect.
/// </summary>
public static class UxrInterpolator
{
#region Public Methods
/// <summary>
/// Smooths a float value using the previous value, new value and a smooth value between [0.0, 1.0].
/// </summary>
/// <param name="oldValue">Old value</param>
/// <param name="newValue">New value</param>
/// <param name="smooth">Smooth value [0.0, 1.0] where 0.0 is no smoothing and 1.0 is maximum smoothing</param>
/// <returns>Smoothed value</returns>
public static float SmoothDamp(float oldValue, float newValue, float smooth)
{
return Mathf.Lerp(oldValue, newValue, GetSmoothInterpolationValue(smooth, Time.deltaTime));
}
/// <summary>
/// Smooths a position value using the last position, new position and a smooth value between [0.0, 1.0].
/// </summary>
/// <param name="oldPos">Old position</param>
/// <param name="newPos">New position</param>
/// <param name="smooth">Smooth value [0.0, 1.0] where 0.0 is no smoothing and 1.0 is maximum smoothing</param>
/// <returns>Smoothed position value</returns>
public static Vector3 SmoothDampPosition(Vector3 oldPos, Vector3 newPos, float smooth)
{
return Vector3.Lerp(oldPos, newPos, GetSmoothInterpolationValue(smooth, Time.deltaTime));
}
/// <summary>
/// Smooths a rotation value using the last rotation, new rotation and a smooth value between [0.0, 1.0].
/// This tries to do something similar to <see cref="Vector3.SmoothDamp" /> but for rotations.
/// </summary>
/// <param name="oldRot">Old rotation</param>
/// <param name="newRot">New rotation</param>
/// <param name="smooth">Smooth value [0.0, 1.0] where 0.0 is no smoothing and 1.0 is maximum smoothing</param>
/// <returns>Smoothed rotation value</returns>
public static Quaternion SmoothDampRotation(Quaternion oldRot, Quaternion newRot, float smooth)
{
return Quaternion.Slerp(oldRot, newRot, GetSmoothInterpolationValue(smooth, Time.deltaTime));
}
/// <summary>
/// Interpolates between two floating point values using a t between range [0.0, 1.0] and a given easing.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor</param>
/// <param name="easing">Easing</param>
/// <returns>Interpolated value</returns>
public static float Interpolate(float a, float b, float t, UxrEasing easing)
{
return Interpolate(a, b, 1.0f, 0.0f, Mathf.Clamp01(t), easing);
}
/// <summary>
/// Interpolates between two points using a t between range [0.0, 1.0] and a given easing.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor</param>
/// <param name="easing">Easing</param>
/// <returns>Interpolated value</returns>
public static Vector3 Interpolate(Vector3 a, Vector3 b, float t, UxrEasing easing)
{
return Interpolate(a, b, 1.0f, 0.0f, Mathf.Clamp01(t), easing);
}
/// <summary>
/// Spherically interpolates (SLERP) between two quaternions using a t between range [0.0, 1.0] and a given easing.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor</param>
/// <param name="easing">Easing</param>
/// <returns>Interpolated value</returns>
public static Quaternion Interpolate(Quaternion a, Quaternion b, float t, UxrEasing easing)
{
return Interpolate(a, b, 1.0f, 0.0f, Mathf.Clamp01(t), easing);
}
/// <summary>
/// Interpolates between two floating point values.
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="duration">
/// The duration of the interpolation. If there is looping (loopMode != LoopMode.None) then it will
/// specify the duration of a single loop
/// </param>
/// <param name="delay">The delay duration before the interpolation starts</param>
/// <param name="time">
/// The time value. This value will be clamped between [delay, delay + duration] or if there is looping
/// (loopMode != LoopMode.None) then it will be clamped between [delay, delay + loopedDuration]. In this case
/// duration will specify the duration of the loop
/// </param>
/// <param name="easing">The interpolation method to use. See @Easing</param>
/// <param name="loopMode">Which looping mode to use. See @LoopMode</param>
/// <param name="loopedDuration">
/// If loopMode is not LoopMode.None then loopedDuration will specify the duration of the
/// interpolation including all the loops. A negative value will make it loop forever.
/// </param>
/// <param name="delayUsingEndValue">
/// Tells whether to use the interpolation end value during the delay, if there is a <paramref name="delay" />
/// specified. By default it's false, which means the interpolation start value is used during the delay.
/// </param>
/// <returns>Interpolated floating point value</returns>
public static float Interpolate(float startValue,
float endValue,
float duration,
float delay,
float time,
UxrEasing easing,
UxrLoopMode loopMode = UxrLoopMode.None,
float loopedDuration = -1.0f,
bool delayUsingEndValue = false)
{
return Interpolate(new Vector4(startValue, 0, 0, 0), new Vector4(endValue, 0, 0, 0), duration, delay, time, easing, loopMode, loopedDuration, delayUsingEndValue).x;
}
/// <summary>
/// Interpolates between two floating point values.
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="time">The time value</param>
/// <param name="settings">The interpolation settings to use</param>
/// <returns>Interpolated floating point value</returns>
/// <exception cref="ArgumentNullException">
/// When <paramref name="settings" /> is null.
/// </exception>
public static float Interpolate(float startValue, float endValue, float time, UxrInterpolationSettings settings)
{
settings.ThrowIfNull(nameof(settings));
return Interpolate(new Vector4(startValue, 0, 0, 0),
new Vector4(endValue, 0, 0, 0),
settings.DurationSeconds,
settings.DelaySeconds,
time,
settings.Easing,
settings.LoopMode,
settings.LoopedDurationSeconds,
settings.DelayUsingEndValue).x;
}
/// <summary>
/// Gets the T value used for linear interpolations like Vector3.Lerp or Quaternion.Slerp using easing and loop.
/// </summary>
/// <param name="t">Value between range [0.0f, 1.0f]</param>
/// <param name="easing">The interpolation method to use.</param>
/// <param name="loopMode">Which looping mode to use.</param>
/// <param name="loopedDuration">
/// If loopMode is not LoopMode.None then loopedDuration will specify the duration of the
/// interpolation including all the loops. A negative value will make it loop forever.
/// </param>
/// <returns>The t value used to linearly interpolate using the specified parameters</returns>
public static float GetInterpolationFactor(float t, UxrEasing easing, UxrLoopMode loopMode = UxrLoopMode.None, float loopedDuration = -1.0f)
{
return Interpolate(0.0f, 1.0f, 1.0f, 0.0f, t, easing, loopMode, loopedDuration);
}
/// <summary>
/// Interpolates between two <see cref="Vector4" /> values
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="duration">
/// The duration of the interpolation. If there is looping (loopMode != LoopMode.None) then it will
/// specify the duration of a single loop
/// </param>
/// <param name="delay">The delay duration before the interpolation starts</param>
/// <param name="time">
/// The time value. This value will be clamped between [delay, delay + duration] or if there is looping
/// (loopMode != LoopMode.None) then it will be clamped between [delay, delay + loopedDuration]. In this case
/// duration will specify the duration of the loop
/// </param>
/// <param name="easing">The interpolation method to use. See @Easing</param>
/// <param name="loopMode">Which looping mode to use. See @LoopMode</param>
/// <param name="loopedDuration">
/// If loopMode is not LoopMode.None then loopedDuration will specify the duration of the
/// interpolation including all the loops. A negative value will make it loop forever.
/// </param>
/// <param name="delayUsingEndValue">
/// Tells whether to use the interpolation end value during the delay, if there is a <paramref name="delay" />
/// specified. By default it's false, which means the interpolation start value is used during the delay.
/// </param>
/// <returns>Interpolated <see cref="Vector4" /> value</returns>
public static Vector4 Interpolate(Vector4 startValue,
Vector4 endValue,
float duration,
float delay,
float time,
UxrEasing easing,
UxrLoopMode loopMode = UxrLoopMode.None,
float loopedDuration = -1.0f,
bool delayUsingEndValue = false)
{
if (time < delay)
{
return delayUsingEndValue ? endValue : startValue;
}
// Compute interpolation t
time = Mathf.Max(delay, time);
if (!(loopMode != UxrLoopMode.None && loopedDuration < 0.0f))
{
Mathf.Min(time, delay + (loopMode == UxrLoopMode.None ? duration : loopedDuration));
}
float t = duration == 0.0f ? 0.0f : (time - delay) / duration;
if (loopMode == UxrLoopMode.Loop)
{
t = t - (int)t;
}
else if (loopMode == UxrLoopMode.PingPong)
{
int loopCount = (int)t;
t = t - loopCount;
if ((loopCount & 1) == 1)
{
// It's going back
t = 1.0f - t;
}
}
return InterpolateVector4(startValue, endValue, t, easing);
}
/// <summary>
/// Interpolates between two <see cref="Vector4" /> values
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="time">The time value</param>
/// <param name="settings">Interpolation settings to use</param>
/// <returns>The interpolated <see cref="Vector4" /> value</returns>
/// <exception cref="ArgumentNullException">
/// When <paramref name="settings" /> is null.
/// </exception>
public static Vector4 Interpolate(Vector4 startValue, Vector4 endValue, float time, UxrInterpolationSettings settings)
{
settings.ThrowIfNull(nameof(settings));
return Interpolate(startValue, endValue, settings.DurationSeconds, settings.DelaySeconds, time, settings.Easing, settings.LoopMode, settings.LoopedDurationSeconds, settings.DelayUsingEndValue);
}
/// <summary>
/// Interpolates between two <see cref="Quaternion" /> values. The interpolation uses SLERP.
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="duration">
/// The duration of the interpolation. If there is looping (loopMode != LoopMode.None) then it will
/// specify the duration of a single loop
/// </param>
/// <param name="delay">The delay duration before the interpolation starts</param>
/// <param name="time">
/// The time value. This value will be clamped between [delay, delay + duration] or if there is looping
/// (loopMode != LoopMode.None) then it will be clamped between [delay, delay + loopedDuration]. In this case
/// duration will specify the duration of the loop
/// </param>
/// <param name="easing">The interpolation method to use. See @Easing</param>
/// <param name="loopMode">Which looping mode to use. See @LoopMode</param>
/// <param name="loopedDuration">
/// If loopMode is not LoopMode.None then loopedDuration will specify the duration of the
/// interpolation including all the loops. A negative value will make it loop forever.
/// </param>
/// <param name="delayUsingEndValue">
/// Tells whether to use the interpolation end value during the delay, if there is a <paramref name="delay" />
/// specified. By default it's false, which means the interpolation start value is used during the delay.
/// </param>
/// <returns>Interpolated <see cref="Quaternion" /> value</returns>
public static Quaternion Interpolate(Quaternion startValue,
Quaternion endValue,
float duration,
float delay,
float time,
UxrEasing easing,
UxrLoopMode loopMode = UxrLoopMode.None,
float loopedDuration = -1.0f,
bool delayUsingEndValue = false)
{
float t = Interpolate(0.0f, 1.0f, duration, delay, time, easing, loopMode, loopedDuration);
return Quaternion.Slerp(startValue, endValue, t);
}
/// <summary>
/// Interpolates between two <see cref="Quaternion" /> values. The interpolation uses SLERP.
/// </summary>
/// <param name="startValue">The start value</param>
/// <param name="endValue">The end value</param>
/// <param name="time">The time value</param>
/// <param name="settings">Interpolation settings to use</param>
/// <returns>Interpolated <see cref="Quaternion" /> value</returns>
/// <exception cref="ArgumentNullException">
/// When <paramref name="settings" /> is null.
/// </exception>
public static Quaternion Interpolate(Quaternion startValue,
Quaternion endValue,
float time,
UxrInterpolationSettings settings)
{
settings.ThrowIfNull(nameof(settings));
return Interpolate(startValue, endValue, settings.DurationSeconds, settings.DelaySeconds, time, settings.Easing, settings.LoopMode, settings.LoopedDurationSeconds, settings.DelayUsingEndValue);
}
/// <summary>
/// Interpolates text using a typewriter effect.
/// </summary>
/// <param name="startText">Start text</param>
/// <param name="endText">End text</param>
/// <param name="t">Interpolation t between range [0.0, 1.0]</param>
/// <param name="isForUnityTextUI">
/// If true, uses the rich text properties of the Unity UI text component to add invisible characters during
/// interpolation.
/// These invisible characters will help keeping the final text layout so that there are no line wraps or line jumps
/// during the interpolation.
/// </param>
/// <returns>Interpolated text</returns>
/// <remarks>
/// See <see cref="InterpolateText(float,bool,string,object[])" /> to use a format string and arguments for more
/// advanced interpolation and numerical string interpolation.
/// </remarks>
public static string InterpolateText(string startText, string endText, float t, bool isForUnityTextUI)
{
return InterpolateText(t, isForUnityTextUI, "{0}", startText, endText);
}
/// <summary>
/// Interpolates text using a typewriter effect
/// </summary>
/// <param name="t">Interpolation t between range [0.0, 1.0]</param>
/// <param name="isForUnityTextUI">
/// If true, uses the rich text properties of the Unity UI text component to add invisible characters during
/// interpolation.
/// These invisible characters will help keeping the final text layout so that there are no line wraps or line jumps
/// during the interpolation.
/// </param>
/// <param name="formatString">
/// The format string (what would be the first parameter of <see cref="string.Format(string,object[])" />)
/// </param>
/// <param name="formatStringArgs">
/// <para>
/// Start/end pairs that will be interpolated and fed into <see cref="string.Format(string,object[])" />.
/// These should be sequential pairs of values of the same type that represent the start value and the end value.
/// For instance format could be "{0}:{1}" and args could be startArg0, endArg0, startArg1, endArg1.
/// This will print 2 interpolated values (Arg0 and Arg1) whose start and end values are defined by the other 4
/// parameters.
/// </para>
/// <para>
/// The interpolation can detect numerical values (int/float) and use numerical interpolation instead of raw string
/// interpolation. This can be useful for effects as seen in the examples.
/// </para>
/// </param>
/// <example>
/// Simple typewriter effect to write a sentence starting from empty: (t goes from 0.0 to 1.0)
/// <code>
/// InterpolateText(t, true, "{0}", string.Empty, "Welcome to the Matrix!");
/// </code>
/// </example>
/// <example>
/// Using format string args to create an increasing score animation. The numerical values are interpolated instead of
/// using a typewriter effect. (t goes from 0.0 to 1.0).
/// <code>
/// int finalScore = 999999;
/// InterpolateText(t, true, "Final score: {0:000000}", 0, finalScore);
/// </code>
/// </example>
/// <returns>Interpolated string</returns>
public static string InterpolateText(float t, bool isForUnityTextUI, string formatString, params object[] formatStringArgs)
{
#if UNITY_EDITOR
if (!(formatStringArgs.Length > 0 && formatStringArgs.Length % 2 == 0))
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(InterpolateText)}: The text has no arguments or the number of arguments is not even");
}
return string.Empty;
}
#endif
int numArgs = formatStringArgs.Length / 2;
object[] finalArgs = new object[numArgs];
for (int i = 0; i < numArgs; i++)
{
if (formatStringArgs[i] == null)
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(InterpolateText)}: Argument " + i + " is null");
}
return formatStringArgs[i + numArgs] != null ? formatStringArgs[i + numArgs].ToString() : string.Empty;
}
if (formatStringArgs[i + numArgs] == null)
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(InterpolateText)}: Argument " + (i + numArgs) + " is null");
}
return formatStringArgs[i] != null ? formatStringArgs[i].ToString() : string.Empty;
}
#if UNITY_EDITOR
if (!(formatStringArgs[i].GetType() == formatStringArgs[i + numArgs].GetType()))
{
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule} {nameof(InterpolateText)}: Type of argument " + i + " is not the same as argument " + (i + numArgs));
}
return string.Empty;
}
#endif
if (formatStringArgs[i] is int)
{
finalArgs[i] = Mathf.RoundToInt(Mathf.Lerp((int)formatStringArgs[i], (int)formatStringArgs[i + numArgs], Mathf.Clamp01(t)));
}
else if (formatStringArgs[i] is float)
{
finalArgs[i] = Mathf.Lerp((float)formatStringArgs[i], (float)formatStringArgs[i + numArgs], Mathf.Clamp01(t));
}
else if (formatStringArgs[i] is string)
{
string a = (string)formatStringArgs[i];
string b = (string)formatStringArgs[i + 1];
int startChars = a.Length;
int endChars = b.Length;
int numChars = Mathf.Clamp(Mathf.RoundToInt(Mathf.Lerp(startChars, endChars, t)), 0, b.Length);
if (Mathf.Approximately(t, 1.0f))
{
finalArgs[i] = b;
}
else if (numChars > 0)
{
float letterT = Mathf.Clamp01(Mathf.Repeat(t, 1.0f / endChars) * endChars);
int changingIndex = Mathf.Max(0, numChars - 1);
char startCar = char.IsUpper(b[changingIndex]) ? 'A' : 'a';
char endCar = char.IsUpper(b[changingIndex]) ? 'Z' : 'z';
finalArgs[i] = b.Substring(0, Mathf.Max(0, numChars - 1)) + (char)(startCar + letterT * (endCar - startCar));
if (isForUnityTextUI)
{
// Add the remaining characters as "invisible" to avoid word wrapping effects during interpolation.
string remaining = @"<color=#00000000>" + (endChars > startChars ? b.Substring(numChars, endChars - numChars) : string.Empty) + @"</color>";
if (!UxrRightToLeftSupport.UseRightToLeft)
{
finalArgs[i] += remaining;
}
else
{
finalArgs[i] = remaining + finalArgs[i];
}
}
}
else
{
finalArgs[i] = string.Empty;
}
}
}
return string.Format(formatString, finalArgs);
}
#endregion
#region Internal Methods
/// <summary>
/// Gets a framerate-independent smoothed interpolation value.
/// </summary>
/// <param name="smooth">Smooth value [0.0, 1.0] with 0 meaning no smoothing and 1 maximum smoothing</param>
/// <param name="deltaTime">Elapsed time in seconds</param>
/// <returns>Interpolation value [0.0, 1.0]</returns>
internal static float GetSmoothInterpolationValue(float smooth, float deltaTime)
{
return smooth > 0.0f ? (1.0f - Mathf.Clamp01(smooth)) * deltaTime * MaxSmoothSpeed : 1.0f;
}
#endregion
#region Private Methods
/// <summary>
/// Evaluates a curve using interpolation. This is the core math code that does the actual interpolation.
/// </summary>
/// <param name="start">Initial value</param>
/// <param name="end">End value</param>
/// <param name="t">Interpolation t value (range [0.0f, 1.0f])</param>
/// <param name="easing">Interpolation type</param>
/// <returns>Interpolated value</returns>
private static Vector4 InterpolateVector4(Vector4 start, Vector4 end, float t, UxrEasing easing)
{
Vector4 change = end - start;
switch (easing)
{
///////////////////////////////////////// LINEAR ////////////////////////////////////////////////////
case UxrEasing.Linear: return start + change * t;
///////////////////////////////////////// SINE //////////////////////////////////////////////////////
case UxrEasing.EaseInSine: return -change * Mathf.Cos(t * (Mathf.PI / 2.0f)) + change + start;
case UxrEasing.EaseOutSine: return change * Mathf.Sin(t * (Mathf.PI / 2.0f)) + start;
case UxrEasing.EaseInOutSine: return -change / 2.0f * (Mathf.Cos(Mathf.PI * t) - 1.0f) + start;
case UxrEasing.EaseOutInSine when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutSine);
case UxrEasing.EaseOutInSine: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInSine);
///////////////////////////////////////// QUAD //////////////////////////////////////////////////////
case UxrEasing.EaseInQuad: return start + t * t * change;
case UxrEasing.EaseOutQuad: return (t - 2.0f) * t * -change + start;
case UxrEasing.EaseInOutQuad:
{
t *= 2.0f;
if (t < 1)
{
return change / 2.0f * t * t + start;
}
t -= 1.0f;
return -change / 2.0f * (t * (t - 2.0f) - 1.0f) + start;
}
case UxrEasing.EaseOutInQuad when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutQuad);
case UxrEasing.EaseOutInQuad: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInQuad);
///////////////////////////////////////// CUBIC /////////////////////////////////////////////////////
case UxrEasing.EaseInCubic: return start + t * t * t * change;
case UxrEasing.EaseOutCubic:
t -= 1.0f;
return change * (t * t * t + 1.0f) + start;
case UxrEasing.EaseInOutCubic:
{
t *= 2.0f;
if (t < 1.0f)
{
return change / 2.0f * t * t * t + start;
}
t -= 2.0f;
return change / 2.0f * (t * t * t + 2.0f) + start;
}
case UxrEasing.EaseOutInCubic when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutCubic);
case UxrEasing.EaseOutInCubic: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInCubic);
///////////////////////////////////////// QUART /////////////////////////////////////////////////////
case UxrEasing.EaseInQuart: return start + t * t * t * t * change;
case UxrEasing.EaseOutQuart:
t -= 1.0f;
return -change * (t * t * t * t - 1.0f) + start;
case UxrEasing.EaseInOutQuart:
{
t *= 2.0f;
if (t < 1.0f)
{
return change / 2.0f * t * t * t * t + start;
}
t -= 2.0f;
return -change / 2.0f * (t * t * t * t - 2.0f) + start;
}
case UxrEasing.EaseOutInQuart when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutQuart);
case UxrEasing.EaseOutInQuart: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInQuart);
///////////////////////////////////////// QUINT /////////////////////////////////////////////////////
case UxrEasing.EaseInQuint: return start + t * t * t * t * t * change;
case UxrEasing.EaseOutQuint:
t -= 1.0f;
return change * (t * t * t * t * t + 1.0f) + start;
case UxrEasing.EaseInOutQuint:
{
t *= 2.0f;
if (t < 1.0f)
{
return change / 2.0f * t * t * t * t * t + start;
}
t -= 2.0f;
return change / 2.0f * (t * t * t * t * t + 2.0f) + start;
}
case UxrEasing.EaseOutInQuint when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutQuint);
case UxrEasing.EaseOutInQuint: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInQuint);
///////////////////////////////////////// EXPO //////////////////////////////////////////////////////
case UxrEasing.EaseInExpo: return change * Mathf.Pow(2.0f, 10.0f * (t - 1.0f)) + start;
case UxrEasing.EaseOutExpo: return change * (-Mathf.Pow(2.0f, -10.0f * t) + 1.0f) + start;
case UxrEasing.EaseInOutExpo:
{
t *= 2.0f;
if (t < 1.0f)
{
return change / 2.0f * Mathf.Pow(2.0f, 10.0f * (t - 1.0f)) + start - change * 0.0005f;
}
t -= 1.0f;
return change / 2.0f * 1.0005f * (-Mathf.Pow(2.0f, -10.0f * t) + 2.0f) + start;
}
case UxrEasing.EaseOutInExpo when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutExpo);
case UxrEasing.EaseOutInExpo: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInExpo);
///////////////////////////////////////// CIRC //////////////////////////////////////////////////////
case UxrEasing.EaseInCirc: return -change * (Mathf.Sqrt(1.0f - t * t) - 1.0f) + start;
case UxrEasing.EaseOutCirc:
t -= 1.0f;
return change * Mathf.Sqrt(1.0f - t * t) + start;
case UxrEasing.EaseInOutCirc:
{
t *= 2.0f;
if (t < 1.0f)
{
return -change / 2.0f * (Mathf.Sqrt(1.0f - t * t) - 1.0f) + start;
}
t -= 2.0f;
return change / 2.0f * (Mathf.Sqrt(1.0f - t * t) + 1.0f) + start;
}
case UxrEasing.EaseOutInCirc when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutCirc);
case UxrEasing.EaseOutInCirc: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInCirc);
///////////////////////////////////////// BACK //////////////////////////////////////////////////////
case UxrEasing.EaseInBack:
{
float s = 1.70158f;
return change * (t * t * ((s + 1.0f) * t - s)) + start;
}
case UxrEasing.EaseOutBack:
{
float s = 1.70158f;
t = t - 1.0f;
return change * (t * t * ((s + 1.0f) * t + s) + 1.0f) + start;
}
case UxrEasing.EaseInOutBack:
{
float s = 1.70158f;
t *= 2.0f;
if (t < 1.0f)
{
s *= 1.525f;
return change / 2.0f * (t * t * ((s + 1.0f) * t - s)) + start;
}
s *= 1.525f;
t -= 2.0f;
return change / 2.0f * (t * t * ((s + 1.0f) * t + s) + 2.0f) + start;
}
case UxrEasing.EaseOutInBack when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutBack);
case UxrEasing.EaseOutInBack: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInBack);
///////////////////////////////////////// ELASTIC ///////////////////////////////////////////////////
case UxrEasing.EaseInElastic:
{
float p = 0.3f;
Vector4 a = change;
float s = p / 4.0f;
t -= 1.0f;
return -(Mathf.Pow(2.0f, 10.0f * t) * Mathf.Sin((t - s) * (2.0f * Mathf.PI) / p) * a) + start;
}
case UxrEasing.EaseOutElastic:
{
float p = 0.3f;
Vector4 a = change;
float s = p / 4.0f;
return Mathf.Pow(2.0f, -10.0f * t) * Mathf.Sin((t - s) * (2.0f * Mathf.PI) / p) * a + change + start;
}
case UxrEasing.EaseInOutElastic:
{
t *= 2.0f;
float p = 0.3f * 1.5f;
Vector4 a = change;
float s = p / 4.0f;
if (t < 1.0f)
{
t -= 1.0f;
return -0.5f * (Mathf.Pow(2.0f, 10.0f * t) * Mathf.Sin((t - s) * (2.0f * Mathf.PI) / p) * a) + start;
}
t -= 1.0f;
return 0.5f * Mathf.Pow(2.0f, -10.0f * t) * Mathf.Sin((t - s) * (2.0f * Mathf.PI) / p) * a + change + start;
}
case UxrEasing.EaseOutInElastic when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutElastic);
case UxrEasing.EaseOutInElastic: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInElastic);
///////////////////////////////////////// BOUNCE ////////////////////////////////////////////////////
case UxrEasing.EaseInBounce: return change - InterpolateVector4(Vector4.zero, change, 1.0f - t, UxrEasing.EaseOutBounce) + start;
case UxrEasing.EaseOutBounce when t < 1.0f / 2.75f: return change * (7.5625f * t * t) + start;
case UxrEasing.EaseOutBounce when t < 2.0f / 2.75f:
t -= 1.5f / 2.75f;
return change * (7.5625f * t * t + 0.75f) + start;
case UxrEasing.EaseOutBounce when t < 2.5 / 2.75:
t -= 2.25f / 2.75f;
return change * (7.5625f * t * t + 0.9375f) + start;
case UxrEasing.EaseOutBounce:
t -= 2.625f / 2.75f;
return change * (7.5625f * t * t + 0.984375f) + start;
case UxrEasing.EaseInOutBounce when t < 0.5f: return InterpolateVector4(Vector4.zero, change, t * 2.0f, UxrEasing.EaseInBounce) * 0.5f + start;
case UxrEasing.EaseInOutBounce: return InterpolateVector4(Vector4.zero, change, (t - 0.5f) * 2.0f, UxrEasing.EaseOutBounce) * 0.5f + change * 0.5f + start;
case UxrEasing.EaseOutInBounce when t < 0.5f: return InterpolateVector4(start, start + change * 0.5f, t * 2.0f, UxrEasing.EaseOutBounce);
case UxrEasing.EaseOutInBounce: return InterpolateVector4(start + change * 0.5f, end, (t - 0.5f) * 2.0f, UxrEasing.EaseInBounce);
default:
#if UNITY_EDITOR
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Errors)
{
Debug.LogError($"{UxrConstants.AnimationModule}: {nameof(UxrInterpolator)} Unknown easing mode");
}
#endif
return Vector4.zero;
}
}
#endregion
#region Private Types & Data
/// <summary>
/// Constant used in SmoothDamp methods that controls the speed at which the interpolation will be performed.
/// </summary>
private const float MaxSmoothSpeed = 20.0f;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 89bc39af06e56fd448af1d651e3406ff
timeCreated: 1519632737
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrLoopMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Supported interpolation loop modes.
/// </summary>
public enum UxrLoopMode
{
/// <summary>
/// No looping.
/// </summary>
None,
/// <summary>
/// Will start from the beginning again when reaching the end.
/// </summary>
Loop,
/// <summary>
/// Will go back and forth from beginning to end.
/// </summary>
PingPong
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 69c8ab420cd7412f874ff3ad7346f6f0
timeCreated: 1642849559

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrQuaternionInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Quaternion" />.
/// </summary>
public class UxrQuaternionInterpolator : UxrVarInterpolator<Quaternion>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrQuaternionInterpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Quaternion>
/// <inheritdoc />
protected override Quaternion GetInterpolatedValue(Quaternion a, Quaternion b, float t)
{
return Quaternion.Slerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrQuaternionInterpolator DefaultInterpolator = new UxrQuaternionInterpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d4d58a25345c54847a2aa0b8ee01846b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,102 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVarInterpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Base for interpolator classes that interpolate with optional smooth damping.
/// Child classes provide interpolation for different variable types.
/// </summary>
public abstract class UxrVarInterpolator
{
#region Public Types & Data
/// <summary>
/// Gets whether to always return the first variable when interpolating.
/// </summary>
public bool UseStep { get; set; }
/// <summary>
/// Gets or sets the smoothing value [0.0, 1.0]. 0 means no smoothing.
/// </summary>
public float SmoothDamp
{
get => _smoothDamp;
set => _smoothDamp = Mathf.Clamp01(value);
}
#endregion
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp value [0.0, 1.0]</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
protected UxrVarInterpolator(float smoothDamp = 0.0f, bool useStep = false)
{
SmoothDamp = smoothDamp;
UseStep = useStep;
}
#endregion
#region Public Methods
/// <summary>
/// Interpolates between 2 values.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
/// <returns>Interpolated value</returns>
/// <remarks>
/// The interpolated value will be affected by smoothing if the object was initialized with a smoothDamp value
/// greater than 0.
/// </remarks>
public abstract object Interpolate(object a, object b, float t);
/// <summary>
/// Resets the "memory" of the smooth damp effect, so that the interpolation will restart from the next time
/// <see cref="Interpolate" /> is called.
/// </summary>
public void RestartSmoothDamp()
{
RequiresSmoothDampRestart = true;
}
#endregion
#region Protected Methods
/// <summary>
/// Clears the smooth damp restart variable.
/// </summary>
protected void ClearSmoothDampRestart()
{
RequiresSmoothDampRestart = false;
}
#endregion
#region Protected Types & Data
/// <summary>
/// Gets whether the smooth damp needs to be restarted the next time <see cref="Interpolate" /> is called.
/// </summary>
protected bool RequiresSmoothDampRestart { get; private set; }
#endregion
#region Private Types & Data
private float _smoothDamp;
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5837862ce9de7a4b83241489dea3040
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,103 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVarInterpolator_1.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Generic base for interpolator classes.
/// </summary>
/// <typeparam name="T">The type the class will interpolate</typeparam>
public abstract class UxrVarInterpolator<T> : UxrVarInterpolator
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp value [0.0, 1.0]</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
protected UxrVarInterpolator(float smoothDamp, bool useStep) : base(smoothDamp, useStep)
{
}
#endregion
#region Public Overrides UxrVarInterpolator
/// <inheritdoc />
public override object Interpolate(object a, object b, float t)
{
if (UseStep)
{
return a;
}
if (a is not T ta)
{
return default(T);
}
if (b is not T tb)
{
return default(T);
}
return Interpolate(ta, tb, t);
}
#endregion
#region Public Methods
/// <summary>
/// Interpolates between 2 values.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
/// <returns>Interpolated value</returns>
/// <remarks>
/// The interpolated value will be affected by smoothing if the object was initialized with a smoothDamp value
/// greater than 0
/// </remarks>
public T Interpolate(T a, T b, float t)
{
T result = GetInterpolatedValue(a, b, t);
if (!RequiresSmoothDampRestart && SmoothDamp > 0.0f)
{
result = GetInterpolatedValue(_lastValue, result, UxrInterpolator.GetSmoothInterpolationValue(SmoothDamp, Time.deltaTime));
}
ClearSmoothDampRestart();
_lastValue = result;
return result;
}
#endregion
#region Protected Methods
/// <summary>
/// Interpolates between 2 values. To be interpolated in child classes.
/// </summary>
/// <param name="a">Start value</param>
/// <param name="b">End value</param>
/// <param name="t">Interpolation factor [0.0, 1.0]</param>
/// <returns>Interpolated value</returns>
protected abstract T GetInterpolatedValue(T a, T b, float t);
#endregion
#region Private Types & Data
private T _lastValue;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 771aad43346641e6800a127a336ab6f2
timeCreated: 1709572336

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVector2Interpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Vector2" />.
/// </summary>
public class UxrVector2Interpolator : UxrVarInterpolator<Vector2>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrVector2Interpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Vector2>
/// <inheritdoc />
protected override Vector2 GetInterpolatedValue(Vector2 a, Vector2 b, float t)
{
return Vector2.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrVector2Interpolator DefaultInterpolator = new UxrVector2Interpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eff4611b1ef6b94499a241654ef279db
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVector3Interpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Vector3" />.
/// </summary>
public class UxrVector3Interpolator : UxrVarInterpolator<Vector3>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrVector3Interpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Vector3>
/// <inheritdoc />
protected override Vector3 GetInterpolatedValue(Vector3 a, Vector3 b, float t)
{
return Vector3.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrVector3Interpolator DefaultInterpolator = new UxrVector3Interpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c94c2ed8d15094e429c52fc69cac5256
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrVector4Interpolator.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UnityEngine;
namespace UltimateXR.Animation.Interpolation
{
/// <summary>
/// Interpolator for <see cref="Vector4" />.
/// </summary>
public class UxrVector4Interpolator : UxrVarInterpolator<Vector4>
{
#region Constructors & Finalizer
/// <summary>
/// Constructor.
/// </summary>
/// <param name="smoothDamp">Smooth damp for interpolation [0.0, 1.0] where 0.0 means no smoothing</param>
/// <param name="useStep">Whether to use step interpolation, where the interpolation will always return the start value</param>
public UxrVector4Interpolator(float smoothDamp = 0.0f, bool useStep = false) : base(smoothDamp, useStep)
{
}
#endregion
#region Protected Overrides UxrVarInterpolator<Vector4>
/// <inheritdoc />
protected override Vector4 GetInterpolatedValue(Vector4 a, Vector4 b, float t)
{
return Vector4.Lerp(a, b, t);
}
#endregion
#region Public Types & Data
/// <summary>
/// Default interpolator with smoothing.
/// </summary>
public static readonly UxrVector4Interpolator DefaultInterpolator = new UxrVector4Interpolator(0.0f, false);
#endregion
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 31352d3b132104a4b8f58490f88c8253
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 731650480b7d431eb51cb9c5a685a927
timeCreated: 1643799599

View File

@@ -0,0 +1,265 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAnimatedLightIntensity.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.Lights
{
/// <summary>
/// Component that allows to animate a light's intensity.
/// </summary>
public class UxrAnimatedLightIntensity : UxrAnimatedComponent<UxrAnimatedLightIntensity>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private Light _light;
#endregion
#region Public Types & Data
/// <inheritdoc cref="UxrAnimatedComponent{T}.Speed" />
public new float Speed
{
get => base.Speed.x;
set => base.Speed = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.InterpolatedValueStart" />
public new float InterpolatedValueStart
{
get => base.InterpolatedValueStart.x;
set => base.InterpolatedValueStart = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.InterpolatedValueEnd" />
public new float InterpolatedValueEnd
{
get => base.InterpolatedValueEnd.x;
set => base.InterpolatedValueEnd = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.InterpolatedValueWhenDisabled" />
public new float InterpolatedValueWhenDisabled
{
get => base.InterpolatedValueWhenDisabled.x;
set => base.InterpolatedValueWhenDisabled = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseValueMin" />
public new float NoiseValueMin
{
get => base.NoiseValueMin.x;
set => base.NoiseValueMin = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseValueMax" />
public new float NoiseValueMax
{
get => base.NoiseValueMax.x;
set => base.NoiseValueMax = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseValueStart" />
public new float NoiseValueStart
{
get => base.NoiseValueStart.x;
set => base.NoiseValueStart = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseValueEnd" />
public new float NoiseValueEnd
{
get => base.NoiseValueEnd.x;
set => base.NoiseValueEnd = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseFrequency" />
public new float NoiseFrequency
{
get => base.NoiseFrequency.x;
set => base.NoiseFrequency = ToVector4(value);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.NoiseOffset" />
public new float NoiseOffset
{
get => base.NoiseOffset.x;
set => base.NoiseOffset = ToVector4(value);
}
#endregion
#region Public Methods
/// <summary>
/// Starts an animation at a constant speed
/// </summary>
/// <param name="light">The light component to apply the animation to</param>
/// <param name="speed">
/// The animation speed. For int/float values use .x, for Vector2 use x and y. For
/// Vector3 use x, y, z. etc.
/// </param>
/// <param name="useUnscaledTime">
/// If it is true then Time.unscaledTime will be used
/// to count seconds. By default it is false meaning Time.time will be used instead.
/// Time.time is affected by Time.timeScale which in many cases is used for application pauses
/// or bullet-time effects, while Time.unscaledTime is not.
/// </param>
/// <returns>Animation component</returns>
public static UxrAnimatedLightIntensity Animate(Light light, float speed, bool useUnscaledTime = false)
{
UxrAnimatedLightIntensity component = light.gameObject.GetOrAddComponent<UxrAnimatedLightIntensity>();
if (component)
{
component._light = light;
component.AnimationMode = UxrAnimationMode.Speed;
component.Speed = speed;
component.UseUnscaledTime = useUnscaledTime;
component.StartTimer();
}
return component;
}
/// <summary>
/// Starts an animation using an interpolation curve
/// </summary>
/// <param name="light">The light component to apply the animation to</param>
/// <param name="startValue">The start intensity value</param>
/// <param name="endValue">The end intensity value</param>
/// <param name="settings">The interpolation settings with the curve parameters</param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>Animation component</returns>
public static UxrAnimatedLightIntensity AnimateInterpolation(Light light, float startValue, float endValue, UxrInterpolationSettings settings, Action finishedCallback = null)
{
UxrAnimatedLightIntensity component = light.gameObject.GetOrAddComponent<UxrAnimatedLightIntensity>();
if (component)
{
component._light = light;
component.AnimationMode = UxrAnimationMode.Interpolate;
component.InterpolatedValueStart = startValue;
component.InterpolatedValueEnd = endValue;
component.InterpolationSettings = settings;
component._finishedCallback = finishedCallback;
component.StartTimer();
}
return component;
}
/// <summary>
/// Starts an animation using noise.
/// </summary>
/// <param name="light">The light component to apply the animation to</param>
/// <param name="noiseTimeStart">The time in seconds the noise will start (Time.time or Time.unscaledTime value)</param>
/// <param name="noiseTimeDuration">The duration in seconds of the noise animation</param>
/// <param name="noiseValueStart">The start intensity value</param>
/// <param name="noiseValueEnd">The end intensity value</param>
/// <param name="noiseValueMin">The minimum intensity value for the noise</param>
/// <param name="noiseValueMax">The maximum intensity value for the noise</param>
/// <param name="noiseValueFrequency">The noise frequency</param>
/// <param name="useUnscaledTime">If true it will use Time.unscaledTime, if false it will use Time.time</param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>Animation component</returns>
public static UxrAnimatedLightIntensity AnimateNoise(Light light,
float noiseTimeStart,
float noiseTimeDuration,
float noiseValueStart,
float noiseValueEnd,
float noiseValueMin,
float noiseValueMax,
float noiseValueFrequency,
bool useUnscaledTime = false,
Action finishedCallback = null)
{
UxrAnimatedLightIntensity component = light.gameObject.GetOrAddComponent<UxrAnimatedLightIntensity>();
if (component)
{
component._light = light;
component.AnimationMode = UxrAnimationMode.Noise;
component.NoiseTimeStart = noiseTimeStart;
component.NoiseDurationSeconds = noiseTimeDuration;
component.NoiseValueStart = noiseValueStart;
component.NoiseValueEnd = noiseValueEnd;
component.NoiseValueMin = noiseValueMin;
component.NoiseValueMax = noiseValueMax;
component.NoiseFrequency = noiseValueFrequency;
component.UseUnscaledTime = useUnscaledTime;
component._finishedCallback = finishedCallback;
component.StartTimer();
}
return component;
}
#endregion
#region Unity
/// <summary>
/// Stores the initial light intensity
/// </summary>
protected override void Awake()
{
base.Awake();
_initialIntensity = _light != null ? _light.intensity : 0.0f;
}
#endregion
#region Event Trigger Methods
/// <inheritdoc cref="UxrAnimatedComponent{T}.OnFinished" />
protected override void OnFinished(UxrAnimatedLightIntensity anim)
{
base.OnFinished(anim);
_finishedCallback?.Invoke();
}
#endregion
#region Protected Overrides UxrAnimatedComponent<UxrAnimatedLightIntensity>
/// <inheritdoc cref="UxrAnimatedComponent{T}.RestoreOriginalValue" />
protected override void RestoreOriginalValue()
{
if (_light != null)
{
_light.intensity = _initialIntensity;
}
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.GetParameterValue" />
protected override Vector4 GetParameterValue()
{
return ToVector4(_light != null ? _light.intensity : 0.0f);
}
/// <inheritdoc cref="UxrAnimatedComponent{T}.SetParameterValue" />
protected override void SetParameterValue(Vector4 value)
{
if (_light != null)
{
_light.intensity = value.x;
}
}
#endregion
#region Private Types & Data
private float _initialIntensity;
private Action _finishedCallback;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: a9bfd497bfea50143a9aaebc8203630b
timeCreated: 1516185390
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 315df74e11e8d5440821cbd873a70ec2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,702 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAnimatedMaterial.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Animation.Interpolation;
using UltimateXR.Core;
using UltimateXR.Core.Settings;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// Component that allows to animate material properties.
/// </summary>
public class UxrAnimatedMaterial : UxrAnimatedComponent<UxrAnimatedMaterial>
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _animateSelf = true;
[SerializeField] private GameObject _targetGameObject;
[SerializeField] private int _materialSlot;
[SerializeField] private UxrMaterialMode _materialMode = UxrMaterialMode.InstanceOnly;
[SerializeField] private bool _restoreWhenFinished = true;
[SerializeField] private UxrMaterialParameterType _parameterType = UxrMaterialParameterType.Vector4;
[SerializeField] private string _parameterName = "";
#endregion
#region Public Types & Data
/// <summary>
/// The default blink frequency
/// </summary>
public const float DefaultBlinkFrequency = 3.0f;
/// <summary>
/// Gets or sets if the original material value should be restored when finished.
/// </summary>
public bool RestoreWhenFinished
{
get => _restoreWhenFinished;
set => _restoreWhenFinished = value;
}
/// <summary>
/// Gets or sets whether the animation will be applied to the GameObject where the component is, or an external one.
/// </summary>
public bool AnimateSelf
{
get => _animateSelf;
set => _animateSelf = value;
}
/// <summary>
/// Gets or sets the target GameObject when <see cref="AnimateSelf" /> is true.
/// </summary>
public GameObject TargetGameObject
{
get => _targetGameObject;
set => _targetGameObject = value;
}
/// <summary>
/// Gets or sets the material slot to apply the material animation to.
/// </summary>
public int MaterialSlot
{
get => _materialSlot;
set => _materialSlot = value;
}
/// <summary>
/// Gets or sets the material mode, whether to use the instanced material or the shared material.
/// </summary>
public UxrMaterialMode MaterialMode
{
get => _materialMode;
set => _materialMode = value;
}
/// <summary>
/// Gets or sets the material's parameter type.
/// </summary>
public UxrMaterialParameterType ParameterType
{
get => _parameterType;
set => _parameterType = value;
}
/// <summary>
/// Gets or sets the material's parameter name.
/// </summary>
public string ParameterName
{
get => _parameterName;
set => _parameterName = value;
}
#endregion
#region Public Methods
/// <summary>
/// Starts an animation at a constant speed
/// </summary>
/// <param name="gameObject">The GameObject with the material to apply the animation to</param>
/// <param name="materialSlot">The renderer material slot where the material is</param>
/// <param name="materialMode">
/// The material mode. Use instance to animate the material of a single object,
/// use shared to also affect all other objects that share the same material
/// </param>
/// <param name="parameterType">Selects the type of the parameter to animate</param>
/// <param name="parameterName">
/// Selects the name of the parameter to animate.
/// This name is the name in the shader, not in the inspector!
/// </param>
/// <param name="speed">
/// The animation speed. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="durationSeconds">
/// Duration in seconds of the animation. Use a negative value to keep updating until stopped
/// manually.
/// </param>
/// <param name="useUnscaledTime">
/// If it is true then Time.unscaledTime will be used
/// to count seconds. By default it is false meaning Time.time will be used instead.
/// Time.time is affected by Time.timeScale which in many cases is used for application pauses
/// or bullet-time effects, while Time.unscaledTime is not.
/// </param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>The animation component</returns>
public static UxrAnimatedMaterial Animate(GameObject gameObject,
int materialSlot,
UxrMaterialMode materialMode,
UxrMaterialParameterType parameterType,
string parameterName,
Vector4 speed,
float durationSeconds = -1.0f,
bool useUnscaledTime = false,
Action finishedCallback = null)
{
UxrAnimatedMaterial component = gameObject.GetOrAddComponent<UxrAnimatedMaterial>();
if (component)
{
component._restoreWhenFinished = false;
component._animateSelf = true;
component._materialSlot = materialSlot;
component._materialMode = materialMode;
component._parameterType = parameterType;
component._parameterName = parameterName;
component.AnimationMode = UxrAnimationMode.Speed;
component.Speed = speed;
component.UseUnscaledTime = useUnscaledTime;
component.SpeedDurationSeconds = durationSeconds;
component._finishedCallback = finishedCallback;
component.Initialize();
component.StartTimer();
}
return component;
}
/// <summary>
/// Starts a material parameter animation using an interpolation curve
/// </summary>
/// <param name="gameObject">The GameObject with the material to apply the animation to</param>
/// <param name="materialSlot">The renderer material slot where the material is</param>
/// <param name="materialMode">
/// The material mode. Use instance to animate the material of a single object,
/// use shared to also affect all other objects that share the same material
/// </param>
/// <param name="parameterType">Selects the type of the parameter to animate</param>
/// <param name="parameterName">
/// Selects the name of the parameter to animate.
/// This name is the name in the shader, not in the inspector!
/// </param>
/// <param name="startValue">
/// The start value. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="endValue">
/// The end value. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="settings">The interpolation settings with the curve parameters</param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>The animation component</returns>
public static UxrAnimatedMaterial AnimateInterpolation(GameObject gameObject,
int materialSlot,
UxrMaterialMode materialMode,
UxrMaterialParameterType parameterType,
string parameterName,
Vector4 startValue,
Vector4 endValue,
UxrInterpolationSettings settings,
Action finishedCallback = null)
{
UxrAnimatedMaterial component = gameObject.GetOrAddComponent<UxrAnimatedMaterial>();
if (component)
{
component._restoreWhenFinished = false;
component._animateSelf = true;
component._materialSlot = materialSlot;
component._materialMode = materialMode;
component._parameterType = parameterType;
component._parameterName = parameterName;
component.AnimationMode = UxrAnimationMode.Interpolate;
component.InterpolatedValueStart = startValue;
component.InterpolatedValueEnd = endValue;
component.InterpolationSettings = settings;
component._finishedCallback = finishedCallback;
component.Initialize();
component.StartTimer();
component.InterpolatedValueWhenDisabled = component._valueBeforeAnimation;
}
return component;
}
/// <summary>
/// Starts a material parameter animation using noise
/// </summary>
/// <param name="gameObject">The GameObject with the material to apply the animation to</param>
/// <param name="materialSlot">The renderer material slot where the material is</param>
/// <param name="materialMode">
/// The material mode. Use instance to animate the material of a single object,
/// use shared to also affect all other objects that share the same material
/// </param>
/// <param name="parameterType">Selects the type of the parameter to animate</param>
/// <param name="parameterName">
/// Selects the name of the parameter to animate.
/// This name is the name in the shader, not in the inspector!
/// </param>
/// <param name="noiseTimeStart">The time in seconds the noise will start (Time.time or Time.unscaledTime value)</param>
/// <param name="noiseTimeDuration">The duration in seconds of the noise animation</param>
/// <param name="noiseValueStart">
/// The start value. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="noiseValueEnd">
/// The end value. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="noiseValueMin">
/// The minimum intensity value for the noise. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="noiseValueMax">
/// The maximum intensity value for the noise. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="noiseValueFrequency">
/// The noise frequency. For int/float values use .x, for Vector2 use x and y.
/// For Vector3 use x, y, z. etc.
/// </param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <param name="useUnscaledTime">If true it will use Time.unscaledTime, if false it will use Time.time</param>
public static void AnimateNoise(GameObject gameObject,
int materialSlot,
UxrMaterialMode materialMode,
UxrMaterialParameterType parameterType,
string parameterName,
float noiseTimeStart,
float noiseTimeDuration,
Vector4 noiseValueStart,
Vector4 noiseValueEnd,
Vector4 noiseValueMin,
Vector4 noiseValueMax,
Vector4 noiseValueFrequency,
bool useUnscaledTime = false,
Action finishedCallback = null)
{
UxrAnimatedMaterial component = gameObject.GetOrAddComponent<UxrAnimatedMaterial>();
if (component)
{
component._restoreWhenFinished = false;
component._animateSelf = true;
component._materialSlot = materialSlot;
component._materialMode = materialMode;
component._parameterType = parameterType;
component._parameterName = parameterName;
component.AnimationMode = UxrAnimationMode.Noise;
component.NoiseTimeStart = noiseTimeStart;
component.NoiseDurationSeconds = noiseTimeDuration;
component.NoiseValueStart = noiseValueStart;
component.NoiseValueEnd = noiseValueEnd;
component.NoiseValueMin = noiseValueMin;
component.NoiseValueMax = noiseValueMax;
component.NoiseFrequency = noiseValueFrequency;
component.UseUnscaledTime = useUnscaledTime;
component._finishedCallback = finishedCallback;
component.Initialize();
component.StartTimer();
component.InterpolatedValueWhenDisabled = component._valueBeforeAnimation;
}
}
/// <summary>
/// Starts animating a GameObject's material making one if its float parameters blink.
/// </summary>
/// <param name="gameObject">GameObject whose material to animate</param>
/// <param name="varNameFloat">The float var name</param>
/// <param name="valueMin">The minimum float value in the blink</param>
/// <param name="valueMax">The maximum float value in the blink</param>
/// <param name="blinkFrequency">The blinking frequency</param>
/// <param name="durationSeconds">
/// The duration in seconds. Use a negative value to keep keep blinking until stopping
/// manually.
/// </param>
/// <param name="materialMode">The material mode</param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>Material animation component</returns>
public static UxrAnimatedMaterial AnimateFloatBlink(GameObject gameObject,
string varNameFloat,
float valueMin = 0.0f,
float valueMax = 1.0f,
float blinkFrequency = DefaultBlinkFrequency,
float durationSeconds = -1.0f,
UxrMaterialMode materialMode = UxrMaterialMode.InstanceOnly,
Action finishedCallback = null)
{
return AnimateInterpolation(gameObject,
0,
materialMode,
UxrMaterialParameterType.Float,
varNameFloat,
Vector4.one * valueMin,
Vector4.one * valueMax,
new UxrInterpolationSettings(1.0f / blinkFrequency * 0.5f, 0.0f, UxrEasing.EaseInOutSine, UxrLoopMode.PingPong, durationSeconds),
finishedCallback);
}
/// <summary>
/// Starts animating a GameObject's material making one if its color parameters blink.
/// </summary>
/// <param name="gameObject">GameObject whose material to animate</param>
/// <param name="varNameColor">The float var name</param>
/// <param name="colorOff">The minimum color value in the blink</param>
/// <param name="colorOn">The maximum color value in the blink</param>
/// <param name="blinkFrequency">The blinking frequency</param>
/// <param name="durationSeconds">
/// The duration in seconds. Use a negative value to keep keep blinking until stopping
/// manually.
/// </param>
/// <param name="materialMode">The material mode</param>
/// <param name="finishedCallback">Optional callback when the animation finished</param>
/// <returns>Material animation component</returns>
public static UxrAnimatedMaterial AnimateBlinkColor(GameObject gameObject,
string varNameColor,
Color colorOff,
Color colorOn,
float blinkFrequency = DefaultBlinkFrequency,
float durationSeconds = -1.0f,
UxrMaterialMode materialMode = UxrMaterialMode.InstanceOnly,
Action finishedCallback = null)
{
return AnimateInterpolation(gameObject,
0,
materialMode,
UxrMaterialParameterType.Color,
varNameColor,
colorOff,
colorOn,
new UxrInterpolationSettings(1.0f / blinkFrequency * 0.5f, 0.0f, UxrEasing.EaseInOutSine, UxrLoopMode.PingPong, durationSeconds),
finishedCallback);
}
/// <summary>
/// Restores the original (shared) material. This may have some performance advantages.
/// </summary>
public void RestoreOriginalSharedMaterial()
{
if (_renderer)
{
if (_materialSlot == 0)
{
_renderer.sharedMaterial = _originalMaterial;
}
else
{
_renderer.sharedMaterials = _originalMaterials;
}
}
}
#endregion
#region Unity
/// <summary>
/// Initializes internal variables
/// </summary>
protected override void Awake()
{
base.Awake();
Initialize();
}
#endregion
#region Event Trigger Methods
/// <inheritdoc cref="UxrAnimatedComponent{T}.OnFinished" />
protected override void OnFinished(UxrAnimatedMaterial anim)
{
base.OnFinished(anim);
if (RestoreWhenFinished && MaterialMode == UxrMaterialMode.InstanceOnly)
{
RestoreOriginalSharedMaterial();
}
_finishedCallback?.Invoke();
}
#endregion
#region Protected Overrides UxrAnimatedComponent<UxrAnimatedMaterial>
/// <summary>
/// Restores the original value before the animation started.
/// </summary>
protected override void RestoreOriginalValue()
{
if (_valueBeforeAnimationInitialized)
{
SetParameterValue(_valueBeforeAnimation);
}
}
/// <summary>
/// Gets the parameter value from the material
/// </summary>
/// <returns>
/// Vector4 containing the value. This value may not use all components depending on the parameter type.
/// </returns>
protected override Vector4 GetParameterValue()
{
if (_renderer && _materialSlot < _renderer.sharedMaterials.Length)
{
switch (_parameterType)
{
case UxrMaterialParameterType.Int:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
return _materialSlot == 0 ? new Vector4(_renderer.material.GetInt(_parameterName), 0, 0, 0) : new Vector4(_renderer.materials[_materialSlot].GetInt(_parameterName), 0, 0, 0);
}
else
{
return _materialSlot == 0 ? new Vector4(_renderer.sharedMaterial.GetInt(_parameterName), 0, 0, 0) : new Vector4(_renderer.sharedMaterials[_materialSlot].GetInt(_parameterName), 0, 0, 0);
}
case UxrMaterialParameterType.Float:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
return _materialSlot == 0 ? new Vector4(_renderer.material.GetFloat(_parameterName), 0, 0, 0) : new Vector4(_renderer.materials[_materialSlot].GetFloat(_parameterName), 0, 0, 0);
}
else
{
return _materialSlot == 0 ? new Vector4(_renderer.sharedMaterial.GetFloat(_parameterName), 0, 0, 0) : new Vector4(_renderer.sharedMaterials[_materialSlot].GetFloat(_parameterName), 0, 0, 0);
}
case UxrMaterialParameterType.Vector2:
case UxrMaterialParameterType.Vector3:
case UxrMaterialParameterType.Vector4:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
return _materialSlot == 0 ? _renderer.material.GetVector(_parameterName) : _renderer.materials[_materialSlot].GetVector(_parameterName);
}
else
{
return _materialSlot == 0 ? _renderer.sharedMaterial.GetVector(_parameterName) : _renderer.sharedMaterials[_materialSlot].GetVector(_parameterName);
}
case UxrMaterialParameterType.Color:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
return _materialSlot == 0 ? _renderer.material.GetColor(_parameterName) : _renderer.materials[_materialSlot].GetColor(_parameterName);
}
else
{
return _materialSlot == 0 ? _renderer.sharedMaterial.GetColor(_parameterName) : _renderer.sharedMaterials[_materialSlot].GetColor(_parameterName);
}
}
}
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.AnimationModule} Material slot {_materialSlot} for {this.GetPathUnderScene()} is not valid");
}
return Vector4.zero;
}
/// <summary>
/// Sets the material parameter value
/// </summary>
/// <param name="value">
/// Vector4 containing the value. This value may not use all components depending on the parameter type
/// </param>
protected override void SetParameterValue(Vector4 value)
{
Material[] materials = null;
if (_renderer && _materialSlot < _renderer.sharedMaterials.Length)
{
switch (_parameterType)
{
case UxrMaterialParameterType.Int:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
if (_materialSlot == 0)
{
_renderer.material.SetInt(_parameterName, Mathf.RoundToInt(value.x));
}
else
{
materials = _renderer.materials;
materials[_materialSlot].SetInt(_parameterName, Mathf.RoundToInt(value.x));
_renderer.materials = materials;
}
}
else
{
if (_materialSlot == 0)
{
_renderer.sharedMaterial.SetInt(_parameterName, Mathf.RoundToInt(value.x));
}
else
{
materials = _renderer.sharedMaterials;
materials[_materialSlot].SetInt(_parameterName, Mathf.RoundToInt(value.x));
_renderer.sharedMaterials = materials;
}
}
return;
case UxrMaterialParameterType.Float:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
if (_materialSlot == 0)
{
_renderer.material.SetFloat(_parameterName, value.x);
}
else
{
materials = _renderer.materials;
materials[_materialSlot].SetFloat(_parameterName, value.x);
_renderer.materials = materials;
}
}
else
{
if (_materialSlot == 0)
{
_renderer.sharedMaterial.SetFloat(_parameterName, value.x);
}
else
{
materials = _renderer.sharedMaterials;
materials[_materialSlot].SetFloat(_parameterName, value.x);
_renderer.sharedMaterials = materials;
}
}
return;
case UxrMaterialParameterType.Vector2:
case UxrMaterialParameterType.Vector3:
case UxrMaterialParameterType.Vector4:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
if (_materialSlot == 0)
{
_renderer.material.SetVector(_parameterName, value);
}
else
{
materials = _renderer.materials;
materials[_materialSlot].SetVector(_parameterName, value);
_renderer.materials = materials;
}
}
else
{
if (_materialSlot == 0)
{
_renderer.sharedMaterial.SetVector(_parameterName, value);
}
else
{
materials = _renderer.sharedMaterials;
materials[_materialSlot].SetVector(_parameterName, value);
_renderer.sharedMaterials = materials;
}
}
return;
case UxrMaterialParameterType.Color:
if (_materialMode == UxrMaterialMode.InstanceOnly)
{
if (_materialSlot == 0)
{
_renderer.material.SetColor(_parameterName, value);
}
else
{
materials = _renderer.materials;
materials[_materialSlot].SetColor(_parameterName, value);
_renderer.materials = materials;
}
}
else
{
if (_materialSlot == 0)
{
_renderer.sharedMaterial.SetColor(_parameterName, value);
}
else
{
materials = _renderer.sharedMaterials;
materials[_materialSlot].SetColor(_parameterName, value);
_renderer.sharedMaterials = materials;
}
}
return;
}
}
if (UxrGlobalSettings.Instance.LogLevelAnimation >= UxrLogLevel.Warnings)
{
Debug.LogWarning($"{UxrConstants.AnimationModule} Material slot " + _materialSlot + " for object " + name + " is not valid");
}
}
#endregion
#region Private Methods
/// <summary>
/// Initializes internal data
/// </summary>
private void Initialize()
{
if (_renderer == null)
{
_renderer = _animateSelf || !_targetGameObject ? GetComponent<Renderer>() : _targetGameObject.GetComponent<Renderer>();
if (_renderer)
{
if (_materialSlot == 0)
{
_originalMaterial = _renderer.sharedMaterial;
}
else
{
_originalMaterials = _renderer.sharedMaterials;
}
}
}
if (_renderer && !string.IsNullOrEmpty(_parameterName) && !_valueBeforeAnimationInitialized)
{
_valueBeforeAnimation = GetParameterValue();
_valueBeforeAnimationInitialized = true;
}
}
#endregion
#region Private Types & Data
private Renderer _renderer;
private Material _originalMaterial;
private Material[] _originalMaterials;
private bool _valueBeforeAnimationInitialized;
private Vector4 _valueBeforeAnimation;
private Action _finishedCallback;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 2bbad2839f9d42d41ba6b2d584190630
timeCreated: 1516185390
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,350 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrAnimatedTextureFlipbook.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System;
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UnityEngine;
using Random = UnityEngine.Random;
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// Component that allows to animate a material's texture that contains multiple animation frames.
/// </summary>
public class UxrAnimatedTextureFlipbook : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _animateSelf = true;
[SerializeField] private GameObject _targetGameObject;
[SerializeField] private string _scaleOffsetVarName = UxrConstants.Shaders.StandardMainTextureScaleOffsetVarName;
[SerializeField] private int _flipBookColumns = 1;
[SerializeField] private int _flipBookRows = 1;
[SerializeField] private int _totalFrames = 1;
[SerializeField] private UxrFlipbookAnimationMode _loopMode = UxrFlipbookAnimationMode.SingleSequence;
[SerializeField] private bool _randomFrameStart;
[SerializeField] private float _fps = 10;
[SerializeField] private UxrFlipbookFinishedAction _whenFinished = UxrFlipbookFinishedAction.DoNothing;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Public Types & Data
/// <summary>
/// Called when the animation finished.
/// </summary>
public event Action Finished;
/// <summary>
/// Gets or sets the target renderer whose material will be animated.
/// </summary>
public Renderer TargetRenderer { get; set; }
/// <summary>
/// Gets or sets the material's shader scale/offset variable name, usually _MainTex_ST.
/// </summary>
public string ScaleOffsetVarName
{
get => _scaleOffsetVarName;
set => _scaleOffsetVarName = value;
}
/// <summary>
/// Gets or sets the number of columns in the texture animation sheet.
/// </summary>
public int FlipBookColumns
{
get => _flipBookColumns;
set => _flipBookColumns = value;
}
/// <summary>
/// Gets or sets the number of rows in the texture animation sheet.
/// </summary>
public int FlipBookRows
{
get => _flipBookRows;
set => _flipBookRows = value;
}
/// <summary>
/// Gets or sets the total number of frames in the texture animation sheet.
/// </summary>
public int TotalFrames
{
get => _totalFrames;
set => _totalFrames = value;
}
/// <summary>
/// Gets or sets the animation loop mode.
/// </summary>
public UxrFlipbookAnimationMode LoopMode
{
get => _loopMode;
set => _loopMode = value;
}
/// <summary>
/// Gets or sets whether to start the animation in a random frame position.
/// </summary>
public bool RandomFrameStart
{
get => _randomFrameStart;
set => _randomFrameStart = value;
}
/// <summary>
/// Gets or sets the frames per second to play the animation.
/// </summary>
public float FPS
{
get => _fps;
set => _fps = value;
}
/// <summary>
/// Gets or sets the action to perform when the animation finished. The only animation that can finish is when
/// <see cref="AnimationPlayMode" /> is <see cref="UxrFlipbookAnimationMode.SingleSequence" />.
/// </summary>
public UxrFlipbookFinishedAction WhenFinished
{
get => _whenFinished;
set => _whenFinished = value;
}
#endregion
#region Unity
/// <summary>
/// Initializes internal variables
/// </summary>
protected override void Awake()
{
base.Awake();
if (TargetRenderer == null)
{
TargetRenderer = _animateSelf || !_targetGameObject ? GetComponent<Renderer>() : _targetGameObject.GetComponent<Renderer>();
}
_hasFinished = false;
_frameStart = 0;
SetFrame(0);
}
/// <summary>
/// Called each time the object is enabled. Reset timer and set the curve state to unfinished.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_hasFinished = false;
_lastFrame = -1;
_lastLinearFrame = -1;
if (_randomFrameStart)
{
_frameStart = Mathf.RoundToInt(Random.value * (_totalFrames - 1));
}
if (TargetRenderer && _whenFinished == UxrFlipbookFinishedAction.DisableRenderer)
{
TargetRenderer.enabled = true;
}
}
/// <summary>
/// Enables the correct flipbook frame and checks if it finished
/// </summary>
private void Update()
{
if (_hasFinished)
{
return;
}
float currentTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
int linearFrame = (int)((currentTime - _startTime) * _fps);
switch (_loopMode)
{
case UxrFlipbookAnimationMode.SingleSequence:
if (linearFrame >= _totalFrames)
{
ExecuteFinishAction();
_hasFinished = true;
}
else
{
SetFrame(_totalFrames > 0 ? (linearFrame + _frameStart) % _totalFrames : 0);
}
break;
case UxrFlipbookAnimationMode.Loop:
SetFrame(_totalFrames > 0 ? (linearFrame + _frameStart) % _totalFrames : 0);
break;
case UxrFlipbookAnimationMode.PingPong:
if (_totalFrames > 1)
{
if (linearFrame < _totalFrames)
{
SetFrame(linearFrame);
}
else
{
bool forward = ((linearFrame - _totalFrames) / (_totalFrames - 1) & 1) == 1;
int correctFrame = (linearFrame - _totalFrames) % (_totalFrames - 1);
SetFrame(forward ? correctFrame + 1 : _totalFrames - correctFrame - 2);
}
}
else if (_lastFrame != 0)
{
SetFrame(0);
}
break;
case UxrFlipbookAnimationMode.RandomFrame:
if (linearFrame != _lastLinearFrame)
{
SetFrame(Random.Range(0, _totalFrames));
}
break;
case UxrFlipbookAnimationMode.RandomFrameNoRepetition:
if (linearFrame != _lastLinearFrame)
{
if (_totalFrames < 2)
{
SetFrame(0);
}
else if (_totalFrames == 2)
{
SetFrame(_lastFrame == 0 ? 1 : 0);
}
else
{
int frame = Random.Range(0, _totalFrames);
while (frame == _lastFrame)
{
frame = Random.Range(0, _totalFrames);
}
SetFrame(frame);
}
}
break;
default: throw new ArgumentOutOfRangeException();
}
_lastLinearFrame = linearFrame;
}
#endregion
#region Private Methods
/// <summary>
/// Sets the current flipbook texture frame
/// </summary>
/// <param name="frame">Flipbook frame</param>
private void SetFrame(int frame)
{
if (TargetRenderer && _lastFrame != frame)
{
Vector4 vecScaleOffset = TargetRenderer.material.GetVector(_scaleOffsetVarName);
if (_flipBookColumns > 0)
{
int column = frame % _flipBookColumns;
vecScaleOffset.x = 1.0f / _flipBookColumns;
vecScaleOffset.z = column * vecScaleOffset.x;
}
if (_flipBookRows > 0 && _flipBookColumns > 0)
{
int row = frame / _flipBookColumns;
vecScaleOffset.y = 1.0f / _flipBookRows;
vecScaleOffset.w = 1.0f - (row + 1) * vecScaleOffset.y;
}
TargetRenderer.material.SetVector(_scaleOffsetVarName, vecScaleOffset);
_lastFrame = frame;
}
}
/// <summary>
/// Executes the action when the animation finished.
/// </summary>
private void ExecuteFinishAction()
{
Finished?.Invoke();
switch (_whenFinished)
{
case UxrFlipbookFinishedAction.DoNothing: break;
case UxrFlipbookFinishedAction.DisableRenderer:
if (TargetRenderer)
{
TargetRenderer.enabled = false;
}
break;
case UxrFlipbookFinishedAction.DisableGameObject:
if (TargetRenderer)
{
TargetRenderer.gameObject.SetActive(false);
}
break;
case UxrFlipbookFinishedAction.DestroyGameObject:
if (TargetRenderer)
{
Destroy(TargetRenderer.gameObject);
}
break;
default: throw new ArgumentOutOfRangeException();
}
}
#endregion
#region Private Types & Data
private int _frameStart;
private float _startTime;
private bool _hasFinished;
private int _lastFrame = -1;
private int _lastLinearFrame = -1;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 13ede170b3667dc4882754072020a994
timeCreated: 1516185390
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFlipbookAnimationMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// The different animation modes available in <see cref="UxrAnimatedTextureFlipbook" />
/// </summary>
public enum UxrFlipbookAnimationMode
{
/// <summary>
/// Frames are played back in a sequence, ending with the last frame.
/// </summary>
SingleSequence,
/// <summary>
/// Frames are played back in a sequence up to the last frame. The sequence starts again from the beginning
/// indefinitely.
/// </summary>
Loop,
/// <summary>
/// Frames are played back in a sequence up to the last frame and then back to the beginning again. This process is
/// repeated indefinitely.
/// </summary>
PingPong,
/// <summary>
/// Random frames are played indefinitely.
/// </summary>
RandomFrame,
/// <summary>
/// Random frames are played indefinitely but there are never two same frames played one after the other.
/// </summary>
RandomFrameNoRepetition,
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 04054975fe5140d6a343f675c1e9d284
timeCreated: 1643216355

View File

@@ -0,0 +1,34 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrFlipbookFinishedAction.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// What should be done when a <see cref="UxrAnimatedTextureFlipbook" /> animation finished. This is only supported
/// with <see cref="UxrFlipbookAnimationMode.SingleSequence" />.
/// </summary>
public enum UxrFlipbookFinishedAction
{
/// <summary>
/// Nothing happens when the animation finished.
/// </summary>
DoNothing,
/// <summary>
/// After showing the last frame, the renderer is disabled.
/// </summary>
DisableRenderer,
/// <summary>
/// After showing the last frame, the GameObject the component is attached to is disabled.
/// </summary>
DisableGameObject,
/// <summary>
/// After showing the last frame, the GameObject the component is attached to is destroyed.
/// </summary>
DestroyGameObject
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eef28cb2025e45128264c3a742b1a9ac
timeCreated: 1643216370

View File

@@ -0,0 +1,24 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMaterialMode.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// The material modes supported by <see cref="UxrAnimatedMaterial" />. It can animate the object's instanced material
/// or all the objects that share the same material.
/// </summary>
public enum UxrMaterialMode
{
/// <summary>
/// Animate this instance of the material only.
/// </summary>
InstanceOnly,
/// <summary>
/// Animate the material, so that all renderers that share the same material are affected too.
/// </summary>
Shared
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9746372080bd49198a73205527919f3e
timeCreated: 1643214925

View File

@@ -0,0 +1,43 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMaterialParameterType.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// Material parameter types that can be animated by <see cref="UxrAnimatedMaterial" />.
/// </summary>
public enum UxrMaterialParameterType
{
/// <summary>
/// Integer value.
/// </summary>
Int,
/// <summary>
/// Single floating point value.
/// </summary>
Float,
/// <summary>
/// Vector2 value representing two floating points.
/// </summary>
Vector2,
/// <summary>
/// Vector3 value representing three floating points.
/// </summary>
Vector3,
/// <summary>
/// Vector4 value representing four floating points.
/// </summary>
Vector4,
/// <summary>
/// Color represented by 4 values RGBA.
/// </summary>
Color
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 18df256b817b45148477817eeaa9dc4d
timeCreated: 1643214917

View File

@@ -0,0 +1,89 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrMaterialRenderQueue.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UnityEngine;
namespace UltimateXR.Animation.Materials
{
/// <summary>
/// Component that changes the RenderQueue of a material. Changes will be applied at runtime.
/// </summary>
public class UxrMaterialRenderQueue : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private bool _instanceOnly;
[SerializeField] private bool _everyFrame = true;
[SerializeField] private int _slot;
[SerializeField] private int _value;
#endregion
#region Unity
/// <summary>
/// Gets the component and applies the RenderQueue value.
/// </summary>
protected override void Start()
{
base.Start();
_renderer = GetComponent<Renderer>();
Apply();
}
/// <summary>
/// Applies the RenderQueue each frame if required.
/// </summary>
private void LateUpdate()
{
if (_everyFrame)
{
Apply();
}
}
#endregion
#region Private Methods
/// <summary>
/// Internal method that applies the RenderQueue value.
/// </summary>
private void Apply()
{
if (_renderer != null)
{
if (_instanceOnly)
{
Material[] materials = _renderer.materials;
if (_slot >= 0 && _slot < materials.Length)
{
materials[_slot].renderQueue = _value;
}
_renderer.materials = materials;
}
else
{
Material[] materials = _renderer.sharedMaterials;
if (_slot >= 0 && _slot < materials.Length)
{
materials[_slot].renderQueue = _value;
}
_renderer.sharedMaterials = materials;
}
}
}
#endregion
#region Private Types & Data
private Renderer _renderer;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: 1f664d8d69876084f9aaa68e8d224537
timeCreated: 1527163303
licenseType: Free
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d63fe01f3012402c98871d81a03bdd96
timeCreated: 1643801495

View File

@@ -0,0 +1,170 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrToggleEmitParticles.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core.Components;
using UltimateXR.Extensions.System.Collections;
using UnityEngine;
namespace UltimateXR.Animation.ParticleSystems
{
/// <summary>
/// Component that allows to toggle particle emission enabled state back and forth at random times.
/// </summary>
public class UxrToggleEmitParticles : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private ParticleSystem _particleSystem;
[SerializeField] private GameObject[] _toggleAdditionalGameObjects;
[SerializeField] private float _enabledDurationMin;
[SerializeField] private float _enabledDurationMax;
[SerializeField] private float _disabledDurationMin;
[SerializeField] private float _disabledDurationMax;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Public Types & Data
/// <summary>
/// The particle system to toggle.
/// </summary>
public ParticleSystem TargetParticleSystem
{
get => _particleSystem;
set => _particleSystem = value;
}
/// <summary>
/// Additional objects whose active state is toggled too.
/// </summary>
public GameObject[] ToggleAdditionalGameObjects
{
get => _toggleAdditionalGameObjects;
set => _toggleAdditionalGameObjects = value;
}
/// <summary>
/// The minimum amount of seconds the emission will be enabled when toggled on.
/// </summary>
public float EnabledSecondsMin
{
get => _enabledDurationMin;
set => _enabledDurationMin = value;
}
/// <summary>
/// The maximum amount of seconds the emission will be enabled when toggled on.
/// </summary>
public float EnabledSecondsMax
{
get => _enabledDurationMax;
set => _enabledDurationMax = value;
}
/// <summary>
/// The minimum amount of seconds the emission will be disabled when toggled off.
/// </summary>
public float DisabledSecondsMin
{
get => _disabledDurationMin;
set => _disabledDurationMin = value;
}
/// <summary>
/// The minimum amount of seconds the emission will be disabled when toggled off.
/// </summary>
public float DisabledSecondsMax
{
get => _disabledDurationMax;
set => _disabledDurationMax = value;
}
/// <summary>
/// Whether to use <see cref="Time.time" /> or <see cref="Time.unscaledTime" /> for timing.
/// </summary>
public bool UseUnscaledTime
{
get => _useUnscaledTime;
set => _useUnscaledTime = value;
}
#endregion
#region Unity
/// <summary>
/// Called each time the component is enabled. Sets up the next toggle time.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
_startTime = _useUnscaledTime ? Time.unscaledTime : Time.time;
_nextToggleTime = GetNextRelativeToggleTime();
}
/// <summary>
/// Called on each update. Checks if it is time to toggle the emission state.
/// </summary>
private void Update()
{
float time = CurrentTime - _startTime;
if (time > _nextToggleTime)
{
// Toggle GameObjects
_toggleAdditionalGameObjects.ForEach(go => go.SetActive(!go.activeSelf));
// Toggle particle emission
if (_particleSystem != null)
{
ParticleSystem.EmissionModule emissionModule = _particleSystem.emission;
emissionModule.enabled = !emissionModule.enabled;
}
// Setup next toggle time
_startTime = CurrentTime;
_nextToggleTime = GetNextRelativeToggleTime();
}
}
#endregion
#region Private Methods
/// <summary>
/// Gets the next time the components will be toggled
/// </summary>
/// <returns>Next toggle time in seconds relative to the current time</returns>
private float GetNextRelativeToggleTime()
{
if (_particleSystem != null && _particleSystem.emission.enabled)
{
return Random.Range(_enabledDurationMin, _enabledDurationMax);
}
if (_particleSystem != null && !_particleSystem.emission.enabled)
{
return Random.Range(_disabledDurationMin, _disabledDurationMax);
}
return 0.0f;
}
#endregion
#region Private Types & Data
private float CurrentTime => _useUnscaledTime ? Time.unscaledTime : Time.time;
private float _startTime;
private float _nextToggleTime;
#endregion
}
}

View File

@@ -0,0 +1,13 @@
fileFormatVersion: 2
guid: d641254e5316ee8448d1aefd9377ca1e
timeCreated: 1522911056
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f84c254c56dd4624087f700babd193f4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,139 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrCatmullRomSpline.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using System.Collections.Generic;
using UnityEngine;
namespace UltimateXR.Animation.Splines
{
/// <summary>
/// Catmull-Rom spline. It is used to interpolate smoothly between a set of points.
/// </summary>
public class UxrCatmullRomSpline : UxrSpline
{
#region Public Overrides UxrSpline
/// <summary>
/// Does the object contain valid data in order to evaluate the curve?
/// </summary>
public override bool HasValidData => _points != null && _points.Count > 1;
/// <summary>
/// Evaluates the curve
/// </summary>
/// <param name="t">Interpolation parameter [0.0f, 1.0f]</param>
/// <param name="position">Interpolated point</param>
/// <returns>Success or failure</returns>
public override bool Evaluate(float t, out Vector3 position)
{
return Evaluate(t, out position, out float _);
}
#endregion
#region Public Methods
/// <summary>
/// Smoothly interpolates, using Catmull-Rom equations, from p1 to p2 using additional p0 and p3 points.
/// </summary>
/// <param name="p0">Point 0</param>
/// <param name="p1">Point 1</param>
/// <param name="p2">Point 2</param>
/// <param name="p3">Point 3</param>
/// <param name="t">Interpolation parameter [0.0f, 1.0f]</param>
/// <returns>Interpolated point</returns>
public static Vector3 Evaluate(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
{
Vector3 ret = new Vector3();
float t2 = t * t;
float t3 = t2 * t;
ret.x = 0.5f * (2.0f * p1.x + (-p0.x + p2.x) * t + (2.0f * p0.x - 5.0f * p1.x + 4 * p2.x - p3.x) * t2 + (-p0.x + 3.0f * p1.x - 3.0f * p2.x + p3.x) * t3);
ret.y = 0.5f * (2.0f * p1.y + (-p0.y + p2.y) * t + (2.0f * p0.y - 5.0f * p1.y + 4 * p2.y - p3.y) * t2 + (-p0.y + 3.0f * p1.y - 3.0f * p2.y + p3.y) * t3);
ret.z = 0.5f * (2.0f * p1.z + (-p0.z + p2.z) * t + (2.0f * p0.z - 5.0f * p1.z + 4 * p2.z - p3.z) * t2 + (-p0.z + 3.0f * p1.z - 3.0f * p2.z + p3.z) * t3);
return ret;
}
/// <summary>
/// Creates a spline. If <see cref="UxrSpline.UsePrecomputedSampleCount" /> > 0 it will also precompute samples in
/// order to evaluate the spline using arc-length parameter.
/// </summary>
/// <param name="inOutMultiplier">
/// Magnitude of spline start and end dummy tangent vectors
/// compared to their respective control points. A value of 1 (default) will create dummies
/// mirroring p1 and p(n-1) vectors. A different value will multiply these vectors by it.
/// It can be used to change the spline start/end curvature.
/// </param>
/// <param name="points">Set of points defining the curve</param>
/// <returns>Success or failure</returns>
public bool Create(float inOutMultiplier = 1.0f, params Vector3[] points)
{
_points = new List<Vector3>(points);
if (points.Length < 2)
{
return false;
}
_dummyStart = points[0] + (points[0] - points[1]) * inOutMultiplier;
_dummyEnd = points[points.Length - 1] + (points[points.Length - 1] - points[points.Length - 2]) * inOutMultiplier;
ComputeArcLengthSamples();
return true;
}
#endregion
#region Private Methods
/// <summary>
/// Interpolates the curve using Catmull-Rom equations.
/// </summary>
/// <param name="t">Interpolation parameter [0.0f, 1.0f]</param>
/// <param name="position">Interpolated position</param>
/// <param name="segmentLength">Length of the segment that this point belongs to</param>
/// <returns>Success or failure</returns>
private bool Evaluate(float t, out Vector3 position, out float segmentLength)
{
position = Vector3.zero;
segmentLength = 0.0f;
t = Mathf.Clamp01(t);
// Compute the index of p1 and build a Catmull segment with 4 points from there
int indexA = Mathf.FloorToInt(t * (_points.Count - 1));
float segmentT = t * (_points.Count - 1) - indexA;
if (indexA >= _points.Count - 1)
{
indexA = _points.Count - 2;
segmentT = 1.0f;
}
Vector3 p0 = indexA == 0 ? _dummyStart : _points[indexA - 1];
Vector3 p1 = _points[indexA];
Vector3 p2 = _points[indexA + 1];
Vector3 p3 = indexA >= _points.Count - 2 ? _dummyEnd : _points[indexA + 2];
segmentLength = Vector3.Distance(p1, p2);
// Interpolate
position = Evaluate(p0, p1, p2, p3, segmentT);
return true;
}
#endregion
#region Private Types & Data
private List<Vector3> _points;
private Vector3 _dummyStart;
private Vector3 _dummyEnd;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1d8fc5a7ef3e485fb34cf8b6e1873e86
timeCreated: 1617136491

Some files were not shown because too many files have changed in this diff Show More