Files
Fishing2/Assets/FImpossible Creations/Plugins - Animating/Tail Animator/Code/TailAnimator.Logic.Limits.cs
2025-11-10 00:08:26 +08:00

354 lines
15 KiB
C#

using UnityEngine;
namespace FIMSpace.FTail
{
public partial class TailAnimator2
{
Vector3 _limiting_limitPosition = Vector3.zero;
Vector3 _limiting_influenceOffset = Vector3.zero;
/// <summary> Helping stretching limiting be more responsible (lerp value help) </summary>
float _limiting_stretchingHelperTooLong = 0f;
float _limiting_stretchingHelperTooShort = 0f;
/// <summary>
/// If tail is too long we making it shorter if too short - back to default scale
/// </summary>
protected void StretchingLimiting(TailSegment bone)
{
Vector3 backDir = (bone.ParentBone.ProceduralPosition) - (bone.ProceduralPosition);
float dist = backDir.magnitude;
if (dist > 0f)
{
float maxDist = bone.BoneLengthScaled + bone.BoneLengthScaled * 2.5f * MaxStretching;
if (dist > maxDist) // If tail too long
{
if (MaxStretching == 0f)
{
_limiting_limitPosition = bone.ProceduralPosition + backDir * ((dist - bone.BoneLengthScaled) / dist);
bone.ProceduralPosition = _limiting_limitPosition;
}
else
{
_limiting_limitPosition = bone.ParentBone.ProceduralPosition - backDir.normalized * maxDist;
//bone.ProceduralPosition = _limiting_limitPosition;
float limValue = Mathf.InverseLerp(dist, 0f, maxDist) + _limiting_stretchingHelperTooLong; if (limValue > 0.999f) limValue = 0.99f;
if (ReactionSpeed < 0.5f) limValue *= deltaForLerps * (10f + ReactionSpeed * 30f);
bone.ProceduralPosition = Vector3.Lerp(bone.ProceduralPosition, _limiting_limitPosition, limValue);
}
}
else // If tail too short
{
maxDist = bone.BoneLengthScaled + bone.BoneLengthScaled * 1.1f * MaxStretching;
if (dist < maxDist)
{
_limiting_limitPosition = bone.ProceduralPosition + backDir * ((dist - bone.BoneLengthScaled) / dist);
if (MaxStretching == 0f)
bone.ProceduralPosition = _limiting_limitPosition;
else
bone.ProceduralPosition = Vector3.LerpUnclamped(bone.ProceduralPosition, _limiting_limitPosition, Mathf.InverseLerp(dist, 0f, maxDist) + _limiting_stretchingHelperTooShort);
}
}
}
}
Quaternion _limiting_angle_ToTargetRot;
Quaternion _limiting_angle_targetInLocal;
Quaternion _limiting_angle_newLocal;
/// <summary>
/// If segment rotation is in too big angle we straighten it
/// </summary>
protected Vector3 AngleLimiting(TailSegment child, Vector3 targetPos)
{
float angleFactor = 0f;
_limiting_limitPosition = targetPos;
_limiting_angle_ToTargetRot = (
Quaternion.FromToRotation
(
child.ParentBone.transform.TransformDirection(child.LastKeyframeLocalPosition),
targetPos - child.ParentBone.ProceduralPosition)
)
* child.ParentBone.transform.rotation;
_limiting_angle_targetInLocal = FEngineering.QToLocal(child.ParentBone.transform.rotation, _limiting_angle_ToTargetRot); // Quaternion.Inverse(child.ParentBone.PreviousRotation) * _limiting_angle_ToTargetRot;
// Limiting all axis or one
float angleDiffToInitPose = 0f;
if (AngleLimitAxis.sqrMagnitude == 0f) // All axis limit angle
angleDiffToInitPose = Quaternion.Angle(_limiting_angle_targetInLocal, child.LastKeyframeLocalRotation);
else // Selective axis
{
#region Selective axis limits
AngleLimitAxis.Normalize();
if (LimitAxisRange.x == LimitAxisRange.y)
{
angleDiffToInitPose = Mathf.DeltaAngle(
Vector3.Scale(child.InitialLocalRotation.eulerAngles, AngleLimitAxis).magnitude,
Vector3.Scale(_limiting_angle_targetInLocal.eulerAngles, AngleLimitAxis).magnitude);
if (angleDiffToInitPose < 0f) angleDiffToInitPose = -angleDiffToInitPose;
}
else
{
angleDiffToInitPose = Mathf.DeltaAngle(
Vector3.Scale(child.InitialLocalRotation.eulerAngles, AngleLimitAxis).magnitude,
Vector3.Scale(_limiting_angle_targetInLocal.eulerAngles, AngleLimitAxis).magnitude);
if (angleDiffToInitPose > LimitAxisRange.x && angleDiffToInitPose < LimitAxisRange.y) angleDiffToInitPose = 0f;
if (angleDiffToInitPose < 0) angleDiffToInitPose = -angleDiffToInitPose;
}
#endregion
}
#region Debug
//Debug.Log("Atarget in local = " +
// FEngineering.WrapVector(_limiting_angle_targetInLocal.eulerAngles) + " last key local = " +
// FEngineering.WrapVector(child.lastKeyframeLocalRotation.eulerAngles) + " angle = " + angleDiffToInitPose);
#endregion
// Finding rotate back to limited angle coordinates
if (angleDiffToInitPose > AngleLimit)
{
float exceededAngle = Mathf.Abs(Mathf.DeltaAngle(angleDiffToInitPose, AngleLimit));
angleFactor = Mathf.InverseLerp(0f, AngleLimit, exceededAngle); // percentage value (0-1) from target rotation to limit
#region Debug
//Debug.DrawLine(child.ParentBone.ParentBone.transform.position + child.ParentBone.ParentBone.ProceduralRotation * child.ParentBone.transform.localPosition,
//child.ProceduralPosition, Color.red, 1f);
//Debug.Log("[" + child.Index + "] diff = "
// + angleDiffToInitPose + " exc = "
// + exceededAngle + " fact = "
// + angleFactor);
#endregion
if (LimitSmoothing > Mathf.Epsilon)
{
float smooth = Mathf.Lerp(55f, 15f, LimitSmoothing);
_limiting_angle_newLocal = Quaternion.SlerpUnclamped(_limiting_angle_targetInLocal, child.LastKeyframeLocalRotation, deltaForLerps * smooth * angleFactor);
}
else
_limiting_angle_newLocal = Quaternion.SlerpUnclamped(_limiting_angle_targetInLocal, child.LastKeyframeLocalRotation, angleFactor);
_limiting_angle_ToTargetRot = FEngineering.QToWorld(child.ParentBone.transform.rotation, _limiting_angle_newLocal);
_limiting_limitPosition = child.ParentBone.ProceduralPosition + _limiting_angle_ToTargetRot * Vector3.Scale(child.transform.lossyScale, child.LastKeyframeLocalPosition);
}
if (angleFactor > Mathf.Epsilon) return _limiting_limitPosition; else return targetPos;
}
/// <summary>
/// Limiting tail motion in world space position movement
/// </summary>
void MotionInfluenceLimiting()
{
if (MotionInfluence != 1f)
{ // one - param: param = 1 -> 0 param = 0 -> 1
_limiting_influenceOffset = (BaseTransform.position - previousWorldPosition) * (1f - MotionInfluence);
if (MotionInfluenceInY < 1f)
_limiting_influenceOffset.y = (BaseTransform.position.y - previousWorldPosition.y) * (1f - MotionInfluenceInY);
for (int i = 0; i < TailSegments.Count; i++)
{
TailSegments[i].ProceduralPosition += _limiting_influenceOffset;
TailSegments[i].PreviousPosition += _limiting_influenceOffset;
}
GhostChild.ProceduralPosition += _limiting_influenceOffset;
GhostChild.PreviousPosition += _limiting_influenceOffset;
}
}
/// <summary> Helper gravity calculations variable to avoid GC </summary>
Vector3 _tc_segmentGravityOffset = Vector3.zero;
/// <summary> Helper gravity calculations variable to avoid GC </summary>
Vector3 _tc_segmentGravityToParentDir = Vector3.zero;
Vector3 _tc_preGravOff = Vector3.zero;
/// <summary>
/// Calculating gravity parameter position offset for tail segment with some vector direction operations
/// </summary>
void CalculateGravityPositionOffsetForSegment(TailSegment bone)
{
//if (updateLoops > 0)
{
_tc_segmentGravityOffset = (bone.Gravity + WindEffect) * bone.BoneLengthScaled;
_tc_segmentGravityToParentDir = bone.ProceduralPosition - bone.ParentBone.ProceduralPosition;
_tc_preGravOff = (_tc_segmentGravityToParentDir + _tc_segmentGravityOffset).normalized * _tc_segmentGravityToParentDir.magnitude;
// Keeping same length of the bone to prevent gravity effect from stretching bones but offsetting in computed direction
bone.ProceduralPosition = bone.ParentBone.ProceduralPosition + _tc_preGravOff;
}
//else
//{
// bone.ProceduralPosition = bone.ParentBone.ProceduralPosition + _tc_preGravOff;
//}
}
/// <summary>
/// Limiting movement of tail bones in selected axis
/// </summary>
void Axis2DLimit(TailSegment child)
{
child.ProceduralPosition -=
FEngineering.VAxis2DLimit(
child.ParentBone.transform,
child.ParentBone.ProceduralPosition,
child.ProceduralPosition, Axis2D);
}
#region Distance Limiting Calculations
[Tooltip("If you want to use max distance fade option to smoothly disable tail animator when object is going far away from camera")]
public bool UseMaxDistance = false;
[Tooltip("(By default camera transform) Measuring distance from this object to define if object is too far and not need to update tail animator")]
public Transform DistanceFrom;
[HideInInspector]
public Transform _distanceFrom_Auto;
[Tooltip("Max distance to main camera / target object to smoothly turn off tail animator.")]
public float MaximumDistance = 35f;
[Tooltip("If object in range should be detected only when is nearer than 'MaxDistance' to avoid stuttery enabled - disable switching")]
[Range(0.0f, 1f)]
public float MaxOutDistanceFactor = 0f;
[Tooltip("If distance should be measured not using Up (y) axis")]
public bool DistanceWithoutY = false;
[Tooltip("Offsetting point from which we want to measure distance to target")]
public Vector3 DistanceMeasurePoint;
[Tooltip("Disable fade duration in seconds")]
[Range(0.25f, 2f)]
public float FadeDuration = 0.75f;
private bool maxDistanceExceed = false;
private Transform finalDistanceFrom;
private bool wasCameraSearch = false;
/// <summary> Multiplier for blend weight when tail animator is far from camera or provided object </summary>
private float distanceWeight = 1f;
/// <summary>
/// Getting distance value from distance measure point to target position
/// </summary>
public float GetDistanceMeasure(Vector3 targetPosition)
{
if (DistanceWithoutY)
{
Vector3 p = BaseTransform.position + BaseTransform.TransformVector(DistanceMeasurePoint);
Vector2 p2 = new Vector2(p.x, p.z);
return Vector2.Distance(p2, new Vector2(targetPosition.x, targetPosition.z));
}
else
return Vector3.Distance(BaseTransform.position + BaseTransform.TransformVector(DistanceMeasurePoint), targetPosition);
}
/// <summary>
/// Handling max distance feature
/// </summary>
private void MaxDistanceCalculations()
{
if (DistanceFrom != null)
finalDistanceFrom = DistanceFrom;
#region Defining distance measure reference if not found
else
{
if (finalDistanceFrom == null)
{
if (_distanceFrom_Auto == null)
{
Camera c = Camera.main;
if (c) _distanceFrom_Auto = c.transform;
else
{
if (!wasCameraSearch)
{
#if UNITY_2023_1_OR_NEWER
c = FindFirstObjectByType<Camera>();
#else
c = FindObjectOfType<Camera>();
#endif
if (c) _distanceFrom_Auto = c.transform;
wasCameraSearch = true;
}
}
}
finalDistanceFrom = _distanceFrom_Auto;
}
}
#endregion
// If we are using distance limitation
if (MaximumDistance > 0f && finalDistanceFrom != null)
{
if (!maxDistanceExceed) // If look motion is not out of look range etc.
{
float distance = GetDistanceMeasure(finalDistanceFrom.position);
if (distance > MaximumDistance + MaximumDistance * MaxOutDistanceFactor)
maxDistanceExceed = true;
distanceWeight += Time.unscaledDeltaTime * (1f / FadeDuration);
if (distanceWeight > 1f) distanceWeight = 1f;
}
else // When disabling tail animator
{
// Entering back distance range
float distance = GetDistanceMeasure(finalDistanceFrom.position);
if (distance <= MaximumDistance) maxDistanceExceed = false;
distanceWeight -= Time.unscaledDeltaTime * (1f / FadeDuration);
if (distanceWeight < 0f) distanceWeight = 0f;
}
}
else // If we don't use max of distance feature
{
maxDistanceExceed = false;
distanceWeight = 1f;
}
}
#endregion
}
}