File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
using UnityEngine;
using System.Collections.Generic;
[AddComponentMenu("Dynamic Bone/Dynamic Bone")]
public class DynamicBone : MonoBehaviour
{
#if UNITY_5_3_OR_NEWER
[Tooltip("The root of the transform hierarchy to apply physics.")]
#endif
public Transform m_Root = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("Internal physics simulation rate.")]
#endif
public float m_UpdateRate = 60.0f;
public enum UpdateMode
{
Normal,
AnimatePhysics,
UnscaledTime
}
public UpdateMode m_UpdateMode = UpdateMode.Normal;
#if UNITY_5_3_OR_NEWER
[Tooltip("How much the bones slowed down.")]
#endif
[Range(0, 1)]
public float m_Damping = 0.1f;
public AnimationCurve m_DampingDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("How much the force applied to return each bone to original orientation.")]
#endif
[Range(0, 1)]
public float m_Elasticity = 0.1f;
public AnimationCurve m_ElasticityDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("How much bone's original orientation are preserved.")]
#endif
[Range(0, 1)]
public float m_Stiffness = 0.1f;
public AnimationCurve m_StiffnessDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("How much character's position change is ignored in physics simulation.")]
#endif
[Range(0, 1)]
public float m_Inert = 0;
public AnimationCurve m_InertDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("How much the bones slowed down when collide.")]
#endif
public float m_Friction = 0;
public AnimationCurve m_FrictionDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("Each bone can be a sphere to collide with colliders. Radius describe sphere's size.")]
#endif
public float m_Radius = 0;
public AnimationCurve m_RadiusDistrib = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("If End Length is not zero, an extra bone is generated at the end of transform hierarchy.")]
#endif
public float m_EndLength = 0;
#if UNITY_5_3_OR_NEWER
[Tooltip("If End Offset is not zero, an extra bone is generated at the end of transform hierarchy.")]
#endif
public Vector3 m_EndOffset = Vector3.zero;
#if UNITY_5_3_OR_NEWER
[Tooltip("The force apply to bones. Partial force apply to character's initial pose is cancelled out.")]
#endif
public Vector3 m_Gravity = Vector3.zero;
#if UNITY_5_3_OR_NEWER
[Tooltip("The force apply to bones.")]
#endif
public Vector3 m_Force = Vector3.zero;
#if UNITY_5_3_OR_NEWER
[Tooltip("Collider objects interact with the bones.")]
#endif
public List<DynamicBoneColliderBase> m_Colliders = null;
#if UNITY_5_3_OR_NEWER
[Tooltip("Bones exclude from physics simulation.")]
#endif
public List<Transform> m_Exclusions = null;
public enum FreezeAxis
{
None, X, Y, Z
}
#if UNITY_5_3_OR_NEWER
[Tooltip("Constrain bones to move on specified plane.")]
#endif
public FreezeAxis m_FreezeAxis = FreezeAxis.None;
#if UNITY_5_3_OR_NEWER
[Tooltip("Disable physics simulation automatically if character is far from camera or player.")]
#endif
public bool m_DistantDisable = false;
public Transform m_ReferenceObject = null;
public float m_DistanceToObject = 20;
Vector3 m_LocalGravity = Vector3.zero;
Vector3 m_ObjectMove = Vector3.zero;
Vector3 m_ObjectPrevPosition = Vector3.zero;
float m_BoneTotalLength = 0;
float m_ObjectScale = 1.0f;
float m_Time = 0;
float m_Weight = 1.0f;
bool m_DistantDisabled = false;
class Particle
{
public Transform m_Transform = null;
public int m_ParentIndex = -1;
public float m_Damping = 0;
public float m_Elasticity = 0;
public float m_Stiffness = 0;
public float m_Inert = 0;
public float m_Friction = 0;
public float m_Radius = 0;
public float m_BoneLength = 0;
public bool m_isCollide = false;
public Vector3 m_Position = Vector3.zero;
public Vector3 m_PrevPosition = Vector3.zero;
public Vector3 m_EndOffset = Vector3.zero;
public Vector3 m_InitLocalPosition = Vector3.zero;
public Quaternion m_InitLocalRotation = Quaternion.identity;
}
List<Particle> m_Particles = new List<Particle>();
void Start()
{
SetupParticles();
}
void FixedUpdate()
{
if (m_UpdateMode == UpdateMode.AnimatePhysics)
PreUpdate();
}
void Update()
{
if (m_UpdateMode != UpdateMode.AnimatePhysics)
PreUpdate();
}
void LateUpdate()
{
if (m_DistantDisable)
CheckDistance();
if (m_Weight > 0 && !(m_DistantDisable && m_DistantDisabled))
{
#if UNITY_5_3_OR_NEWER
float dt = m_UpdateMode == UpdateMode.UnscaledTime ? Time.unscaledDeltaTime : Time.deltaTime;
#else
float dt = Time.deltaTime;
#endif
UpdateDynamicBones(dt);
}
}
void PreUpdate()
{
if (m_Weight > 0 && !(m_DistantDisable && m_DistantDisabled))
InitTransforms();
}
void CheckDistance()
{
Transform rt = m_ReferenceObject;
if (rt == null && Camera.main != null)
rt = Camera.main.transform;
if (rt != null)
{
float d = (rt.position - transform.position).sqrMagnitude;
bool disable = d > m_DistanceToObject * m_DistanceToObject;
if (disable != m_DistantDisabled)
{
if (!disable)
ResetParticlesPosition();
m_DistantDisabled = disable;
}
}
}
void OnEnable()
{
ResetParticlesPosition();
}
void OnDisable()
{
InitTransforms();
}
void OnValidate()
{
m_UpdateRate = Mathf.Max(m_UpdateRate, 0);
m_Damping = Mathf.Clamp01(m_Damping);
m_Elasticity = Mathf.Clamp01(m_Elasticity);
m_Stiffness = Mathf.Clamp01(m_Stiffness);
m_Inert = Mathf.Clamp01(m_Inert);
m_Friction = Mathf.Clamp01(m_Friction);
m_Radius = Mathf.Max(m_Radius, 0);
if (Application.isEditor && Application.isPlaying)
{
InitTransforms();
SetupParticles();
}
}
void OnDrawGizmosSelected()
{
if (!enabled || m_Root == null)
return;
if (Application.isEditor && !Application.isPlaying && transform.hasChanged)
{
InitTransforms();
SetupParticles();
}
Gizmos.color = Color.white;
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
if (p.m_ParentIndex >= 0)
{
Particle p0 = m_Particles[p.m_ParentIndex];
Gizmos.DrawLine(p.m_Position, p0.m_Position);
}
if (p.m_Radius > 0)
Gizmos.DrawWireSphere(p.m_Position, p.m_Radius * m_ObjectScale);
}
}
public void SetWeight(float w)
{
if (m_Weight != w)
{
if (w == 0)
InitTransforms();
else if (m_Weight == 0)
ResetParticlesPosition();
m_Weight = w;
}
}
public float GetWeight()
{
return m_Weight;
}
void UpdateDynamicBones(float t)
{
if (m_Root == null)
return;
m_ObjectScale = Mathf.Abs(transform.lossyScale.x);
m_ObjectMove = transform.position - m_ObjectPrevPosition;
m_ObjectPrevPosition = transform.position;
int loop = 1;
if (m_UpdateRate > 0)
{
float dt = 1.0f / m_UpdateRate;
m_Time += t;
loop = 0;
while (m_Time >= dt)
{
m_Time -= dt;
if (++loop >= 3)
{
m_Time = 0;
break;
}
}
}
if (loop > 0)
{
for (int i = 0; i < loop; ++i)
{
UpdateParticles1();
UpdateParticles2();
m_ObjectMove = Vector3.zero;
}
}
else
{
SkipUpdateParticles();
}
ApplyParticlesToTransforms();
}
void SetupParticles()
{
m_Particles.Clear();
if (m_Root == null)
return;
m_LocalGravity = m_Root.InverseTransformDirection(m_Gravity);
m_ObjectScale = Mathf.Abs(transform.lossyScale.x);
m_ObjectPrevPosition = transform.position;
m_ObjectMove = Vector3.zero;
m_BoneTotalLength = 0;
AppendParticles(m_Root, -1, 0);
UpdateParameters();
}
void AppendParticles(Transform b, int parentIndex, float boneLength)
{
Particle p = new Particle();
p.m_Transform = b;
p.m_ParentIndex = parentIndex;
if (b != null)
{
p.m_Position = p.m_PrevPosition = b.position;
p.m_InitLocalPosition = b.localPosition;
p.m_InitLocalRotation = b.localRotation;
}
else // end bone
{
Transform pb = m_Particles[parentIndex].m_Transform;
if (m_EndLength > 0)
{
Transform ppb = pb.parent;
if (ppb != null)
p.m_EndOffset = pb.InverseTransformPoint((pb.position * 2 - ppb.position)) * m_EndLength;
else
p.m_EndOffset = new Vector3(m_EndLength, 0, 0);
}
else
{
p.m_EndOffset = pb.InverseTransformPoint(transform.TransformDirection(m_EndOffset) + pb.position);
}
p.m_Position = p.m_PrevPosition = pb.TransformPoint(p.m_EndOffset);
}
if (parentIndex >= 0)
{
boneLength += (m_Particles[parentIndex].m_Transform.position - p.m_Position).magnitude;
p.m_BoneLength = boneLength;
m_BoneTotalLength = Mathf.Max(m_BoneTotalLength, boneLength);
}
int index = m_Particles.Count;
m_Particles.Add(p);
if (b != null)
{
for (int i = 0; i < b.childCount; ++i)
{
bool exclude = false;
if (m_Exclusions != null)
{
for (int j = 0; j < m_Exclusions.Count; ++j)
{
Transform e = m_Exclusions[j];
if (e == b.GetChild(i))
{
exclude = true;
break;
}
}
}
if (!exclude)
AppendParticles(b.GetChild(i), index, boneLength);
else if (m_EndLength > 0 || m_EndOffset != Vector3.zero)
AppendParticles(null, index, boneLength);
}
if (b.childCount == 0 && (m_EndLength > 0 || m_EndOffset != Vector3.zero))
AppendParticles(null, index, boneLength);
}
}
public void UpdateParameters()
{
if (m_Root == null)
return;
m_LocalGravity = m_Root.InverseTransformDirection(m_Gravity);
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
p.m_Damping = m_Damping;
p.m_Elasticity = m_Elasticity;
p.m_Stiffness = m_Stiffness;
p.m_Inert = m_Inert;
p.m_Friction = m_Friction;
p.m_Radius = m_Radius;
if (m_BoneTotalLength > 0)
{
float a = p.m_BoneLength / m_BoneTotalLength;
if (m_DampingDistrib != null && m_DampingDistrib.keys.Length > 0)
p.m_Damping *= m_DampingDistrib.Evaluate(a);
if (m_ElasticityDistrib != null && m_ElasticityDistrib.keys.Length > 0)
p.m_Elasticity *= m_ElasticityDistrib.Evaluate(a);
if (m_StiffnessDistrib != null && m_StiffnessDistrib.keys.Length > 0)
p.m_Stiffness *= m_StiffnessDistrib.Evaluate(a);
if (m_InertDistrib != null && m_InertDistrib.keys.Length > 0)
p.m_Inert *= m_InertDistrib.Evaluate(a);
if (m_FrictionDistrib != null && m_FrictionDistrib.keys.Length > 0)
p.m_Friction *= m_FrictionDistrib.Evaluate(a);
if (m_RadiusDistrib != null && m_RadiusDistrib.keys.Length > 0)
p.m_Radius *= m_RadiusDistrib.Evaluate(a);
}
p.m_Damping = Mathf.Clamp01(p.m_Damping);
p.m_Elasticity = Mathf.Clamp01(p.m_Elasticity);
p.m_Stiffness = Mathf.Clamp01(p.m_Stiffness);
p.m_Inert = Mathf.Clamp01(p.m_Inert);
p.m_Friction = Mathf.Clamp01(p.m_Friction);
p.m_Radius = Mathf.Max(p.m_Radius, 0);
}
}
void InitTransforms()
{
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
if (p.m_Transform != null)
{
p.m_Transform.localPosition = p.m_InitLocalPosition;
p.m_Transform.localRotation = p.m_InitLocalRotation;
}
}
}
void ResetParticlesPosition()
{
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
if (p.m_Transform != null)
{
p.m_Position = p.m_PrevPosition = p.m_Transform.position;
}
else // end bone
{
Transform pb = m_Particles[p.m_ParentIndex].m_Transform;
p.m_Position = p.m_PrevPosition = pb.TransformPoint(p.m_EndOffset);
}
p.m_isCollide = false;
}
m_ObjectPrevPosition = transform.position;
}
void UpdateParticles1()
{
Vector3 force = m_Gravity;
Vector3 fdir = m_Gravity.normalized;
Vector3 rf = m_Root.TransformDirection(m_LocalGravity);
Vector3 pf = fdir * Mathf.Max(Vector3.Dot(rf, fdir), 0); // project current gravity to rest gravity
force -= pf; // remove projected gravity
force = (force + m_Force) * m_ObjectScale;
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
if (p.m_ParentIndex >= 0)
{
// verlet integration
Vector3 v = p.m_Position - p.m_PrevPosition;
Vector3 rmove = m_ObjectMove * p.m_Inert;
p.m_PrevPosition = p.m_Position + rmove;
float damping = p.m_Damping;
if (p.m_isCollide)
{
damping += p.m_Friction;
if (damping > 1)
damping = 1;
p.m_isCollide = false;
}
p.m_Position += v * (1 - damping) + force + rmove;
}
else
{
p.m_PrevPosition = p.m_Position;
p.m_Position = p.m_Transform.position;
}
}
}
void UpdateParticles2()
{
Plane movePlane = new Plane();
for (int i = 1; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
Particle p0 = m_Particles[p.m_ParentIndex];
float restLen;
if (p.m_Transform != null)
restLen = (p0.m_Transform.position - p.m_Transform.position).magnitude;
else
restLen = p0.m_Transform.localToWorldMatrix.MultiplyVector(p.m_EndOffset).magnitude;
// keep shape
float stiffness = Mathf.Lerp(1.0f, p.m_Stiffness, m_Weight);
if (stiffness > 0 || p.m_Elasticity > 0)
{
Matrix4x4 m0 = p0.m_Transform.localToWorldMatrix;
m0.SetColumn(3, p0.m_Position);
Vector3 restPos;
if (p.m_Transform != null)
restPos = m0.MultiplyPoint3x4(p.m_Transform.localPosition);
else
restPos = m0.MultiplyPoint3x4(p.m_EndOffset);
Vector3 d = restPos - p.m_Position;
p.m_Position += d * p.m_Elasticity;
if (stiffness > 0)
{
d = restPos - p.m_Position;
float len = d.magnitude;
float maxlen = restLen * (1 - stiffness) * 2;
if (len > maxlen)
p.m_Position += d * ((len - maxlen) / len);
}
}
// collide
if (m_Colliders != null)
{
float particleRadius = p.m_Radius * m_ObjectScale;
for (int j = 0; j < m_Colliders.Count; ++j)
{
DynamicBoneColliderBase c = m_Colliders[j];
if (c != null && c.enabled)
p.m_isCollide |= c.Collide(ref p.m_Position, particleRadius);
}
}
// freeze axis, project to plane
if (m_FreezeAxis != FreezeAxis.None)
{
switch (m_FreezeAxis)
{
case FreezeAxis.X:
movePlane.SetNormalAndPosition(p0.m_Transform.right, p0.m_Position);
break;
case FreezeAxis.Y:
movePlane.SetNormalAndPosition(p0.m_Transform.up, p0.m_Position);
break;
case FreezeAxis.Z:
movePlane.SetNormalAndPosition(p0.m_Transform.forward, p0.m_Position);
break;
}
p.m_Position -= movePlane.normal * movePlane.GetDistanceToPoint(p.m_Position);
}
// keep length
Vector3 dd = p0.m_Position - p.m_Position;
float leng = dd.magnitude;
if (leng > 0)
p.m_Position += dd * ((leng - restLen) / leng);
}
}
// only update stiffness and keep bone length
void SkipUpdateParticles()
{
for (int i = 0; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
if (p.m_ParentIndex >= 0)
{
p.m_PrevPosition += m_ObjectMove;
p.m_Position += m_ObjectMove;
Particle p0 = m_Particles[p.m_ParentIndex];
float restLen;
if (p.m_Transform != null)
restLen = (p0.m_Transform.position - p.m_Transform.position).magnitude;
else
restLen = p0.m_Transform.localToWorldMatrix.MultiplyVector(p.m_EndOffset).magnitude;
// keep shape
float stiffness = Mathf.Lerp(1.0f, p.m_Stiffness, m_Weight);
if (stiffness > 0)
{
Matrix4x4 m0 = p0.m_Transform.localToWorldMatrix;
m0.SetColumn(3, p0.m_Position);
Vector3 restPos;
if (p.m_Transform != null)
restPos = m0.MultiplyPoint3x4(p.m_Transform.localPosition);
else
restPos = m0.MultiplyPoint3x4(p.m_EndOffset);
Vector3 d = restPos - p.m_Position;
float len = d.magnitude;
float maxlen = restLen * (1 - stiffness) * 2;
if (len > maxlen)
p.m_Position += d * ((len - maxlen) / len);
}
// keep length
Vector3 dd = p0.m_Position - p.m_Position;
float leng = dd.magnitude;
if (leng > 0)
p.m_Position += dd * ((leng - restLen) / leng);
}
else
{
p.m_PrevPosition = p.m_Position;
p.m_Position = p.m_Transform.position;
}
}
}
static Vector3 MirrorVector(Vector3 v, Vector3 axis)
{
return v - axis * (Vector3.Dot(v, axis) * 2);
}
void ApplyParticlesToTransforms()
{
#if !UNITY_5_4_OR_NEWER
// detect negative scale
Vector3 ax = Vector3.right;
Vector3 ay = Vector3.up;
Vector3 az = Vector3.forward;
bool nx = false, ny = false, nz = false;
Vector3 loosyScale = transform.lossyScale;
if (loosyScale.x < 0 || loosyScale.y < 0 || loosyScale.z < 0)
{
Transform mirrorObject = transform;
do
{
Vector3 ls = mirrorObject.localScale;
nx = ls.x < 0;
if (nx)
ax = mirrorObject.right;
ny = ls.y < 0;
if (ny)
ay = mirrorObject.up;
nz = ls.z < 0;
if (nz)
az = mirrorObject.forward;
if (nx || ny || nz)
break;
mirrorObject = mirrorObject.parent;
}
while (mirrorObject != null);
}
#endif
for (int i = 1; i < m_Particles.Count; ++i)
{
Particle p = m_Particles[i];
Particle p0 = m_Particles[p.m_ParentIndex];
if (p0.m_Transform.childCount <= 1) // do not modify bone orientation if has more then one child
{
Vector3 v;
if (p.m_Transform != null)
v = p.m_Transform.localPosition;
else
v = p.m_EndOffset;
Vector3 v2 = p.m_Position - p0.m_Position;
#if !UNITY_5_4_OR_NEWER
if (nx)
v2 = MirrorVector(v2, ax);
if (ny)
v2 = MirrorVector(v2, ay);
if (nz)
v2 = MirrorVector(v2, az);
#endif
Quaternion rot = Quaternion.FromToRotation(p0.m_Transform.TransformDirection(v), v2);
p0.m_Transform.rotation = rot * p0.m_Transform.rotation;
}
if (p.m_Transform != null)
p.m_Transform.position = p.m_Position;
}
}
}