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;
}
}
}