359 lines
9.3 KiB
C#
359 lines
9.3 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEngine.Scripting.APIUpdating;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
|
|
namespace Streamingle.Utils.LerpConstraints
|
|
{
|
|
public enum LerpConstraintUpdateMode
|
|
{
|
|
Update,
|
|
LateUpdate,
|
|
FixedUpdate,
|
|
Manual
|
|
}
|
|
|
|
[MovedFrom(true, "Streamingle.Utils.RoughConstraints", "Assembly-CSharp", "RoughConstraintBase")]
|
|
[ExecuteAlways]
|
|
public abstract class LerpConstraintBase : MonoBehaviour
|
|
{
|
|
[Header("Constraint")]
|
|
public bool constraintActive = true;
|
|
public bool locked = true;
|
|
|
|
[Range(0f, 1f)]
|
|
public float weight = 1f;
|
|
|
|
public List<LerpConstraintSource> sources = new List<LerpConstraintSource>();
|
|
|
|
[Header("Lerp Follow")]
|
|
[Min(0f)]
|
|
public float damping = 12f;
|
|
|
|
public LerpConstraintUpdateMode updateMode = LerpConstraintUpdateMode.LateUpdate;
|
|
public bool useUnscaledTime;
|
|
public bool snapOnEnable = true;
|
|
|
|
#if UNITY_EDITOR
|
|
private bool editorUpdateRegistered;
|
|
#endif
|
|
|
|
protected float CurrentDeltaTime
|
|
{
|
|
get
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
return 1f / 60f;
|
|
}
|
|
|
|
if (updateMode == LerpConstraintUpdateMode.FixedUpdate)
|
|
{
|
|
return useUnscaledTime ? Time.fixedUnscaledDeltaTime : Time.fixedDeltaTime;
|
|
}
|
|
|
|
return useUnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
|
|
}
|
|
}
|
|
|
|
protected float FollowT
|
|
{
|
|
get
|
|
{
|
|
if (damping <= 0f)
|
|
{
|
|
return 1f;
|
|
}
|
|
|
|
return 1f - Mathf.Exp(-damping * Mathf.Max(0f, CurrentDeltaTime));
|
|
}
|
|
}
|
|
|
|
protected virtual void OnEnable()
|
|
{
|
|
#if UNITY_EDITOR
|
|
RegisterEditorUpdate();
|
|
#endif
|
|
|
|
if (!snapOnEnable)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SnapToSources();
|
|
}
|
|
|
|
protected virtual void OnDisable()
|
|
{
|
|
#if UNITY_EDITOR
|
|
UnregisterEditorUpdate();
|
|
#endif
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
if (updateMode != LerpConstraintUpdateMode.Manual)
|
|
{
|
|
Evaluate();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (updateMode == LerpConstraintUpdateMode.Update)
|
|
{
|
|
Evaluate();
|
|
}
|
|
}
|
|
|
|
private void LateUpdate()
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (updateMode == LerpConstraintUpdateMode.LateUpdate)
|
|
{
|
|
Evaluate();
|
|
}
|
|
}
|
|
|
|
private void FixedUpdate()
|
|
{
|
|
if (!Application.isPlaying)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (updateMode == LerpConstraintUpdateMode.FixedUpdate)
|
|
{
|
|
Evaluate();
|
|
}
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
private void RegisterEditorUpdate()
|
|
{
|
|
if (editorUpdateRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EditorApplication.update += EditorUpdate;
|
|
editorUpdateRegistered = true;
|
|
}
|
|
|
|
private void UnregisterEditorUpdate()
|
|
{
|
|
if (!editorUpdateRegistered)
|
|
{
|
|
return;
|
|
}
|
|
|
|
EditorApplication.update -= EditorUpdate;
|
|
editorUpdateRegistered = false;
|
|
}
|
|
|
|
private void EditorUpdate()
|
|
{
|
|
if (this == null)
|
|
{
|
|
UnregisterEditorUpdate();
|
|
return;
|
|
}
|
|
|
|
if (Application.isPlaying || updateMode == LerpConstraintUpdateMode.Manual)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Evaluate();
|
|
}
|
|
#endif
|
|
|
|
public void Evaluate()
|
|
{
|
|
if (!CanEvaluate())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ExecuteConstraint(FollowT);
|
|
}
|
|
|
|
public void SnapToSources()
|
|
{
|
|
if (!CanEvaluate())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ExecuteConstraint(1f);
|
|
}
|
|
|
|
public void SetLocked(bool value)
|
|
{
|
|
if (value && !locked)
|
|
{
|
|
CaptureOffsetsFromCurrentPose();
|
|
}
|
|
|
|
locked = value;
|
|
}
|
|
|
|
public void ActivateConstraint()
|
|
{
|
|
constraintActive = true;
|
|
locked = true;
|
|
SnapToSources();
|
|
}
|
|
|
|
protected abstract void ExecuteConstraint(float followT);
|
|
|
|
protected virtual void CaptureOffsetsFromCurrentPose()
|
|
{
|
|
}
|
|
|
|
private bool CanEvaluate()
|
|
{
|
|
return constraintActive && locked && weight > 0f;
|
|
}
|
|
|
|
protected bool TryGetWeightedPosition(out Vector3 position)
|
|
{
|
|
position = Vector3.zero;
|
|
var totalWeight = 0f;
|
|
|
|
for (var i = 0; i < sources.Count; i++)
|
|
{
|
|
var source = sources[i];
|
|
if (source.sourceTransform == null || source.weight <= 0f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
position += source.sourceTransform.position * source.weight;
|
|
totalWeight += source.weight;
|
|
}
|
|
|
|
if (totalWeight <= 0f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
position /= totalWeight;
|
|
return true;
|
|
}
|
|
|
|
protected bool TryGetWeightedRotation(out Quaternion rotation)
|
|
{
|
|
rotation = Quaternion.identity;
|
|
var hasRotation = false;
|
|
var accumulatedWeight = 0f;
|
|
|
|
for (var i = 0; i < sources.Count; i++)
|
|
{
|
|
var source = sources[i];
|
|
if (source.sourceTransform == null || source.weight <= 0f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var sourceRotation = source.sourceTransform.rotation;
|
|
if (!hasRotation)
|
|
{
|
|
rotation = sourceRotation;
|
|
accumulatedWeight = source.weight;
|
|
hasRotation = true;
|
|
continue;
|
|
}
|
|
|
|
var lerpWeight = source.weight / (accumulatedWeight + source.weight);
|
|
rotation = Quaternion.Slerp(rotation, sourceRotation, lerpWeight);
|
|
accumulatedWeight += source.weight;
|
|
}
|
|
|
|
return hasRotation;
|
|
}
|
|
|
|
protected bool TryGetWeightedScale(out Vector3 scale)
|
|
{
|
|
scale = Vector3.zero;
|
|
var totalWeight = 0f;
|
|
|
|
for (var i = 0; i < sources.Count; i++)
|
|
{
|
|
var source = sources[i];
|
|
if (source.sourceTransform == null || source.weight <= 0f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
scale += source.sourceTransform.localScale * source.weight;
|
|
totalWeight += source.weight;
|
|
}
|
|
|
|
if (totalWeight <= 0f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
scale /= totalWeight;
|
|
return true;
|
|
}
|
|
|
|
protected static Vector3 ApplyAxisMask(Vector3 current, Vector3 target, Vector3Bool constrainedAxis)
|
|
{
|
|
if (!constrainedAxis.x) target.x = current.x;
|
|
if (!constrainedAxis.y) target.y = current.y;
|
|
if (!constrainedAxis.z) target.z = current.z;
|
|
return target;
|
|
}
|
|
|
|
protected static Vector3 ApplyEulerAxisMask(Quaternion current, Quaternion target, Vector3Bool constrainedAxis)
|
|
{
|
|
var currentEuler = current.eulerAngles;
|
|
var targetEuler = target.eulerAngles;
|
|
|
|
if (!constrainedAxis.x) targetEuler.x = currentEuler.x;
|
|
if (!constrainedAxis.y) targetEuler.y = currentEuler.y;
|
|
if (!constrainedAxis.z) targetEuler.z = currentEuler.z;
|
|
|
|
return targetEuler;
|
|
}
|
|
|
|
protected static Quaternion LookRotationForAxis(Vector3 direction, Vector3 worldUp, Vector3 localForward, Vector3 localUp)
|
|
{
|
|
var safeDirection = SafeNormal(direction, Vector3.forward);
|
|
var safeWorldUp = SafeUp(safeDirection, worldUp);
|
|
var safeLocalForward = SafeNormal(localForward, Vector3.forward);
|
|
var safeLocalUp = SafeUp(safeLocalForward, localUp);
|
|
|
|
var targetWorldRotation = Quaternion.LookRotation(safeDirection, safeWorldUp);
|
|
var localAxisRotation = Quaternion.LookRotation(safeLocalForward, safeLocalUp);
|
|
return targetWorldRotation * Quaternion.Inverse(localAxisRotation);
|
|
}
|
|
|
|
private static Vector3 SafeNormal(Vector3 value, Vector3 fallback)
|
|
{
|
|
return value.sqrMagnitude > 0.000001f ? value.normalized : fallback;
|
|
}
|
|
|
|
private static Vector3 SafeUp(Vector3 forward, Vector3 up)
|
|
{
|
|
var safeUp = SafeNormal(up, Vector3.up);
|
|
if (Mathf.Abs(Vector3.Dot(forward, safeUp)) < 0.999f)
|
|
{
|
|
return safeUp;
|
|
}
|
|
|
|
return Mathf.Abs(Vector3.Dot(forward, Vector3.up)) < 0.999f ? Vector3.up : Vector3.right;
|
|
}
|
|
}
|
|
}
|