添加插件
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 202e9deb41375844daf2c8a489b596d5
|
||||
folderAsset: yes
|
||||
timeCreated: 1532283218
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6e3d55ec0583b8f458db21ee51c4ead3
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,463 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
/// <summary>
|
||||
/// FM: Helper class to animate tail bones freely
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class TailSegment
|
||||
{
|
||||
public TailSegment ParentBone { get; private set; }
|
||||
public TailSegment ChildBone { get; private set; }
|
||||
|
||||
/// <summary> All references must have transform reference except GhostChild bone, GhostParent can have same transform as first bone in chain </summary>
|
||||
public Transform transform { get; private set; }
|
||||
|
||||
/// <summary> Index of bone in Tail Animator list </summary>
|
||||
public int Index { get; private set; }
|
||||
|
||||
/// <summary> For quicker value getting from curves </summary>
|
||||
public float IndexOverlLength { get; private set; }
|
||||
|
||||
/// <summary> Procedural position for tail motion without weight blending etc. </summary>
|
||||
public Vector3 ProceduralPosition = Vector3.zero;
|
||||
/// <summary> Final procedural position for tail transforms so weight blended </summary>
|
||||
public Vector3 ProceduralPositionWeightBlended = Vector3.zero;
|
||||
|
||||
/// <summary> Final rotation for tail transform so rotated towards blended position </summary>
|
||||
public Quaternion TrueTargetRotation = Quaternion.identity;
|
||||
/// <summary> Reference rotation for position offset in parent orientation used in rotation smoothing </summary>
|
||||
public Quaternion PosRefRotation = Quaternion.identity;
|
||||
/// <summary> Memory for slithery motion </summary>
|
||||
public Quaternion PreviousPosReferenceRotation = Quaternion.identity;
|
||||
|
||||
/// <summary> Memory for velocity (PreviousProceduralPosition) </summary>
|
||||
public Vector3 PreviousPosition;
|
||||
|
||||
/// <summary> Blend with tail animator motion value used in partial blending </summary>
|
||||
public float BlendValue = 1f;
|
||||
|
||||
/// <summary> Length of the bone in initial world space - distance to next bone transform </summary>
|
||||
public float BoneLength { get; private set; }
|
||||
/// <summary> Length of bone basing on intial positions / keyframe positions scaled with transform scale and length multiplier </summary>
|
||||
public Vector3 BoneDimensionsScaled;
|
||||
public float BoneLengthScaled;
|
||||
|
||||
public Vector3 InitialLocalPosition = Vector3.zero;
|
||||
public Vector3 InitialLocalPositionInRoot = Vector3.zero;
|
||||
public Quaternion InitialLocalRotationInRoot = Quaternion.identity;
|
||||
public Vector3 LocalOffset = Vector3.zero;
|
||||
public Quaternion InitialLocalRotation = Quaternion.identity;
|
||||
|
||||
// Collision related variables
|
||||
public float ColliderRadius = 1f;
|
||||
|
||||
|
||||
public bool CollisionContactFlag = false;
|
||||
public float CollisionContactRelevancy = -1f;
|
||||
public Collision collisionContacts = null;
|
||||
|
||||
// Animation styles helper variables
|
||||
public Vector3 VelocityHelper = Vector3.zero;
|
||||
public Quaternion QVelocityHelper = Quaternion.identity;
|
||||
|
||||
/// <summary> Helper variable for 'Sustain' parameter </summary>
|
||||
public Vector3 PreviousPush = Vector3.zero;
|
||||
|
||||
// Shaping
|
||||
public Quaternion Curving = Quaternion.identity;
|
||||
public Vector3 Gravity = Vector3.zero;
|
||||
public Vector3 GravityLookOffset = Vector3.zero;
|
||||
public float LengthMultiplier = 1f;
|
||||
|
||||
// Expert
|
||||
public float PositionSpeed = 1f;
|
||||
public float RotationSpeed = 1f;
|
||||
public float Springiness = 0f;
|
||||
public float Slithery = 1f;
|
||||
public float Curling = 0.5f;
|
||||
public float Slippery = 1f;
|
||||
|
||||
public TailCollisionHelper CollisionHelper { get; internal set; }
|
||||
|
||||
|
||||
#region Constructors
|
||||
|
||||
public TailSegment() { Index = -1; Curving = Quaternion.identity; Gravity = Vector3.zero; LengthMultiplier = 1f; Deflection = Vector3.zero; DeflectionFactor = 0f; DeflectionRelevancy = -1; deflectionSmoothVelo = 0f; }
|
||||
public TailSegment(Transform transform) : this()
|
||||
{
|
||||
if (transform == null) return;
|
||||
|
||||
this.transform = transform;
|
||||
|
||||
// Init position setup
|
||||
ProceduralPosition = transform.position;
|
||||
PreviousPosition = transform.position;
|
||||
PosRefRotation = transform.rotation;
|
||||
PreviousPosReferenceRotation = PosRefRotation;
|
||||
TrueTargetRotation = PosRefRotation;
|
||||
ReInitializeLocalPosRot(transform.localPosition, transform.localRotation);
|
||||
BoneLength = 0.1f;
|
||||
//ReInitializeLocalPosRot(transform.localPosition, transform.localRotation);
|
||||
}
|
||||
|
||||
public TailSegment(TailSegment copyFrom) : this(copyFrom.transform)
|
||||
{
|
||||
this.transform = copyFrom.transform;
|
||||
|
||||
// Init position setup
|
||||
Index = copyFrom.Index;
|
||||
IndexOverlLength = copyFrom.IndexOverlLength;
|
||||
|
||||
ProceduralPosition = copyFrom.ProceduralPosition;
|
||||
PreviousPosition = copyFrom.PreviousPosition;
|
||||
ProceduralPositionWeightBlended = copyFrom.ProceduralPosition;
|
||||
PosRefRotation = copyFrom.PosRefRotation;
|
||||
PreviousPosReferenceRotation = PosRefRotation;
|
||||
TrueTargetRotation = copyFrom.TrueTargetRotation;
|
||||
|
||||
ReInitializeLocalPosRot(copyFrom.InitialLocalPosition, copyFrom.InitialLocalRotation);
|
||||
}
|
||||
|
||||
|
||||
public void ReInitializeLocalPosRot(Vector3 initLocalPos, Quaternion initLocalRot)
|
||||
{
|
||||
InitialLocalPosition = initLocalPos;
|
||||
InitialLocalRotation = initLocalRot;
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void SetIndex(int i, int tailSegments)
|
||||
{
|
||||
Index = i;
|
||||
if (i < 0) IndexOverlLength = 0f;
|
||||
else IndexOverlLength = (float)i / (float)tailSegments;
|
||||
}
|
||||
|
||||
public void SetParentRef(TailSegment parent)
|
||||
{ ParentBone = parent; BoneLength = (ProceduralPosition - ParentBone.ProceduralPosition).magnitude; }
|
||||
|
||||
public void SetChildRef(TailSegment child)
|
||||
{ ChildBone = child; }
|
||||
|
||||
public float GetRadiusScaled()
|
||||
{ return ColliderRadius * transform.lossyScale.x; }
|
||||
|
||||
|
||||
public bool IsDetachable { get; private set; }
|
||||
public void AssignDetachedRootCoords(Transform root)
|
||||
{
|
||||
InitialLocalPositionInRoot = root.InverseTransformPoint(transform.position);
|
||||
InitialLocalRotationInRoot = FEngineering.QToLocal(root.rotation, transform.rotation);
|
||||
IsDetachable = true;
|
||||
}
|
||||
|
||||
//public static Vector3 CalculateLocalLook(Transform parent, Transform child)
|
||||
//{ return -(parent.InverseTransformPoint(child.position) - parent.InverseTransformPoint(parent.position)).normalized; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Blend toward target position
|
||||
/// </summary>
|
||||
internal Vector3 BlendMotionWeight(Vector3 newPosition)
|
||||
{
|
||||
return Vector3.LerpUnclamped
|
||||
(
|
||||
// Blending from default pose to new position
|
||||
ParentBone.ProceduralPosition + FEngineering.TransformVector(ParentBone.LastKeyframeLocalRotation, ParentBone.transform.lossyScale, LastKeyframeLocalPosition),
|
||||
newPosition,
|
||||
BlendValue
|
||||
);
|
||||
}
|
||||
|
||||
internal void PreCalibrate()
|
||||
{
|
||||
transform.localPosition = InitialLocalPosition;
|
||||
transform.localRotation = InitialLocalRotation;
|
||||
}
|
||||
|
||||
internal void Validate()
|
||||
{
|
||||
if (BoneLength == 0f) BoneLength = 0.001f;
|
||||
}
|
||||
|
||||
|
||||
#region Zero keyframe detection
|
||||
|
||||
public void RefreshKeyLocalPosition()
|
||||
{
|
||||
if (transform)
|
||||
LastKeyframeLocalRotation =(transform.localRotation);
|
||||
else
|
||||
LastKeyframeLocalRotation = (InitialLocalRotation);
|
||||
}
|
||||
|
||||
|
||||
public void RefreshKeyLocalPositionAndRotation()
|
||||
{
|
||||
if ( transform)
|
||||
RefreshKeyLocalPositionAndRotation(transform.localPosition, transform.localRotation);
|
||||
else
|
||||
RefreshKeyLocalPositionAndRotation(InitialLocalPosition, InitialLocalRotation);
|
||||
}
|
||||
|
||||
public void RefreshKeyLocalPositionAndRotation(Vector3 p, Quaternion r)
|
||||
{
|
||||
LastKeyframeLocalPosition = p;
|
||||
LastKeyframeLocalRotation = r;
|
||||
}
|
||||
|
||||
/// <summary> Initial local rotation or keyframe local rotation </summary>
|
||||
public Quaternion LastKeyframeLocalRotation;// { get { if (transform) return transform.localRotation; else return InitialLocalRotation; } }
|
||||
/// <summary> Initial local position or keyframe local position </summary>
|
||||
public Vector3 LastKeyframeLocalPosition;// { get { if (transform) return transform.localPosition; else return InitialLocalPosition; } }
|
||||
|
||||
public Vector3 LastFinalPosition { get; private set; }
|
||||
public Quaternion LastFinalRotation { get; private set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Offsset to default segment position from parent relation
|
||||
/// </summary>
|
||||
internal Vector3 ParentToFrontOffset()
|
||||
{
|
||||
return FEngineering.TransformVector
|
||||
(
|
||||
ParentBone.LastKeyframeLocalRotation,
|
||||
ParentBone.transform.lossyScale,
|
||||
LastKeyframeLocalPosition
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public void RefreshFinalPos(Vector3 pos)
|
||||
{ LastFinalPosition = pos; }
|
||||
|
||||
public void RefreshFinalRot(Quaternion rot)
|
||||
{ LastFinalRotation = rot; }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Deflection feature support
|
||||
|
||||
/// <summary> Dot product of deflection angle for segment </summary>
|
||||
public float DeflectionFactor { get; private set; }
|
||||
/// <summary> Deflection direction </summary>
|
||||
public Vector3 Deflection { get; private set; }
|
||||
public float DeflectionSmooth { get; private set; }
|
||||
private float deflectionSmoothVelo; // Helper variable for smooth damp
|
||||
|
||||
/// <summary> Deflection world position </summary>
|
||||
public Vector3 DeflectionWorldPosition { get; private set; }
|
||||
/// <summary> Relevancy of deflection in list used for optimization </summary>
|
||||
public int DeflectionRelevancy { get; private set; }
|
||||
public FImp_ColliderData_Base LatestSelectiveCollision { get; internal set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checking relation between reference position to define deflection
|
||||
/// </summary>
|
||||
public bool CheckDeflectionState(float zeroWhenLower, float smoothTime, float delta)
|
||||
{
|
||||
Vector3 deflection = LastKeyframeLocalPosition - ParentBone.transform.InverseTransformVector(ProceduralPosition - ParentBone.ProceduralPosition);
|
||||
|
||||
#region Debugging
|
||||
//deflection = LastKeyframeLocalPosition - FEngineering.TransformVector(ParentBone.ParentBone.ProceduralRotation, ParentBone.transform.lossyScale, ProceduralPosition - ParentBone.ProceduralPosition);
|
||||
//Debug.DrawRay(ParentBone.transform.position + Vector3.up * 1.1f, ParentBone.transform.TransformVector(ProceduralPosition - ParentBone.ProceduralPosition), Color.red);
|
||||
//Debug.DrawRay(ParentBone.ProceduralPosition + Vector3.up * 1.1f, FEngineering.TransformVector(ParentBone.ParentBone.ProceduralRotation, ParentBone.transform.lossyScale, ProceduralPosition - ParentBone.ProceduralPosition), new Color(1f, 0.5f, 0f, 1f) );
|
||||
#endregion
|
||||
|
||||
DeflectionFactor = Vector3.Dot(LastKeyframeLocalPosition.normalized, deflection.normalized); // Calculating factor in local space
|
||||
|
||||
#if UNITY_EDITOR // Debugging
|
||||
deflection = ChildBone.ParentBone.transform.TransformVector(deflection); // Transforming into world space offset for segments calculations
|
||||
#endif
|
||||
|
||||
// Reseting when too small angle
|
||||
if (DeflectionFactor < zeroWhenLower)
|
||||
{
|
||||
if (smoothTime <= Mathf.Epsilon)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Deflection = Vector3.zero;
|
||||
#endif
|
||||
DeflectionSmooth = 0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Deflection = Vector3.zero;
|
||||
#endif
|
||||
|
||||
DeflectionSmooth = Mathf.SmoothDamp(DeflectionSmooth, -Mathf.Epsilon, ref deflectionSmoothVelo, smoothTime / 1.5f, Mathf.Infinity, delta);
|
||||
}
|
||||
}
|
||||
else // Enabling or translating with smooth time
|
||||
{
|
||||
if (smoothTime <= Mathf.Epsilon)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Deflection = deflection;
|
||||
#endif
|
||||
DeflectionSmooth = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Deflection = deflection;
|
||||
#endif
|
||||
|
||||
DeflectionSmooth = Mathf.SmoothDamp(DeflectionSmooth, 1f, ref deflectionSmoothVelo, smoothTime, Mathf.Infinity, delta);
|
||||
}
|
||||
}
|
||||
|
||||
if (DeflectionSmooth <= Mathf.Epsilon) return true;
|
||||
else
|
||||
{
|
||||
if (ChildBone.ChildBone != null)
|
||||
DeflectionWorldPosition = ChildBone.ChildBone.ProceduralPosition;
|
||||
else
|
||||
DeflectionWorldPosition = ChildBone.ProceduralPosition;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Signing bone as deflection point relevant
|
||||
/// </summary>
|
||||
/// <returns> True when deflection just detrected </returns>
|
||||
public bool DeflectionRelevant()
|
||||
{
|
||||
if (DeflectionRelevancy == -1)
|
||||
{
|
||||
DeflectionRelevancy = 3;
|
||||
return true;
|
||||
}
|
||||
|
||||
DeflectionRelevancy = 3;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/// <returns> True when relevancy is high null when it's remove point relevancy false when it's unrelevant </returns>
|
||||
public bool? DeflectionRestoreState()
|
||||
{
|
||||
if (DeflectionRelevancy > 0)
|
||||
{
|
||||
DeflectionRelevancy--;
|
||||
|
||||
if (DeflectionRelevancy == 0) // When relevancy hit 0 then remove callback and set to -1
|
||||
{
|
||||
DeflectionRelevancy = -1;
|
||||
return null;
|
||||
}
|
||||
else return true; // True when relevancy > 0
|
||||
}
|
||||
else
|
||||
return false; // Unrelevant deflection state
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Post Processing and others
|
||||
|
||||
/// <summary>
|
||||
/// Copying animation parameters from other segment
|
||||
/// </summary>
|
||||
internal void ParamsFrom(TailSegment other)
|
||||
{
|
||||
BlendValue = other.BlendValue;
|
||||
ColliderRadius = other.ColliderRadius;
|
||||
Gravity = other.Gravity;
|
||||
LengthMultiplier = other.LengthMultiplier;
|
||||
BoneLength = other.BoneLength;
|
||||
BoneLengthScaled = other.BoneLengthScaled;
|
||||
BoneDimensionsScaled = other.BoneDimensionsScaled;
|
||||
collisionContacts = other.collisionContacts;
|
||||
CollisionHelper = other.CollisionHelper;
|
||||
|
||||
PositionSpeed = other.PositionSpeed;
|
||||
RotationSpeed = other.RotationSpeed;
|
||||
Springiness = other.Springiness;
|
||||
Slithery = other.Slithery;
|
||||
Curling = other.Curling;
|
||||
Slippery = other.Slippery;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copying all available parameters from other segment
|
||||
/// </summary>
|
||||
internal void ParamsFromAll(TailSegment other)
|
||||
{
|
||||
ParamsFrom(other);
|
||||
InitialLocalPosition = other.InitialLocalPosition;
|
||||
InitialLocalRotation = other.InitialLocalRotation;
|
||||
LastFinalPosition = other.LastFinalPosition;
|
||||
LastFinalRotation = other.LastFinalRotation;
|
||||
ProceduralPosition = other.ProceduralPosition;
|
||||
ProceduralPositionWeightBlended = other.ProceduralPositionWeightBlended;
|
||||
TrueTargetRotation = other.TrueTargetRotation;
|
||||
PosRefRotation = other.PosRefRotation;
|
||||
PreviousPosReferenceRotation = other.PreviousPosReferenceRotation;
|
||||
PreviousPosition = other.PreviousPosition;
|
||||
BoneLength = other.BoneLength;
|
||||
BoneDimensionsScaled = other.BoneDimensionsScaled;
|
||||
BoneLengthScaled = other.BoneLengthScaled;
|
||||
LocalOffset = other.LocalOffset;
|
||||
ColliderRadius = other.ColliderRadius;
|
||||
VelocityHelper = other.VelocityHelper;
|
||||
QVelocityHelper = other.QVelocityHelper;
|
||||
PreviousPush = other.PreviousPush;
|
||||
}
|
||||
|
||||
internal void User_ReassignTransform(Transform t)
|
||||
{
|
||||
transform = t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Resetting
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
PreviousPush = Vector3.zero;
|
||||
VelocityHelper = Vector3.zero;
|
||||
QVelocityHelper = Quaternion.identity;
|
||||
|
||||
if (transform)
|
||||
{
|
||||
ProceduralPosition = transform.position;
|
||||
PosRefRotation = transform.rotation;
|
||||
PreviousPosReferenceRotation = transform.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ParentBone.transform)
|
||||
ProceduralPosition = ParentBone.transform.position + ParentToFrontOffset();
|
||||
}
|
||||
|
||||
PreviousPosition = ProceduralPosition;
|
||||
ProceduralPositionWeightBlended = ProceduralPosition;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c0f1ecbfe3d7aa458e3e48da5898b29
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,576 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
public enum ECollisionSpace { World_Slow, Selective_Fast }
|
||||
public enum ECollisionMode { m_3DCollision, m_2DCollision }
|
||||
|
||||
[Tooltip("Using some simple calculations to make tail bend on colliders")]
|
||||
public bool UseCollision = false;
|
||||
|
||||
[Tooltip("How collision should be detected, world gives you collision on all world colliders but with more use of cpu (using unity's rigidbodies), 'Selective' gives you possibility to detect collision on selected colliders without using Rigidbodies, it also gives smoother motion (deactivated colliders will still detect collision, unless its game object is disabled)")]
|
||||
public ECollisionSpace CollisionSpace = ECollisionSpace.Selective_Fast;
|
||||
public ECollisionMode CollisionMode = ECollisionMode.m_3DCollision;
|
||||
|
||||
/// <summary> Available only when using dynamic world collisions inclusion mode </summary>
|
||||
public SphereCollider GeneratedDynamicInclusionCollider { get; private set; }
|
||||
/// <summary> Available only when using dynamic world collisions inclusion 2D mode </summary>
|
||||
public CircleCollider2D GeneratedDynamicInclusionCollider2D { get; private set; }
|
||||
|
||||
#region Selective Variables
|
||||
|
||||
|
||||
[Tooltip("If you want to stop checking collision if segment collides with one collider\n\nSegment collision with two or more colliders in the same time with this option enabled can result in stuttery motion")]
|
||||
public bool CheapCollision = false;
|
||||
|
||||
[Tooltip("Using trigger collider to include encountered colliders into collide with list")]
|
||||
public bool DynamicWorldCollidersInclusion = false;
|
||||
|
||||
[Tooltip("Radius of trigger collider for dynamic inclusion of colliders")]
|
||||
public float InclusionRadius = 1f;
|
||||
public bool IgnoreMeshColliders = true;
|
||||
|
||||
public List<Collider> IncludedColliders;
|
||||
public List<Collider2D> IncludedColliders2D;
|
||||
/// <summary>Colliders always included with 'Dynamic World Colliders Inclusion' mode</summary>
|
||||
public List<Component> DynamicAlwaysInclude { get; private set; }
|
||||
protected List<FImp_ColliderData_Base> IncludedCollidersData;
|
||||
|
||||
/// <summary> List of collider datas to be checked by every tail segment</summary>
|
||||
protected List<FImp_ColliderData_Base> CollidersDataToCheck;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region World Collision Variables
|
||||
|
||||
|
||||
[Tooltip("Capsules can give much more precise collision detection")]
|
||||
public int CollidersType = 0;
|
||||
public bool CollideWithOtherTails = false;
|
||||
[Tooltip("Collision with colliders even if they're disabled (but game object must be enabled)\nHelpful to setup character limbs collisions without need to create new Layer")]
|
||||
public bool CollideWithDisabledColliders = true;
|
||||
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float CollisionSlippery = 1f;
|
||||
[Tooltip("If tail colliding objects should fit to colliders (0) or be reflect from them (Reflecting Only with 'Slithery' parameter greater than ~0.2)")]
|
||||
[Range(0f, 1f)]
|
||||
public float ReflectCollision = 0f;
|
||||
|
||||
public AnimationCurve CollidersScaleCurve = AnimationCurve.Linear(0, 1, 1, 1);
|
||||
public float CollidersScaleMul = 6.5f;
|
||||
[Range(0f, 1f)]
|
||||
public float CollisionsAutoCurve = 0.5f;
|
||||
|
||||
public List<Collider> IgnoredColliders;
|
||||
public List<Collider2D> IgnoredColliders2D;
|
||||
public bool CollidersSameLayer = true;
|
||||
[Tooltip("If you add rigidbodies to each tail segment's collider, collision will work on everything but it will be less optimal, you don't have to add here rigidbodies but then you must have not kinematic rigidbodies on objects segments can collide")]
|
||||
public bool CollidersAddRigidbody = true;
|
||||
public float RigidbodyMass = 1f;
|
||||
|
||||
[FPD_Layers]
|
||||
public int CollidersLayer = 0;
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
void RefreshSegmentsColliders()
|
||||
{
|
||||
if (CollisionSpace == ECollisionSpace.Selective_Fast)
|
||||
{
|
||||
if (TailSegments != null)
|
||||
if (TailSegments.Count > 1)
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
TailSegments[i].ColliderRadius = GetColliderSphereRadiusFor(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BeginCollisionsUpdate()
|
||||
{
|
||||
if (CollisionSpace == ECollisionSpace.Selective_Fast)
|
||||
{
|
||||
RefreshIncludedCollidersDataList();
|
||||
|
||||
// Letting every tail segment check only enabled colliders by game object
|
||||
CollidersDataToCheck.Clear();
|
||||
|
||||
for (int i = 0; i < IncludedCollidersData.Count; i++)
|
||||
{
|
||||
if (IncludedCollidersData[i].Transform == null) { forceRefreshCollidersData = true; break; }
|
||||
|
||||
if (IncludedCollidersData[i].Transform.gameObject.activeInHierarchy)
|
||||
{
|
||||
|
||||
// Collisions with all even disabled colliders
|
||||
if (CollideWithDisabledColliders)
|
||||
{
|
||||
IncludedCollidersData[i].RefreshColliderData();
|
||||
CollidersDataToCheck.Add(IncludedCollidersData[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CollisionMode == ECollisionMode.m_3DCollision)
|
||||
{
|
||||
if (IncludedCollidersData[i].Collider == null)
|
||||
{
|
||||
forceRefreshCollidersData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// If we want to collide even with disabled colliders
|
||||
if (IncludedCollidersData[i].Collider.enabled)
|
||||
{
|
||||
IncludedCollidersData[i].RefreshColliderData();
|
||||
CollidersDataToCheck.Add(IncludedCollidersData[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( IncludedCollidersData[i].Collider2D == null)
|
||||
{
|
||||
forceRefreshCollidersData = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (IncludedCollidersData[i].Collider2D.enabled)
|
||||
{
|
||||
IncludedCollidersData[i].RefreshColliderData();
|
||||
CollidersDataToCheck.Add(IncludedCollidersData[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generating colliders on tail with provided settings
|
||||
/// If collision space is world then rigidbody colliders are added, if selective no additional components are needed
|
||||
/// </summary>
|
||||
void SetupSphereColliders()
|
||||
{
|
||||
if (CollisionSpace == ECollisionSpace.World_Slow)
|
||||
{
|
||||
for (int i = 1; i < _TransformsGhostChain.Count; i++)
|
||||
{
|
||||
if (CollidersSameLayer) _TransformsGhostChain[i].gameObject.layer = gameObject.layer; else _TransformsGhostChain[i].gameObject.layer = CollidersLayer;
|
||||
}
|
||||
|
||||
if (CollidersType != 0)
|
||||
{
|
||||
for (int i = 1; i < _TransformsGhostChain.Count - 1; i++)
|
||||
{
|
||||
CapsuleCollider caps = _TransformsGhostChain[i].gameObject.AddComponent<CapsuleCollider>();
|
||||
|
||||
TailCollisionHelper tcol = _TransformsGhostChain[i].gameObject.AddComponent<TailCollisionHelper>().Init(CollidersAddRigidbody, RigidbodyMass);
|
||||
tcol.TailCollider = caps;
|
||||
tcol.Index = i;
|
||||
tcol.ParentTail = this;
|
||||
|
||||
caps.radius = GetColliderSphereRadiusFor(_TransformsGhostChain, i);
|
||||
caps.direction = 2;
|
||||
caps.height = (_TransformsGhostChain[i].position - _TransformsGhostChain[i + 1].position).magnitude * 2f - caps.radius;
|
||||
caps.center = _TransformsGhostChain[i].InverseTransformPoint(Vector3.Lerp(_TransformsGhostChain[i].position, _TransformsGhostChain[i + 1].position, 0.5f));
|
||||
|
||||
TailSegments[i].ColliderRadius = caps.radius;
|
||||
TailSegments[i].CollisionHelper = tcol;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 1; i < _TransformsGhostChain.Count; i++)
|
||||
{
|
||||
SphereCollider s = _TransformsGhostChain[i].gameObject.AddComponent<SphereCollider>();
|
||||
TailCollisionHelper tcol = _TransformsGhostChain[i].gameObject.AddComponent<TailCollisionHelper>().Init(CollidersAddRigidbody, RigidbodyMass);
|
||||
tcol.TailCollider = s;
|
||||
tcol.Index = i;
|
||||
tcol.ParentTail = this;
|
||||
s.radius = GetColliderSphereRadiusFor(_TransformsGhostChain, i);
|
||||
TailSegments[i].ColliderRadius = s.radius;
|
||||
TailSegments[i].CollisionHelper = tcol;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _TransformsGhostChain.Count; i++)
|
||||
TailSegments[i].ColliderRadius = GetColliderSphereRadiusFor(i);
|
||||
|
||||
IncludedCollidersData = new List<FImp_ColliderData_Base>();
|
||||
CollidersDataToCheck = new List<FImp_ColliderData_Base>();
|
||||
|
||||
#region Dynamic inclusion preparations
|
||||
|
||||
if (DynamicWorldCollidersInclusion)
|
||||
{
|
||||
if (CollisionMode == ECollisionMode.m_3DCollision)
|
||||
for (int i = 0; i < IncludedColliders.Count; i++) DynamicAlwaysInclude.Add(IncludedColliders[i]);
|
||||
else
|
||||
for (int i = 0; i < IncludedColliders2D.Count; i++) DynamicAlwaysInclude.Add(IncludedColliders2D[i]);
|
||||
|
||||
Transform middleSegm = TailSegments[TailSegments.Count / 2].transform;
|
||||
float scaleRef = Vector3.Distance(_TransformsGhostChain[0].position, _TransformsGhostChain[_TransformsGhostChain.Count - 1].position);
|
||||
|
||||
TailCollisionHelper cHelper = middleSegm.gameObject.AddComponent<TailCollisionHelper>();
|
||||
cHelper.ParentTail = this;
|
||||
SphereCollider triggerC = null;
|
||||
CircleCollider2D triggerC2D = null;
|
||||
|
||||
if (CollisionMode == ECollisionMode.m_3DCollision)
|
||||
{
|
||||
triggerC = middleSegm.gameObject.AddComponent<SphereCollider>();
|
||||
triggerC.isTrigger = true;
|
||||
cHelper.TailCollider = triggerC;
|
||||
GeneratedDynamicInclusionCollider = triggerC;
|
||||
}
|
||||
else
|
||||
{
|
||||
triggerC2D = middleSegm.gameObject.AddComponent<CircleCollider2D>();
|
||||
triggerC2D.isTrigger = true;
|
||||
cHelper.TailCollider2D = triggerC2D;
|
||||
GeneratedDynamicInclusionCollider2D = triggerC2D;
|
||||
}
|
||||
|
||||
cHelper.Init(true, 1f, true);
|
||||
|
||||
float scale = Mathf.Abs(middleSegm.transform.lossyScale.z);
|
||||
if (scale == 0f) scale = 1f;
|
||||
if (triggerC != null) triggerC.radius = (scaleRef / scale) * InclusionRadius;
|
||||
else triggerC2D.radius = (scaleRef / scale) * InclusionRadius;
|
||||
|
||||
if (CollidersSameLayer)
|
||||
middleSegm.gameObject.layer = gameObject.layer;
|
||||
else
|
||||
middleSegm.gameObject.layer = CollidersLayer;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
RefreshIncludedCollidersDataList();
|
||||
}
|
||||
|
||||
collisionInitialized = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collision data sent by single tail segment
|
||||
/// </summary>
|
||||
internal void CollisionDetection(int index, Collision collision)
|
||||
{
|
||||
TailSegments[index].collisionContacts = collision;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Exitting collision
|
||||
/// </summary>
|
||||
internal void ExitCollision(int index)
|
||||
{
|
||||
TailSegments[index].collisionContacts = null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Use saved collision contact in right moment when uxecuting update methods
|
||||
/// </summary>
|
||||
protected bool UseCollisionContact(int index, ref Vector3 pos)
|
||||
{
|
||||
if (TailSegments[index].collisionContacts == null) return false;
|
||||
if (TailSegments[index].collisionContacts.contacts.Length == 0) return false; // In newest Unity 2018 versions 'Collision' class is generated even there are no collision contacts
|
||||
|
||||
Collision collision = TailSegments[index].collisionContacts;
|
||||
float thisCollRadius = FImp_ColliderData_Sphere.CalculateTrueRadiusOfSphereCollider(TailSegments[index].transform, TailSegments[index].ColliderRadius) * 0.95f;
|
||||
|
||||
if (collision.collider)
|
||||
{
|
||||
SphereCollider collidedSphere = collision.collider as SphereCollider;
|
||||
|
||||
// If we collide sphere we can calculate precise segment offset for it
|
||||
if (collidedSphere)
|
||||
{
|
||||
FImp_ColliderData_Sphere.PushOutFromSphereCollider(collidedSphere, thisCollRadius, ref pos, Vector3.zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
CapsuleCollider collidedCapsule = collision.collider as CapsuleCollider;
|
||||
|
||||
// If we collide capsule we can calculate precise segment offset for it
|
||||
if (collidedCapsule)
|
||||
{
|
||||
FImp_ColliderData_Capsule.PushOutFromCapsuleCollider(collidedCapsule, thisCollRadius, ref pos, Vector3.zero);
|
||||
}
|
||||
else
|
||||
{
|
||||
BoxCollider collidedBox = collision.collider as BoxCollider;
|
||||
|
||||
// If we collide box we can calculate precise segment offset for it
|
||||
if (collidedBox)
|
||||
{
|
||||
if (TailSegments[index].CollisionHelper.RigBody)
|
||||
{
|
||||
if (collidedBox.attachedRigidbody)
|
||||
{
|
||||
if (TailSegments[index].CollisionHelper.RigBody.mass > 1f)
|
||||
{
|
||||
FImp_ColliderData_Box.PushOutFromBoxCollider(collidedBox, collision, thisCollRadius, ref pos);
|
||||
Vector3 pusherPos = pos;
|
||||
FImp_ColliderData_Box.PushOutFromBoxCollider(collidedBox, thisCollRadius, ref pos);
|
||||
|
||||
pos = Vector3.Lerp(pos, pusherPos, TailSegments[index].CollisionHelper.RigBody.mass / 5f);
|
||||
}
|
||||
else
|
||||
FImp_ColliderData_Box.PushOutFromBoxCollider(collidedBox, thisCollRadius, ref pos);
|
||||
}
|
||||
else
|
||||
FImp_ColliderData_Box.PushOutFromBoxCollider(collidedBox, thisCollRadius, ref pos);
|
||||
}
|
||||
else
|
||||
FImp_ColliderData_Box.PushOutFromBoxCollider(collidedBox, thisCollRadius, ref pos);
|
||||
}
|
||||
else // If we collide mesh we can't calculate very precise segment offset but we can support it in some way
|
||||
{
|
||||
MeshCollider collidedMesh = collision.collider as MeshCollider;
|
||||
if (collidedMesh)
|
||||
{
|
||||
FImp_ColliderData_Mesh.PushOutFromMeshCollider(collidedMesh, collision, thisCollRadius, ref pos);
|
||||
}
|
||||
else // If we collide terrain we can calculate very precise segment offset because terrain not rotates
|
||||
{
|
||||
TerrainCollider terrain = collision.collider as TerrainCollider;
|
||||
FImp_ColliderData_Terrain.PushOutFromTerrain(terrain, thisCollRadius, ref pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Refreshing colliders data for included colliders when it's needed
|
||||
/// </summary>
|
||||
public void RefreshIncludedCollidersDataList()
|
||||
{
|
||||
bool refr = false;
|
||||
|
||||
if (CollisionMode == ECollisionMode.m_3DCollision)
|
||||
{
|
||||
if (IncludedColliders.Count != IncludedCollidersData.Count || forceRefreshCollidersData)
|
||||
{
|
||||
IncludedCollidersData.Clear();
|
||||
|
||||
for (int i = IncludedColliders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (IncludedColliders[i] == null) { IncludedColliders.RemoveAt(i); continue; }
|
||||
FImp_ColliderData_Base colData = FImp_ColliderData_Base.GetColliderDataFor(IncludedColliders[i]);
|
||||
IncludedCollidersData.Add(colData);
|
||||
}
|
||||
|
||||
refr = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IncludedColliders2D.Count != IncludedCollidersData.Count || forceRefreshCollidersData)
|
||||
{
|
||||
IncludedCollidersData.Clear();
|
||||
|
||||
for (int i = IncludedColliders2D.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (IncludedColliders2D[i] == null) { IncludedColliders2D.RemoveAt(i); continue; }
|
||||
FImp_ColliderData_Base colData = FImp_ColliderData_Base.GetColliderDataFor(IncludedColliders2D[i]);
|
||||
IncludedCollidersData.Add(colData);
|
||||
}
|
||||
|
||||
refr = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (refr) forceRefreshCollidersData = false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Pushing tail segment from detected collider
|
||||
/// </summary>
|
||||
public bool PushIfSegmentInsideCollider(TailSegment bone, ref Vector3 targetPoint)
|
||||
{
|
||||
bool pushed = false;
|
||||
|
||||
if (!CheapCollision) // Detailed Collision
|
||||
{
|
||||
for (int i = 0; i < CollidersDataToCheck.Count; i++)
|
||||
{
|
||||
bool push = CollidersDataToCheck[i].PushIfInside(ref targetPoint, bone.GetRadiusScaled(), Vector3.zero);
|
||||
if (!pushed) if (push)
|
||||
{
|
||||
pushed = true;
|
||||
bone.LatestSelectiveCollision = CollidersDataToCheck[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < CollidersDataToCheck.Count; i++)
|
||||
{
|
||||
if (CollidersDataToCheck[i].PushIfInside(ref targetPoint, bone.GetRadiusScaled(), Vector3.zero))
|
||||
{
|
||||
bone.LatestSelectiveCollision = CollidersDataToCheck[i];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pushed;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating automatically scale for colliders on tail, which will be automatically assigned after initialization
|
||||
/// </summary>
|
||||
protected float GetColliderSphereRadiusFor(int i)
|
||||
{
|
||||
TailSegment tailPoint = TailSegments[i];
|
||||
float refDistance = 1f;
|
||||
|
||||
if (i >= _TransformsGhostChain.Count) return refDistance;
|
||||
|
||||
if (_TransformsGhostChain.Count > 1)
|
||||
{
|
||||
refDistance = Vector3.Distance(_TransformsGhostChain[1].position, _TransformsGhostChain[0].position);
|
||||
}
|
||||
|
||||
float singleScale = refDistance;
|
||||
if (i != 0) singleScale = Mathf.Lerp(refDistance, Vector3.Distance(_TransformsGhostChain[i - 1].position, _TransformsGhostChain[i].position) * 0.5f, CollisionsAutoCurve);
|
||||
|
||||
float div = _TransformsGhostChain.Count - 1;
|
||||
if (div <= 0f) div = 1f;
|
||||
float step = 1f / div;
|
||||
|
||||
return 0.5f * singleScale * CollidersScaleMul * CollidersScaleCurve.Evaluate(step * (float)i);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating automatically scale for colliders on tail, which will be automatically assigned after initialization
|
||||
/// </summary>
|
||||
protected float GetColliderSphereRadiusFor(List<Transform> transforms, int i)
|
||||
{
|
||||
float refDistance = 1f;
|
||||
if (transforms.Count > 1) refDistance = Vector3.Distance(_TransformsGhostChain[1].position, _TransformsGhostChain[0].position);
|
||||
|
||||
float nextDistance = refDistance;
|
||||
if (i != 0) nextDistance = Vector3.Distance(_TransformsGhostChain[i - 1].position, _TransformsGhostChain[i].position);
|
||||
|
||||
float singleScale = Mathf.Lerp(refDistance, nextDistance * 0.5f, CollisionsAutoCurve);
|
||||
float step = 1f / (float)(transforms.Count - 1);
|
||||
return 0.5f * singleScale * CollidersScaleMul * CollidersScaleCurve.Evaluate(step * (float)i);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adding collider to included colliders list
|
||||
/// </summary>
|
||||
public void AddCollider(Collider collider)
|
||||
{
|
||||
if (IncludedColliders.Contains(collider)) return;
|
||||
IncludedColliders.Add(collider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adding collider to included colliders list
|
||||
/// </summary>
|
||||
public void AddCollider(Collider2D collider)
|
||||
{
|
||||
if (IncludedColliders2D.Contains(collider)) return;
|
||||
IncludedColliders2D.Add(collider);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checking if colliders list don't have duplicates
|
||||
/// </summary>
|
||||
public void CheckForColliderDuplicatesAndNulls()
|
||||
{
|
||||
for (int i = 0; i < IncludedColliders.Count; i++)
|
||||
{
|
||||
Collider col = IncludedColliders[i];
|
||||
int count = IncludedColliders.Count(o => o == col);
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
IncludedColliders.RemoveAll(o => o == col);
|
||||
IncludedColliders.Add(col);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
for (int i = IncludedColliders.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (IncludedColliders[i] == null) IncludedColliders.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void CheckForColliderDuplicatesAndNulls2D()
|
||||
{
|
||||
for (int i = 0; i < IncludedColliders2D.Count; i++)
|
||||
{
|
||||
Collider2D col = IncludedColliders2D[i];
|
||||
int count = IncludedColliders2D.Count(o => o == col);
|
||||
|
||||
if (count > 1)
|
||||
{
|
||||
IncludedColliders2D.RemoveAll(o => o == col);
|
||||
IncludedColliders2D.Add(col);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void TailCalculations_ComputeSegmentCollisions(TailSegment bone, ref Vector3 position)
|
||||
{
|
||||
// Computing collision contact timer
|
||||
if (bone.CollisionContactFlag) bone.CollisionContactFlag = false;
|
||||
else if (bone.CollisionContactRelevancy > 0f) bone.CollisionContactRelevancy -= justDelta;
|
||||
|
||||
if (CollisionSpace == ECollisionSpace.Selective_Fast)
|
||||
{
|
||||
// Setting collision contact flag
|
||||
if (PushIfSegmentInsideCollider(bone, ref position))
|
||||
{
|
||||
bone.CollisionContactFlag = true;
|
||||
bone.CollisionContactRelevancy = justDelta * 7f;
|
||||
bone.ChildBone.CollisionContactRelevancy = Mathf.Max(bone.ChildBone.CollisionContactRelevancy, justDelta * 3.5f);
|
||||
if (bone.ChildBone.ChildBone != null) bone.ChildBone.ChildBone.CollisionContactRelevancy = Mathf.Max(bone.ChildBone.CollisionContactRelevancy, justDelta * 3f);
|
||||
//bone.ParentBone.CollisionContactRelevancy = Mathf.Max(bone.ParentBone.CollisionContactRelevancy, delta * 3f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Setting collision contact flag
|
||||
if (UseCollisionContact(bone.Index, ref position))
|
||||
{
|
||||
bone.CollisionContactFlag = true;
|
||||
bone.CollisionContactRelevancy = justDelta * 7f;
|
||||
bone.ChildBone.CollisionContactRelevancy = Mathf.Max(bone.ChildBone.CollisionContactRelevancy, justDelta * 3.5f);
|
||||
if (bone.ChildBone.ChildBone != null) bone.ChildBone.ChildBone.CollisionContactRelevancy = Mathf.Max(bone.ChildBone.CollisionContactRelevancy, justDelta * 3f);
|
||||
//bone.ParentBone.CollisionContactRelevancy = Mathf.Max(bone.ParentBone.CollisionContactRelevancy, delta * 2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d17f10109a8683944b551359096896ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,195 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
public bool UseSlitheryCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0f, 1f, 1.2f, .1f, 0.8f, 1f, 0.9f)]
|
||||
public AnimationCurve SlitheryCurve = AnimationCurve.EaseInOut(0f, .75f, 1f, 1f);
|
||||
float lastSlithery = -1f;
|
||||
Keyframe[] lastSlitheryCurvKeys;
|
||||
|
||||
public bool UseCurlingCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0f, 1f, 1f, .65f, 0.4f, 1f, 0.9f)]
|
||||
public AnimationCurve CurlingCurve = AnimationCurve.EaseInOut(0f, .7f, 1f, 0.3f);
|
||||
float lastCurling = -1f;
|
||||
Keyframe[] lastCurlingCurvKeys;
|
||||
|
||||
public bool UseSpringCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.9f, 0.7f, 0.2f, 0.9f)]
|
||||
public AnimationCurve SpringCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 0f);
|
||||
float lastSpringiness = -1f;
|
||||
Keyframe[] lastSpringCurvKeys;
|
||||
|
||||
public bool UseSlipperyCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.2f, 0.9f, 0.6f, 0.9f)]
|
||||
public AnimationCurve SlipperyCurve = AnimationCurve.EaseInOut(0f, .7f, 1f, 1f);
|
||||
float lastSlippery = -1f;
|
||||
Keyframe[] lastSlipperyCurvKeys;
|
||||
|
||||
public bool UsePosSpeedCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, .2f, 1f, 0.3f, .9f)]
|
||||
public AnimationCurve PosCurve = AnimationCurve.EaseInOut(0f, .7f, 1f, 1f);
|
||||
float lastPosSpeeds = -1f;
|
||||
Keyframe[] lastPosCurvKeys;
|
||||
|
||||
public bool UseRotSpeedCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.7f, 0.7f, 0.7f, 0.9f)]
|
||||
public AnimationCurve RotCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, 0.9f);
|
||||
float lastRotSpeeds = -1f;
|
||||
Keyframe[] lastRotCurvKeys;
|
||||
|
||||
|
||||
[Tooltip("Spreading Tail Animator motion weight over bones")]
|
||||
public bool UsePartialBlend = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.2f, .5f, 0.85f)]
|
||||
public AnimationCurve BlendCurve = AnimationCurve.EaseInOut(0f, .95f, 1f, .45f);
|
||||
float lastTailAnimatorAmount = -1f;
|
||||
Keyframe[] lastBlendCurvKeys;
|
||||
TailSegment _ex_bone;
|
||||
|
||||
|
||||
void ExpertParamsUpdate()
|
||||
{
|
||||
Expert_UpdatePosSpeed();
|
||||
Expert_UpdateRotSpeed();
|
||||
Expert_UpdateSpringiness();
|
||||
Expert_UpdateSlithery();
|
||||
Expert_UpdateCurling();
|
||||
Expert_UpdateSlippery();
|
||||
Expert_UpdateBlending();
|
||||
}
|
||||
|
||||
|
||||
void ExpertCurvesEndUpdate()
|
||||
{
|
||||
lastPosSpeeds = ReactionSpeed;
|
||||
if (!UsePosSpeedCurve) if (lastPosCurvKeys != null) { lastPosCurvKeys = null; lastPosSpeeds += 0.001f; }
|
||||
|
||||
lastRotSpeeds = RotationRelevancy;
|
||||
if (!UseRotSpeedCurve) if (lastRotCurvKeys != null) { lastRotCurvKeys = null; lastRotSpeeds += 0.001f; }
|
||||
|
||||
lastSpringiness = Springiness;
|
||||
if (!UseSpringCurve) if (lastSpringCurvKeys != null) { lastSpringCurvKeys = null; lastSpringiness += 0.001f; }
|
||||
|
||||
lastSlithery = Slithery;
|
||||
if (!UseSlitheryCurve) if (lastSlitheryCurvKeys != null) { lastSlitheryCurvKeys = null; lastSlithery += 0.001f; }
|
||||
|
||||
lastCurling = Curling;
|
||||
if (!UseCurlingCurve) if (lastCurlingCurvKeys != null) { lastCurlingCurvKeys = null; lastCurling += 0.001f; }
|
||||
|
||||
lastSlippery = CollisionSlippery;
|
||||
if (!UseSlipperyCurve) if (lastSlipperyCurvKeys != null) { lastSlipperyCurvKeys = null; lastSlippery += 0.001f; }
|
||||
|
||||
lastTailAnimatorAmount = TailAnimatorAmount;
|
||||
if (!UsePartialBlend) if (lastBlendCurvKeys != null) { lastBlendCurvKeys = null; lastTailAnimatorAmount += 0.001f; }
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdatePosSpeed()
|
||||
{
|
||||
if (UsePosSpeedCurve)
|
||||
{
|
||||
//if (KeysChanged(PosCurve.keys, lastPosCurvKeys)) // for (int i = 0; i < TailBones.Count; i++) TailBones[i].PositionSpeed = GetValueFromCurve(i, PosCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.PositionSpeed = PosCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastPosSpeeds != ReactionSpeed) // for (int i = 0; i < TailBones.Count; i++) TailBones[i].PositionSpeed = ReactionSpeed;
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.PositionSpeed = ReactionSpeed; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateRotSpeed()
|
||||
{
|
||||
if (UseRotSpeedCurve)
|
||||
{
|
||||
//if (KeysChanged(RotCurve.keys, lastRotCurvKeys)) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].RotationSpeed = GetValueFromCurve(i, RotCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.RotationSpeed = RotCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastRotSpeeds != RotationRelevancy) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].RotationSpeed = RotationRelevancy;
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.RotationSpeed = RotationRelevancy; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateSpringiness()
|
||||
{
|
||||
if (UseSpringCurve)
|
||||
{
|
||||
//if (KeysChanged(SpringCurve.keys, lastSpringCurvKeys)) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Springiness = GetValueFromCurve(i, SpringCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Springiness = SpringCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastSpringiness != Springiness) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Springiness = Springiness;
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Springiness = Springiness; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateSlithery()
|
||||
{
|
||||
if (UseSlitheryCurve)
|
||||
{
|
||||
//if (KeysChanged(SlitheryCurve, lastSlitheryCurvKeys)) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Slithery = GetValueFromCurve(i, SlitheryCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Slithery = SlitheryCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastSlithery != Slithery)
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Slithery = Slithery; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateCurling()
|
||||
{
|
||||
if (UseCurlingCurve)
|
||||
{
|
||||
//if (KeysChanged(CurlingCurve.keys, lastCurlingCurvKeys)) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Curling = GetValueFromCurve(i, CurlingCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Curling = CurlingCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastCurling != Curling)
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Curling = Curling; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateSlippery()
|
||||
{
|
||||
if (UseSlipperyCurve)
|
||||
{
|
||||
//if (KeysChanged(SlipperyCurve.keys, lastSlipperyCurvKeys)) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Slippery = GetValueFromCurve(i, SlipperyCurve);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Slippery = SlipperyCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastSlippery != CollisionSlippery) //for (int i = 0; i < TailBones.Count; i++) TailBones[i].Slippery = CollisionSlippery;
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.Slippery = CollisionSlippery; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Expert_UpdateBlending()
|
||||
{
|
||||
if (UsePartialBlend)
|
||||
{
|
||||
//if (KeysChanged(BlendCurve.keys, lastBlendCurvKeys)) // for (int i = 0; i < TailBones.Count; i++) TailBones[i].BlendValue = GetValueFromCurve(i, BlendCurve);//Mathf.Clamp(Mathf.Pow(GetValueFromCurve(i, BlendCurve), .3f /*3*/), 0f, 1.5f);
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.BlendValue = BlendCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastTailAnimatorAmount != TailAnimatorAmount)
|
||||
{ _ex_bone = TailSegments[0]; while (_ex_bone != null) { _ex_bone.BlendValue = TailAnimatorAmount; _ex_bone = _ex_bone.ChildBone; } }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18157dba56a9a3f42a851b679615af5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,236 @@
|
||||
using FIMSpace.FTools;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
// IK Definition
|
||||
public bool UseIK = false;
|
||||
bool ikInitialized = false;
|
||||
[SerializeField] FIK_CCDProcessor IK;
|
||||
|
||||
// IK Parameters
|
||||
[Tooltip("Target object to follow by IK")]
|
||||
public Transform IKTarget;
|
||||
[Tooltip("Making end bone be rotated as IK target object")]
|
||||
[Range(0f, 1f)] public float IKAlignEndWithTarget = 0f;
|
||||
|
||||
public bool IKAutoWeights = true;
|
||||
[Range(0f, 1f)]
|
||||
public float IKBaseReactionWeight = .65f;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.2f, .5f, 0.85f)]
|
||||
public AnimationCurve IKReactionWeightCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, .25f);
|
||||
|
||||
public bool IKAutoAngleLimits = true;
|
||||
//[Range(0f, 181f)]
|
||||
[FPD_Suffix(0, 181, FPD_SuffixAttribute.SuffixMode.FromMinToMaxRounded, "°")]
|
||||
public float IKAutoAngleLimit = 40f;
|
||||
[Tooltip("If ik process should work referencing to previously computed CCDIK pose (can be more precise but need more adjusting in weights and angle limits)")]
|
||||
public bool IKContinousSolve = false;
|
||||
[Tooltip("Inverting ik iteration order to generate different pose results - more straight towards target")]
|
||||
public bool IkInvertOrder = false;
|
||||
|
||||
[FPD_Suffix(0f, 1f)]
|
||||
[Tooltip("How much IK motion sohuld be used in tail animator motion -> 0: turned off")]
|
||||
public float IKBlend = 1f;
|
||||
[FPD_Suffix(0f, 1f)]
|
||||
[Tooltip("If syncing with animator then applying motion of keyframe animation for IK")]
|
||||
public float IKAnimatorBlend = 0.5f;
|
||||
|
||||
[Range(1, 32)]
|
||||
[Tooltip("How much iterations should do CCDIK algorithm in one frame")]
|
||||
public int IKReactionQuality = 2;
|
||||
[Range(0f, 1f)]
|
||||
[Tooltip("Smoothing reactions in CCD IK algorithm")]
|
||||
public float IKSmoothing = .0f;
|
||||
[Range(0f, 1.5f)]
|
||||
public float IKStretchToTarget = 0f;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.9f, .4f, 0.5f)]
|
||||
public AnimationCurve IKStretchCurve = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
|
||||
public List<IKBoneSettings> IKLimitSettings;
|
||||
public bool IKSelectiveChain = false;
|
||||
|
||||
void InitIK()
|
||||
{
|
||||
if (IKSelectiveChain == false)
|
||||
IK = new FIK_CCDProcessor(_TransformsGhostChain.ToArray());
|
||||
else
|
||||
{
|
||||
List<Transform> ikChain = new List<Transform>();
|
||||
if (IKLimitSettings.Count != _TransformsGhostChain.Count) ikChain = _TransformsGhostChain;
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _TransformsGhostChain.Count; i++)
|
||||
{
|
||||
if (IKLimitSettings[i].UseInChain)
|
||||
ikChain.Add(_TransformsGhostChain[i]);
|
||||
}
|
||||
}
|
||||
|
||||
IK = new FIK_CCDProcessor(ikChain.ToArray());
|
||||
}
|
||||
|
||||
if (IKAutoWeights) IK.AutoWeightBones(IKBaseReactionWeight); else IK.AutoWeightBones(IKReactionWeightCurve);
|
||||
if (IKAutoAngleLimits) IK.AutoLimitAngle(IKAutoAngleLimit, 4f + IKAutoAngleLimit / 15f);
|
||||
|
||||
if (IKSelectiveChain == false)
|
||||
IK.Init(_TransformsGhostChain[0]);
|
||||
else
|
||||
IK.Init(IK.Bones[0].transform);
|
||||
|
||||
ikInitialized = true;
|
||||
|
||||
IK_ApplyLimitBoneSettings();
|
||||
}
|
||||
|
||||
|
||||
Vector3? _IKCustomPos = null;
|
||||
public void IKSetCustomPosition(Vector3? tgt)
|
||||
{
|
||||
_IKCustomPos = tgt;
|
||||
}
|
||||
|
||||
void UpdateIK()
|
||||
{
|
||||
if (!ikInitialized) InitIK();
|
||||
if (IKBlend <= Mathf.Epsilon) return;
|
||||
|
||||
if (_IKCustomPos != null) IK.IKTargetPosition = _IKCustomPos.Value;
|
||||
else
|
||||
{
|
||||
if (IKTarget == null) IK.IKTargetPosition = TailSegments[TailSegments.Count - 1].ProceduralPosition;
|
||||
else IK.IKTargetPosition = IKTarget.position;
|
||||
}
|
||||
|
||||
IK.Invert = IkInvertOrder;
|
||||
IK.IKWeight = IKBlend;
|
||||
IK.SyncWithAnimator = IKAnimatorBlend; //else IK.SyncWithAnimator = 0f;
|
||||
IK.ReactionQuality = IKReactionQuality;
|
||||
IK.Smoothing = IKSmoothing;
|
||||
IK.StretchToTarget = IKStretchToTarget;
|
||||
IK.StretchCurve = IKStretchCurve;
|
||||
IK.ContinousSolving = IKContinousSolve;
|
||||
if (IK.StretchToTarget > 0f) IK.ContinousSolving = false;
|
||||
if (Axis2D == 3) IK.Use2D = true; else IK.Use2D = false;
|
||||
|
||||
IK.Update();
|
||||
|
||||
if (IKAlignEndWithTarget > 0f && IKTarget != null && IK.Bones.Length > 1)
|
||||
{
|
||||
var lastBone = IK.Bones[IK.Bones.Length - 1];
|
||||
|
||||
#region Old faster code but for two axes only
|
||||
|
||||
//Quaternion targetRotation = Quaternion.FromToRotation((lastBone.transform.position - lastBone.transform.parent.position).normalized, IKTarget.forward);
|
||||
//targetRotation = targetRotation * lastBone.transform.rotation;
|
||||
//if (IKAlignEndWithTarget < 1f) targetRotation = Quaternion.Slerp(lastBone.transform.rotation, targetRotation, IKAlignEndWithTarget);
|
||||
//lastBone.transform.rotation = targetRotation;
|
||||
|
||||
#endregion
|
||||
|
||||
Vector3 boneForward = (lastBone.transform.position - lastBone.transform.parent.position).normalized;
|
||||
Vector3 boneUp = lastBone.transform.up;
|
||||
Vector3 targetForward = IKTarget.forward;
|
||||
Vector3 targetUp = IKTarget.up;
|
||||
|
||||
Quaternion boneRot = Quaternion.LookRotation(boneForward, boneUp);
|
||||
Quaternion targetRot = Quaternion.LookRotation(targetForward, targetUp);
|
||||
Quaternion delta = targetRot * Quaternion.Inverse(boneRot);
|
||||
|
||||
Quaternion finalRot = delta * lastBone.transform.rotation;
|
||||
if (IKAlignEndWithTarget < 1f) finalRot = Quaternion.Slerp(lastBone.transform.rotation, finalRot, IKAlignEndWithTarget);
|
||||
lastBone.transform.rotation = finalRot;
|
||||
}
|
||||
|
||||
if (DetachChildren)
|
||||
{
|
||||
TailSegment child = TailSegments[0];
|
||||
|
||||
child = TailSegments[1];
|
||||
if (IncludeParent == false)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation(child.InitialLocalPosition, child.InitialLocalRotation);
|
||||
child = TailSegments[2];
|
||||
}
|
||||
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation(child.InitialLocalPosition, child.InitialLocalRotation);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TailSegment child = TailSegments[0];
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation();
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// FC: Helper class to manage ik bones settings
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKBoneSettings
|
||||
{
|
||||
[Range(0f, 181f)]
|
||||
public float AngleLimit = 45f;
|
||||
[Range(0f, 181f)]
|
||||
public float TwistAngleLimit = 5f;
|
||||
|
||||
public bool UseInChain = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Applying changes to IK bones only when changes occuring
|
||||
/// </summary>
|
||||
public void IK_ApplyLimitBoneSettings()
|
||||
{
|
||||
if (!IKAutoAngleLimits)
|
||||
{
|
||||
if (IKLimitSettings.Count != _TransformsGhostChain.Count)
|
||||
IK_RefreshLimitSettingsContainer();
|
||||
|
||||
if (IK.IKBones.Length != IKLimitSettings.Count)
|
||||
{
|
||||
Debug.Log("[TAIL ANIMATOR IK] Wrong IK bone count!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IKAutoAngleLimits)
|
||||
{
|
||||
for (int i = 0; i < IKLimitSettings.Count; i++)
|
||||
{
|
||||
IK.IKBones[i].AngleLimit = IKLimitSettings[i].AngleLimit;
|
||||
IK.IKBones[i].TwistAngleLimit = IKLimitSettings[i].TwistAngleLimit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ikInitialized) if (IKAutoWeights) IK.AutoWeightBones(IKBaseReactionWeight); else IK.AutoWeightBones(IKReactionWeightCurve);
|
||||
if (IKAutoAngleLimits) IK.AutoLimitAngle(IKAutoAngleLimit, 10f + IKAutoAngleLimit / 10f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generating new IK Limit Settings list with the same length as ghost transforms chain
|
||||
/// </summary>
|
||||
public void IK_RefreshLimitSettingsContainer()
|
||||
{
|
||||
IKLimitSettings = new List<IKBoneSettings>();
|
||||
for (int i = 0; i < _TransformsGhostChain.Count; i++)
|
||||
{
|
||||
IKLimitSettings.Add(new IKBoneSettings());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adf0396b6095c7c4f93a78f8ee022984
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
// Positions Post Processing support calculations for effects like deflection
|
||||
// which needs baked position to correctly detect needed stuff for
|
||||
// every frame update
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
/// <summary> Always return chain with coordinates without any POST processes as reference for POST processing algorithms. Separated list of tail bones operating on the same transforms but without post processed coords </summary>
|
||||
private List<TailSegment> _pp_reference;
|
||||
|
||||
// Artificial bone points bakery
|
||||
private TailSegment _pp_ref_rootParent;
|
||||
private TailSegment _pp_ref_lastChild;
|
||||
|
||||
private bool _pp_initialized = false;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Return true if Tail Aniamtor is using feature which requires post processing support
|
||||
/// </summary>
|
||||
bool PostProcessingNeeded()
|
||||
{
|
||||
if (Deflection > Mathf.Epsilon) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post processing start frame calculations
|
||||
/// </summary>
|
||||
void PostProcessing_Begin()
|
||||
{
|
||||
TailSegments_UpdateCoordsForRootBone(_pp_reference[_tc_startI]);
|
||||
|
||||
// Deflection support
|
||||
if (Deflection > Mathf.Epsilon) Deflection_BeginUpdate();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Computing reference coordinates for POST processing
|
||||
/// </summary>
|
||||
void PostProcessing_ReferenceUpdate()
|
||||
{
|
||||
TailSegment child = _pp_reference[_tc_startI];
|
||||
|
||||
#region Prepare base positions calculation for tail segments to use in coords calculations and as reference
|
||||
|
||||
|
||||
while (child != _pp_ref_lastChild)
|
||||
{
|
||||
child.ParamsFrom(TailSegments[child.Index]); // Copying parameters settings from true bones
|
||||
TailSegment_PrepareVelocity(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
|
||||
// Udpate for artificial end bone
|
||||
TailSegment_PrepareMotionParameters(_pp_ref_lastChild);
|
||||
TailSegment_PrepareVelocity(_pp_ref_lastChild);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Processing segments, calculating full target coords and apply to transforms
|
||||
|
||||
child = _pp_reference[_tc_startII];
|
||||
|
||||
if (!DetachChildren)
|
||||
{
|
||||
while (child != _pp_ref_lastChild)
|
||||
{
|
||||
TailSegment_PrepareRotation(child);
|
||||
TailSegment_BaseSwingProcessing(child);
|
||||
TailCalculations_SegmentPreProcessingStack(child);
|
||||
TailSegment_PreRotationPositionBlend(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (child != _pp_ref_lastChild)
|
||||
{
|
||||
TailSegment_PrepareRotationDetached(child);
|
||||
TailSegment_BaseSwingProcessing(child);
|
||||
TailCalculations_SegmentPreProcessingStack(child);
|
||||
TailSegment_PreRotationPositionBlend(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
|
||||
// Applying processing for artificial child bone without transform
|
||||
TailCalculations_UpdateArtificialChildBone(_pp_ref_lastChild);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
child = _pp_reference[_tc_startII];
|
||||
while (child != _pp_ref_lastChild)
|
||||
{
|
||||
// Calculate rotation
|
||||
TailCalculations_SegmentRotation(child, child.LastKeyframeLocalPosition);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
|
||||
// If ghost child has transform let's apply motion too (change rotation of last bone)
|
||||
TailCalculations_SegmentRotation(child, child.LastKeyframeLocalPosition);
|
||||
child.ParentBone.RefreshFinalRot(child.ParentBone.TrueTargetRotation);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9655b675af6b2334ea694f30ff189f44
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
[Tooltip("Rotation offset for tail (just first (root) bone is rotated)")]
|
||||
public Quaternion RotationOffset = Quaternion.identity;
|
||||
|
||||
|
||||
[Tooltip("Rotate each segment a bit to create curving effect")]
|
||||
public Quaternion Curving = Quaternion.identity;
|
||||
[Tooltip("Spread curving rotation offset weight over tail segments")]
|
||||
public bool UseCurvingCurve = false;
|
||||
[FPD_FixedCurveWindow(0f, -1f, 1f, 1f, 0.75f, .75f, 0.75f, 0.85f)]
|
||||
public AnimationCurve CurvCurve = AnimationCurve.EaseInOut(0f, 0.75f, 1f, 1f);
|
||||
Quaternion lastCurving = Quaternion.identity;
|
||||
Keyframe[] lastCurvingKeys;
|
||||
|
||||
[Tooltip("Make tail longer or shorter")]
|
||||
public float LengthMultiplier = 1f;
|
||||
[Tooltip("Spread length multiplier weight over tail segments")]
|
||||
public bool UseLengthMulCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 3f)]
|
||||
public AnimationCurve LengthMulCurve = AnimationCurve.EaseInOut(0f, 1f, 1f, 1f);
|
||||
float lastLengthMul = 1f;
|
||||
Keyframe[] lastLengthKeys;
|
||||
|
||||
[Tooltip("Spread gravity weight over tail segments")]
|
||||
public bool UseGravityCurve = false;
|
||||
[FPD_FixedCurveWindow(0, 0, 1f, 1f, 0.85f, .35f, 0.25f, 0.85f)]
|
||||
[Tooltip("Spread gravity weight over tail segments")]
|
||||
public AnimationCurve GravityCurve = AnimationCurve.EaseInOut(0f, .65f, 1f, 1f);
|
||||
[Tooltip("Simulate gravity weight for tail logics")]
|
||||
public Vector3 Gravity = Vector3.zero;
|
||||
Vector3 lastGravity = Vector3.zero;
|
||||
Keyframe[] lastGravityKeys;
|
||||
|
||||
|
||||
void ShapingParamsUpdate()
|
||||
{
|
||||
Shaping_UpdateCurving();
|
||||
Shaping_UpdateGravity();
|
||||
Shaping_UpdateLengthMultiplier();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void Shaping_UpdateCurving()
|
||||
{
|
||||
if (!FEngineering.QIsZero(Curving))
|
||||
{
|
||||
if (UseCurvingCurve) // Operations with curve
|
||||
{
|
||||
_ex_bone = TailSegments[0];
|
||||
while (_ex_bone != null) { _ex_bone.Curving = Quaternion.LerpUnclamped(Quaternion.identity, Curving, CurvCurve.Evaluate(_ex_bone.IndexOverlLength)); _ex_bone = _ex_bone.ChildBone; }
|
||||
}
|
||||
else // Operations without curve
|
||||
{
|
||||
if (!FEngineering.QIsSame(Curving, lastCurving))
|
||||
for (int i = 0; i < TailSegments.Count; i++) TailSegments[i].Curving = Curving;
|
||||
}
|
||||
}
|
||||
else // Curving reset
|
||||
{
|
||||
if (!FEngineering.QIsSame(Curving, lastCurving))
|
||||
for (int i = 0; i < TailSegments.Count; i++) TailSegments[i].Curving = Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Shaping_UpdateGravity()
|
||||
{
|
||||
if (!FEngineering.VIsZero(Gravity))
|
||||
{
|
||||
if (UseGravityCurve) // Operations with curve
|
||||
{
|
||||
//if (!FEngineering.VIsSame(Gravity, lastGravity) || KeysChanged(GravityCurve.keys, lastGravityKeys))
|
||||
//{
|
||||
// for (int i = 0; i < TailSegments.Count; i++)
|
||||
// {
|
||||
// TailSegments[i].Gravity = Gravity * GetValueFromCurve(i, GravityCurve) / 40f;
|
||||
// TailSegments[i].Gravity *= (1 + ((TailSegments[i].Index / 2f) * (1f - TailSegments[i].Slithery)));
|
||||
// }
|
||||
//}
|
||||
|
||||
_ex_bone = TailSegments[0];
|
||||
while (_ex_bone != null) { _ex_bone.Gravity = Gravity * 40f * GravityCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; }
|
||||
}
|
||||
else // Operations without curve
|
||||
{
|
||||
if (!FEngineering.VIsSame(Gravity, lastGravity))
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
{
|
||||
TailSegments[i].Gravity = Gravity / 40f;
|
||||
TailSegments[i].Gravity *= (1 + ((TailSegments[i].Index / 2f) * (1f - TailSegments[i].Slithery)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Gravity reset
|
||||
{
|
||||
if (!FEngineering.VIsSame(Gravity, lastGravity))
|
||||
{
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
{
|
||||
TailSegments[i].Gravity = Vector3.zero;
|
||||
TailSegments[i].GravityLookOffset = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Shaping_UpdateLengthMultiplier()
|
||||
{
|
||||
if (UseLengthMulCurve)
|
||||
{
|
||||
//if (lastLengthMul != LengthMultiplier || KeysChanged(LengthMulCurve.keys, lastLengthKeys))
|
||||
//{
|
||||
// for (int i = 0; i < TailSegments.Count; i++) TailSegments[i].LengthMultiplier = /*LengthMultiplier */ GetValueFromCurve(i, LengthMulCurve);
|
||||
//}
|
||||
|
||||
_ex_bone = TailSegments[0];
|
||||
while (_ex_bone != null) { _ex_bone.LengthMultiplier = LengthMulCurve.Evaluate(_ex_bone.IndexOverlLength); _ex_bone = _ex_bone.ChildBone; }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastLengthMul != LengthMultiplier)
|
||||
for (int i = 0; i < TailSegments.Count; i++) TailSegments[i].LengthMultiplier = LengthMultiplier;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Remembering previous parameters to avoid triggering full tail iterations every frame
|
||||
/// </summary>
|
||||
void ShapingEndUpdate()
|
||||
{
|
||||
lastCurving = Curving;
|
||||
if (!UseCurvingCurve) if (lastCurvingKeys != null) { lastCurvingKeys = null; lastCurving.x += 0.001f; }
|
||||
//if (UseCurvingCurve) lastCurvingKeys = CurvCurve.keys; else if (lastCurvingKeys != null) { lastCurvingKeys = null; lastCurving.x += 0.001f; }
|
||||
|
||||
lastGravity = Gravity;
|
||||
if (!UseGravityCurve) if (lastGravityKeys != null) { lastGravityKeys = null; lastGravity.x += 0.001f; }
|
||||
//if (UseGravityCurve) lastGravityKeys = GravityCurve.keys; else if (lastGravityKeys != null) { lastGravityKeys = null; lastGravity.x += 0.001f; }
|
||||
|
||||
lastLengthMul = LengthMultiplier;
|
||||
if (!UseLengthMulCurve) if (lastLengthKeys != null) { lastLengthKeys = null; lastLengthMul += 0.0001f; }
|
||||
//if (UseLengthMulCurve) lastLengthKeys = LengthMulCurve.keys; else if (lastLengthKeys != null) { lastLengthKeys = null; lastLengthMul += 0.0001f; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9d872664813634c49b522c82ee361f4c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,165 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
|
||||
#region Inspector Variables
|
||||
|
||||
[Tooltip("Using auto waving option to give floating effect")]
|
||||
public bool UseWaving = true;
|
||||
|
||||
[Tooltip("Adding some variation to waving animation")]
|
||||
public bool CosinusAdd = false;
|
||||
|
||||
[Tooltip("If you want few tails to wave in the same way you can set this sinus period cycle value")]
|
||||
public float FixedCycle = 0f;
|
||||
|
||||
[Tooltip("How frequent swings should be")]
|
||||
public float WavingSpeed = 3f;
|
||||
[Tooltip("How big swings should be")]
|
||||
public float WavingRange = 0.8f;
|
||||
|
||||
[Tooltip("What rotation axis should be used in auto waving")]
|
||||
public Vector3 WavingAxis = new Vector3(1.0f, 1.0f, 1.0f);
|
||||
|
||||
public Quaternion WavingRotationOffset { get; private set; }
|
||||
|
||||
[Tooltip("Type of waving animation algorithm, it can be simple trigonometric wave or animation based on noises (advanced)")]
|
||||
public FEWavingType WavingType = FEWavingType.Advanced;
|
||||
public enum FEWavingType { Simple, Advanced }
|
||||
|
||||
[Tooltip("Offsetting perlin noise to generate different variation of tail rotations")]
|
||||
public float AlternateWave = 1f;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
// ------------------------ Auto Waving Section ------------------------ \\
|
||||
|
||||
/// <summary> Trigonometric function time variable </summary>
|
||||
float _waving_waveTime;
|
||||
float _waving_cosTime;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defining time variables for trigonometrics
|
||||
/// </summary>
|
||||
void Waving_Initialize()
|
||||
{
|
||||
if (FixedCycle == 0f)
|
||||
{
|
||||
_waving_waveTime = Random.Range(-Mathf.PI, Mathf.PI) * 100f;
|
||||
_waving_cosTime = Random.Range(-Mathf.PI, Mathf.PI) * 50f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_waving_waveTime = FixedCycle;
|
||||
_waving_cosTime = FixedCycle;
|
||||
}
|
||||
|
||||
_waving_sustain = Vector3.zero;
|
||||
//_waving_sustainVelo = Vector3.zero;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating trigonometric waving feature
|
||||
/// </summary>
|
||||
void Waving_AutoSwingUpdate()
|
||||
{
|
||||
// Defining base variables
|
||||
_waving_waveTime += justDelta * (2 * WavingSpeed);
|
||||
|
||||
// Simple trigonometrical waving
|
||||
if (WavingType == FEWavingType.Simple)
|
||||
{
|
||||
float sinVal = Mathf.Sin(_waving_waveTime) * (30f * WavingRange);
|
||||
|
||||
if (CosinusAdd)
|
||||
{
|
||||
_waving_cosTime += justDelta * (2.535f * WavingSpeed);
|
||||
sinVal += Mathf.Cos(_waving_cosTime) * (27f * WavingRange);
|
||||
}
|
||||
|
||||
WavingRotationOffset = Quaternion.Euler(sinVal * WavingAxis * TailSegments[0].BlendValue);
|
||||
}
|
||||
else
|
||||
// Advanced waving based on perlin noise
|
||||
{
|
||||
float perTime = _waving_waveTime * 0.23f;
|
||||
|
||||
float altX = AlternateWave * -5f;
|
||||
float altY = AlternateWave * 100f;
|
||||
float altZ = AlternateWave * 20f;
|
||||
|
||||
float x = Mathf.PerlinNoise(perTime, altX) * 2.0f - 1.0f;
|
||||
float y = Mathf.PerlinNoise(altY + perTime, perTime + altY) * 2.0f - 1.0f;
|
||||
float z = Mathf.PerlinNoise(altZ, perTime) * 2.0f - 1.0f;
|
||||
|
||||
WavingRotationOffset = Quaternion.Euler(Vector3.Scale(WavingAxis * WavingRange * 35f * TailSegments[0].BlendValue, new Vector3(x, y, z)));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// ------------------------ Sustain Section ------------------------ \\
|
||||
|
||||
|
||||
/// <summary> Sustain smooth damp helper variable </summary>
|
||||
//Vector3 _waving_sustainVelo = Vector3.zero;
|
||||
/// <summary> Sustain position offset for previous procedural position</summary>
|
||||
Vector3 _waving_sustain = Vector3.zero;
|
||||
|
||||
/// <summary>
|
||||
/// Calculating sustain feature offset
|
||||
/// </summary>
|
||||
void Waving_SustainUpdate()
|
||||
{
|
||||
TailSegment firstB = TailSegments[0];
|
||||
|
||||
// When tail is short and have few segments we have to ampify sustain effect behaviour and vice versa
|
||||
float tailRatio = (_TC_TailLength / (float)TailSegments.Count);
|
||||
tailRatio = Mathf.Pow(tailRatio, 1.65f);
|
||||
tailRatio = (_sg_curly / tailRatio) / 6f;
|
||||
|
||||
// Clamp extreme values
|
||||
if (tailRatio < 0.1f) tailRatio = 0.1f; else if (tailRatio > 1f) tailRatio = 1f;
|
||||
|
||||
int mid = (int)Mathf.LerpUnclamped(TailSegments.Count * 0.4f, TailSegments.Count * 0.6f, Sustain);
|
||||
|
||||
float susFact = FEasing.EaseOutExpo(1f, 0.09f, Sustain);
|
||||
|
||||
float mild = 1.5f;
|
||||
mild *= (1f - TailSegments[0].Curling / 8f);
|
||||
mild *= (1.5f - tailRatio / 1.65f);
|
||||
mild *= Mathf.Lerp(0.7f, 1.2f, firstB.Slithery);
|
||||
mild *= FEasing.EaseOutExpo(1f, susFact, firstB.Springiness);
|
||||
|
||||
Vector3 velo = TailSegments[mid].PreviousPush;
|
||||
if (mid + 1 < TailSegments.Count) velo += TailSegments[mid + 1].PreviousPush;
|
||||
if (mid - 1 > TailSegments.Count) velo += TailSegments[mid - 1].PreviousPush;
|
||||
|
||||
|
||||
_waving_sustain = velo * Sustain * mild * 2f;
|
||||
|
||||
#region Backup
|
||||
//_waving_sustain = Vector3.Lerp(_waving_sustain, TailBones[mid].PreviousPush * Sustain * mild, unifiedDelta);
|
||||
//Vector3.SmoothDamp
|
||||
//(
|
||||
//_waving_sustain,
|
||||
//TailBones[mid].PreviousPush * Sustain * mild,
|
||||
//ref _waving_sustainVelo,
|
||||
//Mathf.LerpUnclamped(0.15f + tailRatio / 5f, 0.1f + tailRatio / 5f, Sustain),
|
||||
//Mathf.Infinity, Time.smoothDeltaTime);
|
||||
|
||||
//TailBones[Mathf.Min(TailBones.Count - 1, _tc_startII)].PreviousPosition += _waving_sustain * mild;
|
||||
#endregion
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcfde61a5f0bcac4fac26bf2152d6a2e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,26 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
public bool UseWind = false;
|
||||
[FPD_Suffix(0f, 2.5f, FPD_SuffixAttribute.SuffixMode.PercentageUnclamped)]
|
||||
public float WindEffectPower = 1f;
|
||||
[FPD_Suffix(0f, 2.5f, FPD_SuffixAttribute.SuffixMode.PercentageUnclamped)]
|
||||
public float WindTurbulencePower = 1f;
|
||||
[FPD_Suffix(0f, 1.5f, FPD_SuffixAttribute.SuffixMode.PercentageUnclamped)]
|
||||
public float WindWorldNoisePower = 0.5f;
|
||||
|
||||
/// <summary> Wind power vector used by Tail Animator Wind component </summary>
|
||||
public Vector3 WindEffect = Vector3.zero;
|
||||
|
||||
void WindEffectUpdate()
|
||||
{
|
||||
if (TailAnimatorWind.Instance)
|
||||
{
|
||||
TailAnimatorWind.Instance.AffectTailWithWind(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfa827566ed68c5469e8b78e674d1d89
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,424 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using FIMSpace.FEditor;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
[FPD_Header("Debug Switch")]
|
||||
[SerializeField]
|
||||
private bool fDebug = false;
|
||||
|
||||
public Transform _gizmosEditorStartPreview;
|
||||
public Transform _gizmospesp;
|
||||
public Transform _gizmosEditorEndPreview;
|
||||
public Transform _gizmospeep;
|
||||
|
||||
public string _editor_Title = "Tail Animator 2";
|
||||
public bool _editor_IsInspectorViewingColliders = true;
|
||||
public bool _editor_IsInspectorViewingIncludedColliders = false;
|
||||
public int _editor_animatorViewedCounter = -1;
|
||||
|
||||
void OnDrawGizmosSelected()
|
||||
{
|
||||
RefreshTransformsList();
|
||||
if (_TransformsGhostChain.Count == 0) return;
|
||||
if (!DrawGizmos) return;
|
||||
|
||||
if (UseIK) Gizmos_DrawIK();
|
||||
|
||||
if (UseMaxDistance) Gizmos_DrawMaxDistance();
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
if (!initialized) return;
|
||||
|
||||
if (!IncludeParent) Editor_TailCalculations_RefreshArtificialParentBone();
|
||||
|
||||
if (_Editor_Category == ETailCategory.Shaping || fDebug)
|
||||
{
|
||||
Handles.color = new Color(0.2f, .2f, 0.85f, 0.5f);
|
||||
Handles.SphereHandleCap(0, TailSegments[0].transform.position, Quaternion.identity, HandleUtility.GetHandleSize(TailSegments[0].transform.position) * 0.05f, EventType.Repaint);
|
||||
|
||||
Handles.color = new Color(0.5f, 1f, 0.35f, 0.3f);
|
||||
|
||||
for (int i = 1; i < TailSegments.Count; i++)
|
||||
{
|
||||
Handles.SphereHandleCap(0, TailSegments[i].ProceduralPosition, Quaternion.identity, HandleUtility.GetHandleSize(TailSegments[i].ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
Handles.DrawDottedLine(TailSegments[i].ProceduralPosition, TailSegments[i].ParentBone.ProceduralPosition, 2f);
|
||||
}
|
||||
|
||||
if (IncludeParent) Handles.color = new Color(1f, .2f, 0.6f, 1f);
|
||||
Handles.DrawDottedLine(TailSegments[0].ProceduralPosition, TailSegments[0].ParentBone.ProceduralPosition, 2f);
|
||||
|
||||
Handles.color = new Color(1f, .2f, 0.6f, 1f);
|
||||
Handles.DrawDottedLine(GhostChild.ProceduralPosition, GhostChild.ParentBone.ProceduralPosition, 2f);
|
||||
|
||||
Handles.SphereHandleCap(0, GhostParent.ProceduralPosition, Quaternion.identity, HandleUtility.GetHandleSize(GhostParent.ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
Handles.SphereHandleCap(0, GhostChild.ProceduralPosition, Quaternion.identity, HandleUtility.GetHandleSize(GhostParent.ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
}
|
||||
|
||||
|
||||
if (fDebug)
|
||||
{
|
||||
Vector3 uoff;
|
||||
|
||||
if (PostProcessingNeeded() && _pp_initialized)
|
||||
{
|
||||
uoff = Vector3.up * _TC_TailLength * 0.065f;
|
||||
Gizmos.color = new Color(0.8f, 0.2f, 0.5f, 0.4f); // Post processing reference line (transparent dark pinnk)
|
||||
for (int i = 0; i < _pp_reference.Count; i++)
|
||||
{
|
||||
Gizmos.DrawSphere(_pp_reference[i].ProceduralPosition, .3f);
|
||||
|
||||
Gizmos.DrawLine(_pp_reference[i].ParentBone.ProceduralPosition + uoff, _pp_reference[i].ProceduralPosition + uoff);
|
||||
}
|
||||
|
||||
Handles.color = new Color(0.8f, 0.2f, 0.5f, 0.4f); // Deflection source segment sphere
|
||||
for (int i = 0; i < _defl_source.Count; i++)
|
||||
Handles.SphereHandleCap(0, _defl_source[i].ParentBone.ProceduralPosition + uoff, Quaternion.identity, HandleUtility.GetHandleSize(_defl_source[i].ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
|
||||
Handles.color = new Color(0.55f, .2f, 0.8f, 0.6f); // Deflection source segment sphere
|
||||
for (int i = 0; i < _defl_source.Count; i++)
|
||||
Handles.SphereHandleCap(0, Vector3.LerpUnclamped(TailSegments[_defl_source[i].Index].ParentBone.ProceduralPosition, TailSegments[_defl_source[i].Index].ProceduralPosition, 0.5f) + uoff, Quaternion.identity, HandleUtility.GetHandleSize(_defl_source[i].ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
|
||||
|
||||
#region Debug
|
||||
|
||||
//Handles.color = new Color(0.55f, .2f, 0.8f, 0.8f);
|
||||
//for (int i = 0; i < _defl_source.Count; i++)
|
||||
// Handles.DrawDottedLine(_defl_source[i].ProceduralPosition + uoff, _defl_source[i].DeflectionWorldPosition + uoff, 2f);
|
||||
|
||||
//Handles.color = new Color(0.0f, 1f, 1f, 0.8f);
|
||||
//for (int i = 0; i < _defl_source.Count; i++)
|
||||
//{
|
||||
// int backRange = _tc_startI; if (i > 0) backRange = _defl_source[i - 1].Index;
|
||||
|
||||
// for (int k = _defl_source[i].Index; k >= backRange; k--)
|
||||
// Handles.DrawDottedLine(TailBones[k].ProceduralPosition + uoff, _defl_source[i].DeflectionWorldPosition + uoff, 2f);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
Gizmos.color = new Color(1f, 0.1f, 0.35f, 0.6f); // Deflection push focus (magenta)
|
||||
for (int i = 0; i < _defl_source.Count; i++)
|
||||
{
|
||||
Gizmos.DrawLine(_defl_source[i].DeflectionWorldPosition + uoff, _defl_source[i].ParentBone.ProceduralPosition + uoff);
|
||||
//Handles.Label(_pp_reference[_defl_source[i].Index].ParentBone.ProceduralPosition + uoff + transform.right * _tc_tailLength * 0.032f + uoff, "b.par=[" + _defl_source[i].Index + "]");
|
||||
Handles.DrawDottedLine(_pp_reference[_defl_source[i].Index].ParentBone.ProceduralPosition + uoff + transform.right * _TC_TailLength * 0.032f + uoff, TailSegments[_defl_source[i].Index].ProceduralPosition + uoff, 3f);
|
||||
}
|
||||
|
||||
|
||||
uoff = Vector3.up * _TC_TailLength * 0.044f;
|
||||
Gizmos.color = new Color(0.2f, 0.9f, 0.4f, 0.5f); // True position line
|
||||
for (int i = 0; i < _pp_reference.Count; i++)
|
||||
Gizmos.DrawLine(TailSegments[i].ParentBone.ProceduralPosition + uoff, TailSegments[i].ProceduralPosition + uoff);
|
||||
|
||||
Gizmos.DrawLine(_pp_ref_lastChild.ParentBone.ProceduralPosition + uoff, _pp_ref_lastChild.ProceduralPosition + uoff);
|
||||
|
||||
|
||||
// All segments debug draw
|
||||
uoff = Vector3.up * _TC_TailLength * 0.065f;
|
||||
Handles.color = new Color(1f, 1f, 1f, 0.4f);
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
{
|
||||
Handles.SphereHandleCap(0, TailSegments[i].ProceduralPosition, Quaternion.identity, HandleUtility.GetHandleSize(TailSegments[i].ProceduralPosition) * 0.09f, EventType.Repaint);
|
||||
Handles.Label(TailSegments[i].ProceduralPosition - transform.right * _TC_TailLength * 0.02f + uoff, "[" + i + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (_Editor_Category == ETailCategory.Setup)
|
||||
{
|
||||
if (_TransformsGhostChain != null)
|
||||
{
|
||||
if (_TransformsGhostChain.Count > 1)
|
||||
{
|
||||
|
||||
if (!IncludeParent) Handles.color = new Color(.9f, .4f, .7f, .9f); else Handles.color = new Color(0.5f, 1f, 0.35f, 0.8f);
|
||||
|
||||
if (_TransformsGhostChain.Count > 1)
|
||||
{
|
||||
FGUI_Handles.DrawBoneHandle(_TransformsGhostChain[0].position, _TransformsGhostChain[1].position, BaseTransform.forward, 1f);
|
||||
Handles.SphereHandleCap(0, _TransformsGhostChain[0].position, Quaternion.identity, HandleUtility.GetHandleSize(_TransformsGhostChain[0].position) * 0.09f, EventType.Repaint);
|
||||
}
|
||||
|
||||
|
||||
for (int i = 1; i < _TransformsGhostChain.Count - 1; i++) // -1 because we painting bones from i to i+1
|
||||
{
|
||||
Handles.color = new Color(0.5f, 1f, 0.35f, 0.8f);
|
||||
FGUI_Handles.DrawBoneHandle(_TransformsGhostChain[i].position, _TransformsGhostChain[i + 1].position, BaseTransform.forward, 1f);
|
||||
|
||||
Handles.color = new Color(0.5f, 1f, 0.35f, 0.3f);
|
||||
Handles.SphereHandleCap(0, _TransformsGhostChain[i].position, Quaternion.identity, HandleUtility.GetHandleSize(_TransformsGhostChain[i].position) * 0.09f, EventType.Repaint);
|
||||
}
|
||||
|
||||
if (_TransformsGhostChain.Count > 1)
|
||||
if (IncludeParent)
|
||||
{
|
||||
if (_TransformsGhostChain[0].parent)
|
||||
{
|
||||
Handles.color = new Color(1f, .2f, 0.6f, 0.3f);
|
||||
FGUI_Handles.DrawBoneHandle(_TransformsGhostChain[0].parent.position, _TransformsGhostChain[0].position, BaseTransform.forward, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_TransformsGhostChain.Count > 0)
|
||||
{
|
||||
if (_TransformsGhostChain[0].parent)
|
||||
{
|
||||
Transform t = _TransformsGhostChain[0]; Transform p = _TransformsGhostChain[0].parent;
|
||||
|
||||
Handles.color = new Color(0.8f, .8f, 0.2f, 0.8f);
|
||||
FGUI_Handles.DrawBoneHandle(_TransformsGhostChain[0].parent.position, _TransformsGhostChain[0].position, BaseTransform.forward, 1f);
|
||||
Handles.color = new Color(0.8f, .8f, 0.2f, 0.3f);
|
||||
Handles.SphereHandleCap(0, _TransformsGhostChain[0].position, Quaternion.identity, HandleUtility.GetHandleSize(_TransformsGhostChain[0].position) * 0.135f, EventType.Repaint);
|
||||
Handles.Label(t.position + Vector3.Cross(t.forward, p.forward).normalized * (t.position - p.position).magnitude / 2f, new GUIContent("[i]", "Tail chain with one bone setup - try using End Bone Offset in 'Setup' tab"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_TransformsGhostChain.Count < 2)
|
||||
{
|
||||
Handles.color = new Color(1f, 1f, 1f, 0.5f);
|
||||
Handles.SphereHandleCap(0, _TransformsGhostChain[0].position, Quaternion.identity, HandleUtility.GetHandleSize(_TransformsGhostChain[0].position) * 0.135f, EventType.Repaint);
|
||||
|
||||
if (_TransformsGhostChain.Count > 0)
|
||||
{
|
||||
if (!_TransformsGhostChain[0].parent)
|
||||
{
|
||||
Handles.color = new Color(1f, .2f, 0.2f, 0.8f);
|
||||
Vector3 infoPos = _TransformsGhostChain[0].position + (_TransformsGhostChain[0].up + _TransformsGhostChain[0].right) * 0.2f;
|
||||
Handles.DrawDottedLine(_TransformsGhostChain[0].position, infoPos, 2f);
|
||||
Handles.DrawDottedLine(_TransformsGhostChain[0].position, transform.position, 2f);
|
||||
Handles.Label(infoPos, new GUIContent(FGUI_Resources.Tex_Warning, "No parent in this bone, tail chain can't be created"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (_Editor_Category == ETailCategory.Setup)
|
||||
{
|
||||
Handles.color = new Color(1f, .2f, 0.2f, 0.8f);
|
||||
Gizmos_DrawEndBoneJoint();
|
||||
}
|
||||
|
||||
|
||||
if (_editor_IsInspectorViewingColliders && _Editor_Category == ETailCategory.Features) Gizmos_DrawColliders();
|
||||
}
|
||||
|
||||
|
||||
void Gizmos_DrawEndBoneJoint()
|
||||
{
|
||||
// Drawing auto end joint offset
|
||||
if (FEngineering.VIsZero(EndBoneJointOffset))
|
||||
{
|
||||
Transform t = _TransformsGhostChain[_TransformsGhostChain.Count - 1];
|
||||
Transform p = _TransformsGhostChain[_TransformsGhostChain.Count - 1].parent;
|
||||
|
||||
if (p) // Reference from parent
|
||||
{
|
||||
Vector3 worldOffset = t.position - p.position;
|
||||
|
||||
Handles.color = new Color(0.3f, .3f, 1f, 0.8f);
|
||||
FGUI_Handles.DrawBoneHandle(t.position, t.position + worldOffset, BaseTransform.forward, 1f);
|
||||
}
|
||||
else // Reference to child
|
||||
{
|
||||
if (t.childCount > 0)
|
||||
{
|
||||
Transform ch = _TransformsGhostChain[0].GetChild(0);
|
||||
Vector3 worldOffset = ch.position - t.position;
|
||||
FGUI_Handles.DrawBoneHandle(t.position, t.position + worldOffset, BaseTransform.forward, 1f);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Drawing custom joint offset
|
||||
else
|
||||
{
|
||||
Transform t = _TransformsGhostChain[_TransformsGhostChain.Count - 1];
|
||||
Handles.color = new Color(0.3f, .3f, 1f, 0.8f);
|
||||
FGUI_Handles.DrawBoneHandle(t.position, t.position + t.TransformVector(EndBoneJointOffset), BaseTransform.forward, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Gizmos_DrawColliders()
|
||||
{
|
||||
if (UseCollision)
|
||||
{
|
||||
if (IncludedColliders == null) IncludedColliders = new System.Collections.Generic.List<Collider>();
|
||||
|
||||
Color preCol = Gizmos.color;
|
||||
Gizmos.color = new Color(0.2f, 1f, 0.2f, 0.25f);
|
||||
|
||||
for (int i = 1; i < _TransformsGhostChain.Count - 1; i++)
|
||||
{
|
||||
if (_TransformsGhostChain[i] == null) continue;
|
||||
Matrix4x4 curr = Matrix4x4.TRS(_TransformsGhostChain[i].position, _TransformsGhostChain[i].rotation, _TransformsGhostChain[i].lossyScale);
|
||||
float radius = 1f;
|
||||
if (Application.isPlaying) radius = TailSegments[i].ColliderRadius; else radius = GetColliderSphereRadiusFor(_TransformsGhostChain, i);
|
||||
|
||||
if (CollidersType != 0)
|
||||
{
|
||||
if (CollisionSpace == ECollisionSpace.World_Slow)
|
||||
{
|
||||
float preRadius = 1f;
|
||||
if (Application.isPlaying) preRadius = TailSegments[i - 1].ColliderRadius; else preRadius = GetColliderSphereRadiusFor(_TransformsGhostChain, i - 1);
|
||||
|
||||
Gizmos.color = Color.HSVToRGB((float)i / (float)_TransformsGhostChain.Count, 0.9f, 0.9f) * new Color(1f, 1f, 1f, 0.55f);
|
||||
|
||||
Matrix4x4 pre = Matrix4x4.TRS(_TransformsGhostChain[i - 1].position, _TransformsGhostChain[i - 1].rotation, _TransformsGhostChain[i - 1].lossyScale);
|
||||
|
||||
Gizmos.DrawLine(pre.MultiplyPoint(Vector3.up * preRadius), curr.MultiplyPoint(Vector3.up * radius));
|
||||
Gizmos.DrawLine(pre.MultiplyPoint(-Vector3.up * preRadius), curr.MultiplyPoint(-Vector3.up * radius));
|
||||
Gizmos.DrawLine(pre.MultiplyPoint(Vector3.right * preRadius), curr.MultiplyPoint(Vector3.right * radius));
|
||||
Gizmos.DrawLine(pre.MultiplyPoint(Vector3.left * preRadius), curr.MultiplyPoint(Vector3.left * radius));
|
||||
|
||||
if (i % 2 == 0)
|
||||
{
|
||||
Gizmos.matrix = curr;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, radius);
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.matrix = curr;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, radius);
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.matrix = curr;
|
||||
Gizmos.DrawWireSphere(Vector3.zero, radius);
|
||||
Gizmos.matrix = Matrix4x4.identity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (CollisionSpace == ECollisionSpace.Selective_Fast)
|
||||
{
|
||||
if (_editor_IsInspectorViewingIncludedColliders)
|
||||
{
|
||||
Handles.color = new Color(0.4f, 1f, 0.25f, 0.22f);
|
||||
int c = Mathf.Min(IncludedColliders.Count, 10); // Drawing max 10 lines toward included colliders
|
||||
for (int i = 0; i < c; i++)
|
||||
{
|
||||
if (IncludedColliders[i] != null)
|
||||
{
|
||||
Handles.DrawDottedLine(_TransformsGhostChain[0].position, IncludedColliders[i].transform.position, 2f);
|
||||
Handles.SphereHandleCap(0, IncludedColliders[i].transform.position, Quaternion.identity, HandleUtility.GetHandleSize(IncludedColliders[i].transform.position) * 0.09f, EventType.Repaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Application.isPlaying)
|
||||
if (DynamicWorldCollidersInclusion)
|
||||
{
|
||||
float scaleRef = Vector3.Distance(_TransformsGhostChain[0].position, _TransformsGhostChain[_TransformsGhostChain.Count - 1].position);
|
||||
Transform collSource = _TransformsGhostChain[_TransformsGhostChain.Count / 2];
|
||||
Gizmos.DrawWireSphere(collSource.position, scaleRef * InclusionRadius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Gizmos.color = preCol;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Gizmos_DrawIK()
|
||||
{
|
||||
if (_TransformsGhostChain.Count < 2) return;
|
||||
if (_Editor_Category != ETailCategory.Features) return;
|
||||
if (_Editor_FeaturesCategory != ETailFeaturesCategory.IK) return;
|
||||
|
||||
Vector3 start = _TransformsGhostChain[0].position;
|
||||
Vector3 end = _TransformsGhostChain[_TransformsGhostChain.Count - 1].position;
|
||||
Vector3 targetIK;
|
||||
|
||||
if (IKTarget)
|
||||
{
|
||||
Handles.color = new Color(0f, 0.7f, 0.3f, 0.7f);
|
||||
targetIK = IKTarget.position;
|
||||
}
|
||||
else
|
||||
{
|
||||
Handles.color = new Color(0.7f, 0.0f, 0.3f, 0.7f);
|
||||
targetIK = _TransformsGhostChain[_TransformsGhostChain.Count - 1].position;
|
||||
}
|
||||
|
||||
float d = (start - end).magnitude * 0.06f;
|
||||
|
||||
Handles.DrawDottedLine(start, targetIK, 3f);
|
||||
Handles.DrawDottedLine(end, targetIK, 3f);
|
||||
|
||||
Handles.DrawLine(targetIK - BaseTransform.forward * d, targetIK + BaseTransform.forward * d);
|
||||
Handles.DrawLine(targetIK - BaseTransform.right * d, targetIK + BaseTransform.right * d);
|
||||
Handles.DrawLine(targetIK - BaseTransform.up * d, targetIK + BaseTransform.up * d);
|
||||
Handles.SphereHandleCap(0, targetIK, Quaternion.identity, d * 0.25f, EventType.Repaint);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void Gizmos_DrawMaxDistance()
|
||||
{
|
||||
if (MaximumDistance <= 0f) return;
|
||||
|
||||
float a = 0.525f;
|
||||
Vector3 startPos = BaseTransform.position + BaseTransform.TransformVector(DistanceMeasurePoint);
|
||||
|
||||
if (DistanceWithoutY)
|
||||
{
|
||||
if (maxDistanceExceed)
|
||||
Handles.color = new Color(1f, .1f, .1f, a);
|
||||
else Handles.color = new Color(0.02f, .65f, 0.2f, a);
|
||||
|
||||
Handles.DrawWireDisc(startPos, Vector3.up, MaximumDistance);
|
||||
|
||||
if (DistanceMeasurePoint != Vector3.zero)
|
||||
{
|
||||
Gizmos.color = new Color(0.02f, .65f, 0.2f, a);
|
||||
Gizmos.DrawLine(startPos - Vector3.right * (MaximumDistance + MaximumDistance * MaxOutDistanceFactor), startPos + Vector3.right * (MaximumDistance + MaximumDistance * MaxOutDistanceFactor));
|
||||
Gizmos.DrawLine(startPos - Vector3.forward * (MaximumDistance + MaximumDistance * MaxOutDistanceFactor), startPos + Vector3.forward * (MaximumDistance + MaximumDistance * MaxOutDistanceFactor));
|
||||
}
|
||||
|
||||
if (MaxOutDistanceFactor > 0f)
|
||||
{
|
||||
Handles.color = new Color(.835f, .135f, .08f, a);
|
||||
Handles.DrawWireDisc(startPos, Vector3.up, MaximumDistance + MaximumDistance * MaxOutDistanceFactor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (maxDistanceExceed)
|
||||
Gizmos.color = new Color(1f, .1f, .1f, a);
|
||||
else
|
||||
Gizmos.color = new Color(0.02f, .65f, 0.2f, a);
|
||||
|
||||
Gizmos.DrawWireSphere(startPos, MaximumDistance);
|
||||
|
||||
if (MaxOutDistanceFactor > 0f)
|
||||
{
|
||||
Gizmos.color = new Color(.835f, .135f, .08f, a);
|
||||
Gizmos.DrawWireSphere(startPos, MaximumDistance + MaximumDistance * MaxOutDistanceFactor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 516e3bfcbbd526a419d62b47bb170926
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,312 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
/// <summary> Procedural tail bone list </summary>
|
||||
public List<TailSegment> TailSegments;
|
||||
|
||||
/// <summary> Artificial or existing parent bone instance for slithery motion support </summary>
|
||||
[SerializeField] TailSegment GhostParent;
|
||||
[SerializeField] TailSegment GhostChild;
|
||||
|
||||
/// <summary>
|
||||
/// Method to initialize component, to have more controll than waiting for Start() method, init can be executed before or after start, as programmer need it.
|
||||
/// </summary>
|
||||
protected virtual void Init()
|
||||
{
|
||||
if (initialized) return;
|
||||
|
||||
|
||||
// Checking if we have transform to create tail chain from
|
||||
if (_TransformsGhostChain == null || _TransformsGhostChain.Count == 0)
|
||||
{
|
||||
_TransformsGhostChain = new List<Transform>();
|
||||
GetGhostChain();
|
||||
}
|
||||
|
||||
|
||||
// Generating tail instances for procedural animation
|
||||
TailSegments = new List<TailSegment>();
|
||||
|
||||
for (int i = 0; i < _TransformsGhostChain.Count; i++)
|
||||
{
|
||||
if (_TransformsGhostChain[i] == null)
|
||||
{
|
||||
Debug.Log("[Tail Animator] Null bones in " + name + " !");
|
||||
continue;
|
||||
}
|
||||
|
||||
TailSegment b = new TailSegment(_TransformsGhostChain[i]);
|
||||
b.SetIndex(i, _TransformsGhostChain.Count);
|
||||
TailSegments.Add(b);
|
||||
}
|
||||
|
||||
|
||||
// Checking correctness
|
||||
if (TailSegments.Count == 0)
|
||||
{
|
||||
Debug.Log("[Tail Animator] Could not create tail bones chain in " + name + " !");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_TC_TailLength = 0f;
|
||||
_baseTransform = _TransformsGhostChain[0];
|
||||
|
||||
//if (_baseTransform.parent)
|
||||
// _baseTransform = _baseTransform.parent;
|
||||
//else
|
||||
// IncludeParent = false;
|
||||
|
||||
|
||||
// Setting parent-child relation for tail logics
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
{
|
||||
TailSegment current = TailSegments[i];
|
||||
TailSegment parent;
|
||||
|
||||
#region Defining Parent Bones
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
if (current.transform.parent)
|
||||
{
|
||||
// Creating parent and setting safety parent
|
||||
parent = new TailSegment(current.transform.parent);
|
||||
parent.SetParentRef(new TailSegment(parent.transform.parent));
|
||||
}
|
||||
else
|
||||
#region If first bone is parentless
|
||||
{
|
||||
parent = new TailSegment(current.transform);
|
||||
|
||||
Vector3 toStartDir;
|
||||
|
||||
if (_TransformsGhostChain.Count > 1)
|
||||
{
|
||||
toStartDir = _TransformsGhostChain[0].position - _TransformsGhostChain[1].position;
|
||||
if (toStartDir.magnitude == 0) toStartDir = transform.position - _TransformsGhostChain[1].position;
|
||||
}
|
||||
else
|
||||
{
|
||||
toStartDir = current.transform.position - _TransformsGhostChain[0].position;
|
||||
}
|
||||
|
||||
if (toStartDir.magnitude == 0) toStartDir = transform.position - _TransformsGhostChain[0].position;
|
||||
if (toStartDir.magnitude == 0) toStartDir = transform.forward;
|
||||
|
||||
parent.LocalOffset = parent.transform.InverseTransformPoint(parent.transform.position + toStartDir);
|
||||
parent.SetParentRef(new TailSegment(current.transform));
|
||||
}
|
||||
#endregion
|
||||
|
||||
//current.InitialLocalRotation = Quaternion.Inverse(current.transform.localRotation);
|
||||
GhostParent = parent;
|
||||
GhostParent.Validate();
|
||||
current.SetParentRef(GhostParent);
|
||||
}
|
||||
else // i != 0
|
||||
{
|
||||
parent = TailSegments[i - 1];
|
||||
// If bones are removed manually from chain we support custom length of bone undependent from transform parenting chain structure
|
||||
current.ReInitializeLocalPosRot(parent.transform.InverseTransformPoint(current.transform.position), current.transform.localRotation);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Defining Last Child Bone
|
||||
|
||||
if (i == TailSegments.Count - 1)
|
||||
{
|
||||
Transform childT = null;
|
||||
if (current.transform.childCount > 0) childT = current.transform.GetChild(0);
|
||||
|
||||
GhostChild = new TailSegment(childT);
|
||||
|
||||
// Scale ref for ghosting object position offset
|
||||
Vector3 scaleDir;
|
||||
|
||||
if (FEngineering.VIsZero(EndBoneJointOffset))
|
||||
{
|
||||
if (current.transform.parent)
|
||||
{ scaleDir = current.transform.position - current.transform.parent.position; }
|
||||
else
|
||||
if (current.transform.childCount > 0)
|
||||
{ scaleDir = current.transform.GetChild(0).position - current.transform.position; }
|
||||
else
|
||||
{ scaleDir = current.transform.TransformDirection(Vector3.forward) * 0.05f; }
|
||||
}
|
||||
else
|
||||
scaleDir = current.transform.TransformVector(EndBoneJointOffset);
|
||||
|
||||
|
||||
GhostChild.ProceduralPosition = current.transform.position + scaleDir;
|
||||
GhostChild.ProceduralPositionWeightBlended = GhostChild.ProceduralPosition;
|
||||
GhostChild.PreviousPosition = GhostChild.ProceduralPosition;
|
||||
GhostChild.PosRefRotation = Quaternion.identity;
|
||||
GhostChild.PreviousPosReferenceRotation = Quaternion.identity;
|
||||
GhostChild.ReInitializeLocalPosRot(current.transform.InverseTransformPoint(GhostChild.ProceduralPosition), Quaternion.identity);
|
||||
GhostChild.RefreshFinalPos(GhostChild.ProceduralPosition);
|
||||
GhostChild.RefreshFinalRot(GhostChild.PosRefRotation);
|
||||
GhostChild.TrueTargetRotation = GhostChild.PosRefRotation;
|
||||
current.TrueTargetRotation = current.transform.rotation;
|
||||
|
||||
current.SetChildRef(GhostChild);
|
||||
GhostChild.SetParentRef(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
current.SetChildRef(TailSegments[i + 1]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
current.SetParentRef(parent);
|
||||
|
||||
_TC_TailLength += Vector3.Distance(current.ProceduralPosition, parent.ProceduralPosition);
|
||||
|
||||
if (current.transform != _baseTransform) current.AssignDetachedRootCoords(BaseTransform);
|
||||
}
|
||||
|
||||
|
||||
// List with ghosts for curves etc.
|
||||
GhostParent.SetIndex(-1, TailSegments.Count);
|
||||
GhostChild.SetIndex(TailSegments.Count, TailSegments.Count);
|
||||
GhostParent.SetChildRef(TailSegments[0]);
|
||||
|
||||
previousWorldPosition = BaseTransform.position;
|
||||
WavingRotationOffset = Quaternion.identity;
|
||||
|
||||
if (CollidersDataToCheck == null) CollidersDataToCheck = new List<FImp_ColliderData_Base>();
|
||||
|
||||
DynamicAlwaysInclude = new List<Component>();
|
||||
if (UseCollision) SetupSphereColliders();
|
||||
|
||||
// List instance for deflection feature
|
||||
if (_defl_source == null) _defl_source = new List<TailSegment>();
|
||||
|
||||
Waving_Initialize();
|
||||
|
||||
if (DetachChildren) DetachChildrenTransforms();
|
||||
|
||||
initialized = true;
|
||||
|
||||
if (TailSegments.Count == 1)
|
||||
{
|
||||
if (TailSegments[0].transform.parent == null)
|
||||
{
|
||||
Debug.Log("[Tail Animator] Can't initialize one-bone length chain on bone which don't have any parent!");
|
||||
Debug.LogError("[Tail Animator] Can't initialize one-bone length chain on bone which don't have any parent!");
|
||||
TailAnimatorAmount = 0f;
|
||||
initialized = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (UseWind) TailAnimatorWind.Refresh();
|
||||
|
||||
if (PostProcessingNeeded()) if (!_pp_initialized) InitializePostProcessing();
|
||||
|
||||
#region Prewarming tail to target state
|
||||
|
||||
if (Prewarm)
|
||||
{
|
||||
ShapingParamsUpdate();
|
||||
ExpertParamsUpdate();
|
||||
|
||||
Update();
|
||||
LateUpdate();
|
||||
|
||||
justDelta = rateDelta;
|
||||
secPeriodDelta = 1f;
|
||||
deltaForLerps = secPeriodDelta;
|
||||
rateDelta = 1f / 60f;
|
||||
|
||||
CheckIfTailAnimatorShouldBeUpdated();
|
||||
|
||||
if (updateTailAnimator)
|
||||
{
|
||||
int loopCount = 60 + TailSegments.Count / 2;
|
||||
|
||||
for (int d = 0; d < loopCount; d++)
|
||||
{
|
||||
PreCalibrateBones();
|
||||
LateUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Detaching children for optimized work of Unity transform matrixes when tail is very long
|
||||
/// </summary>
|
||||
public void DetachChildrenTransforms()
|
||||
{
|
||||
int to = IncludeParent ? 0 : 1;
|
||||
for (int i = TailSegments.Count - 1; i >= to; i--)
|
||||
{
|
||||
if (TailSegments[i].transform)
|
||||
TailSegments[i].transform.DetachChildren();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize post processing reference points
|
||||
/// </summary>
|
||||
void InitializePostProcessing()
|
||||
{
|
||||
_pp_reference = new List<TailSegment>();
|
||||
|
||||
// Generating copy of whole tail processing chain
|
||||
_pp_ref_rootParent = new TailSegment(GhostParent);
|
||||
for (int i = 0; i < TailSegments.Count; i++) { TailSegment bone = new TailSegment(TailSegments[i]); _pp_reference.Add(bone); }
|
||||
_pp_ref_lastChild = new TailSegment(GhostChild);
|
||||
|
||||
|
||||
// Setting child parent relation
|
||||
_pp_ref_rootParent.SetChildRef(_pp_reference[0]); // root have just child
|
||||
_pp_ref_rootParent.SetParentRef(new TailSegment(GhostParent.ParentBone.transform)); // Safety parent
|
||||
|
||||
for (int i = 0; i < _pp_reference.Count; i++)
|
||||
{
|
||||
TailSegment bone = _pp_reference[i];
|
||||
bone.SetIndex(i, TailSegments.Count);
|
||||
|
||||
if (i == 0) // First bone have ghost parent
|
||||
{
|
||||
bone.SetParentRef(_pp_ref_rootParent);
|
||||
bone.SetChildRef(_pp_reference[i + 1]);
|
||||
}
|
||||
else if (i == _pp_reference.Count - 1) // last bone have ghost child
|
||||
{
|
||||
bone.SetParentRef(_pp_reference[i - 1]);
|
||||
bone.SetChildRef(_pp_ref_lastChild);
|
||||
}
|
||||
else // Default bone from chain middle points
|
||||
{
|
||||
bone.SetParentRef(_pp_reference[i - 1]);
|
||||
bone.SetChildRef(_pp_reference[i + 1]);
|
||||
}
|
||||
}
|
||||
|
||||
_pp_ref_lastChild.SetParentRef(_pp_reference[_pp_reference.Count - 1]); // end have just parent
|
||||
|
||||
|
||||
_pp_initialized = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3b722fb0fe3ed44685a7cfc9ad05ac8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,354 @@
|
||||
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
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bfe18301d3b8b4e49b7767eb91d25c7e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
|
||||
#region Position
|
||||
|
||||
/// <summary> [Tail Calculations - TC] Smoothing position for _tc_bone changing _tc_smoothPos variable in iteration towards _tc_targetPos </summary>
|
||||
Vector3 TailCalculations_SmoothPosition(Vector3 from, Vector3 to, TailSegment bone)
|
||||
{
|
||||
if (SmoothingStyle == EAnimationStyle.Accelerating)
|
||||
return TailCalculations_SmoothPositionSmoothDamp(from, to, ref bone.VelocityHelper, bone.PositionSpeed);
|
||||
else if (SmoothingStyle == EAnimationStyle.Quick)
|
||||
return TailCalculations_SmoothPositionLerp(from, to, bone.PositionSpeed);
|
||||
else
|
||||
return TailCalculations_SmoothPositionLinear(from, to, bone.PositionSpeed );
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Smoothing position for _tc_bone with lerp divide logics changing _tc_smoothPos variable in iteration towards _tc_targetPos </summary>
|
||||
Vector3 TailCalculations_SmoothPositionLerp(Vector3 from, Vector3 to, float speed)
|
||||
{
|
||||
return Vector3.Lerp(from, to, secPeriodDelta * speed);
|
||||
}
|
||||
|
||||
/// <summary> Smoothing position for _tc_bone with smooth damp method changing _tc_smoothPos variable in iteration towards _tc_targetPos </summary>
|
||||
Vector3 TailCalculations_SmoothPositionSmoothDamp(Vector3 from, Vector3 to, ref Vector3 velo, float speed)
|
||||
{
|
||||
return Vector3.SmoothDamp(from, to, ref velo, Mathf.LerpUnclamped(0.08f, 0.0001f, Mathf.Sqrt(Mathf.Sqrt(speed))), Mathf.Infinity, rateDelta);
|
||||
}
|
||||
|
||||
/// <summary> Smoothing position for _tc_bone linearly changing _tc_smoothPos variable in iteration towards _tc_targetPos </summary>
|
||||
Vector3 TailCalculations_SmoothPositionLinear(Vector3 from, Vector3 to, float speed)
|
||||
{
|
||||
return Vector3.MoveTowards(from, to, deltaForLerps * speed * 45f);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Rotation
|
||||
|
||||
|
||||
|
||||
/// <summary> [Tail Calculations - TC] Smoothing rotation for _tc_bone changing _tc_smoothRot variable in iteration towards _tc_targetRot </summary>
|
||||
Quaternion TailCalculations_SmoothRotation(Quaternion from, Quaternion to, TailSegment bone)
|
||||
{
|
||||
if (SmoothingStyle == EAnimationStyle.Accelerating)
|
||||
return TailCalculations_SmoothRotationSmoothDamp(from, to, ref bone.QVelocityHelper, bone.RotationSpeed);
|
||||
else
|
||||
if (SmoothingStyle == EAnimationStyle.Quick)
|
||||
return TailCalculations_SmoothRotationLerp(from, to, bone.RotationSpeed);
|
||||
else
|
||||
return TailCalculations_SmoothRotationLinear(from, to, bone.RotationSpeed);
|
||||
}
|
||||
|
||||
/// <summary> Smoothing rotation for _tc_bone with lerp divide logics changing _tc_smoothRot variable in iteration towards _tc_targetRot </summary>
|
||||
Quaternion TailCalculations_SmoothRotationLerp(Quaternion from, Quaternion to, float speed)
|
||||
{
|
||||
return Quaternion.Lerp(from, to, secPeriodDelta * speed);
|
||||
}
|
||||
|
||||
/// <summary> Smoothing rotation for _tc_bone with smooth damp method changing _tc_smoothRot variable in iteration towards _tc_targetRot </summary>
|
||||
Quaternion TailCalculations_SmoothRotationSmoothDamp(Quaternion from, Quaternion to, ref Quaternion velo, float speed)
|
||||
{
|
||||
return FEngineering.SmoothDampRotation(from, to, ref velo, Mathf.LerpUnclamped(0.25f, 0.0001f, Mathf.Sqrt(Mathf.Sqrt(speed))), rateDelta);
|
||||
}
|
||||
|
||||
/// <summary> Smoothing rotation for _tc_bone linearly changing _tc_smoothRot variable in iteration towards _tc_targetRot </summary>
|
||||
Quaternion TailCalculations_SmoothRotationLinear(Quaternion from, Quaternion to, float speed)
|
||||
{
|
||||
return (Quaternion.RotateTowards(from, to, speed * deltaForLerps * 1600f));
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1f8936842303b74dbace3df066c79d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,213 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
/// <summary> Tail calculations start index for interations </summary>
|
||||
int _tc_startI = 0;
|
||||
/// <summary> Indexes in front of root </summary>
|
||||
int _tc_startII = 1;
|
||||
/// <summary> Delta time multiplied one itme instead of i-times </summary>
|
||||
//float _tc_offsetDelta = 0.1f;
|
||||
/// <summary> Full length of tail computed at initialize in world space, used for unify animation feature </summary>
|
||||
public float _TC_TailLength { get; private set; }
|
||||
|
||||
/// <summary> Tail calculations helper bone class instance reference </summary>
|
||||
TailSegment _tc_rootBone = null;
|
||||
|
||||
/// <summary> Tail calculations helper rotation variable to avoid GC </summary>
|
||||
Quaternion _tc_lookRot = Quaternion.identity;
|
||||
/// <summary> Tail calculations helper rotation variable to avoid GC </summary>
|
||||
Quaternion _tc_targetParentRot = Quaternion.identity;
|
||||
|
||||
/// <summary> Chain root offset rotation for shaping and auto-waving features </summary>
|
||||
Quaternion _tc_startBoneRotOffset = Quaternion.identity;
|
||||
float _tc_tangle = 1f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Define root of chain >> _tc_rootBone - root bone of tail chain >> TailBones[0] or GhostParent
|
||||
/// </summary>
|
||||
void TailCalculations_Begin()
|
||||
{
|
||||
if (IncludeParent) // Include first bone in chain to be modified
|
||||
{
|
||||
_tc_startI = 0;
|
||||
_tc_rootBone = TailSegments[0];
|
||||
}
|
||||
else // Leaving first bone in chain intact (exclude)
|
||||
{
|
||||
_tc_startI = 1;
|
||||
if (TailSegments.Count > 1) _tc_rootBone = TailSegments[1]; else { _tc_rootBone = TailSegments[0]; _tc_startI = -1; }
|
||||
}
|
||||
|
||||
_tc_startII = _tc_startI + 1;
|
||||
if (_tc_startII > TailSegments.Count - 1) _tc_startII = -1;
|
||||
|
||||
if (Deflection > Mathf.Epsilon) if (!_pp_initialized) InitializePostProcessing();
|
||||
|
||||
|
||||
if (Tangle < 0)
|
||||
_tc_tangle = Mathf.LerpUnclamped(1f, 1.5f, Tangle + 1f);
|
||||
else
|
||||
_tc_tangle = Mathf.LerpUnclamped(1f, -4f, Tangle);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Defining start bone root reference coordinates and initial processing
|
||||
/// </summary>
|
||||
void TailSegments_UpdateRootFeatures()
|
||||
{
|
||||
// Root bone rotation offset for auto-waving feature
|
||||
if (UseWaving)
|
||||
{
|
||||
Waving_AutoSwingUpdate();
|
||||
_tc_startBoneRotOffset = (WavingRotationOffset * RotationOffset);
|
||||
}
|
||||
else
|
||||
_tc_startBoneRotOffset = RotationOffset;
|
||||
|
||||
// Sustain support
|
||||
if (Sustain > Mathf.Epsilon) Waving_SustainUpdate();
|
||||
|
||||
// Post process advanced features
|
||||
if (PostProcessingNeeded()) PostProcessing_Begin();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Processing calculated simple segment position with special effects like limiting / collision / smoothing etc.
|
||||
/// Calling methods using bone's parent variables
|
||||
/// </summary>
|
||||
void TailCalculations_SegmentPreProcessingStack(TailSegment child)
|
||||
{
|
||||
if (!UseCollision) // Basic motion without collision
|
||||
// Different update order with enable/disable collisions to avoid jittering on angle limiting
|
||||
{
|
||||
// Limit segments angles
|
||||
if (AngleLimit < 181) child.ProceduralPosition = AngleLimiting(child, child.ProceduralPosition);
|
||||
|
||||
// Smoothing motion
|
||||
if (child.PositionSpeed < 1f) child.ProceduralPosition = TailCalculations_SmoothPosition(child.PreviousPosition /*+ _limiting_influenceOffset*/, child.ProceduralPosition, child);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Smoothing motion
|
||||
if (child.PositionSpeed < 1f) child.ProceduralPosition = TailCalculations_SmoothPosition(child.PreviousPosition /*+ _limiting_influenceOffset*/, child.ProceduralPosition, child);
|
||||
|
||||
// Computing collision offset as first thing
|
||||
TailCalculations_ComputeSegmentCollisions(child, ref child.ProceduralPosition);
|
||||
|
||||
// Limit segments angles
|
||||
if (AngleLimit < 181) child.ProceduralPosition = AngleLimiting(child, child.ProceduralPosition);
|
||||
}
|
||||
|
||||
|
||||
// Control stretching
|
||||
if (MaxStretching < 1f) StretchingLimiting(child);
|
||||
|
||||
// Apply gravity
|
||||
if (!FEngineering.VIsZero(child.Gravity) || UseWind) CalculateGravityPositionOffsetForSegment(child);
|
||||
|
||||
if (Axis2D > 0) Axis2DLimit(child);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Post processing for segment if used for example deflection
|
||||
/// </summary>
|
||||
void TailCalculations_SegmentPostProcessing(TailSegment bone)
|
||||
{
|
||||
// Applying deflection
|
||||
if (Deflection > Mathf.Epsilon) Deflection_SegmentOffsetSimple(bone, ref bone.ProceduralPosition);
|
||||
|
||||
// Soon there may be more post processes
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Second iteration for segments rotation calculations
|
||||
/// Rotating segments towards calculated procedural positions with blending if used
|
||||
/// </summary>
|
||||
void TailCalculations_SegmentRotation(TailSegment child, Vector3 localOffset)
|
||||
{
|
||||
|
||||
// Calculating correct rotation towards calculated position in parent orientation
|
||||
_tc_lookRot = Quaternion.FromToRotation
|
||||
( // Support negative scale with transform direction
|
||||
child.ParentBone.transform.TransformDirection(localOffset), // Child local pos offset - direction from parent true position towards default bone postion from keyframe animation
|
||||
child.ProceduralPositionWeightBlended - child.ParentBone.ProceduralPositionWeightBlended // Direction from parent calculated position towards child target calculated postion
|
||||
);
|
||||
|
||||
// Rotating towards desired orientation
|
||||
_tc_targetParentRot = _tc_lookRot * child.ParentBone.transform.rotation;
|
||||
|
||||
if ( AnimateRoll)
|
||||
{
|
||||
_tc_targetParentRot = Quaternion.Lerp(child.ParentBone.TrueTargetRotation, _tc_targetParentRot, deltaForLerps * Mathf.LerpUnclamped(10f, 60f, child.RotationSpeed));
|
||||
}
|
||||
|
||||
// Notice than we setting parent rotation from child relation : parent rotation is dictating child position
|
||||
// Remember transform rotaiton before smoothing reference rotation !!!
|
||||
child.ParentBone.TrueTargetRotation = _tc_targetParentRot;
|
||||
|
||||
// Remembering previous value before changing to new
|
||||
child.ParentBone.PreviousPosReferenceRotation = child.ParentBone.PosRefRotation;
|
||||
|
||||
// Setting positions reference rotation separately
|
||||
if ( !AnimateRoll) if (child.RotationSpeed < 1f) _tc_targetParentRot = TailCalculations_SmoothRotation(child.ParentBone.PosRefRotation, _tc_targetParentRot, child);
|
||||
|
||||
child.ParentBone.PosRefRotation = _tc_targetParentRot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Detached tail mode works on slightly different vector logics
|
||||
/// </summary>
|
||||
void TailCalculations_SegmentRotationDetached(TailSegment child, Vector3 localOffset)
|
||||
{
|
||||
//Quaternion tgtRot = child.ParentBone.TrueTargetRotation;
|
||||
//if (child.IsDetachable)
|
||||
//{
|
||||
// tgtRot = FEngineering.QToWorld(BaseTransform.rotation, child.ParentBone.InitialLocalRotationInRoot);
|
||||
//}
|
||||
|
||||
_tc_lookRot = Quaternion.FromToRotation
|
||||
(
|
||||
child.ParentBone.transform.TransformDirection(localOffset), // Child local pos offset - direction from parent true position towards default bone postion
|
||||
child.ProceduralPositionWeightBlended - child.ParentBone.ProceduralPositionWeightBlended // Direction from parent calculated position towards child target calculated postion
|
||||
);
|
||||
|
||||
|
||||
|
||||
_tc_targetParentRot = _tc_lookRot * child.transform.rotation;
|
||||
if (AnimateRoll) _tc_targetParentRot = Quaternion.Lerp(child.ParentBone.TrueTargetRotation, _tc_targetParentRot, deltaForLerps * Mathf.LerpUnclamped(10f, 60f, child.RotationSpeed));
|
||||
|
||||
child.ParentBone.TrueTargetRotation = _tc_targetParentRot;
|
||||
child.ParentBone.PreviousPosReferenceRotation = child.ParentBone.PosRefRotation;
|
||||
|
||||
if (!AnimateRoll) if (child.RotationSpeed < 1f) _tc_targetParentRot = TailCalculations_SmoothRotation(child.ParentBone.PosRefRotation, _tc_targetParentRot, child);
|
||||
|
||||
child.ParentBone.PosRefRotation = _tc_targetParentRot;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Applying tail motion to transforms
|
||||
/// Accesing parent of bone to change it's rotation
|
||||
/// </summary>
|
||||
void TailCalculations_ApplySegmentMotion(TailSegment child)
|
||||
{
|
||||
child.ParentBone.transform.rotation = child.ParentBone.TrueTargetRotation;
|
||||
child.transform.position = child.ProceduralPositionWeightBlended;
|
||||
|
||||
child.RefreshFinalPos(child.ProceduralPositionWeightBlended);
|
||||
child.ParentBone.RefreshFinalRot(child.ParentBone.TrueTargetRotation);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94df3f76d7ce99142bd705f911882f19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,334 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FC: Calculations focused on single tail bone segments operations
|
||||
/// </summary>
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
// Motion controll variables
|
||||
float _sg_springVelo = 0.5f;
|
||||
//float _sg_curl = 0.5f;
|
||||
float _sg_curly = 0.5f;
|
||||
|
||||
// Motion calculation variables
|
||||
Vector3 _sg_push;
|
||||
Vector3 _sg_targetPos;
|
||||
Vector3 _sg_targetChildWorldPosInParentFront;
|
||||
Vector3 _sg_dirToTargetParentFront;
|
||||
Quaternion _sg_orientation;
|
||||
float _sg_slitFactor = 0.5f;
|
||||
|
||||
|
||||
void TailSegment_PrepareBoneLength(TailSegment child)
|
||||
{
|
||||
// Remember bone scale referenced from initial position
|
||||
child.BoneDimensionsScaled = Vector3.Scale(child.ParentBone.transform.lossyScale * child.LengthMultiplier, child.LastKeyframeLocalPosition);
|
||||
child.BoneLengthScaled = child.BoneDimensionsScaled.magnitude; //(child.ParentBone.transform.position - child.transform.position).magnitude * child.LengthMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Preparing motion parameters for individual segment settings
|
||||
/// </summary>
|
||||
void TailSegment_PrepareMotionParameters(TailSegment child)
|
||||
{
|
||||
//// Non-Slithery
|
||||
//_sg_curly = Mathf.LerpUnclamped(0.6f, 0.15f, child.Curling);
|
||||
//_sg_springVelo = Mathf.LerpUnclamped(0.65f, 0.9f, child.Springiness);
|
||||
|
||||
//// Slithery Blend
|
||||
//_sg_curly = Mathf.Lerp(_sg_curly, Mathf.LerpUnclamped(0.99f, 0.135f, child.Curling), child.Slithery);
|
||||
//_sg_springVelo = Mathf.Lerp(_sg_springVelo, Mathf.LerpUnclamped(0.0f, 0.8f, child.Springiness), child.Slithery);
|
||||
|
||||
// Non-Slithery
|
||||
_sg_curly = Mathf.LerpUnclamped(0.5f, 0.125f, child.Curling);
|
||||
_sg_springVelo = Mathf.LerpUnclamped(0.65f, 0.9f, child.Springiness);
|
||||
|
||||
// Slithery Blend
|
||||
_sg_curly = Mathf.Lerp(_sg_curly, Mathf.LerpUnclamped(0.95f, 0.135f, child.Curling), child.Slithery);
|
||||
_sg_springVelo = Mathf.Lerp(_sg_springVelo, Mathf.LerpUnclamped(0.1f, 0.85f, child.Springiness), child.Slithery);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Preparing segment positioning parameters
|
||||
/// </summary>
|
||||
void TailSegment_PrepareVelocity(TailSegment child)
|
||||
{
|
||||
// Velocity change check
|
||||
_sg_push = (child.ProceduralPosition - child.PreviousPosition);
|
||||
|
||||
// Remember actual position after using previous position memory
|
||||
child.PreviousPosition = child.ProceduralPosition;
|
||||
|
||||
// Collision slippery modify
|
||||
float swinginess = _sg_springVelo;
|
||||
if (child.CollisionContactFlag) swinginess *= child.Slippery;
|
||||
|
||||
// Tail Motion velocity motion base calculations
|
||||
child.ProceduralPosition += _sg_push * swinginess;
|
||||
|
||||
// Remember previous push for sustain feature
|
||||
child.PreviousPush = _sg_push;
|
||||
}
|
||||
|
||||
|
||||
void TailSegment_PrepareRotation(TailSegment child)
|
||||
{
|
||||
_sg_targetChildWorldPosInParentFront = child.ParentBone.ProceduralPosition + TailSegment_GetSwingRotation(child, _sg_slitFactor) * child.BoneDimensionsScaled; // Local offset scaled with custom rotation
|
||||
}
|
||||
|
||||
void TailSegment_PrepareRotationDetached(TailSegment child)
|
||||
{
|
||||
_sg_targetChildWorldPosInParentFront = child.ParentBone.ProceduralPosition + TailSegment_GetSwingRotationDetached(child, _sg_slitFactor) * child.BoneDimensionsScaled; // Local offset scaled with custom rotation
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base position processing for swingy parameters animation
|
||||
/// Changing UnprocessedPosition value in child argument
|
||||
/// </summary>
|
||||
void TailSegment_BaseSwingProcessing(TailSegment child)
|
||||
{
|
||||
_sg_slitFactor = child.Slithery; // Collision fixing for slithery calculations
|
||||
if (child.CollisionContactRelevancy > 0f) _sg_slitFactor = ReflectCollision; // (0.5f - Mathf.Min(0.5f, child.CollisionContactRelevancy * 10f) ) * child.Slithery;
|
||||
|
||||
_sg_dirToTargetParentFront = _sg_targetChildWorldPosInParentFront - child.ProceduralPosition;
|
||||
|
||||
// Unifying bendiness parameters on tail length and segments count then translating
|
||||
if (UnifyBendiness > 0f)
|
||||
child.ProceduralPosition += _sg_dirToTargetParentFront * secPeriodDelta *
|
||||
_sg_curly * TailSegment_GetUnifiedBendinessMultiplier(child);
|
||||
else
|
||||
// Translating toward target position without restricitons
|
||||
child.ProceduralPosition += _sg_dirToTargetParentFront * _sg_curly * secPeriodDelta;
|
||||
|
||||
if (Tangle != 0f)
|
||||
if (child.Slithery >= 1f)
|
||||
child.ProceduralPosition = Vector3.LerpUnclamped(child.ProceduralPosition, _sg_targetChildWorldPosInParentFront, _tc_tangle);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Processing position for blending weight
|
||||
/// </summary>
|
||||
void TailSegment_PreRotationPositionBlend(TailSegment child)
|
||||
{
|
||||
if (child.BlendValue * conditionalWeight < 1f)
|
||||
child.ProceduralPositionWeightBlended = Vector3.LerpUnclamped(
|
||||
child.transform.position, child.ProceduralPosition, child.BlendValue * conditionalWeight);
|
||||
//child.ParentBone.transform.TransformVector(child.LastFinalLocalPosition), child.ProceduralPosition, child.BlendValue);
|
||||
else
|
||||
child.ProceduralPositionWeightBlended = child.ProceduralPosition;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculations for slithery tail motion reference parent rotation
|
||||
/// </summary>
|
||||
Quaternion TailSegment_RotationSlithery(TailSegment child)
|
||||
{
|
||||
|
||||
if (!FEngineering.QIsZero(child.Curving))
|
||||
{
|
||||
return GetSlitheryReferenceRotation(child) // Back rotation for parent of parent bone
|
||||
* child.Curving // Curving
|
||||
* child.ParentBone.LastKeyframeLocalRotation; // Sync with animator
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetSlitheryReferenceRotation(child) // Back rotation for parent of parent bone
|
||||
* child.ParentBone.LastKeyframeLocalRotation; // Sync with animator
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculations for slithery tail motion reference parent rotation
|
||||
/// </summary>
|
||||
Quaternion TailSegment_RotationSlitheryDetached(TailSegment child)
|
||||
{
|
||||
|
||||
if (!FEngineering.QIsZero(child.Curving))
|
||||
{
|
||||
return GetSlitheryReferenceRotation(child) // Back rotation for parent of parent bone
|
||||
* child.Curving // Curving
|
||||
* child.ParentBone.InitialLocalRotation; // Sync with animator
|
||||
}
|
||||
else
|
||||
{
|
||||
return GetSlitheryReferenceRotation(child) // Back rotation for parent of parent bone
|
||||
* child.ParentBone.InitialLocalRotation; // Sync with animator
|
||||
}
|
||||
}
|
||||
|
||||
Quaternion GetSlitheryReferenceRotation(TailSegment child)
|
||||
{
|
||||
if (child.Slithery <= 1f)
|
||||
return child.ParentBone.ParentBone.PosRefRotation;
|
||||
else
|
||||
{
|
||||
return Quaternion.LerpUnclamped(child.ParentBone.ParentBone.PosRefRotation,
|
||||
child.ParentBone.ParentBone.PreviousPosReferenceRotation, (child.Slithery - 1f) * 5f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calculations for stiff tail motion reference parent rotation
|
||||
/// </summary>
|
||||
Quaternion TailSegment_RotationStiff(TailSegment child)
|
||||
{
|
||||
// Curving feature
|
||||
if (!FEngineering.QIsZero(child.Curving))
|
||||
{
|
||||
return child.ParentBone.transform.rotation * // Parent bone rotation
|
||||
MultiplyQ(child.Curving, child.Index * 2f) // Curving multiplier
|
||||
;
|
||||
}
|
||||
else // No Curving
|
||||
{
|
||||
return child.ParentBone.transform.rotation; // Parent bone rotation
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Defining style of tail motion
|
||||
/// </summary>
|
||||
Quaternion TailSegment_GetSwingRotation(TailSegment child, float curlFactor)
|
||||
{
|
||||
if (curlFactor >= 1f) // Slithery == 1
|
||||
{
|
||||
return TailSegment_RotationSlithery(child);
|
||||
}
|
||||
else if (curlFactor > Mathf.Epsilon) // Blend non slithery with slithery feature
|
||||
{
|
||||
return Quaternion.LerpUnclamped(
|
||||
TailSegment_RotationStiff(child), // A - Stiff
|
||||
TailSegment_RotationSlithery(child), // B - Slithery
|
||||
curlFactor); // Lerp
|
||||
}
|
||||
else // Slithery == 0
|
||||
return TailSegment_RotationStiff(child);
|
||||
}
|
||||
|
||||
|
||||
Quaternion TailSegment_GetSwingRotationDetached(TailSegment child, float curlFactor)
|
||||
{
|
||||
if (curlFactor >= 1f) // Slithery == 1
|
||||
{
|
||||
return TailSegment_RotationSlitheryDetached(child);
|
||||
}
|
||||
else if (curlFactor > Mathf.Epsilon) // Blend non slithery with slithery feature
|
||||
{
|
||||
return Quaternion.LerpUnclamped(
|
||||
TailSegment_RotationStiff(child), // A - Stiff
|
||||
TailSegment_RotationSlitheryDetached(child), // B - Slithery
|
||||
curlFactor); // Lerp
|
||||
}
|
||||
else // Slithery == 0
|
||||
return TailSegment_RotationStiff(child);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating multiplier value for translation based on tail length and segments count
|
||||
/// To make different length and segment count tails behave in similar way
|
||||
/// </summary>
|
||||
float TailSegment_GetUnifiedBendinessMultiplier(TailSegment child)
|
||||
{
|
||||
// When short tail -> 0.2f / 1.25f -> 0.16 -> We want here small mul value
|
||||
// When long and many segments -> 0.1f / 8f -> 0.0125 -> We want here high mul value
|
||||
float segmentRatio = (child.BoneLength / _TC_TailLength);
|
||||
segmentRatio = Mathf.Pow(segmentRatio, 0.5f);
|
||||
|
||||
if (segmentRatio == 0f) segmentRatio = 1f; // No dividing by zero ¯\_(ツ)_/¯
|
||||
|
||||
float unifyMul = (_sg_curly / segmentRatio) / 2f;
|
||||
unifyMul = Mathf.LerpUnclamped(_sg_curly, unifyMul, UnifyBendiness);
|
||||
|
||||
// Clamp extreme values
|
||||
if (unifyMul < 0.15f) unifyMul = 0.15f; else if (unifyMul > 1.4f) unifyMul = 1.4f;
|
||||
|
||||
//Debug.Log(System.Math.Round( unifyMul, 3) + " Count = " + TailSegments.Count + " child.BoneLength " + child.BoneLength + "_tc_tailLength " + _TC_TailLength + "curl = " + _sg_curly + " Mul " + unifyMul + " segmentRatio " + segmentRatio + " mixed = " + (_sg_curly * unifyMul) );
|
||||
return unifyMul;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updateing root parent bone with component parameters and features
|
||||
/// </summary>
|
||||
public void TailSegments_UpdateCoordsForRootBone(TailSegment parent)
|
||||
{
|
||||
TailSegment root = TailSegments[0];
|
||||
root.transform.localRotation = root.LastKeyframeLocalRotation * _tc_startBoneRotOffset;
|
||||
//parent.transform.localRotation = parent.LastKeyframeLocalRotation * _tc_startBoneRotOffset;
|
||||
|
||||
parent.PreviousPosReferenceRotation = parent.PosRefRotation;
|
||||
parent.PosRefRotation = parent.transform.rotation;
|
||||
|
||||
parent.PreviousPosition = parent.ProceduralPosition;
|
||||
parent.ProceduralPosition = parent.transform.position;
|
||||
if (DetachChildren) root.TrueTargetRotation = root.transform.rotation;
|
||||
|
||||
parent.RefreshFinalPos(parent.transform.position);
|
||||
|
||||
parent.ProceduralPositionWeightBlended = parent.ProceduralPosition;
|
||||
|
||||
if (parent.ParentBone.transform != null)
|
||||
{
|
||||
// Update of ghost parent for slithery motion
|
||||
parent.ParentBone.PreviousPosReferenceRotation = parent.ParentBone.PosRefRotation;
|
||||
parent.ParentBone.PreviousPosition = parent.ParentBone.ProceduralPosition;
|
||||
|
||||
parent.ParentBone.ProceduralPosition = parent.ParentBone.transform.position;
|
||||
parent.ParentBone.PosRefRotation = parent.ParentBone.transform.rotation;
|
||||
|
||||
parent.ParentBone.ProceduralPositionWeightBlended = parent.ParentBone.ProceduralPosition;
|
||||
}
|
||||
|
||||
TailSegments[_tc_startI].ChildBone.PreviousPosition += _waving_sustain;
|
||||
root.RefreshKeyLocalPosition();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Begin update operations for additionaly genrated child bone of chain
|
||||
/// </summary>
|
||||
public void TailCalculations_UpdateArtificialChildBone(TailSegment child)
|
||||
{
|
||||
// Pre processing with limiting, gravity etc.
|
||||
//TailCalculations_SegmentPreProcessingStack(lastChild);
|
||||
|
||||
//// Blending animation weight
|
||||
//TailSegment_PreRotationPositionBlend(lastChild);
|
||||
|
||||
if (DetachChildren) TailSegment_PrepareRotationDetached(child); else TailSegment_PrepareRotation(child);
|
||||
TailSegment_BaseSwingProcessing(child);
|
||||
|
||||
if (child.PositionSpeed < 1f) child.ProceduralPosition = TailCalculations_SmoothPosition(child.PreviousPosition /*+ _limiting_influenceOffset*/, child.ProceduralPosition, child);
|
||||
|
||||
if (MaxStretching < 1f) StretchingLimiting(child);
|
||||
|
||||
if (!FEngineering.VIsZero(child.Gravity) || UseWind) CalculateGravityPositionOffsetForSegment(child);
|
||||
|
||||
if (Axis2D > 0) Axis2DLimit(child);
|
||||
|
||||
child.CollisionContactRelevancy = -1f;
|
||||
|
||||
// Blending or just setting target position
|
||||
if (child.BlendValue * conditionalWeight < 1f)
|
||||
child.ProceduralPositionWeightBlended = Vector3.LerpUnclamped(
|
||||
child.ParentBone.transform.TransformPoint(child.LastKeyframeLocalPosition), child.ProceduralPosition, child.BlendValue * conditionalWeight);
|
||||
else
|
||||
child.ProceduralPositionWeightBlended = child.ProceduralPosition;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Refreshing position for additionaly generated parent bone
|
||||
/// </summary>
|
||||
public void Editor_TailCalculations_RefreshArtificialParentBone()
|
||||
{
|
||||
GhostParent.ProceduralPosition = GhostParent.transform.position + FEngineering.TransformVector(GhostParent.transform.rotation, GhostParent.transform.lossyScale, GhostParent.LocalOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 90e74887a690e1c44b4c416583a50f76
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,255 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
|
||||
void SimulateTailMotionFrame(bool pp)
|
||||
{
|
||||
#region Prepare base positions calculation for tail segments to use in coords calculations and as reference
|
||||
|
||||
TailSegments_UpdateRootFeatures();
|
||||
|
||||
TailSegments_UpdateCoordsForRootBone(_tc_rootBone);
|
||||
|
||||
if (pp) PostProcessing_ReferenceUpdate();
|
||||
|
||||
|
||||
if (_tc_startI > -1)
|
||||
{
|
||||
TailSegment child = TailSegments[_tc_startI]; // Used in while() loops below
|
||||
// Going in while is 2x faster than for(i;i;i) loop
|
||||
|
||||
if (!DetachChildren)
|
||||
{
|
||||
while (child != GhostChild)
|
||||
{
|
||||
// Remember bone scale referenced from initial position
|
||||
child.BoneDimensionsScaled = Vector3.Scale(child.ParentBone.transform.lossyScale * child.LengthMultiplier, child.LastKeyframeLocalPosition);
|
||||
child.BoneLengthScaled = child.BoneDimensionsScaled.magnitude; //(child.ParentBone.transform.position - child.transform.position).magnitude * child.LengthMultiplier;
|
||||
|
||||
// Preparing parameter values adapted to stiff and slithery character and blended
|
||||
TailSegment_PrepareBoneLength(child);
|
||||
TailSegment_PrepareMotionParameters(child);
|
||||
|
||||
// Velocity changes detection
|
||||
TailSegment_PrepareVelocity(child);
|
||||
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (child != GhostChild)
|
||||
{
|
||||
// Remember bone scale referenced from initial position
|
||||
child.BoneDimensionsScaled = Vector3.Scale(child.ParentBone.transform.lossyScale * child.LengthMultiplier, child.InitialLocalPosition);
|
||||
child.BoneLengthScaled = child.BoneDimensionsScaled.magnitude; //(child.ParentBone.transform.position - child.transform.position).magnitude * child.LengthMultiplier;
|
||||
TailSegment_PrepareMotionParameters(child);
|
||||
TailSegment_PrepareVelocity(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Udpate for artificial end bone
|
||||
TailSegment_PrepareBoneLength(GhostChild);
|
||||
TailSegment_PrepareMotionParameters(GhostChild);
|
||||
TailSegment_PrepareVelocity(GhostChild);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Processing segments, calculating full target coords and apply to transforms
|
||||
|
||||
if (_tc_startII > -1)
|
||||
{
|
||||
// Ignoring root related calculations
|
||||
TailSegment child = TailSegments[_tc_startII];
|
||||
|
||||
if (!DetachChildren)
|
||||
{
|
||||
while (child != GhostChild)
|
||||
{
|
||||
TailSegment_PrepareRotation(child);
|
||||
TailSegment_BaseSwingProcessing(child);
|
||||
|
||||
// Pre processing with limiting, gravity etc.
|
||||
TailCalculations_SegmentPreProcessingStack(child);
|
||||
|
||||
if (pp) TailCalculations_SegmentPostProcessing(child);
|
||||
|
||||
// Blending animation weight
|
||||
TailSegment_PreRotationPositionBlend(child);
|
||||
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
while (child != GhostChild)
|
||||
{
|
||||
TailSegment_PrepareRotationDetached(child);
|
||||
TailSegment_BaseSwingProcessing(child);
|
||||
TailCalculations_SegmentPreProcessingStack(child);
|
||||
if (pp) TailCalculations_SegmentPostProcessing(child);
|
||||
TailSegment_PreRotationPositionBlend(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Applying processing for artificial child bone without transform
|
||||
TailCalculations_UpdateArtificialChildBone(GhostChild);
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
void UpdateTailAlgorithm()
|
||||
{
|
||||
TailCalculations_Begin(); // Root definition
|
||||
|
||||
if (DeltaType != EFDeltaType.UnscaledDeltaTime && Time.timeScale == 0f) // Pause motion
|
||||
{
|
||||
if (_tc_startI > -1)
|
||||
{
|
||||
TailSegment segment = TailSegments[_tc_startI];
|
||||
|
||||
while (segment != null)
|
||||
{
|
||||
if (segment.transform)
|
||||
{
|
||||
segment.transform.position = segment.LastFinalPosition;
|
||||
segment.transform.rotation = segment.LastFinalRotation;
|
||||
}
|
||||
else break;
|
||||
|
||||
segment = segment.ChildBone;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (framesToSimulate != 0) // If framerate not defined then framesToSimulate is always == 1
|
||||
{
|
||||
|
||||
if (UseCollision) BeginCollisionsUpdate(); // Updating colliders to collide with
|
||||
|
||||
// If post processing is needed we computing reference coordinates
|
||||
bool postProcesses = PostProcessingNeeded();
|
||||
|
||||
MotionInfluenceLimiting();
|
||||
|
||||
for (int i = 0; i < framesToSimulate; i++) // Simulating update frames
|
||||
SimulateTailMotionFrame(postProcesses);
|
||||
|
||||
// Updating root bone position
|
||||
TailSegments[_tc_startI].transform.position = TailSegments[_tc_startI].ProceduralPositionWeightBlended;
|
||||
TailSegments[_tc_startI].RefreshFinalPos(TailSegments[_tc_startI].ProceduralPositionWeightBlended);
|
||||
//TailSegments[_tc_startI].RefreshFinalLocalPos(TailSegments[_tc_startI].transform.localPosition);
|
||||
|
||||
if (!DetachChildren) // When using common algorithm
|
||||
{
|
||||
// Applying calculated coords to transforms
|
||||
if (_tc_startII > -1)
|
||||
{
|
||||
TailSegment child = TailSegments[_tc_startII]; // Used in while() loops below
|
||||
while (child != GhostChild)
|
||||
{
|
||||
// Calculate rotation
|
||||
TailCalculations_SegmentRotation(child, child.LastKeyframeLocalPosition);
|
||||
|
||||
// Apply coords to segments
|
||||
TailCalculations_ApplySegmentMotion(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // Detached mode needs some changes
|
||||
{
|
||||
#region Detached Mode
|
||||
|
||||
if (_tc_startII > -1)
|
||||
{
|
||||
TailSegment child = TailSegments[_tc_startII]; // Used in while() loops below
|
||||
while (child != GhostChild)
|
||||
{
|
||||
TailCalculations_SegmentRotation(child, child.InitialLocalPosition);
|
||||
//TailCalculations_SegmentRotationDetached(child, child.InitialLocalPosition);
|
||||
TailCalculations_ApplySegmentMotion(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
// If ghost child has transform let's apply motion too (change rotation of last bone)
|
||||
TailCalculations_SegmentRotation(GhostChild, GhostChild.LastKeyframeLocalPosition);
|
||||
GhostChild.ParentBone.transform.rotation = GhostChild.ParentBone.TrueTargetRotation;
|
||||
GhostChild.ParentBone.RefreshFinalRot(GhostChild.ParentBone.TrueTargetRotation);
|
||||
|
||||
if (GhostChild.transform)
|
||||
{
|
||||
GhostChild.RefreshFinalPos(GhostChild.transform.position);
|
||||
GhostChild.RefreshFinalRot(GhostChild.transform.rotation);
|
||||
}
|
||||
}
|
||||
else // Skipping tail motion simulation and just applying coords computed lately
|
||||
// Executed only when using target UpdateRate
|
||||
{
|
||||
|
||||
if (InterpolateRate)
|
||||
{
|
||||
secPeriodDelta = rateDelta / 24f;
|
||||
deltaForLerps = secPeriodDelta; // Unify delta value not amplified -> 1f / rate
|
||||
|
||||
SimulateTailMotionFrame(PostProcessingNeeded());
|
||||
|
||||
if (_tc_startII > -1)
|
||||
{
|
||||
TailSegment child = TailSegments[_tc_startII];
|
||||
while (child != GhostChild)
|
||||
{
|
||||
TailCalculations_SegmentRotation(child, child.LastKeyframeLocalPosition);
|
||||
TailCalculations_ApplySegmentMotion(child);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
|
||||
TailCalculations_SegmentRotation(GhostChild, GhostChild.LastKeyframeLocalPosition);
|
||||
GhostChild.ParentBone.transform.rotation = GhostChild.ParentBone.TrueTargetRotation;
|
||||
GhostChild.ParentBone.RefreshFinalRot(GhostChild.ParentBone.TrueTargetRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_tc_startI > -1)
|
||||
{
|
||||
TailSegment segment = TailSegments[_tc_startI];
|
||||
|
||||
while (segment != null)
|
||||
{
|
||||
if (segment.transform)
|
||||
{
|
||||
segment.transform.position = segment.LastFinalPosition;
|
||||
segment.transform.rotation = segment.LastFinalRotation;
|
||||
}
|
||||
else break;
|
||||
|
||||
segment = segment.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6fd7b67b43fe5784cb55474315c6c30e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,396 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
/// <summary> For resetting tail procedural data when just enabling animator </summary>
|
||||
bool wasDisabled = true;
|
||||
/// <summary> Just selected delta value from Time.delta unmodified </summary>
|
||||
float justDelta = 0.016f;
|
||||
/// <summary> Delta used for component logics -> it's value is near full 1f </summary>
|
||||
float secPeriodDelta = 0.5f;
|
||||
/// <summary> Delta for pos = Lerp(pos, target) operations -> It's value is desired to be around 0.016f but much higher in higher fps domain </summary>
|
||||
float deltaForLerps = 0.016f;
|
||||
/// <summary> Helper delta for target update rate usage -> -> It's value is desired to be 1f / targetRate </summary>
|
||||
float rateDelta = 0.016f;
|
||||
|
||||
/// <summary> Helper for calculating stable delta calculations </summary>
|
||||
protected float collectedDelta = 0f;
|
||||
/// <summary> How many udpate loops should be done according to stable update rate </summary>
|
||||
protected int framesToSimulate = 1;
|
||||
protected int previousframesToSimulate = 1;
|
||||
|
||||
bool updateTailAnimator = false;
|
||||
|
||||
/// <summary>
|
||||
/// Conditions to do any calculations within Tail Animator
|
||||
/// </summary>
|
||||
void CheckIfTailAnimatorShouldBeUpdated()
|
||||
{
|
||||
if (!initialized)
|
||||
{
|
||||
if (StartAfterTPose)
|
||||
{
|
||||
startAfterTPoseCounter++;
|
||||
if (startAfterTPoseCounter > 6) Init();
|
||||
}
|
||||
|
||||
updateTailAnimator = false;
|
||||
return;
|
||||
}
|
||||
|
||||
#region Debug "`" disable key for editor only
|
||||
|
||||
#if UNITY_EDITOR
|
||||
#if ENABLE_LEGACY_INPUT_MANAGER
|
||||
if (Input.GetKey(KeyCode.BackQuote))
|
||||
{
|
||||
updateTailAnimator = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
if (UseMaxDistance)
|
||||
{
|
||||
MaxDistanceCalculations();
|
||||
conditionalWeight = OverrideWeight * distanceWeight;
|
||||
}
|
||||
else
|
||||
conditionalWeight = OverrideWeight;
|
||||
|
||||
|
||||
#region Force disable transition implementation
|
||||
|
||||
if (_forceDisable) // fade to disabled state
|
||||
{
|
||||
if (FadeDuration > 0f)
|
||||
{
|
||||
_forceDisableElapsed += Time.unscaledDeltaTime * (1f / FadeDuration);
|
||||
if (_forceDisableElapsed > 1f) _forceDisableElapsed = 1f;
|
||||
}
|
||||
else
|
||||
_forceDisableElapsed = 1f;
|
||||
|
||||
conditionalWeight *= 1f - _forceDisableElapsed;
|
||||
}
|
||||
else // Fade back in
|
||||
{
|
||||
if (_forceDisableElapsed > 0f)
|
||||
if (FadeDuration > 0f)
|
||||
{
|
||||
_forceDisableElapsed -= Time.unscaledDeltaTime * (1f / FadeDuration);
|
||||
if (_forceDisableElapsed < 0f) _forceDisableElapsed = 0f;
|
||||
conditionalWeight *= 1f - _forceDisableElapsed;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
if (DisabledByInvisibility()) return;
|
||||
|
||||
//#region Triggering Animate Physics Support
|
||||
|
||||
//if (AnimatePhysics)
|
||||
//{
|
||||
// if (!animatePhysicsWorking) StartCoroutine(AnimatePhysicsClock());
|
||||
// if (!triggerAnimatePhysics) { updateTailAnimator = false; return; } else triggerAnimatePhysics = false;
|
||||
//}
|
||||
|
||||
//#endregion
|
||||
|
||||
if (UseCollision) if (!collisionInitialized) SetupSphereColliders();
|
||||
|
||||
if (TailSegments.Count == 0)
|
||||
{
|
||||
Debug.LogError("[TAIL ANIMATOR] No tail bones defined in " + name + " !");
|
||||
initialized = false;
|
||||
updateTailAnimator = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Enabling / disabling with blending value
|
||||
if (TailAnimatorAmount * conditionalWeight <= Mathf.Epsilon)
|
||||
{
|
||||
wasDisabled = true;
|
||||
updateTailAnimator = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (wasDisabled)
|
||||
{
|
||||
User_ReposeTail();
|
||||
previousWorldPosition = transform.position;
|
||||
wasDisabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (IncludeParent) if (TailSegments.Count > 0) if (!TailSegments[0].transform.parent) IncludeParent = false;
|
||||
|
||||
if (TailSegments.Count < 1)
|
||||
{
|
||||
updateTailAnimator = false;
|
||||
return;
|
||||
}
|
||||
|
||||
updateTailAnimator = true;
|
||||
}
|
||||
|
||||
|
||||
public bool DisabledByInvisibility()
|
||||
{
|
||||
if (OptimizeWithMesh != null)
|
||||
{
|
||||
bool somethingVisible = false;
|
||||
if (OptimizeWithMesh.isVisible) somethingVisible = true;
|
||||
else
|
||||
{
|
||||
if (OptimizeWithMeshes != null)
|
||||
if (OptimizeWithMeshes.Length > 0)
|
||||
for (int i = 0; i < OptimizeWithMeshes.Length; i++)
|
||||
{
|
||||
if (OptimizeWithMeshes[i] == null) continue;
|
||||
if (OptimizeWithMeshes[i].isVisible) { somethingVisible = true; break; }
|
||||
}
|
||||
}
|
||||
|
||||
if (somethingVisible == false) { updateTailAnimator = false; return true; }
|
||||
}
|
||||
|
||||
return false; // Not Disabled
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Just defining delta time for component operations
|
||||
/// </summary>
|
||||
void DeltaTimeCalculations()
|
||||
{
|
||||
if (UpdateRate > 0) // If we setted target update rate
|
||||
{
|
||||
switch (DeltaType)
|
||||
{
|
||||
case EFDeltaType.SafeDelta: case EFDeltaType.DeltaTime: justDelta = Time.deltaTime / Mathf.Clamp(Time.timeScale, 0.01f, 1f); break;
|
||||
case EFDeltaType.SmoothDeltaTime: justDelta = Time.smoothDeltaTime; break;
|
||||
case EFDeltaType.UnscaledDeltaTime: justDelta = Time.unscaledDeltaTime; break;
|
||||
case EFDeltaType.FixedDeltaTime: justDelta = Time.fixedDeltaTime; break;
|
||||
}
|
||||
|
||||
justDelta *= TimeScale;
|
||||
secPeriodDelta = 1f;
|
||||
deltaForLerps = secPeriodDelta;
|
||||
rateDelta = 1f / (float)UpdateRate;
|
||||
|
||||
StableUpdateRateCalculations();
|
||||
}
|
||||
else // Unlimited update rate
|
||||
{
|
||||
switch (DeltaType)
|
||||
{
|
||||
case EFDeltaType.SafeDelta: justDelta = Mathf.Lerp(justDelta, GetClampedSmoothDelta(), 0.075f); break;
|
||||
case EFDeltaType.DeltaTime: justDelta = Time.deltaTime; break;
|
||||
case EFDeltaType.SmoothDeltaTime: justDelta = Time.smoothDeltaTime; break;
|
||||
case EFDeltaType.UnscaledDeltaTime: justDelta = Time.unscaledDeltaTime; break;
|
||||
case EFDeltaType.FixedDeltaTime: justDelta = Time.fixedDeltaTime; break;
|
||||
}
|
||||
|
||||
rateDelta = justDelta;
|
||||
deltaForLerps = Mathf.Pow(secPeriodDelta, 0.1f) * 0.02f;
|
||||
justDelta *= TimeScale;
|
||||
|
||||
// Helper parameter to not calculate "*60" i-times
|
||||
secPeriodDelta = Mathf.Min(1f, justDelta * 60f);
|
||||
|
||||
framesToSimulate = 1;
|
||||
previousframesToSimulate = 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating how many update loops should be done in this frame according to target update rate and elapsed deltaTime
|
||||
/// </summary>
|
||||
void StableUpdateRateCalculations()
|
||||
{
|
||||
previousframesToSimulate = framesToSimulate; // Remembering how many loops used in this frame for bones animation calibration in next frame
|
||||
collectedDelta += justDelta; // Collecting delta time from game frames
|
||||
framesToSimulate = 0; // Collecting delta for [one second] div by [UpdateRate] update so for one frame in static defined time rate
|
||||
|
||||
while (collectedDelta >= rateDelta) // Collected delta is big enough to do tail motion frame
|
||||
{
|
||||
collectedDelta -= rateDelta;
|
||||
framesToSimulate += 1;
|
||||
if (framesToSimulate >= 3) { collectedDelta = 0; break; } // Simulating up to 3 frames update in one unity game frame
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Preparing not animated bones, if animated they will be changed after Update() and before LateUpdate() by Unity Animator
|
||||
/// </summary>
|
||||
void PreCalibrateBones()
|
||||
{
|
||||
TailSegment child = TailSegments[0];
|
||||
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.PreCalibrate();
|
||||
child = child.ChildBone;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Preparing bones for animation synchronized with keyframe animations
|
||||
/// </summary>
|
||||
void CalibrateBones()
|
||||
{
|
||||
if (UseIK)
|
||||
if (IKBlend > 0f)
|
||||
{
|
||||
UpdateIK();
|
||||
}
|
||||
|
||||
_limiting_stretchingHelperTooLong = Mathf.Lerp(0.4f, 0.0f, MaxStretching);
|
||||
_limiting_stretchingHelperTooShort = _limiting_stretchingHelperTooLong * 1.5f;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checking for null referencees in ghost chain list
|
||||
/// </summary>
|
||||
public void CheckForNullsInGhostChain()
|
||||
{
|
||||
if (_TransformsGhostChain == null) _TransformsGhostChain = new System.Collections.Generic.List<Transform>();
|
||||
for (int i = _TransformsGhostChain.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_TransformsGhostChain[i] == null) _TransformsGhostChain.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary> Helper variable for start after t-pose feature </summary>
|
||||
int startAfterTPoseCounter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Limiting smooth delta in certain ranges to prevent jittery
|
||||
/// </summary>
|
||||
float GetClampedSmoothDelta()
|
||||
{
|
||||
return Mathf.Clamp(Time.smoothDeltaTime, 0f, 0.25f);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Helper quaternion multiply method for non-slithery motion
|
||||
/// </summary>
|
||||
Quaternion MultiplyQ(Quaternion rotation, float times)
|
||||
{
|
||||
return Quaternion.AngleAxis(rotation.x * Mathf.Rad2Deg * times, Vector3.right) *
|
||||
Quaternion.AngleAxis(rotation.z * Mathf.Rad2Deg * times, Vector3.forward) *
|
||||
Quaternion.AngleAxis(rotation.y * Mathf.Rad2Deg * times, Vector3.up);
|
||||
}
|
||||
|
||||
|
||||
#region Curves Operations
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Evaluating value for tail segment from given clamped curve
|
||||
/// </summary>
|
||||
public float GetValueFromCurve(int i, AnimationCurve c)
|
||||
{
|
||||
if (!initialized) return c.Evaluate((float)i / (float)_TransformsGhostChain.Count);
|
||||
else
|
||||
return c.Evaluate(TailSegments[i].IndexOverlLength);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Clamping curve keys to fit in given bounds
|
||||
/// </summary>
|
||||
public AnimationCurve ClampCurve(AnimationCurve a, float timeStart, float timeEnd, float lowest, float highest)
|
||||
{
|
||||
Keyframe[] keys = a.keys;
|
||||
|
||||
for (int i = 0; i < keys.Length; i++)
|
||||
{
|
||||
if (keys[i].time < timeStart) keys[i].time = timeStart; else if (keys[i].time > timeEnd) keys[i].time = timeEnd;
|
||||
if (keys[i].value < lowest) keys[i].value = lowest; else if (keys[i].value > highest) keys[i].value = highest;
|
||||
}
|
||||
|
||||
a.keys = keys;
|
||||
return a;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checking if curve keyframes are not equal
|
||||
/// </summary>
|
||||
//public bool KeysChanged(Keyframe[] a, Keyframe[] b)
|
||||
//{
|
||||
// if (a == null || b == null) return true;
|
||||
// if (a.Length != b.Length) return true;
|
||||
|
||||
// for (int i = 0; i < a.Length; i++)
|
||||
// {
|
||||
// if (a[i].time != b[i].time) return true;
|
||||
// if (a[i].value != b[i].value) return true;
|
||||
// if (a[i].inTangent != b[i].inTangent) return true;
|
||||
// if (a[i].outTangent != b[i].outTangent) return true;
|
||||
// }
|
||||
|
||||
// return false;
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Making sure ghost transform chain list is valid
|
||||
/// </summary>
|
||||
public void RefreshTransformsList()
|
||||
{
|
||||
if (_TransformsGhostChain == null) _TransformsGhostChain = new System.Collections.Generic.List<Transform>();
|
||||
else
|
||||
for (int i = _TransformsGhostChain.Count - 1; i >= 0; i--)
|
||||
if (_TransformsGhostChain[0] == null) _TransformsGhostChain.RemoveAt(i);
|
||||
}
|
||||
|
||||
/// <summary> Getting helper bone marker used for animating last bone in chain </summary>
|
||||
public TailSegment GetGhostChild() { return GhostChild; }
|
||||
|
||||
/// <summary> Helper flag for basic animate physics mode </summary>
|
||||
bool fixedUpdated = false;
|
||||
|
||||
// Supporting second solution for fixed animate physics mode
|
||||
private bool lateFixedIsRunning = false;
|
||||
private bool fixedAllow = true;
|
||||
private IEnumerator LateFixed()
|
||||
{
|
||||
WaitForFixedUpdate fixedWait = new WaitForFixedUpdate();
|
||||
lateFixedIsRunning = true;
|
||||
|
||||
while (true)
|
||||
{
|
||||
yield return fixedWait;
|
||||
PreCalibrateBones();
|
||||
fixedAllow = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4eccae1c8b93bb74ab7e39c82b68bd06
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,130 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// POST POSITION PROCESSING - Deflection
|
||||
/// </summary>
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
[Tooltip("Making tail segment deflection influence back segments")]
|
||||
[Range(0f, 1f)]
|
||||
public float Deflection = 0.0f;
|
||||
[FPD_Suffix(1f, 89f, FPD_SuffixAttribute.SuffixMode.FromMinToMaxRounded, "°")]
|
||||
public float DeflectionStartAngle = 10f;
|
||||
[Range(0f, 1f)]
|
||||
public float DeflectionSmooth = 0f;
|
||||
[FPD_FixedCurveWindow(0, 0f, 1f, 1f, .65f, 0.4f, 1f, 0.9f)]
|
||||
public AnimationCurve DeflectionFalloff = AnimationCurve.EaseInOut(0f, 0f, 1f, 1f);
|
||||
[Tooltip("Deflection can be triggered every time tail is waving but you not always would want this feature be enabled (different behaviour of tail motion)")]
|
||||
public bool DeflectOnlyCollisions = true;
|
||||
|
||||
// Deflection feature variables
|
||||
private List<TailSegment> _defl_source;
|
||||
private float _defl_treshold = 0.01f;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Searching for deflection points on stored segments position
|
||||
/// before post processing with deflection to avoid jitter
|
||||
/// </summary>
|
||||
void Deflection_BeginUpdate()
|
||||
{
|
||||
// Defining constants for segments
|
||||
_defl_treshold = DeflectionStartAngle / 90f;
|
||||
float smoothTime = DeflectionSmooth / 9f;
|
||||
|
||||
//for (int i = TailBones.Count-1; i >=_tc_startII; --i)
|
||||
for (int i = _tc_startII; i < TailSegments.Count; i++)
|
||||
{
|
||||
TailSegment ppChild = _pp_reference[i];
|
||||
|
||||
// Checking deflection state to detect bend amount
|
||||
bool cleared = ppChild.CheckDeflectionState(_defl_treshold, smoothTime, rateDelta);
|
||||
|
||||
// Detecting if deflection occured and adding as deflection source point
|
||||
if (!cleared)
|
||||
{
|
||||
bool dependenciesMeet = true;
|
||||
|
||||
if (DeflectOnlyCollisions)
|
||||
if (ppChild.CollisionContactRelevancy <= 0f)
|
||||
dependenciesMeet = false; // No collision - no deflection
|
||||
|
||||
// Adding to deflection points list
|
||||
if (dependenciesMeet)
|
||||
{
|
||||
Deflection_AddDeflectionSource(ppChild);
|
||||
}
|
||||
else
|
||||
Deflection_RemoveDeflectionSource(ppChild);
|
||||
}
|
||||
else
|
||||
{
|
||||
Deflection_RemoveDeflectionSource(ppChild);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checking conditions to remove deflection source point
|
||||
/// </summary>
|
||||
void Deflection_RemoveDeflectionSource(TailSegment child)
|
||||
{
|
||||
if (child.DeflectionRestoreState() == null)
|
||||
if (_defl_source.Contains(child)) _defl_source.Remove(child);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checking conditions to add deflection source point and sorting list by index
|
||||
/// </summary>
|
||||
void Deflection_AddDeflectionSource(TailSegment child)
|
||||
{
|
||||
if (child.DeflectionRelevant())
|
||||
if (!_defl_source.Contains(child)) _defl_source.Add(child);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Changing position of segment for deflection pose if deflection points are detected
|
||||
/// </summary>
|
||||
void Deflection_SegmentOffsetSimple(TailSegment child, ref Vector3 position)
|
||||
{
|
||||
if (child.Index == _tc_startI) return; // We not affecting first bone
|
||||
|
||||
// We using greatest deflection so we must remember last one used in loop
|
||||
float lastDeflectionPower = 0f;
|
||||
|
||||
// Defining influence of further children deflection on this segment
|
||||
// Going through all detected deflection points in this loop
|
||||
for (int i = 0; i < _defl_source.Count; i++)
|
||||
{
|
||||
// When there is one deflection and another one in next index then deflection is cancelling
|
||||
if (child.Index > _defl_source[i].Index) continue; // When segment is further on tail than deflection point then don't do anything here
|
||||
if (child.Index == _defl_source[i].Index) continue; // Not deflecting deflection source point
|
||||
|
||||
// If we already used deflection with greater power
|
||||
if (_defl_source[i].DeflectionFactor < lastDeflectionPower) continue;
|
||||
lastDeflectionPower = _defl_source[i].DeflectionFactor;
|
||||
|
||||
// Using curve over segments from zero or previous deflection source towards current one
|
||||
float preI = 0; if (i > 0) preI = _defl_source[i].Index; // Index for falloff curve
|
||||
float timeOnCurve = Mathf.InverseLerp(preI, _defl_source[i].Index, child.Index); // Falloff from previous deflection point to next one over curve
|
||||
|
||||
// Direction from procedural position towards deflective position
|
||||
Vector3 towardDeflection = _defl_source[i].DeflectionWorldPosition - child.ParentBone.ProceduralPosition;
|
||||
|
||||
// Calculating position of segment directed towards deflection compensation
|
||||
Vector3 deflectedSegmentPos = child.ParentBone.ProceduralPosition;
|
||||
deflectedSegmentPos += towardDeflection.normalized * child.BoneLengthScaled; // Scale support
|
||||
|
||||
// Applying certain amount of deflection on segment position
|
||||
child.ProceduralPosition = Vector3.LerpUnclamped(child.ProceduralPosition, deflectedSegmentPos, Deflection * DeflectionFalloff.Evaluate(timeOnCurve) * _defl_source[i].DeflectionSmooth);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9279b9459c57b248a52fb0f3d63e82e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,103 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FC: In this parital class you will find methods useful for custom
|
||||
/// coding and dynamic tail hierarchy changes etc.
|
||||
/// </summary>
|
||||
public partial class TailAnimator2
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Re-initialize tail with new transforms chain
|
||||
/// </summary>
|
||||
public void User_SetTailTransforms(List<Transform> list)
|
||||
{
|
||||
StartBone = list[0];
|
||||
EndBone = list[list.Count - 1];
|
||||
_TransformsGhostChain = list;
|
||||
|
||||
StartAfterTPose = false;
|
||||
initialized = false;
|
||||
Init();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Putting additional tail transform in chain list (added to the end of tail)
|
||||
/// </summary>
|
||||
public TailSegment User_AddTailTransform(Transform transform)
|
||||
{
|
||||
TailSegment newSeg = new TailSegment(transform);
|
||||
TailSegment last = TailSegments[TailSegments.Count - 1];
|
||||
newSeg.ParamsFromAll(last);
|
||||
|
||||
newSeg.RefreshFinalPos(newSeg.transform.position);
|
||||
newSeg.RefreshFinalRot(newSeg.transform.rotation);
|
||||
newSeg.ProceduralPosition = newSeg.transform.position;
|
||||
newSeg.PosRefRotation = newSeg.transform.rotation;
|
||||
|
||||
_TransformsGhostChain.Add(transform);
|
||||
TailSegments.Add(newSeg);
|
||||
last.SetChildRef(newSeg);
|
||||
newSeg.SetParentRef(last);
|
||||
newSeg.SetChildRef(GhostChild);
|
||||
GhostChild.SetParentRef(newSeg);
|
||||
|
||||
// Resetting indexes for curves
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
TailSegments[i].SetIndex(i, TailSegments.Count);
|
||||
|
||||
return newSeg;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dynamically removing tail segments from chain
|
||||
/// </summary>
|
||||
/// <param name="fromLastTo"> Segment with this index will be removed too but used as ghosting child </param>
|
||||
public void User_CutEndSegmentsTo(int fromLastTo)
|
||||
{
|
||||
if (fromLastTo < TailSegments.Count)
|
||||
{
|
||||
GhostChild = TailSegments[fromLastTo];
|
||||
GhostChild.SetChildRef(null);
|
||||
|
||||
for (int i = TailSegments.Count - 1; i >= fromLastTo; i--)
|
||||
{
|
||||
TailSegments.RemoveAt(i);
|
||||
_TransformsGhostChain.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("[Tail Animator Cutting] Wrong index, you want cut from end to " + fromLastTo + " segment but there are only " + TailSegments.Count + " segments!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Syncing tail with current transforms positions and rotations
|
||||
/// </summary>
|
||||
public void User_ReposeTail()
|
||||
{
|
||||
GhostParent.Reset();
|
||||
for (int i = 0; i < TailSegments.Count; i++)
|
||||
TailSegments[i].Reset();
|
||||
GhostChild.Reset();
|
||||
}
|
||||
|
||||
|
||||
bool _forceDisable = false;
|
||||
float _forceDisableElapsed = 0f;
|
||||
/// <summary>
|
||||
/// Disable tail animator with fade transition as "Max Distance" fade
|
||||
/// </summary>
|
||||
public void User_ForceDisabled(bool disable)
|
||||
{
|
||||
_forceDisable = disable;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6bf5d257ea4a6e47bbe76c86d216ec6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,149 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
public partial class TailAnimator2 : UnityEngine.EventSystems.IDropHandler, IFHierarchyIcon
|
||||
{
|
||||
|
||||
#region Hierarchy Icon
|
||||
|
||||
public string EditorIconPath { get { if (PlayerPrefs.GetInt("AnimsH", 1) == 0) return ""; else return "Tail Animator/Tail Animator Icon Small"; } }
|
||||
public void OnDrop(UnityEngine.EventSystems.PointerEventData data) { }
|
||||
|
||||
#endregion
|
||||
|
||||
#region Enums
|
||||
|
||||
public enum EFDeltaType { DeltaTime, SmoothDeltaTime, UnscaledDeltaTime, FixedDeltaTime, SafeDelta }
|
||||
public enum EAnimationStyle { Quick, Accelerating, Linear }
|
||||
|
||||
#endregion
|
||||
|
||||
public enum ETailCategory { Setup, Tweak, Features, Shaping }
|
||||
public ETailCategory _Editor_Category = ETailCategory.Setup;
|
||||
public enum ETailFeaturesCategory { Main, Collisions, IK, Experimental }
|
||||
public ETailFeaturesCategory _Editor_FeaturesCategory = ETailFeaturesCategory.Main;
|
||||
|
||||
public bool DrawGizmos = true;
|
||||
|
||||
[Tooltip("First bone of tail motion chain")]
|
||||
public Transform StartBone;
|
||||
[Tooltip("Finish bone of tail motion chain")]
|
||||
public Transform EndBone;
|
||||
|
||||
[Tooltip("Adjusting end point for end tail bone motion")]
|
||||
public Vector3 EndBoneJointOffset = Vector3.zero;
|
||||
|
||||
public List<Transform> _TransformsGhostChain;
|
||||
public int _GhostChainInitCount = -1;
|
||||
|
||||
/// <summary> Initialization method controll flag </summary>
|
||||
protected bool initialized = false;
|
||||
public bool IsInitialized { get { return initialized; } }
|
||||
[Tooltip("Target FPS update rate for Tail Animator.\n\nIf you want Tail Animator to behave the same in low/high fps, set this value for example to 60.\nIt also can help optimizing if your game have more than 60 fps.")]
|
||||
public int UpdateRate = 0;
|
||||
|
||||
[Tooltip("If your character Unity's Animator have update mode set to 'Animate Physics' you should enable it here too")]
|
||||
public EFixedMode AnimatePhysics = EFixedMode.None;
|
||||
public enum EFixedMode { None, Basic, Late }
|
||||
|
||||
[Tooltip("When using target fps rate you can interpolate coordinates for smoother effect when object with tail is moving a lot")]
|
||||
public bool InterpolateRate = false;
|
||||
|
||||
[Tooltip("Simulating tail motion at initiation to prevent jiggle start")]
|
||||
public bool Prewarm = false;
|
||||
|
||||
/// <summary> For custom coding if you want to manipulate tail motion weight in additional way </summary>
|
||||
internal float OverrideWeight = 1f;
|
||||
/// <summary> Multiplier for tail motion weight computed from different conditions (max distance, override weight etc.) </summary>
|
||||
protected float conditionalWeight = 1f;
|
||||
|
||||
protected bool collisionInitialized = false;
|
||||
protected bool forceRefreshCollidersData = false;
|
||||
Vector3 previousWorldPosition;
|
||||
|
||||
/// <summary> Parent transform of first tail transform </summary>
|
||||
protected Transform rootTransform;
|
||||
|
||||
protected bool preAutoCorrect = false;
|
||||
|
||||
|
||||
|
||||
void OnValidate()
|
||||
{
|
||||
if (UpdateRate < 0) UpdateRate = 0;
|
||||
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
RefreshSegmentsColliders();
|
||||
//lastCurving = Curving; lastCurving.x += 0.001f;
|
||||
if (UseIK) IK_ApplyLimitBoneSettings();
|
||||
//if (UseWind) TailAnimatorWind.Refresh(this);
|
||||
}
|
||||
|
||||
if (UsePartialBlend) { ClampCurve(BlendCurve, 0f, 1f, 0f, 1f); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getting list of transform for Tail Animator using Start and End Bone Transform guides
|
||||
/// </summary>
|
||||
public void GetGhostChain()
|
||||
{
|
||||
if (_TransformsGhostChain == null) _TransformsGhostChain = new System.Collections.Generic.List<Transform>();
|
||||
|
||||
if (EndBone == null)
|
||||
{
|
||||
// Just traight forward path through children
|
||||
_TransformsGhostChain.Clear();
|
||||
|
||||
Transform tChild = StartBone;
|
||||
if (tChild == null) tChild = transform;
|
||||
|
||||
_TransformsGhostChain.Add(tChild);
|
||||
|
||||
while (tChild.childCount > 0)
|
||||
{
|
||||
tChild = tChild.GetChild(0);
|
||||
if (!_TransformsGhostChain.Contains(tChild)) _TransformsGhostChain.Add(tChild);
|
||||
}
|
||||
|
||||
//if (!_TransformsGhostChain.Contains(tChild))
|
||||
_GhostChainInitCount = _TransformsGhostChain.Count;
|
||||
}
|
||||
else // Going through parents of 'End Bone' to 'Start Bone'
|
||||
{
|
||||
|
||||
System.Collections.Generic.List<Transform> newTrs = new System.Collections.Generic.List<Transform>();
|
||||
|
||||
Transform sBone = StartBone; if (sBone == null) sBone = transform;
|
||||
Transform tParent = EndBone;
|
||||
|
||||
newTrs.Add(tParent);
|
||||
|
||||
while (tParent != null && tParent != StartBone)
|
||||
{
|
||||
tParent = tParent.parent;
|
||||
if (!newTrs.Contains(tParent)) newTrs.Add(tParent);
|
||||
}
|
||||
|
||||
if (tParent == null) // No parent of startbone!
|
||||
{
|
||||
Debug.Log("[Tail Animator Editor] " + EndBone.name + " is not child of " + sBone.name + "!");
|
||||
Debug.LogError("[Tail Animator Editor] " + EndBone.name + " is not child of " + sBone.name + "!");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!newTrs.Contains(tParent)) newTrs.Add(tParent);
|
||||
|
||||
_TransformsGhostChain.Clear();
|
||||
_TransformsGhostChain = newTrs;
|
||||
|
||||
_TransformsGhostChain.Reverse();
|
||||
_GhostChainInitCount = _TransformsGhostChain.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2dc5b1e05e72e44e91c60a332e6b401
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0396bd88bfc90b24f8713f695263a0d3
|
||||
folderAsset: yes
|
||||
timeCreated: 1600118724
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e70277fbd3e17b54e91983280ca90b9a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c8a319bfdaa465646b09cab5796d7fe7
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a8cc58bf52c86a47b69e3e0d6a7029a
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c44e8a3d238b4d4f8f36d7af88d25f6
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5b3577c5e8a41c4ab0dd517d5d9737d
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,266 @@
|
||||
_________________________________________________________________________________________________________
|
||||
|
||||
Package "Tail Animator V2"
|
||||
Version 2.0.7.4
|
||||
|
||||
Made by FImpossible Creations - Filip Moeglich
|
||||
https://www.FilipMoeglich.pl
|
||||
FImpossibleGames@Gmail.com or Filip.Moeglich@Gmail.com
|
||||
|
||||
_________________________________________________________________________________________________________
|
||||
|
||||
Unity Connect: https://connect.unity.com/u/5b2e9407880c6425c117fab1
|
||||
Youtube: https://www.youtube.com/channel/UCDvDWSr6MAu1Qy9vX4w8jkw
|
||||
Facebook: https://www.facebook.com/FImpossibleCreations
|
||||
Twitter (@FimpossibleC): https://twitter.com/FImpossibleC
|
||||
Google+: https://plus.google.com/u/3/115325467674876785237
|
||||
|
||||
_________________________________________________________________________________________________________
|
||||
|
||||
Package Files Contests:
|
||||
|
||||
- Tail Animator - V2 Demo Examples.unitypackage:
|
||||
Few exmaple scenes, models and scripts showing some features of Tail Animator V2
|
||||
|
||||
- Tail Animator V1 to V2 Converter.unitypackage:
|
||||
Overriding Tail Animator V1.3.7.1 with buttons to convert Tail Animator V1 to V2
|
||||
(Warning! You have to adjust motion variables anyway to make animation look the same)
|
||||
Package also contains Tail Animator V1 example scenes and example scenes assets.
|
||||
|
||||
- Tail Animator - Tails to Use.unitypackage:
|
||||
Few example models which can be used in any way by owner of the package.
|
||||
|
||||
_________________________________________________________________________________________________________
|
||||
Description:
|
||||
|
||||
- Tail Animator is animating chains of transforms (it can be 3D/2D model bones)
|
||||
simulating physical behaviour and can be highly customized
|
||||
|
||||
- Package is providing mesh skinning API which can be used to create skinned mesh
|
||||
renderers inside editor or in playmode (runtime)
|
||||
|
||||
- Tail Animator component is providing highly customized inspector window (GUI)
|
||||
to help user design desired motion responsively
|
||||
|
||||
- Package is providing many example scenes presenting different features which can
|
||||
be unpacked to project with “Tail Animator - New Demo Scenes” unitypackage file
|
||||
|
||||
- In package’s demo scenes are used models with dense meshes (not optimized for
|
||||
mobile for example) so package is providing few optimized tail models for custom
|
||||
usage or to be used as rigging reference and it can be unpacked to project with
|
||||
“Tail Animator - Tail Models for custom usage” unitypackage file
|
||||
|
||||
- Package is providing tools to convert old Tail Animator V1 scripts to new one, it can
|
||||
be unpacked to project with “Tail Animator - V1 to V2 Converter + Old Examples”
|
||||
|
||||
- Tail Animator motion can be combined with skeleton keyframe animations to make
|
||||
it more elastic and smooth
|
||||
|
||||
_________________________________________________________________________________________________________
|
||||
Version history:
|
||||
|
||||
V2.0.7.4
|
||||
- Fixed issue of Tail Animator not updating after being disabled, when using Fixed Update Late mode.
|
||||
- <IK Align End With Target> now is using all 3 axis for rotation
|
||||
|
||||
V2.0.7.3
|
||||
- Implemented <IK Align End With Target> parameter, to make last tail bone be rotated as IK target object
|
||||
|
||||
V2.0.7.2
|
||||
- Fixed dynamic colliders inclusion trigger sphere radius scale
|
||||
- Dynamic inclusion trigger collider reference is now accessable throught variable "GeneratedDynamicInclusionCollider"
|
||||
|
||||
V2.0.7.1
|
||||
- When time scale is set to zero, tail motion will pause
|
||||
|
||||
V2.0.7
|
||||
- Support for CharacterController collider
|
||||
- Added IK Invert Order option for Tail Animator IK
|
||||
|
||||
V2.0.6
|
||||
- Reworked wind effect
|
||||
- Polished "Detach" feature which makes algorithm works up to 6 times faster but IT DOESN'T WORK WITH ANIMATED MODELS! It's good for example for cloth.
|
||||
- Some optimizations
|
||||
|
||||
V2.0.5
|
||||
- Inspector window now is less vertical long, tweak categories are selected in horizontal menu
|
||||
- Added experimental version for 2D collision detection
|
||||
- Optimized some collision algorithms
|
||||
- Added "Animate" Roll toggle (under "Additional Parameters") which will make smooth rotate bones in forward axis (Tail Animator V1 style)
|
||||
|
||||
|
||||
---------- !!! WARNING FOR BIG UPDATE TO V2 !!! ----------
|
||||
Tail Animator V2 is using only one and new component for all Tail Animator features, old Tail Animators V1 can be converted to V2 component by importing Converter unitypackage.
|
||||
Tail Animator V2 motion can look different after conversion so it needs some adjustements after conversions.
|
||||
|
||||
V2.0.0
|
||||
- New component containing all other components features (only it should used from V2) - Tail Animator V2
|
||||
- New GUI
|
||||
- Language support for inspector tabs titles
|
||||
- New parameters: "Slithery" -> V2 Slithery setted to 1 / 1.2 will make motion look almost the same like V1 motion
|
||||
"Curling": Similar to V1 "Sensitivity" parameter
|
||||
"Sustain" (experimental): Making tail bounce more after moving (more noticable with boosted "Springiness")
|
||||
"Unify Bendiness" (experimental): Making tail bend in similar way no matter how many bones / how long tail model is
|
||||
"Reaction Speed" / "Rotation Relevancy" / "Animation Mode": Working in almost the same way like V1 "positions / rotations speed
|
||||
- Fixed Update Rate mode to make Tail Animator motion look the same in Higher / Low fps domain
|
||||
- "Prewarm" option to pre-animate tail before first game frame to avoid jiggly start in some cases
|
||||
- "Root to parent" parameter replaced by "Include" / "Exclude" dropdown next to "Start Bone"
|
||||
- "Queue to last update" renamed to "Update as last"
|
||||
- "Blend To Original" renamed to "Tail Animator Amount" and reacting in reverse way than old "Blend To Original"
|
||||
- New parameter "Limit Axis 2D" to restrict tail animator for rotating around 2D space (please adjust auto waving axis if you using "Auto Waving" - some cases can't be restricted)
|
||||
- All main parameters now have possibility to spread values separately on each tail segment with curve
|
||||
|
||||
- "Use Waving" renamed to "Auto Waving" and putted into new "Additional Modules" tab
|
||||
- "Fixed Cycle" for syncing tails waving animation for "Auto Waving"
|
||||
- "Collisions" feature in additional modules tab
|
||||
- Collisions algorithm upgraded and enchanced with some new paramters
|
||||
- "Collision Swapping" renamed to "Reflect Collisions" and working in reverse way than V1 parameter
|
||||
- Experimental "Collisions Slippery" parameter to make tails slide a bit less on colliders
|
||||
- Selective collision space have new tab "Dynamic World Colliders Inclusion" to dynamically add colliders from scene with trigger collider
|
||||
- "Partial Blend" as module
|
||||
- New "Inverse Kinematics" feature using CCD IK
|
||||
- New experimental "Deflection" feature to make collision affect back tail segments when deflecting it
|
||||
- New "Disable when Far" feature to smoothly disable tail animator when is far from main camera or other object
|
||||
- Experimental wind effector
|
||||
- Upgraded Mesh Skinning API to be used in Editor but also in Playmode
|
||||
- New demo scenes
|
||||
- Possibility and examples of cutting tail to smaller ones and generating additional segments dynamically
|
||||
- Example of generating tail model procedurally and skinning it in runtime
|
||||
- And much more small improvements
|
||||
|
||||
v1.3.7.1
|
||||
- Added possibility to cull component's calculations with affected mesh visibility
|
||||
- Fixed issue with deactivating tail animator in first frame from code then activating - tail animator wasn't properly initialized in this situation
|
||||
|
||||
v1.3.7
|
||||
- Added capsule collision option for World Space collision (not balanced yet)
|
||||
- Upgraded FTail_Skinner efficiency (spreading weights)
|
||||
- Now after skinning model with FTail_Skinner, vertex colors for meshes will be unchanged
|
||||
- Added download link for DynamicBone syncer in "DynamicBone Syncer.txt" to sync DynamicBone's collision system with tail animator and combine motion
|
||||
|
||||
(between 1.3.7 and 1.3.6)
|
||||
- Added new animation parameter "Motion Influence" - if your character moves too fast you can controll influence of position changes for tail motion.
|
||||
- Added "Animate Root" option, which is applying animation rotations of first chain in bone like "Animate Corrections"
|
||||
- Small changes and fixes inside inspector window
|
||||
|
||||
v1.3.6
|
||||
- Added "Max Angle" variable to limit maximum rotation of each segment from it's initial rotation
|
||||
- Using "Max Angle" parameter, enables other parameters to limit selective axis of rotating, otherwise all angles are clamped to "Max Angle"
|
||||
- Added parameter "Stiff Tail End" in "Experimental" tab which is making last tail segments more stiff for some extra tweak for tail motion
|
||||
- Upgraded behavior of "Gravity" parameter
|
||||
- Added new example scene and models for testing behavior of Tail Animator for hair
|
||||
|
||||
v1.3.5
|
||||
- Added "Sensitivity" variable to give much more animation customization possibilities for tail motion, this variable making tail being more stiff (when lower) or entangled (when higher)
|
||||
- Added "Springiness" variable to give much more animation customization possibilities for tail motion, this variable gives tail more jiggly motion when cranked up
|
||||
- "MaxPositionOffset" variable replaced with "Max Stretching" which is 0-1 slider, on value 1 tail can stretch freely, on value 0 can't stretch a bit, setting this value to about 0.15 is giving the most natural feeling to the motion
|
||||
- Better support for single bone tail chains (root to parent toggle)
|
||||
- Cleaning code resulting in few optimization, preparations before next versions optimizations
|
||||
- New approaches to collisions, now there are three different methods of detecting collisions to choose (only in world space), old one is called "Rotation Offset" (needs bigger sphere tail colliders but works better in collision with mesh colliders), different methods can work better depending on situation etc.
|
||||
- Now you can choose to use world collision detection (collision space variable) or just include needed colliders without use of rigidbodies (quicker and smoother)
|
||||
- "Collision Swapping" (under Physics Tab - not available for 'Parental' look up method - it does it partially anyway) which is making collisions being less reflective in segment rotation (will be polished in next updates)
|
||||
- Added new demo scene "S_TailAnimator_Demo_CollisionWall"
|
||||
- Added info in ReadMe.txt which directories you can remove to purge unnecessary files if you don't need demo scenes anyomore
|
||||
- Some cleaning and changes inside inspector window for quicker navigation
|
||||
+ (hidden update) Different variations of tail model examples models and scene
|
||||
|
||||
|
||||
v1.3.0
|
||||
- SmoothDelta changed and upgraded to SafeDelta to support more smoothed motion when fps are very unstable
|
||||
- Added option "Selective Rotations Not Animated" if some bones in tail chain don't have rotation keyframes in source animation
|
||||
- 'Rolled Bones' replaced with field where we can select LookUp algorithm, added 'CrossUp' algorithm which can support tail motion more precisely than previous methods and 'Parental' which seems to be the most universal but giving a bit different motion (then crank down positions and rotations speeds variable)
|
||||
- Added 'MaxPositionOffset' variable to limit stretching of tail when object moves very fast
|
||||
- Added 'Curving' and changed logics of 'Gravity' variable in "Experimental" tab
|
||||
- Added 'Fuman Elasticness' example scene
|
||||
- Some upgrades in the inspector window
|
||||
|
||||
|
||||
v1.2.9
|
||||
- Updated Skinner component to work with newest unity versions
|
||||
- Updated algorithm for "Rolled Bones" option
|
||||
- Updated algorithm for 'RootToParent' feature
|
||||
|
||||
|
||||
v1.2.8
|
||||
- "Advanced" option for waving animation, waving which is using perlin noise to calculate rotation of optional root animation
|
||||
- Some fixes for inspector windows
|
||||
- Added icon on the right inside hierarchy to easily indicate objects with attached tail animator
|
||||
- Added menu items under "Add Component" > "FImpossible Creations" > "Tail Animator" > Tail Animator Components
|
||||
|
||||
|
||||
v1.2.7
|
||||
- Support for realtime scalling object with tail animator attached
|
||||
|
||||
|
||||
V1.2.6
|
||||
- Added experimental collision detection feature
|
||||
- New example scene with collision feature
|
||||
- Added parameter "Gravity Power" simulating weight of tail
|
||||
- Some fixes inside editor stuff
|
||||
|
||||
|
||||
V1.2.5
|
||||
- Added support for one and two - length bone chains
|
||||
- Added variable "Root To Parent" to make first bone in chain be affected by parent transform (sometimes you will have to toggle "Full Correction" variable to make it work)
|
||||
|
||||
|
||||
V1.2.4
|
||||
- Added component "FTail_Animator_MassUpdater" which handles tail animators update method in one Update tick, it can boost performance of tail animator when you are using a lot of tail animators (from more than 100 it can give some boost, but when more tail animators, then difference more noticable)
|
||||
- Added new example scenes:
|
||||
Fockatrice: Quadroped creature with tail, wings, long neck and feather like elements, all of that enchanced by tail animator to give you some ideas
|
||||
Flime: Not animated slime model with some bones, animated only by tail animator components
|
||||
Hair performance tests: Scene with different hairstyles using lots of tail animator components !read provided info on canvases!
|
||||
Furry Fiped: Rouch example showing how many tails you can compute in the same time with low cpu overload in reference to components count
|
||||
|
||||
|
||||
V1.2.3
|
||||
- Added toggle "Queue To Last Update" which is putting component update order to be last, helpful for integrating other components which affecting bones hierarchy from code like Spine Animator
|
||||
So when you have this option toggles, tail animator will work on bones positions and rotations dictated by spine animator, not by unity animator
|
||||
- Few small polishes
|
||||
|
||||
|
||||
V1.2.2
|
||||
- Small tweaks for inspector window
|
||||
- Added Button "Connect with animator" which is changing variables 'Refresh Helpers', 'Full Correction' and 'Animate Corrections' so you switch to newest feature with one click and more intuitivity
|
||||
- Added toggle 'Refresh Helpers' which is refreshing helper variables in next frame, to use when your model's T-pose is much different from animations of tail chain you want to animate (for example arms)
|
||||
this option allows you to add tail animator to character's arms, pelvis etc. enable 'Full Correction' and 'Animate Corrections' so your model starts to be elastic and you can adjust stiffness
|
||||
- Added manual pdf file with visual friendly description to help you use Tail Animator features in most effetive way
|
||||
|
||||
|
||||
V1.2.1
|
||||
- Option "Auto go through all bones" under "Tuning Parameters" is renamed to "Full correction" and is upgraded
|
||||
to calculate correction for each bone individually, makes it match initial pose at lazy state
|
||||
also added new option "Animate Corrections" when you toggle "Full correction" which is matching keyframed animation's rotations
|
||||
|
||||
|
||||
V1.2.0 [Big Update]
|
||||
- Removed some scripts because they are not needed anymore, they're replaced by more efficient ones
|
||||
(FTail_FixedUpdate etc. because now you can choose which update clock should be used inside inspector)
|
||||
- Animator components are renamed to be more intuitive
|
||||
(FTail_Sinus to FTail_Animator, FTail_MecanimBones to FTail_AnimatorBlending, FTail_2D to FTail_Animator2D etc.)
|
||||
- Upgraded custom inspector and added rendering gizmos in scene view
|
||||
- Added icons for individual components so you find them easier
|
||||
- Added FTail_Editor_Skinner component to skin static meshes onto skinned mesh renderers with tail bone structure inside unity editor
|
||||
- Now bones hierarchy will not be changed at all in order to animate tail
|
||||
|
||||
|
||||
V1.1.2
|
||||
- Added "Automatic" tuning option for fixing orientation axes automatically by default
|
||||
- Added another auto-tuning wrong rotations options, making Tail Animator more universal to cooperate with different skeleton structures
|
||||
|
||||
|
||||
V1.1.0
|
||||
- Added new examples and components to animate transforms with tail animator in 2D space- UI and Sprites
|
||||
- Now you can put one transform to bones list and it will be root bone, so you can have component attached to much other object than tail bone
|
||||
- Custom inspector to see all parameters more clear
|
||||
- New example scenes
|
||||
- School of fish example scene
|
||||
|
||||
|
||||
V1.0.1
|
||||
- Added overrides for Start() method because in some cases it wasn't executed for some reason, probably different .net targets
|
||||
- Updated fSimpleAssets resources to V1.1
|
||||
|
||||
|
||||
V1.0 - 18/07/2018:
|
||||
Initial release
|
||||
@@ -0,0 +1,9 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 212efa10c47ca0c45ac655ef13dafd52
|
||||
timeCreated: 1530012526
|
||||
licenseType: Store
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de387e52748d23b4a8d49123904763ea
|
||||
timeCreated: 1538305915
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,306 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
[AddComponentMenu("FImpossible Creations/Tail Animator 2")]
|
||||
[DefaultExecutionOrder(-4)]
|
||||
[HelpURL( "https://assetstore.unity.com/packages/tools/animation/tail-animator-121819" )]
|
||||
public partial class TailAnimator2 : MonoBehaviour
|
||||
{
|
||||
/// -------- THIS IS PARTIAL CLASS - REST OF THE CODE IN SEPARATED .cs FILES -------- \\\
|
||||
|
||||
|
||||
#region Public Inspector Variables
|
||||
|
||||
[Tooltip("Blending Slithery - smooth & soft tentacle like movement (value = 1)\nwith more stiff & springy motion (value = 0)\n\n0: Stiff somewhat like tree branch\n1: Soft like squid tentacle / Animal tail")]
|
||||
[Range(0f, 1.2f)]
|
||||
public float Slithery = 1f;
|
||||
|
||||
[Tooltip("How curly motion should be applied to tail segments")]
|
||||
[Range(0f, 1f)]
|
||||
public float Curling = 0.5f;
|
||||
|
||||
[Tooltip("Elastic spring effect making motion more 'meaty'")]
|
||||
[Range(0f, 1f)]
|
||||
public float Springiness = 0.0f;
|
||||
|
||||
[Tooltip("If you want to limit stretching/gumminess of position motion when object moves fast. Recommended adjust to go with it under 0.3 value.\nValue = 1: Unlimited stretching")]
|
||||
[Range(0f, 1f)]
|
||||
public float MaxStretching = .375f;
|
||||
|
||||
[Tooltip("Limiting max rotation angle for each tail segment")]
|
||||
[FPD_Suffix(1f, 181f, FPD_SuffixAttribute.SuffixMode.FromMinToMaxRounded, "°")]
|
||||
public float AngleLimit = 181f;
|
||||
[Tooltip("If you need specific axis to be limited.\nLeave unchanged to limit all axes.")]
|
||||
public Vector3 AngleLimitAxis = Vector3.zero;
|
||||
[Tooltip("If you want limit axes symmetrically leave this parameter unchanged, if you want limit one direction of axis more than reversed, tweak this parameter")]
|
||||
public Vector2 LimitAxisRange = Vector2.zero;
|
||||
[Tooltip("If limiting shouldn't be too rapidly performed")]
|
||||
[Range(0f, 1f)]
|
||||
public float LimitSmoothing = 0.5f;
|
||||
|
||||
[Tooltip("If your object moves very fast making tail influenced by speed too much then you can controll it with this parameter")]
|
||||
[FPD_Suffix(0f, 1.5f, FPD_SuffixAttribute.SuffixMode.PercentageUnclamped)]
|
||||
public float MotionInfluence = 1f;
|
||||
[Tooltip("Additional Y influence controll useful when your character is jumping (works only when MotionInfluence value is other than 100%)")]
|
||||
[Range(0f, 1f)] public float MotionInfluenceInY = 1f;
|
||||
|
||||
[Tooltip("If first bone of chain should also be affected with whole chain")]
|
||||
public bool IncludeParent = true;
|
||||
[Tooltip("By basic algorithm of Tail Animator different sized tails with different number of bones would animate with different bending thanks to this toggle every setup bends in very similar amount.\n\nShort tails will bend more and longer oner with bigger amount of bones less with this option enabled.")]
|
||||
[Range(0f, 1f)]
|
||||
public float UnifyBendiness = 0f;
|
||||
|
||||
[Tooltip("Reaction Speed is defining how fast tail segments will return to target position, it gives animation more underwater/floaty feeling if it's lower")]
|
||||
[Range(0f, 1f)]
|
||||
public float ReactionSpeed = .9f;
|
||||
[Tooltip("Sustain is similar to reaction speed in reverse, but providing sustain motion effect when increased")]
|
||||
[Range(0f, 1f)]
|
||||
public float Sustain = 0f;
|
||||
[Tooltip("Rotation speed is defining how fast tail segments will return to target rotation, it gives animation more lazy feeling if it's lower")]
|
||||
[Range(0f, 1f)]
|
||||
public float RotationRelevancy = 1f;
|
||||
|
||||
[Tooltip("Smoothing motion values change over time style to be applied for 'Reaction Speed' and 'Rotation Relevancy' parameters")]
|
||||
public EAnimationStyle SmoothingStyle = EAnimationStyle.Accelerating;
|
||||
[Tooltip("Slowmo or speedup tail animation reaction")]
|
||||
public float TimeScale = 1f;
|
||||
|
||||
[Tooltip("Delta time type to be used by algorithm")]
|
||||
public EFDeltaType DeltaType = EFDeltaType.SafeDelta;
|
||||
|
||||
//[Tooltip("If tail motion should cooperate with keyframed animation if your model is not animated then disable this")]
|
||||
//public bool SyncWithAnimator = true;
|
||||
|
||||
//[Tooltip("IF your model is not animated in any other way than Tail Animator then you can toggle it to avoid some unneccesary operations for optimization")]
|
||||
//public bool NotAnimated = false;
|
||||
|
||||
[Tooltip("Useful when you use other components to affect bones hierarchy and you want this component to follow other component's changes\n\nIt can be really useful when working with 'Spine Animator'")]
|
||||
public bool UpdateAsLast = true;
|
||||
[Tooltip("Checking if keyframed animation has some empty keyframes which could cause unwanted twisting errors")]
|
||||
public bool DetectZeroKeyframes = true;
|
||||
[Tooltip("Initializing Tail Animator after first frames of game to not initialize with model's T-Pose but after playing some other animation")]
|
||||
public bool StartAfterTPose = true;
|
||||
|
||||
[Tooltip("If you want Tail Animator to stop computing when choosed mesh is not visible in any camera view (editor's scene camera is detecting it too)")]
|
||||
public Renderer OptimizeWithMesh;
|
||||
[Tooltip("If you want to check multiple meshes visibility on screen to define if you want to disable tail animator. (useful when using LOD for skinned mesh renderer)")]
|
||||
public Renderer[] OptimizeWithMeshes = null;
|
||||
|
||||
[Tooltip("Blend Source Animation (keyframed / unanimated) and Tail Animator")]
|
||||
[FPD_Suffix(0f, 1f)]
|
||||
public float TailAnimatorAmount = 1f;
|
||||
|
||||
[Tooltip("Removing transforms hierachy structure to optimize Unity's calculations on Matrixes.\nIt can give very big boost in performance for long tails but it can't work with animated models!")]
|
||||
public bool DetachChildren = false;
|
||||
|
||||
[Tooltip("If tail movement should not move in depth you can use this parameter")]
|
||||
/// <summary> 0: Unlimited 1: X is Depth 2: Y is Depth 3: Z is Depth </summary>
|
||||
public int Axis2D = 0;
|
||||
|
||||
[Tooltip("[Experimental: Works only with Slithery Blend set to >= 1] Making each segment go to target pose in front of parent segment creating new animation effect")]
|
||||
[Range(-1f, 1f)]
|
||||
public float Tangle = 0f;
|
||||
|
||||
[Tooltip("Making tail animate also roll rotation like it was done in Tail Animator V1 ! Use Rotation Relevancy Parameter (set lower than 0.5) !")]
|
||||
public bool AnimateRoll = false;
|
||||
|
||||
[Tooltip("Overriding keyframe animation with just Tail Animator option (keyframe animation treated as t-pose bones rotations)")]
|
||||
[Range(0f, 1f)]
|
||||
public float OverrideKeyframeAnimation = 0f;
|
||||
|
||||
public Transform BaseTransform { get { if (_baseTransform) return _baseTransform; else if (_TransformsGhostChain != null) if (_TransformsGhostChain.Count > 0) _baseTransform = _TransformsGhostChain[0]; if (_baseTransform != null) return _baseTransform; return transform; } }
|
||||
private Transform _baseTransform;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Initialize component for correct work
|
||||
/// </summary>
|
||||
void Start()
|
||||
{
|
||||
if (UpdateAsLast) { enabled = false; enabled = true; }
|
||||
if (StartAfterTPose) startAfterTPoseCounter = 6; else Init();
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_2019_1_OR_NEWER
|
||||
/// <summary>
|
||||
/// Setting curves which can't be created automatically when component is added to new object
|
||||
/// </summary>
|
||||
void Reset()
|
||||
{
|
||||
Keyframe key1 = new Keyframe(0f, 0f, 0.1f, 0.1f, 0.0f, 0.5f);
|
||||
Keyframe key2 = new Keyframe(1f, 1f, 5f, 0f, 0.1f, 0.0f);
|
||||
DeflectionFalloff = new AnimationCurve(new Keyframe[2] { key1, key2 });
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Between Update() and LateUpdate() occurs Unity Animator's changes to transforms
|
||||
/// We are using transform addRotation in Tail Animator algorithms and if bones are not animated
|
||||
/// we would overrotate bones every frame, we setting here initial local coords to prevent it
|
||||
/// If bones are animated it's rotations will be overrided after Update()
|
||||
/// </summary>
|
||||
void Update()
|
||||
{
|
||||
CheckIfTailAnimatorShouldBeUpdated();
|
||||
|
||||
// Preparations for target update
|
||||
DeltaTimeCalculations();
|
||||
if (UseWind) WindEffectUpdate();
|
||||
|
||||
if (AnimatePhysics != EFixedMode.None) return;
|
||||
if (!updateTailAnimator) return;
|
||||
if (DetachChildren) { if (_tc_rootBone != null) if (_tc_rootBone.transform) _tc_rootBone.PreCalibrate(); return; }
|
||||
|
||||
if (OverrideKeyframeAnimation < 1f) PreCalibrateBones();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sames as in Update() but for models with 'Animate Physics' enabled
|
||||
/// </summary>
|
||||
void FixedUpdate()
|
||||
{
|
||||
if (AnimatePhysics != EFixedMode.Basic) return;
|
||||
if (!updateTailAnimator) return;
|
||||
if (DetachChildren) { if (_tc_rootBone != null) if (_tc_rootBone.transform) _tc_rootBone.PreCalibrate(); return; }
|
||||
|
||||
fixedUpdated = true;
|
||||
PreCalibrateBones();
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updating bones after unity Animators [execution order -> Update() : UnityAnimators() : LateUpdate()]
|
||||
/// </summary>
|
||||
void LateUpdate()
|
||||
{
|
||||
if (!updateTailAnimator) return;
|
||||
|
||||
|
||||
#region Support second solution for animate physics mode -----
|
||||
|
||||
if (AnimatePhysics == EFixedMode.Late)
|
||||
{
|
||||
if (!lateFixedIsRunning) { StartCoroutine(LateFixed()); }
|
||||
if (fixedAllow) fixedAllow = false; else return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lateFixedIsRunning) { StopCoroutine(LateFixed()); lateFixedIsRunning = false; }
|
||||
|
||||
if (AnimatePhysics == EFixedMode.Basic)
|
||||
{
|
||||
if (fixedUpdated == false) return;
|
||||
fixedUpdated = false;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
|
||||
#region Override Keyframe Animation
|
||||
|
||||
if (DetachChildren)
|
||||
{
|
||||
//PreCalibrateBones();
|
||||
TailSegment child = TailSegments[0];
|
||||
TailSegments[0].RefreshKeyLocalPositionAndRotation(child.InitialLocalPosition, child.InitialLocalRotation);
|
||||
TailSegments[0].PreCalibrate();
|
||||
|
||||
child = TailSegments[1];
|
||||
if (IncludeParent == false)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation(child.InitialLocalPosition, child.InitialLocalRotation);
|
||||
child.PreCalibrate();
|
||||
child = TailSegments[2];
|
||||
}
|
||||
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation(child.InitialLocalPosition, child.InitialLocalRotation);
|
||||
|
||||
//child.transform.localPosition = child.InitialLocalPosition;
|
||||
child.transform.position = _baseTransform.TransformPoint(child.InitialLocalPositionInRoot);
|
||||
// child.transform.localRotation = child.InitialLocalRotation;
|
||||
child.transform.rotation = FEngineering.QToWorld(_baseTransform.rotation, child.InitialLocalRotationInRoot);
|
||||
// tgtRot = FEngineering.QToWorld(BaseTransform.rotation, child.ParentBone.InitialLocalRotationInRoot);
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OverrideKeyframeAnimation > 0f)
|
||||
{
|
||||
if (OverrideKeyframeAnimation >= 1f)
|
||||
{
|
||||
PreCalibrateBones();
|
||||
|
||||
TailSegment child = TailSegments[0];
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation();
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TailSegment child = TailSegments[0];
|
||||
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.transform.localPosition = Vector3.LerpUnclamped(child.transform.localPosition, child.InitialLocalPosition, OverrideKeyframeAnimation);
|
||||
child.transform.localRotation = Quaternion.LerpUnclamped(child.transform.localRotation, child.InitialLocalRotation, OverrideKeyframeAnimation);
|
||||
child.RefreshKeyLocalPositionAndRotation();
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
TailSegment child = TailSegments[0];
|
||||
while (child != GhostChild)
|
||||
{
|
||||
child.RefreshKeyLocalPositionAndRotation();
|
||||
child = child.ChildBone;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
ExpertParamsUpdate();
|
||||
ShapingParamsUpdate();
|
||||
|
||||
// Preparing tail animator for calculating motion
|
||||
CalibrateBones();
|
||||
|
||||
UpdateTailAlgorithm();
|
||||
|
||||
// Shaping / expert parameters refresh + motion influence
|
||||
EndUpdate();
|
||||
|
||||
}
|
||||
|
||||
|
||||
void EndUpdate()
|
||||
{
|
||||
ShapingEndUpdate();
|
||||
ExpertCurvesEndUpdate();
|
||||
previousWorldPosition = BaseTransform.position;
|
||||
}
|
||||
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
lateFixedIsRunning = false;
|
||||
wasDisabled = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 61574e354ba34a141ae2cc1228f5eda3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 2e215648cc15ce04297817f7407c4e7b, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c30d4b51e0607d04cbe6e0f813d883fa
|
||||
folderAsset: yes
|
||||
timeCreated: 1529356541
|
||||
licenseType: Store
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c75d46b0788adf543b0088b6f6275521
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,266 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
#endif
|
||||
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Part of Tail Animator Skinning Static Meshes API
|
||||
/// Methods used by skinner to create skinned mesh renderers from static meshes
|
||||
/// </summary>
|
||||
public static class FTail_Skinning
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Calculating base vertices datas for provided bones setup
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Mesh to be weighted </param>
|
||||
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
|
||||
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
|
||||
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
|
||||
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
|
||||
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
|
||||
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Transform[] bonesCoords, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
|
||||
{
|
||||
Vector3[] pos = new Vector3[bonesCoords.Length];
|
||||
Quaternion[] rot = new Quaternion[bonesCoords.Length];
|
||||
|
||||
//for (int i = 0; i < bonesCoords.Length; i++)
|
||||
//{
|
||||
// pos[i] = bonesCoords[i].position;
|
||||
// rot[i] = bonesCoords[i].rotation;
|
||||
//}
|
||||
|
||||
// We must reset bones structure to identity space
|
||||
for (int i = 0; i < bonesCoords.Length; i++)
|
||||
{
|
||||
// Transforming from world to local space coords
|
||||
pos[i] = bonesCoords[0].parent.InverseTransformPoint(bonesCoords[i].position);
|
||||
rot[i] = FEngineering.QToLocal(bonesCoords[0].parent.rotation, bonesCoords[i].rotation);
|
||||
}
|
||||
|
||||
return CalculateVertexWeightingData(baseMesh, pos, rot, spreadOffset, weightBoneLimit, spreadValue, spreadPower);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating base vertices datas for provided bones setup
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Mesh to be weighted </param>
|
||||
/// <param name="bonesPos"> Mesh local space positions for bones </param>
|
||||
/// <param name="bonesRot"> Mesh local space rotations for bones </param>
|
||||
/// <param name="bonesCoords"> Required local positions and rotations for bones </param>
|
||||
/// <param name="spreadOffset"> Origin weighting offset which can be helpful in some cases, it can be Vector3.zero in most cases </param>
|
||||
/// <param name="weightBoneLimit"> To how many bones vertex can be weighted to create smooth weight effect </param>
|
||||
/// <param name="spreadValue"> Smoothing weights on the edges of bones if lower then more smooth but don't oversmooth it </param>
|
||||
/// <param name="spreadPower"> Making smoothing more sharp on edges </param>
|
||||
public static FTail_SkinningVertexData[] CalculateVertexWeightingData(Mesh baseMesh, Vector3[] bonesPos, Quaternion[] bonesRot, Vector3 spreadOffset, int weightBoneLimit = 2, float spreadValue = 0.8f, float spreadPower = 0.185f)
|
||||
{
|
||||
if (weightBoneLimit < 1) weightBoneLimit = 1;
|
||||
if (weightBoneLimit > 2) weightBoneLimit = 2; // Limiting for now
|
||||
|
||||
#region Editor progress dialogs
|
||||
#if UNITY_EDITOR
|
||||
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
|
||||
watch.Start();
|
||||
#endif
|
||||
#endregion
|
||||
|
||||
int vertCount = baseMesh.vertexCount;
|
||||
FTail_SkinningVertexData[] vertexDatas = new FTail_SkinningVertexData[vertCount];
|
||||
|
||||
// Computing helper segments for weighting bones
|
||||
Vector3[] boneAreas = new Vector3[bonesPos.Length];
|
||||
for (int i = 0; i < bonesPos.Length - 1; i++)
|
||||
{
|
||||
// Direction vector towards further bone
|
||||
boneAreas[i] = bonesPos[i + 1] - bonesPos[i]; //bonesCoords[i + 1].localPosition - bonesCoords[i].localPosition;
|
||||
}
|
||||
|
||||
if (boneAreas.Length > 1) boneAreas[boneAreas.Length - 1] = boneAreas[boneAreas.Length - 2];
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < vertCount; i++)
|
||||
{
|
||||
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
|
||||
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
|
||||
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
// Displaying progress bar when iteration takes too much time
|
||||
if (watch.ElapsedMilliseconds > 1500)
|
||||
if (i % 10 == 0)
|
||||
EditorUtility.DisplayProgressBar("Analizing mesh vertices...", "Analizing Vertices (" + i + "/" + vertCount + ")", ((float)i / (float)vertCount));
|
||||
#endregion
|
||||
}
|
||||
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
EditorUtility.ClearProgressBar();
|
||||
#endregion
|
||||
}
|
||||
catch (System.Exception exc)
|
||||
{
|
||||
Debug.LogError(exc);
|
||||
#region Editor progress dialogs
|
||||
if (!Application.isPlaying)
|
||||
EditorUtility.ClearProgressBar();
|
||||
#endregion
|
||||
}
|
||||
#else
|
||||
for (int i = 0; i < vertCount; i++)
|
||||
{
|
||||
vertexDatas[i] = new FTail_SkinningVertexData(baseMesh.vertices[i]);
|
||||
vertexDatas[i].CalculateVertexParameters(bonesPos, bonesRot, boneAreas, weightBoneLimit, spreadValue, spreadOffset, spreadPower);
|
||||
}
|
||||
#endif
|
||||
|
||||
return vertexDatas;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning target mesh with helper vertex datas which you can get with CalculateVertexWeightingData() method
|
||||
/// Using transforms as guidement for bones positions and rotations
|
||||
/// </summary>
|
||||
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
|
||||
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Transform skinParent, Transform[] bonesStructure, FTail_SkinningVertexData[] vertData)
|
||||
{
|
||||
Vector3[] pos = new Vector3[bonesStructure.Length];
|
||||
Quaternion[] rot = new Quaternion[bonesStructure.Length];
|
||||
|
||||
// We must reset bones structure to identity space
|
||||
for (int i = 0; i < bonesStructure.Length; i++)
|
||||
{
|
||||
// Transforming from world to local space coords
|
||||
pos[i] = skinParent.InverseTransformPoint(bonesStructure[i].position);
|
||||
rot[i] = FEngineering.QToLocal(skinParent.rotation, bonesStructure[i].rotation);
|
||||
}
|
||||
|
||||
SkinnedMeshRenderer skin = SkinMesh(baseMesh, pos, rot, vertData);
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning target mesh with helper vertex datas which you can
|
||||
/// </summary>
|
||||
/// <param name="baseMesh"> Base static mesh to be skinned </param>
|
||||
/// <param name="bonesPositions"> Bones positions in mesh local space </param>
|
||||
/// <param name="bonesRotations"> Bones rotations in mesh local space </param>
|
||||
/// <param name="vertData"> Get it with CalculateVertexWeightingData() method </param>
|
||||
/// <returns> Skinned mesh can't be returned as 'Mesh' type because skinned mesh is mesh + bones transforms etc. </returns>
|
||||
public static SkinnedMeshRenderer SkinMesh(Mesh baseMesh, Vector3[] bonesPositions, Quaternion[] bonesRotations, FTail_SkinningVertexData[] vertData)
|
||||
{
|
||||
if (bonesPositions == null) return null;
|
||||
if (bonesRotations == null) return null;
|
||||
if (baseMesh == null) return null;
|
||||
if (vertData == null) return null;
|
||||
|
||||
// Creating copy of target mesh and refreshing it
|
||||
Mesh newMesh = GameObject.Instantiate(baseMesh);
|
||||
newMesh.name = baseMesh.name + " [FSKINNED]";
|
||||
|
||||
// Preparing new object which will have skinned mesh renderer with new mesh and bones in it
|
||||
GameObject newSkinObject = new GameObject(baseMesh.name + " [FSKINNED]");
|
||||
Transform newParent = newSkinObject.transform;
|
||||
|
||||
// Preparing skin
|
||||
SkinnedMeshRenderer skin = newParent.gameObject.AddComponent<SkinnedMeshRenderer>();
|
||||
|
||||
// Preparing bones for weighting
|
||||
Transform[] bones = new Transform[bonesPositions.Length];
|
||||
Matrix4x4[] bindPoses = new Matrix4x4[bonesPositions.Length];
|
||||
|
||||
string nameString;
|
||||
if (baseMesh.name.Length < 6) nameString = baseMesh.name; else nameString = baseMesh.name.Substring(0, 5);
|
||||
|
||||
for (int i = 0; i < bonesPositions.Length; i++)
|
||||
{
|
||||
bones[i] = new GameObject("BoneF-" + nameString + "[" + i + "]").transform;
|
||||
if (i == 0) bones[i].SetParent(newParent, true); else bones[i].SetParent(bones[i - 1], true);
|
||||
|
||||
bones[i].transform.position = bonesPositions[i];
|
||||
bones[i].transform.rotation = bonesRotations[i];
|
||||
|
||||
bindPoses[i] = bones[i].worldToLocalMatrix * newParent.localToWorldMatrix;
|
||||
}
|
||||
|
||||
BoneWeight[] weights = new BoneWeight[newMesh.vertexCount];
|
||||
for (int v = 0; v < weights.Length; v++) weights[v] = new BoneWeight();
|
||||
|
||||
// Calculating and applying weights for verices
|
||||
for (int i = 0; i < vertData.Length; i++)
|
||||
{
|
||||
for (int w = 0; w < vertData[i].weights.Length; w++)
|
||||
{
|
||||
weights[i] = SetWeightIndex(weights[i], w, vertData[i].bonesIndexes[w]);
|
||||
weights[i] = SetWeightToBone(weights[i], w, vertData[i].weights[w]);
|
||||
}
|
||||
}
|
||||
|
||||
newMesh.bindposes = bindPoses;
|
||||
newMesh.boneWeights = weights;
|
||||
|
||||
List<Vector3> normals = new List<Vector3>();
|
||||
List < Vector4> tangents = new List<Vector4>();
|
||||
baseMesh.GetNormals(normals);
|
||||
baseMesh.GetTangents(tangents);
|
||||
|
||||
newMesh.SetNormals(normals);
|
||||
newMesh.SetTangents(tangents);
|
||||
newMesh.bounds = baseMesh.bounds;
|
||||
|
||||
// Applying generated mesh to skin controller
|
||||
skin.sharedMesh = newMesh;
|
||||
skin.rootBone = bones[0];
|
||||
skin.bones = bones;
|
||||
|
||||
return skin;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Method which is setting certain weight variable from BoneWeight struct
|
||||
/// </summary>
|
||||
public static BoneWeight SetWeightIndex(BoneWeight weight, int bone = 0, int index = 0)
|
||||
{
|
||||
switch (bone)
|
||||
{
|
||||
case 1: weight.boneIndex1 = index; break;
|
||||
case 2: weight.boneIndex2 = index; break;
|
||||
case 3: weight.boneIndex3 = index; break;
|
||||
default: weight.boneIndex0 = index; break;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Method which is setting certain weight variable from BoneWeight struct
|
||||
/// </summary>
|
||||
public static BoneWeight SetWeightToBone(BoneWeight weight, int bone = 0, float value = 1f)
|
||||
{
|
||||
switch (bone)
|
||||
{
|
||||
case 1: weight.weight1 = value; break;
|
||||
case 2: weight.weight2 = value; break;
|
||||
case 3: weight.weight3 = value; break;
|
||||
default: weight.weight0 = value; break;
|
||||
}
|
||||
|
||||
return weight;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dbb5fa7a5ec9084ba3564faf70c8899
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f1f543bbb761e234cbb384a09c3482cc, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,231 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Part of Tail Animator Skinning Static Meshes API
|
||||
/// Simple helper class to store vertices parameters in reference to bones
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class FTail_SkinningVertexData
|
||||
{
|
||||
public Vector3 position;
|
||||
//public Transform[] bones;
|
||||
|
||||
/// <summary> Indexes for helpers in visualization </summary>
|
||||
public int[] bonesIndexes;
|
||||
public int allMeshBonesCount;
|
||||
|
||||
// Assigned during custom weight calculations
|
||||
public float[] weights;
|
||||
|
||||
public FTail_SkinningVertexData(Vector3 pos) { position = pos; }
|
||||
|
||||
/// <summary>
|
||||
/// Distance to bone area for weighting
|
||||
/// </summary>
|
||||
public float DistanceToLine(Vector3 pos, Vector3 lineStart, Vector3 lineEnd)
|
||||
{
|
||||
Vector3 dirVector1 = pos - lineStart;
|
||||
Vector3 dirVector2 = (lineEnd - lineStart).normalized;
|
||||
|
||||
float distance = Vector3.Distance(lineStart, lineEnd);
|
||||
float dot = Vector3.Dot(dirVector2, dirVector1);
|
||||
|
||||
if (dot <= 0) return Vector3.Distance(pos, lineStart);
|
||||
if (dot >= distance) return Vector3.Distance(pos, lineEnd);
|
||||
|
||||
Vector3 dotVector = dirVector2 * dot;
|
||||
Vector3 closestPoint = lineStart + dotVector;
|
||||
|
||||
return Vector3.Distance(pos, closestPoint);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating vertex's distances to 4 nearest bones (4 bone weights is maximum count in Unity)
|
||||
/// for further custom weight calculations
|
||||
/// </summary>
|
||||
public void CalculateVertexParameters(Vector3[] bonesPos, Quaternion[] bonesRot, Vector3[] boneAreas, int maxWeightedBones, float spread, Vector3 spreadOffset, float spreadPower = 1f)
|
||||
{
|
||||
allMeshBonesCount = bonesPos.Length;
|
||||
|
||||
// Using Vector2 for simple two float values in one variable, x = bone index y = distance of vertex to this bone, later we will sort list using distances
|
||||
List<Vector2> calculatedDistances = new List<Vector2>();
|
||||
|
||||
// Check later if we don't need to transpone points to model space scale
|
||||
for (int i = 0; i < bonesPos.Length; i++)
|
||||
{
|
||||
Vector3 boneEnd;
|
||||
if (i != bonesPos.Length - 1)
|
||||
boneEnd = Vector3.Lerp(bonesPos[i], bonesPos[i + 1], 0.9f);
|
||||
else
|
||||
boneEnd = Vector3.Lerp(bonesPos[i], bonesPos[i] + (bonesPos[i] - bonesPos[i - 1]), 0.9f);
|
||||
|
||||
boneEnd += bonesRot[i] * spreadOffset;
|
||||
|
||||
float distance = DistanceToLine(position, bonesPos[i], boneEnd);
|
||||
|
||||
// Making bone offset to behave like bone area
|
||||
calculatedDistances.Add(new Vector2(i, distance));
|
||||
}
|
||||
|
||||
// Sorting by nearest all bones
|
||||
calculatedDistances.Sort((a, b) => a.y.CompareTo(b.y));
|
||||
|
||||
// Limiting vertex weight up to 4 bones
|
||||
int maxBones = (int)Mathf.Min(maxWeightedBones, bonesPos.Length);
|
||||
|
||||
// Assigning max 4 nearest bones and their distances to this vertex
|
||||
bonesIndexes = new int[maxBones];
|
||||
float[] nearestDistances = new float[maxBones];
|
||||
|
||||
for (int i = 0; i < maxBones; i++)
|
||||
{
|
||||
bonesIndexes[i] = (int)calculatedDistances[i].x;
|
||||
nearestDistances[i] = calculatedDistances[i].y;
|
||||
}
|
||||
|
||||
// Basing on spread value we spreading weight to nearest bones
|
||||
// Calculating percentage distances to bones
|
||||
float[] boneWeightsForVertex = new float[maxBones];
|
||||
|
||||
|
||||
AutoSetBoneWeights(boneWeightsForVertex, nearestDistances, spread, spreadPower, boneAreas);
|
||||
|
||||
|
||||
float weightLeft = 1f; // Must amount of weight which needs to be assigned
|
||||
weights = new float[maxBones]; // New weight parameters
|
||||
|
||||
// Applying weights to each bone assigned to vertex
|
||||
for (int i = 0; i < maxBones; i++)
|
||||
{
|
||||
if (spread == 0) if (i > 0) break;
|
||||
|
||||
if (weightLeft <= 0f) // No more weight to apply
|
||||
{
|
||||
weights[i] = 0f;
|
||||
continue;
|
||||
}
|
||||
|
||||
float targetWeight = boneWeightsForVertex[i];
|
||||
|
||||
weightLeft -= targetWeight;
|
||||
if (weightLeft <= 0f) targetWeight += weightLeft; else { if (i == maxBones - 1) targetWeight += weightLeft; } // Using weight amount which is left to assign
|
||||
|
||||
weights[i] = targetWeight;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public float[] debugDists;
|
||||
public float[] debugDistWeights;
|
||||
public float[] debugWeights;
|
||||
|
||||
/// <summary>
|
||||
/// Spreading weights over bones for current vertex
|
||||
/// </summary>
|
||||
public void AutoSetBoneWeights(float[] weightForBone, float[] distToBone, float spread, float spreadPower, Vector3[] boneAreas)
|
||||
{
|
||||
int bonesC = weightForBone.Length;
|
||||
float[] boneLengths = new float[bonesC]; for (int i = 0; i < boneLengths.Length; i++) boneLengths[i] = boneAreas[i].magnitude;
|
||||
float[] normalizedDistanceWeights = new float[bonesC];
|
||||
for (int i = 0; i < weightForBone.Length; i++) weightForBone[i] = 0f;
|
||||
|
||||
float normalizeDistance = 0f;
|
||||
for (int i = 0; i < bonesC; i++) normalizeDistance += distToBone[i];
|
||||
for (int i = 0; i < bonesC; i++) normalizedDistanceWeights[i] = 1f - distToBone[i] / normalizeDistance; // Reversing weight power - nearest (smallest distance) must have biggest weight value
|
||||
|
||||
debugDists = distToBone;
|
||||
|
||||
if (bonesC == 1 || spread == 0f) // Simpliest ONE BONE -------------------------------------------------------------
|
||||
{
|
||||
// [0] - nearest bone
|
||||
weightForBone[0] = 1f; // Just one weight - spread does not change anything
|
||||
}
|
||||
else if (bonesC == 2) // Simple TWO BONES -------------------------------------------------------------
|
||||
{
|
||||
float normalizer = 1f;
|
||||
weightForBone[0] = 1f;
|
||||
|
||||
// distToBone[0] is zero, max spread distance is length of bone / 3
|
||||
float distRange = Mathf.InverseLerp(distToBone[0] + (boneLengths[0] / 1.25f) * spread, distToBone[0], distToBone[1]);
|
||||
debugDists[0] = distRange;
|
||||
|
||||
// 0 -> full nearest bone weight
|
||||
// 1 -> half nearest half second bone weight
|
||||
float value = DistributionIn(Mathf.Lerp(0f, 1f, distRange), Mathf.Lerp(1.5f, 16f, spreadPower));
|
||||
|
||||
weightForBone[1] = value;
|
||||
normalizer += value;
|
||||
|
||||
debugDistWeights = new float[weightForBone.Length];
|
||||
|
||||
weightForBone.CopyTo(debugDistWeights, 0);
|
||||
|
||||
for (int i = 0; i < bonesC; i++) weightForBone[i] /= normalizer;
|
||||
|
||||
debugWeights = weightForBone;
|
||||
}
|
||||
else // Complex > TWO BONES -------------------------------------------------------------
|
||||
{
|
||||
float reffVal = boneLengths[0] / 10f;
|
||||
float refLength = boneLengths[0] / 2f;
|
||||
float normalizer = 0f;
|
||||
|
||||
for (int i = 0; i < bonesC; i++)
|
||||
{
|
||||
float weight = Mathf.InverseLerp(0f, reffVal + refLength * (spread), distToBone[i]);
|
||||
float value = Mathf.Lerp(1f, 0f, weight);
|
||||
if (i == 0) if (value == 0f) value = 1f;
|
||||
|
||||
weightForBone[i] = value;
|
||||
|
||||
normalizer += value;
|
||||
}
|
||||
|
||||
debugDistWeights = new float[weightForBone.Length];
|
||||
weightForBone.CopyTo(debugDistWeights, 0);
|
||||
|
||||
for (int i = 0; i < bonesC; i++) weightForBone[i] /= normalizer;
|
||||
|
||||
debugWeights = weightForBone;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Easing weight distribution
|
||||
/// </summary>
|
||||
public static float DistributionIn(float k, float power)
|
||||
{ return Mathf.Pow(k, power + 1f); }
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returning helper color for bone
|
||||
/// </summary>
|
||||
public static Color GetBoneIndicatorColor(int boneIndex, int bonesCount, float s = 0.9f, float v = 0.9f)
|
||||
{
|
||||
float h = ((float)(boneIndex) * 1.125f) / bonesCount;
|
||||
h += 0.125f * boneIndex;
|
||||
h += 0.3f;
|
||||
h %= 1f;
|
||||
|
||||
return Color.HSVToRGB(h, s, v);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns average color value for weight idicator for this vertex
|
||||
/// </summary>
|
||||
public Color GetWeightColor()
|
||||
{
|
||||
Color lerped = GetBoneIndicatorColor(bonesIndexes[0], allMeshBonesCount, 1f, 1f);
|
||||
|
||||
for (int i = 1; i < bonesIndexes.Length; i++)
|
||||
lerped = Color.Lerp(lerped, GetBoneIndicatorColor(bonesIndexes[i], allMeshBonesCount, 1f, 1f), weights[i]);
|
||||
|
||||
return lerped;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2595a070e1f36f49862752d4a32580f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: f1f543bbb761e234cbb384a09c3482cc, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,454 @@
|
||||
#if UNITY_EDITOR
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FCr: Implementation of Tail Animator Skinning Static Meshes API
|
||||
/// Class to use only in editor, it creates bones with preview static mesh then skin it to skinned mesh renderer
|
||||
/// </summary>
|
||||
[ExecuteInEditMode]
|
||||
[AddComponentMenu("FImpossible Creations/Tail Animator Utilities/Editor Tail Skinner")]
|
||||
public class FTail_Editor_Skinner : MonoBehaviour
|
||||
{
|
||||
|
||||
#region Inspector Variables
|
||||
|
||||
[FPD_Header("SKIN STATIC MESHES INSIDE UNITY", 3, 8, 8)]
|
||||
[BackgroundColor(0.75f, 0.75f, 1.0f, 0.7f)]
|
||||
public int AutoMarkersCount = 8;
|
||||
public float DistanceValue = 0.3f;
|
||||
public Vector3 positionOffset = new Vector3(0, 0f);
|
||||
public Vector2 startDirection = new Vector2(-90, 0f);
|
||||
public Vector2 rotationOffset = new Vector2(0f, 0f);
|
||||
|
||||
[Range(0f, 5f)]
|
||||
public float HelpScaleValue = 1f;
|
||||
|
||||
[BackgroundColor(0.85f, 0.85f, 1.0f, 0.85f)]
|
||||
public AnimationCurve DistancesFaloff = AnimationCurve.Linear(0f, 1f, 1f, 1f);
|
||||
public AnimationCurve RotationsFaloff = AnimationCurve.Linear(0f, 1f, 1f, 1f);
|
||||
|
||||
[BackgroundColor(0.5f, 1f, 0.5f, 0.8f)]
|
||||
[Space(10f, order = 0)]
|
||||
[Header("Left empty if you don't use custom markers", order = 1)]
|
||||
[Space(-7f, order = 2)]
|
||||
[Header("Moving custom markers will not trigger realtime update", order = 3)]
|
||||
public Transform[] CustomBoneMarkers;
|
||||
|
||||
[Space(7f, order = 0)]
|
||||
[FPD_Header("Weights Spread Settings", 7, 4, 4)]
|
||||
[Space(3f, order = 2)]
|
||||
[Range(0f, 1f)]
|
||||
public float SpreadValue = 0.8f;
|
||||
[Range(0f, 1f)]
|
||||
public float SpreadPower = .185f;
|
||||
[Tooltip("Offsetting spreading area, For example 0,0,1 and recommended values from 0 to 2 not bigger")]
|
||||
public Vector3 SpreadOffset = Vector3.zero;
|
||||
[Range(1, 2)]
|
||||
public int LimitBoneWeightCount = 2;
|
||||
|
||||
[BackgroundColor(0.4f, 0.8f, 0.8f, 0.8f)]
|
||||
[FPD_Header("Additional Variables", 7, 4, 4)]
|
||||
[Range(0f, 5f)]
|
||||
public float GizmoSize = 0.1f;
|
||||
[Range(0f, 1f)]
|
||||
public float GizmoAlpha = .65f;
|
||||
|
||||
[BackgroundColor()]
|
||||
[Tooltip("If your model have many vertices, turn it only when neccesary")]
|
||||
public bool RealtimeUpdate = true;
|
||||
public bool ShowPreview = true;
|
||||
public bool DebugMode = false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private variables
|
||||
|
||||
/// <summary> Base Mesh </summary>
|
||||
private Mesh baseMesh;
|
||||
|
||||
[HideInInspector]
|
||||
public List<Color32> baseVertexColor;
|
||||
|
||||
private MeshRenderer meshRenderer;
|
||||
|
||||
|
||||
/// <summary> Fake bones list before creating true skeleton for mesh </summary>
|
||||
private Transform[] ghostBones;
|
||||
|
||||
/// <summary> Vertex datas used for setting weights precisely</summary>
|
||||
private FTail_SkinningVertexData[] vertexDatas;
|
||||
|
||||
/// <summary> Generated marker points for automatic bone points </summary>
|
||||
internal Transform[] autoMarkers;
|
||||
|
||||
// Hide in inspector because when variables are private, they're resetted to null every time code compiles
|
||||
/// <summary> Because we can't destroy gameObjects in OnValidate, we do something similar to object pools </summary>
|
||||
[HideInInspector]
|
||||
public List<Transform> allMarkersTransforms = new List<Transform>();
|
||||
|
||||
/// <summary> Transform with components helping drawing how weights are spread on model </summary>
|
||||
[HideInInspector]
|
||||
public Transform weightPreviewTransform;
|
||||
|
||||
[HideInInspector]
|
||||
public bool popupShown = false;
|
||||
|
||||
internal bool initValues = false;
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// When something changes in inspector, let's recalculate parameters
|
||||
/// </summary>
|
||||
private void OnValidate()
|
||||
{
|
||||
if (!initValues)
|
||||
{
|
||||
MeshRenderer m = GetComponent<MeshRenderer>();
|
||||
if (m) DistanceValue = m.bounds.extents.magnitude / 7f;
|
||||
initValues = true;
|
||||
}
|
||||
|
||||
if (AutoMarkersCount < 2) AutoMarkersCount = 2;
|
||||
|
||||
if (!GetBaseMesh()) return;
|
||||
|
||||
if (CustomBoneMarkers == null) CustomBoneMarkers = new Transform[0]; // Prevent error log when adding component
|
||||
|
||||
// Use only custom markers if they're assigned
|
||||
if (CustomBoneMarkers.Length > 0)
|
||||
ghostBones = CustomBoneMarkers;
|
||||
else // Use auto markers
|
||||
{
|
||||
CalculateAutoMarkers();
|
||||
ghostBones = autoMarkers;
|
||||
}
|
||||
|
||||
if (RealtimeUpdate)
|
||||
{
|
||||
vertexDatas = FTail_Skinning.CalculateVertexWeightingData(
|
||||
GetBaseMesh(), ghostBones, SpreadOffset, LimitBoneWeightCount, SpreadValue, SpreadPower);
|
||||
|
||||
UpdatePreviewMesh();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Drawing helper stuff
|
||||
/// </summary>
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (CustomBoneMarkers == null)
|
||||
CustomBoneMarkers = new Transform[0];
|
||||
|
||||
if (ghostBones[0] == null)
|
||||
CalculateAutoMarkers();
|
||||
|
||||
if (CustomBoneMarkers.Length < 1)
|
||||
DrawMarkers(autoMarkers);
|
||||
else
|
||||
DrawMarkers(CustomBoneMarkers);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calculating auto markers transforms
|
||||
/// </summary>
|
||||
private void CalculateAutoMarkers()
|
||||
{
|
||||
#region Creation of markers' transforms
|
||||
|
||||
if (autoMarkers == null) autoMarkers = new Transform[0];
|
||||
|
||||
if (allMarkersTransforms.Count < AutoMarkersCount)
|
||||
{
|
||||
for (int i = autoMarkers.Length; i < AutoMarkersCount; i++)
|
||||
{
|
||||
GameObject newMarker = new GameObject(name + "-SkinMarker " + i);
|
||||
newMarker.transform.SetParent(transform, true);
|
||||
allMarkersTransforms.Add(newMarker.transform);
|
||||
}
|
||||
}
|
||||
|
||||
if (autoMarkers.Length != AutoMarkersCount)
|
||||
{
|
||||
autoMarkers = new Transform[AutoMarkersCount];
|
||||
for (int i = 0; i < AutoMarkersCount; i++)
|
||||
{
|
||||
autoMarkers[i] = allMarkersTransforms[i];
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
autoMarkers[0].position = transform.position + positionOffset;
|
||||
autoMarkers[0].rotation = Quaternion.Euler(startDirection + rotationOffset);
|
||||
|
||||
float step = 1f / (float)AutoMarkersCount;
|
||||
|
||||
for (int i = 1; i < AutoMarkersCount; i++)
|
||||
{
|
||||
float forwardMultiplier = DistanceValue;
|
||||
forwardMultiplier *= DistancesFaloff.Evaluate(i * step);
|
||||
forwardMultiplier *= HelpScaleValue;
|
||||
Vector3 targetPosition = autoMarkers[i - 1].position + autoMarkers[i - 1].rotation * Vector3.forward * forwardMultiplier;
|
||||
|
||||
Vector3 newRot = startDirection + rotationOffset * (i + 1) * RotationsFaloff.Evaluate(i * step);
|
||||
|
||||
autoMarkers[i].position = targetPosition;
|
||||
autoMarkers[i].rotation = Quaternion.Euler(newRot);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Getting base mesh variable, depends if it's skinned mesh or static mesh
|
||||
/// </summary>
|
||||
private Mesh GetBaseMesh()
|
||||
{
|
||||
if (baseMesh == null)
|
||||
{
|
||||
meshRenderer = GetComponent<MeshRenderer>();
|
||||
MeshFilter meshFilter = GetComponent<MeshFilter>();
|
||||
if (meshFilter) baseMesh = meshFilter.sharedMesh;
|
||||
}
|
||||
else return baseMesh;
|
||||
|
||||
if (!baseMesh)
|
||||
{
|
||||
if (!popupShown)
|
||||
{
|
||||
EditorUtility.DisplayDialog("Tail Skinner Error", "[Tail Skinner] No base mesh! (mesh filter and mesh renderer)", "Ok");
|
||||
popupShown = true;
|
||||
}
|
||||
|
||||
Debug.LogError("No BaseMesh!");
|
||||
}
|
||||
|
||||
if (baseMesh)
|
||||
{
|
||||
if (baseVertexColor == null) baseVertexColor = new List<Color32>();
|
||||
if (baseVertexColor.Count != baseMesh.vertexCount)
|
||||
{
|
||||
baseVertexColor.Clear();
|
||||
baseMesh.GetColors(baseVertexColor);
|
||||
}
|
||||
}
|
||||
|
||||
return baseMesh;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Skinning mesh to new skinned mesh renderer with choosed weight markers settings
|
||||
/// </summary>
|
||||
public void SkinMesh(bool addTailAnimator, Vector3 newObjectOffset)
|
||||
{
|
||||
// Remembering data and preparing objects with components
|
||||
List<Color32> baseVertColors = new List<Color32>();
|
||||
GetBaseMesh().GetColors(baseVertColors);
|
||||
|
||||
// Doing skinning
|
||||
vertexDatas = FTail_Skinning.CalculateVertexWeightingData(
|
||||
GetBaseMesh(), ghostBones, SpreadOffset, LimitBoneWeightCount, SpreadValue, SpreadPower);
|
||||
|
||||
SkinnedMeshRenderer newSkinnedMesh = FTail_Skinning.SkinMesh(baseMesh, transform, ghostBones, vertexDatas);
|
||||
|
||||
if (newSkinnedMesh == null)
|
||||
{ Debug.LogError("[Tail Animator Skinning] Creating skinned mesh failed!"); return; }
|
||||
|
||||
|
||||
// Skin renderer quality
|
||||
switch (LimitBoneWeightCount)
|
||||
{
|
||||
case 1: newSkinnedMesh.quality = SkinQuality.Bone1; break;
|
||||
case 2: newSkinnedMesh.quality = SkinQuality.Bone2; break;
|
||||
case 4: newSkinnedMesh.quality = SkinQuality.Bone4; break;
|
||||
default: newSkinnedMesh.quality = SkinQuality.Auto; break;
|
||||
}
|
||||
|
||||
// Filling new mesh with materials
|
||||
MeshRenderer meshRend = GetComponent<MeshRenderer>();
|
||||
if (meshRend)
|
||||
{
|
||||
newSkinnedMesh.materials = meshRend.sharedMaterials;
|
||||
newSkinnedMesh.sharedMaterials = meshRend.sharedMaterials;
|
||||
}
|
||||
|
||||
// Adding tail animator
|
||||
if (addTailAnimator)
|
||||
{
|
||||
TailAnimator2 t = newSkinnedMesh.bones[0].gameObject.AddComponent<TailAnimator2>();
|
||||
t.StartBone = newSkinnedMesh.bones[0];
|
||||
t.EndBone = newSkinnedMesh.bones[newSkinnedMesh.bones.Length - 1];
|
||||
}
|
||||
|
||||
// Setting new object position to be next to current model
|
||||
newSkinnedMesh.transform.position = transform.position + new Vector3(1f, 1f, 1f);
|
||||
|
||||
// Create asset for new model so it not disappear when we create prefab from this gameObject
|
||||
string newMeshPath = System.IO.Path.GetDirectoryName(AssetDatabase.GetAssetPath(baseMesh));
|
||||
AssetDatabase.CreateAsset(newSkinnedMesh.sharedMesh, newMeshPath + "/" + newSkinnedMesh.name + ".mesh");
|
||||
AssetDatabase.SaveAssets();
|
||||
|
||||
Debug.Log("New skinned mesh '" + newSkinnedMesh.name + ".mesh" + "' saved under path: '" + newMeshPath + "'");
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Make sure everything which was created by this script is destroyed
|
||||
/// </summary>
|
||||
private void OnDestroy()
|
||||
{
|
||||
for (int i = 0; i < allMarkersTransforms.Count; i++) if (allMarkersTransforms[i] != null) DestroyImmediate(allMarkersTransforms[i].gameObject);
|
||||
if (weightPreviewTransform != null) DestroyImmediate(weightPreviewTransform.gameObject);
|
||||
|
||||
if (baseMesh == null) return;
|
||||
meshRenderer.enabled = true;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Drawing markers to be visible in editor window to help place bones correctly
|
||||
/// </summary>
|
||||
public void DrawMarkers(Transform[] markers)
|
||||
{
|
||||
if (markers == null) return;
|
||||
|
||||
for (int i = 0; i < markers.Length; i++)
|
||||
{
|
||||
Gizmos.color = ChangeColorAlpha(GetBoneIndicatorColor(i, markers.Length), GizmoAlpha);
|
||||
|
||||
Vector3 targetPosition = markers[i].position;
|
||||
|
||||
Gizmos.DrawWireSphere(targetPosition, GizmoSize);
|
||||
|
||||
Gizmos.color = ChangeColorAlpha(GetBoneIndicatorColor(i, markers.Length, 1f, 1f), GizmoAlpha * 0.8f);
|
||||
Gizmos.DrawSphere(targetPosition, GizmoSize * 0.7f);
|
||||
|
||||
Gizmos.DrawRay(targetPosition, markers[i].up * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, -markers[i].up * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, markers[i].right * GizmoSize * 1.1f);
|
||||
Gizmos.DrawRay(targetPosition, -markers[i].right * GizmoSize * 1.1f);
|
||||
|
||||
Vector3 targetPoint;
|
||||
if (i < markers.Length - 1) targetPoint = markers[i + 1].position;
|
||||
else
|
||||
targetPoint = markers[i].position + (markers[i].position - markers[i - 1].position);
|
||||
|
||||
Gizmos.DrawLine(targetPosition + markers[i].up * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition - markers[i].up * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition + markers[i].right * GizmoSize * 1.1f, targetPoint);
|
||||
Gizmos.DrawLine(targetPosition - markers[i].right * GizmoSize * 1.1f, targetPoint);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Updating preview mesh to view weights correctly
|
||||
/// </summary>
|
||||
private void UpdatePreviewMesh()
|
||||
{
|
||||
#region Creation of new preview mesh when needed
|
||||
|
||||
if (weightPreviewTransform == null)
|
||||
{
|
||||
weightPreviewTransform = new GameObject(name + "[preview mesh]").transform;
|
||||
weightPreviewTransform.SetParent(transform);
|
||||
weightPreviewTransform.localPosition = Vector3.zero;
|
||||
weightPreviewTransform.localRotation = Quaternion.identity;
|
||||
weightPreviewTransform.localScale = Vector3.one;
|
||||
|
||||
weightPreviewTransform.gameObject.AddComponent<MeshFilter>().mesh = baseMesh;
|
||||
|
||||
Material[] newMaterials = new Material[meshRenderer.sharedMaterials.Length];
|
||||
|
||||
for (int i = 0; i < newMaterials.Length; i++) newMaterials[i] = new Material(Shader.Find("Particles/FVertexLit Blended"));
|
||||
weightPreviewTransform.gameObject.AddComponent<MeshRenderer>().materials = newMaterials;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
if (ShowPreview)
|
||||
{
|
||||
meshRenderer.enabled = false;
|
||||
weightPreviewTransform.gameObject.SetActive(true);
|
||||
List<Color> vColors = new List<Color>();
|
||||
for (int i = 0; i < vertexDatas.Length; i++) vColors.Add(vertexDatas[i].GetWeightColor());
|
||||
baseMesh.SetColors(vColors);
|
||||
weightPreviewTransform.gameObject.GetComponent<MeshFilter>().mesh = baseMesh;
|
||||
}
|
||||
else
|
||||
{
|
||||
meshRenderer.enabled = true;
|
||||
if (baseVertexColor != null) if (baseMesh) if (baseMesh.vertexCount == baseVertexColor.Count) baseMesh.SetColors(baseVertexColor);
|
||||
weightPreviewTransform.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!DebugMode) return;
|
||||
if (vertexDatas == null) return;
|
||||
if (vertexDatas.Length == 0) return;
|
||||
if (vertexDatas[0].bonesIndexes == null) return;
|
||||
|
||||
for (int i = 0; i < vertexDatas.Length; i++)
|
||||
{
|
||||
Handles.color = GetBoneIndicatorColor(vertexDatas[i].bonesIndexes[0], vertexDatas[i].bonesIndexes.Length) * new Color(1f, 1f, 1f, GizmoAlpha);
|
||||
Gizmos.color = Handles.color;
|
||||
|
||||
Handles.Label(transform.TransformPoint(vertexDatas[i].position), "[" + i + "]");
|
||||
//Handles.Label(transform.TransformPoint(vertexDatas[i].position), "[" + i + "]\n" + Math.Round(vertexDatas[i].debugDists[0], 3) + "\n" + Math.Round(vertexDatas[i].debugDists[1], 3));
|
||||
Gizmos.DrawSphere(transform.TransformPoint(vertexDatas[i].position), 0.125f * GizmoSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returning helper color for bone
|
||||
/// </summary>
|
||||
public static Color GetBoneIndicatorColor(int boneIndex, int bonesCount, float s = 0.9f, float v = 0.9f)
|
||||
{
|
||||
float h = ((float)(boneIndex) * 1.125f) / bonesCount;
|
||||
h += 0.125f * boneIndex;
|
||||
h += 0.3f;
|
||||
h %= 1f;
|
||||
return Color.HSVToRGB(h, s, v);
|
||||
}
|
||||
|
||||
|
||||
public static Color ChangeColorAlpha(Color color, float alpha)
|
||||
{ return new Color(color.r, color.g, color.b, alpha); }
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FM: Editor class component to enchance controll over component from inspector window
|
||||
/// </summary>
|
||||
[UnityEditor.CustomEditor(typeof(FTail_Editor_Skinner))]
|
||||
public class FTail_Editor_SkinnerEditor : UnityEditor.Editor
|
||||
{
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
FTail_Editor_Skinner targetScript = (FTail_Editor_Skinner)target;
|
||||
DrawDefaultInspector();
|
||||
|
||||
GUILayout.Space(10f);
|
||||
|
||||
if (GUILayout.Button("Skin It")) targetScript.SkinMesh(false, Vector3.right);
|
||||
if (GUILayout.Button("Skin and add Tail Animator")) targetScript.SkinMesh(true, Vector3.right);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64150bf558082b8468d3f05b3e9d3b5a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7958d01c62579034fa1ffbb911dd6b59, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,301 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FC: Experimental class under developement
|
||||
/// </summary>
|
||||
[AddComponentMenu("FImpossible Creations/Tail Animator Utilities/Tail Animator Wind")]
|
||||
public class TailAnimatorWind : MonoBehaviour, UnityEngine.EventSystems.IDropHandler, IFHierarchyIcon
|
||||
{
|
||||
|
||||
#region Hierarchy Icon
|
||||
|
||||
public string EditorIconPath { get { return "Tail Animator/TailAnimatorWindIconSmall"; } }
|
||||
public void OnDrop(UnityEngine.EventSystems.PointerEventData data) { }
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Singleton
|
||||
|
||||
|
||||
public static TailAnimatorWind Instance { get; private set; } // { get { if (!_instance) GenerateWindComponentInstance(); return _instance; } }
|
||||
//private static TailAnimatorWind _instance;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (!Application.isPlaying) return;
|
||||
Instance = this;
|
||||
if (persistThroughAllScenes) DontDestroyOnLoad(gameObject);
|
||||
}
|
||||
|
||||
public void OnValidate()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
//private static void GenerateWindComponentInstance()
|
||||
//{
|
||||
// GameObject windObj = new GameObject("Tail Animator Wind");
|
||||
// _instance = windObj.AddComponent<TailAnimatorWind>();
|
||||
//}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generating wind component if needed and adding tail animator component to wind affected components list
|
||||
/// </summary>
|
||||
//public static void Refresh(TailAnimator2 tail)
|
||||
//{
|
||||
// if (!_instance) GenerateWindComponentInstance();
|
||||
// if (_instance.WindAffected == null) _instance.WindAffected = new List<TailAnimator2>();
|
||||
// if (tail != null) if (!_instance.WindAffected.Contains(tail)) _instance.WindAffected.Add(tail);
|
||||
//}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
[Header("In playmode you will find this object in DontDestroyOnLoad")]
|
||||
[FPD_Header("Main Wind Setings", 2, 4)]
|
||||
public float power = 1f;
|
||||
public float additionalTurbulence = 1f;
|
||||
public float additionalTurbSpeed = 1f;
|
||||
|
||||
[Space(7)]
|
||||
public WindZone SyncWithUnityWindZone;
|
||||
public float UnityWindZonePowerMul = 2f;
|
||||
public float UnityWindZoneTurbMul = 1f;
|
||||
|
||||
[Header("Overriding wind if value below different than 0,0,0")]
|
||||
public Vector3 overrideWind = Vector3.zero;
|
||||
|
||||
[FPD_Header("Procedural Wind Settings (if not syncing and not overriding)", 6, 4)]
|
||||
[Range(0.1f, 1f)]
|
||||
public float rapidness = 0.95f;
|
||||
[FPD_Suffix(0, 360, FPD_SuffixAttribute.SuffixMode.FromMinToMaxRounded, "°")]
|
||||
public float changesPower = 90f;
|
||||
[Header("Extra")]
|
||||
[Range(0f, 10f)] public float turbulenceSpeed = 1f;
|
||||
|
||||
[FPD_Header("World Position Turbulence", 6, 4)]
|
||||
[Tooltip("Increase to make objects next to each other wave in slightly different way")]
|
||||
public float worldTurb = 1f;
|
||||
[Tooltip("If higher no performance cost, it is just a number")]
|
||||
public float worldTurbScale = 512;
|
||||
public float worldTurbSpeed = 5f;
|
||||
|
||||
[FPD_Header("Tail Compoenents Related", 6, 4)]
|
||||
[Tooltip("When tail is longer then power of wind should be higher")]
|
||||
public bool powerDependOnTailLength = true;
|
||||
[Tooltip("Don't destroy on load")]
|
||||
public bool persistThroughAllScenes = false;
|
||||
//[Tooltip("Finding all TailAnimato2 compoents at start")]
|
||||
//public bool collectFromSceneAtStart = false;
|
||||
|
||||
//public List<TailAnimator2> WindAffected;
|
||||
|
||||
private Vector3 targetWind = Vector3.zero;
|
||||
private Vector3 smoothWind = Vector3.zero;
|
||||
private Vector3 windVeloHelper = Vector3.zero;
|
||||
private Quaternion windOrientation = Quaternion.identity;
|
||||
private Quaternion smoothWindOrient = Quaternion.identity;
|
||||
private Quaternion smoothWindOrientHelper = Quaternion.identity;
|
||||
|
||||
private float[] randNumbers;
|
||||
private float[] randTimes;
|
||||
private float[] randSpeeds;
|
||||
|
||||
private int frameOffset = 2;
|
||||
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (frameOffset > 0) { frameOffset--; return; }
|
||||
|
||||
//if (collectFromSceneAtStart)
|
||||
//{
|
||||
// collectFromSceneAtStart = false;
|
||||
// GetTailAnimatorsFromScene();
|
||||
//}
|
||||
|
||||
ComputeWind();
|
||||
|
||||
#region Hidden backup
|
||||
|
||||
//TailAnimator2 t;
|
||||
//for (int i = 0; i < WindAffected.Count; i++)
|
||||
//{
|
||||
// t = WindAffected[i];
|
||||
|
||||
// if (!t.UseWind) continue;
|
||||
// if (t.WindEffectPower <= 0f) continue;
|
||||
// if (t.TailSegments.Count <= 0) continue;
|
||||
|
||||
// float lengthRatio = 1f;
|
||||
// if (powerDependOnTailLength)
|
||||
// {
|
||||
// lengthRatio = (t._TC_TailLength * t.TailSegments[0].transform.lossyScale.z) / 5f;
|
||||
// if (t.TailSegments.Count > 3) lengthRatio *= Mathf.Lerp(0.7f, 3f, t.TailSegments.Count / 14f);
|
||||
// }
|
||||
|
||||
// if (t.WindWorldNoisePower > 0f)
|
||||
// {
|
||||
// float wTurb = worldTurbSpeed;
|
||||
// if (SyncWithUnityWindZone) wTurb *= SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul;
|
||||
|
||||
// float worldPosTurbulence = (.5f + Mathf.Sin(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.x * worldTurbScale) / 2f) + (.5f + Mathf.Cos(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.z * worldTurbScale) / 2f);
|
||||
// lengthRatio += worldPosTurbulence * worldTurb * t.WindWorldNoisePower;
|
||||
// }
|
||||
|
||||
// lengthRatio *= t.WindEffectPower;
|
||||
|
||||
// if (t.WindTurbulencePower > 0f)
|
||||
// t.WindEffect = new Vector3(targetWind.x * lengthRatio + finalAddTurbulence.x * t.WindTurbulencePower, targetWind.y * lengthRatio + finalAddTurbulence.y * t.WindTurbulencePower, targetWind.z * lengthRatio + finalAddTurbulence.z * t.WindTurbulencePower);
|
||||
// else
|
||||
// t.WindEffect = new Vector3(targetWind.x * lengthRatio, targetWind.y * lengthRatio, targetWind.z * lengthRatio);
|
||||
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
public static void Refresh()
|
||||
{
|
||||
if (Instance == null)
|
||||
{
|
||||
UnityEngine.Debug.Log("[Tail Animator Wind] No Tail Animator Wind component on the scene!");
|
||||
UnityEngine.Debug.LogWarning("[Tail Animator Wind] No Tail Animator Wind component on the scene!");
|
||||
}
|
||||
}
|
||||
|
||||
public void AffectTailWithWind(TailAnimator2 t)
|
||||
{
|
||||
if (!t.UseWind) return;
|
||||
if (t.WindEffectPower <= 0f) return;
|
||||
if (t.TailSegments.Count <= 0) return;
|
||||
|
||||
float lengthRatio = 1f;
|
||||
if (powerDependOnTailLength)
|
||||
{
|
||||
lengthRatio = (t._TC_TailLength * t.TailSegments[0].transform.lossyScale.z) / 5f;
|
||||
if (t.TailSegments.Count > 3) lengthRatio *= Mathf.Lerp(0.7f, 3f, t.TailSegments.Count / 14f);
|
||||
}
|
||||
|
||||
if (t.WindWorldNoisePower > 0f)
|
||||
{
|
||||
float wTurb = worldTurbSpeed;
|
||||
if (SyncWithUnityWindZone) wTurb *= SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul;
|
||||
|
||||
float worldPosTurbulence = (.5f + Mathf.Sin(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.x * worldTurbScale) / 2f) + (.5f + Mathf.Cos(Time.time * wTurb + t.TailSegments[0].ProceduralPosition.z * worldTurbScale) / 2f);
|
||||
lengthRatio += worldPosTurbulence * worldTurb * t.WindWorldNoisePower;
|
||||
}
|
||||
|
||||
lengthRatio *= t.WindEffectPower;
|
||||
|
||||
if (t.WindTurbulencePower > 0f)
|
||||
t.WindEffect = new Vector3(targetWind.x * lengthRatio + finalAddTurbulence.x * t.WindTurbulencePower, targetWind.y * lengthRatio + finalAddTurbulence.y * t.WindTurbulencePower, targetWind.z * lengthRatio + finalAddTurbulence.z * t.WindTurbulencePower);
|
||||
else
|
||||
t.WindEffect = new Vector3(targetWind.x * lengthRatio, targetWind.y * lengthRatio, targetWind.z * lengthRatio);
|
||||
}
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
int numCount = 10;
|
||||
randNumbers = new float[numCount];
|
||||
randTimes = new float[numCount];
|
||||
randSpeeds = new float[numCount];
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
randNumbers[i] = Random.Range(-1000f, 1000f);
|
||||
randTimes[i] = Random.Range(-1000f, 1000f);
|
||||
randSpeeds[i] = Random.Range(0.18f, 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ComputeWind()
|
||||
{
|
||||
|
||||
Vector3 newWind;
|
||||
|
||||
if (SyncWithUnityWindZone)
|
||||
{
|
||||
newWind = SyncWithUnityWindZone.transform.forward * SyncWithUnityWindZone.windMain * UnityWindZonePowerMul;
|
||||
transform.rotation = SyncWithUnityWindZone.transform.rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (overrideWind != Vector3.zero) newWind = overrideWind;
|
||||
else // Procedural wind
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turbulenceSpeed;
|
||||
|
||||
Quaternion windDir = windOrientation;
|
||||
|
||||
float x = -1f + Mathf.PerlinNoise(randTimes[0], 256f + randTimes[1]) * 2f;
|
||||
float y = -1f + Mathf.PerlinNoise(-randTimes[1], 55f + randTimes[2]) * 2f;
|
||||
float z = -1f + Mathf.PerlinNoise(-randTimes[3], 55f + randTimes[0]) * 2f;
|
||||
windDir *= Quaternion.Euler(new Vector3(0, y, 0) * changesPower);
|
||||
windDir = Quaternion.Euler(x * (changesPower / 6f), windDir.eulerAngles.y, z * (changesPower / 6f));
|
||||
|
||||
smoothWindOrient = FEngineering.SmoothDampRotation(smoothWindOrient, windDir, ref smoothWindOrientHelper, 1f - rapidness, Time.deltaTime);
|
||||
|
||||
transform.rotation = smoothWindOrient;
|
||||
newWind = smoothWindOrient * Vector3.forward;
|
||||
}
|
||||
}
|
||||
|
||||
// Additional turbulence
|
||||
smoothAddTurbulence = Vector3.SmoothDamp(smoothAddTurbulence, GetAddTurbulence() * additionalTurbulence, ref addTurbHelper, 0.05f, Mathf.Infinity, Time.deltaTime);
|
||||
|
||||
// Smooth out
|
||||
smoothWind = Vector3.SmoothDamp(smoothWind, newWind, ref windVeloHelper, 0.1f, Mathf.Infinity, Time.deltaTime);
|
||||
|
||||
for (int i = 7; i < 10; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turbulenceSpeed;
|
||||
|
||||
float turbulencedPower = power * 0.015f;
|
||||
turbulencedPower *= 0.5f + Mathf.PerlinNoise(randTimes[7] * 2f, 25 + randTimes[8] * 0.5f);
|
||||
|
||||
finalAddTurbulence = smoothAddTurbulence * turbulencedPower;
|
||||
targetWind = smoothWind * turbulencedPower;
|
||||
}
|
||||
|
||||
Vector3 finalAddTurbulence = Vector3.zero;
|
||||
Vector3 addTurbHelper = Vector3.zero;
|
||||
private Vector3 GetAddTurbulence()
|
||||
{
|
||||
float turb = additionalTurbSpeed;
|
||||
if (SyncWithUnityWindZone) turb *= (SyncWithUnityWindZone.windTurbulence * UnityWindZoneTurbMul);
|
||||
|
||||
for (int i = 4; i < 7; i++)
|
||||
randTimes[i] += Time.deltaTime * randSpeeds[i] * turb;
|
||||
|
||||
float x = -1f + Mathf.PerlinNoise(randTimes[4] + 7.123f, -2.324f + Time.time * 0.24f) * 2f;
|
||||
float y = -1f + Mathf.PerlinNoise(randTimes[5] - 4.7523f, -25.324f + Time.time * 0.54f) * 2f;
|
||||
float z = -1f + Mathf.PerlinNoise(randTimes[6] + 1.123f, -63.324f + Time.time * -0.49f) * 2f;
|
||||
return new Vector3(x, y, z);
|
||||
}
|
||||
|
||||
Vector3 smoothAddTurbulence = Vector3.zero;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Collecting tail animator components from scene (WARNING: Don't execute it every frame)
|
||||
/// </summary>
|
||||
//public void GetTailAnimatorsFromScene()
|
||||
//{
|
||||
// TailAnimator2[] tails = FindObjectsOfType<TailAnimator2>();
|
||||
// for (int i = 0; i < tails.Length; i++)
|
||||
// {
|
||||
// if (!WindAffected.Contains(tails[i])) WindAffected.Add(tails[i]);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ca32c544193be8047ad129a8e7f52f78
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 73e848e8b105ad4419739438d540ff07, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,173 @@
|
||||
using UnityEngine;
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
using UnityEngine.Tilemaps;
|
||||
#endif
|
||||
|
||||
namespace FIMSpace.FTail
|
||||
{
|
||||
/// <summary>
|
||||
/// FM: Simple class sending collision events to main script
|
||||
/// </summary>
|
||||
[AddComponentMenu("FImpossible Creations/Hidden/Tail Collision Helper")]
|
||||
public class TailCollisionHelper : MonoBehaviour
|
||||
{
|
||||
public TailAnimator2 ParentTail;
|
||||
public Collider TailCollider;
|
||||
public Collider2D TailCollider2D;
|
||||
public int Index;
|
||||
|
||||
internal Rigidbody RigBody { get; private set; }
|
||||
internal Rigidbody2D RigBody2D { get; private set; }
|
||||
Transform previousCollision;
|
||||
|
||||
internal TailCollisionHelper Init(bool addRigidbody = true, float mass = 1f, bool kinematic = false)
|
||||
{
|
||||
if (TailCollider2D == null)
|
||||
{
|
||||
if (addRigidbody)
|
||||
{
|
||||
Rigidbody rig = GetComponent<Rigidbody>();
|
||||
if (!rig) rig = gameObject.AddComponent<Rigidbody>();
|
||||
rig.interpolation = RigidbodyInterpolation.Interpolate;
|
||||
rig.useGravity = false;
|
||||
rig.isKinematic = kinematic;
|
||||
rig.constraints = RigidbodyConstraints.FreezeAll;
|
||||
rig.mass = mass;
|
||||
RigBody = rig;
|
||||
}
|
||||
else
|
||||
{
|
||||
RigBody = GetComponent<Rigidbody>();
|
||||
if (RigBody) RigBody.mass = mass;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (addRigidbody)
|
||||
{
|
||||
Rigidbody2D rig = GetComponent<Rigidbody2D>();
|
||||
if (!rig) rig = gameObject.AddComponent<Rigidbody2D>();
|
||||
rig.interpolation = RigidbodyInterpolation2D.Interpolate;
|
||||
rig.gravityScale = 0f;
|
||||
rig.isKinematic = kinematic;
|
||||
rig.constraints = RigidbodyConstraints2D.FreezeAll;
|
||||
rig.mass = mass;
|
||||
RigBody2D = rig;
|
||||
}
|
||||
else
|
||||
{
|
||||
RigBody2D = GetComponent<Rigidbody2D>();
|
||||
if (RigBody2D) RigBody2D.mass = mass;
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (ParentTail == null)
|
||||
{
|
||||
GameObject.Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
TailCollisionHelper helper = collision.transform.GetComponent<TailCollisionHelper>();
|
||||
|
||||
if (helper)
|
||||
{
|
||||
if (ParentTail.CollideWithOtherTails == false) return;
|
||||
if (helper.ParentTail == ParentTail) return;
|
||||
}
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(collision.transform)) return;
|
||||
if (ParentTail.IgnoredColliders.Contains(collision.collider)) return;
|
||||
|
||||
ParentTail.CollisionDetection(Index, collision);
|
||||
previousCollision = collision.transform;
|
||||
}
|
||||
|
||||
|
||||
void OnCollisionExit(Collision collision)
|
||||
{
|
||||
if (collision.transform == previousCollision)
|
||||
{
|
||||
ParentTail.ExitCollision(Index);
|
||||
previousCollision = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTriggerEnter(Collider other)
|
||||
{
|
||||
if (other.isTrigger) return;
|
||||
|
||||
if (ParentTail.IgnoreMeshColliders)
|
||||
if (other is MeshCollider) return;
|
||||
|
||||
if (other is CharacterController) return;
|
||||
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(other.transform)) return;
|
||||
if (ParentTail.IgnoredColliders.Contains(other)) return;
|
||||
|
||||
if (ParentTail.CollideWithOtherTails == false)
|
||||
{
|
||||
TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
if (helper) return;
|
||||
//if (ParentTail.CollideWithOtherTails == false) return;
|
||||
//if (helper.ParentTail == ParentTail) return;
|
||||
}
|
||||
|
||||
ParentTail.AddCollider(other);
|
||||
}
|
||||
|
||||
void OnTriggerExit(Collider other)
|
||||
{
|
||||
if (ParentTail.IncludedColliders.Contains(other))
|
||||
{
|
||||
if (!ParentTail.DynamicAlwaysInclude.Contains(other))
|
||||
ParentTail.IncludedColliders.Remove(other);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void OnTriggerEnter2D(Collider2D other)
|
||||
{
|
||||
if (other.isTrigger) return;
|
||||
|
||||
if (other is CompositeCollider2D) return;
|
||||
#if UNITY_2019_4_OR_NEWER
|
||||
if (other is TilemapCollider2D) return;
|
||||
#endif
|
||||
if (other is EdgeCollider2D) return;
|
||||
|
||||
if (ParentTail._TransformsGhostChain.Contains(other.transform)) return;
|
||||
if (ParentTail.IgnoredColliders2D.Contains(other)) return;
|
||||
|
||||
//TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
//if (helper)
|
||||
//{
|
||||
// if (ParentTail.CollideWithOtherTails == false) return;
|
||||
// if (helper.ParentTail == ParentTail) return;
|
||||
//}
|
||||
|
||||
if (ParentTail.CollideWithOtherTails == false)
|
||||
{
|
||||
TailCollisionHelper helper = other.transform.GetComponent<TailCollisionHelper>();
|
||||
if (helper) return;
|
||||
}
|
||||
|
||||
ParentTail.AddCollider(other);
|
||||
}
|
||||
|
||||
void OnTriggerExit2D(Collider2D other)
|
||||
{
|
||||
if (ParentTail.IncludedColliders2D.Contains(other))
|
||||
{
|
||||
if (!ParentTail.DynamicAlwaysInclude.Contains(other))
|
||||
ParentTail.IncludedColliders2D.Remove(other);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea3bd5313553529418ec44a77ec6f94f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: a05a4f2884a2af44eaeedea1ea5ca396, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user