Recently, I received an internship from a company and requested to integrate Dynamic Bone. Although the distance between nodes in Dynamic Bone is constant, the effect of Magica Cloth in fabric simulation is much better than that of Dynamic Bone (but also in terms of performance consumption), the algorithm of Dynamic Bone is also considered classic and worth writing about.
public List<DynamicBoneColliderBase> m_Colliders = null;//Collision itemset
public List<Transform> m_Exclusions = null;//Exclusion itemset
publicenum FreezeAxis//Locked Axis { None, X, Y, Z } public FreezeAxis m_FreezeAxis = FreezeAxis.None;
publicbool m_DistantDisable = false;//Cutting by distance or not public Transform m_ReferenceObject = null;//Reference object publicfloat m_DistanceToObject = 20;//Reference distance
[HideInInspector] publicbool m_Multithread = true;//Use multithread or not
Vector3 m_ObjectMove;//Move direction Vector3 m_ObjectPrevPosition;//Position of last frame float m_ObjectScale;//Global scale
float m_Time = 0;//Timer float m_Weight = 1.0f;//Bone weight bool m_DistantDisabled = false;//Cutting by distance or not int m_PreUpdateCount = 0;//Multithread counter
public Vector3 m_Position; public Vector3 m_PrevPosition; public Vector3 m_EndOffset; public Vector3 m_InitLocalPosition; public Quaternion m_InitLocalRotation;
//Prepared data public Vector3 m_TransformPosition; public Vector3 m_TransformLocalPosition; public Matrix4x4 m_TransformLocalToWorldMatrix; }
classParticleTree//Single bone { public Transform m_Root; public Vector3 m_LocalGravity; public Matrix4x4 m_RootWorldToLocalMatrix; publicfloat m_BoneTotalLength; public List<Particle> m_Particles = new List<Particle>();//Node list
//Prepared data public Vector3 m_RestGravity; }
List<ParticleTree> m_ParticleTrees = new List<ParticleTree>();//Bone tree
//Prepared data float m_DeltaTime;//Classical Δt List<DynamicBoneColliderBase> m_EffectiveColliders;//Collider list
if (m_Root != null)//The previous version only had m_Roots but not m_Roots, which is compatible here { AppendParticleTree(m_Root); }
if (m_Roots != null)//Join the root nodes in sequence in m-Roots { for (int i = 0; i < m_Roots.Count; ++i) { Transform root = m_Roots[i]; if (root == null) continue;
if (m_ParticleTrees.Exists(x => x.m_Root == root))//Use lambda to remove duplicates of m_Roots in m_Roots continue;
AppendParticleTree(root);//Add nodes } }
m_ObjectScale = Mathf.Abs(transform.lossyScale.x);//Global scale m_ObjectPrevPosition = transform.position;//Initialize previous position m_ObjectMove = Vector3.zero;//Initialize move direction
for (int i = 0; i < m_ParticleTrees.Count; ++i) { ParticleTree pt = m_ParticleTrees[i]; AppendParticles(pt, pt.m_Root, -1, 0);//Add child nodes' information }
voidAppendParticleTree(Transform root) { if (root == null) return;
var pt = new ParticleTree(); pt.m_Root = root; pt.m_RootWorldToLocalMatrix = root.worldToLocalMatrix; m_ParticleTrees.Add(pt);//Add each bone root node to the bone tree }
voidAppendParticles(ParticleTree pt, Transform b, int parentIndex, float boneLength) { var p = new Particle(); p.m_Transform = b; p.m_TransformNotNull = b != null; p.m_ParentIndex = parentIndex;
if (parentIndex >= 0)//Calculation of bone length and number of child nodes in non root nodes { boneLength += (pt.m_Particles[parentIndex].m_Transform.position - p.m_Position).magnitude; p.m_BoneLength = boneLength; pt.m_BoneTotalLength = Mathf.Max(pt.m_BoneTotalLength, boneLength); ++pt.m_Particles[parentIndex].m_ChildCount; }
int index = pt.m_Particles.Count; pt.m_Particles.Add(p);
if (b != null) { for (int i = 0; i < b.childCount; ++i) { Transform child = b.GetChild(i); bool exclude = false; if (m_Exclusions != null)//Traverse to exclude nodes { exclude = m_Exclusions.Contains(child); } if (!exclude)//No exclusion, recursively adding nodes { AppendParticles(pt, child, index, boneLength); } elseif (m_EndLength > 0 || m_EndOffset != Vector3.zero)//Excluded, generate virtual tail nodes { AppendParticles(pt, null, index, boneLength); } }
for (int i = 0; i < m_ParticleTrees.Count; ++i) { UpdateParameters(m_ParticleTrees[i]); } }
voidUpdateParameters(ParticleTree pt) { //Calculate local gravity direction pt.m_LocalGravity=pt.m_RootWorldToLocalMatrix.MultiplyVector(m_Gravity).normalized*m_Gravity.magnitude;
for (int i = 0; i < pt.m_Particles.Count; ++i) { Particle p = pt.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 (pt.m_BoneTotalLength > 0)//Assign values based on the proportion of animation curves and bone length { float a = p.m_BoneLength / pt.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); } //Make parameters conform to physics 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); } }
Update()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
voidUpdate() { if (m_UpdateMode != UpdateMode.AnimatePhysics) { PreUpdate();//Initialize local spatial information of bones }
voidInitTransforms() { for (int i = 0; i < m_ParticleTrees.Count; ++i) { InitTransforms(m_ParticleTrees[i]); } }
voidInitTransforms(ParticleTree pt) { for (int i = 0; i < pt.m_Particles.Count; ++i) { Particle p = pt.m_Particles[i]; if (p.m_TransformNotNull)//Change local coordinates to initial values { p.m_Transform.localPosition = p.m_InitLocalPosition; p.m_Transform.localRotation = p.m_InitLocalRotation; } } }
AddPendingWork()
1 2 3 4
staticvoidAddPendingWork(DynamicBone db) { s_PendingWorks.Add(db); //Add bones to the waiting queue }
voidLateUpdate() { if (m_PreUpdateCount == 0) return;
if (s_UpdateCount > 0) { s_UpdateCount = 0; ++s_PrepareFrame; }
SetWeight(m_BlendWeight);//Set mixed weights for animation and physics
#if ENABLE_MULTITHREAD if (m_WorkAdded) //If bones were added to the multi-threaded queue before { m_WorkAdded = false;//Set back to its original state ExecuteWorks();//Execute multithreading } else #endif { CheckDistance();//Distance judgment if (IsNeedUpdate())//Need physical calculations or not { Prepare();//Processing of data prepared in data structures UpdateParticles();//Physical simulation of bones ApplyParticlesToTransforms();//Applied Physics Simulation Results } }
m_PreUpdateCount = 0; }
SetWeight()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
publicvoidSetWeight(float w) { if (m_Weight != w) { if (w == 0) { InitTransforms();//Reset bone coordinate information } elseif (m_Weight == 0) { ResetParticlesPosition();//Reset current position coordinates } m_Weight = m_BlendWeight = w;//M_Weight is an interface left over from previous versions, only for access here } }
voidResetParticlesPosition() { for (int i = 0; i < m_ParticleTrees.Count; ++i) { ResetParticlesPosition(m_ParticleTrees[i]); }
m_ObjectPrevPosition = transform.position;//Retain previous frame information }
voidResetParticlesPosition(ParticleTree pt) { for (int i = 0; i < pt.m_Particles.Count; ++i) { Particle p = pt.m_Particles[i]; if (p.m_TransformNotNull)//Set both the current position and the previous frame position to Trans values { p.m_Position = p.m_PrevPosition = p.m_Transform.position; } else//Virtual tail node processing { Transform pb = pt.m_Particles[p.m_ParentIndex].m_Transform; p.m_Position = p.m_PrevPosition = pb.TransformPoint(p.m_EndOffset); } p.m_isCollide = false; } }
for (int i = 0; i < m_ParticleTrees.Count; ++i) { ParticleTree pt = m_ParticleTrees[i]; pt.m_RestGravity = pt.m_Root.TransformDirection(pt.m_LocalGravity);//Gravity changes from local coordinates to world coordinates
for (int j = 0; j < pt.m_Particles.Count; ++j) { Particle p = pt.m_Particles[j]; if (p.m_TransformNotNull)//Record relevant data { p.m_TransformPosition = p.m_Transform.position; p.m_TransformLocalPosition = p.m_Transform.localPosition; p.m_TransformLocalToWorldMatrix = p.m_Transform.localToWorldMatrix; } } }
if (m_EffectiveColliders != null)//Destroy collision list { m_EffectiveColliders.Clear(); }
if (m_Colliders != null)//Create a new collision list { for (int i = 0; i < m_Colliders.Count; ++i) { DynamicBoneColliderBase c = m_Colliders[i]; if (c != null && c.enabled) { if (m_EffectiveColliders == null) { m_EffectiveColliders = new List<DynamicBoneColliderBase>(); } m_EffectiveColliders.Add(c);
if (c.PrepareFrame != s_PrepareFrame)//Initialize collision only once { c.Prepare(); c.PrepareFrame = s_PrepareFrame; } } } } }
if (loop > 0)//Simulate iterations { for (int i = 0; i < loop; ++i) { UpdateParticles1(timeVar, i);//Inertia and force UpdateParticles2(timeVar);//Elasticity and stiffness } } else { SkipUpdateParticles();//Skip simulation } }
voidUpdateParticles1(float timeVar, int loopIndex) { for (int i = 0; i < m_ParticleTrees.Count; ++i) { UpdateParticles1(m_ParticleTrees[i], timeVar, loopIndex); } }
voidUpdateParticles1(ParticleTree pt, float timeVar, int loopIndex) { Vector3 force = m_Gravity;//Gravity Vector3 fdir = m_Gravity.normalized;//Gravity unit vector Vector3 pf = fdir * Mathf.Max(Vector3.Dot(pt.m_RestGravity, fdir), 0);//Projected gravity force -= pf;//Removing the influence of gravity force = (force + m_Force) * (m_ObjectScale * timeVar);//Apply time and scale
Vector3 objectMove = loopIndex == 0 ? m_ObjectMove : Vector3.zero;//Only the first loop changes objectMove
for (int i = 0; i < pt.m_Particles.Count; ++i) { Particle p = pt.m_Particles[i]; if (p.m_ParentIndex >= 0) { //Verlet integration Vector3 v = p.m_Position - p.m_PrevPosition;//Node motion direction Vector3 rmove = objectMove * p.m_Inert;//Predicting displacement p.m_PrevPosition = p.m_Position + rmove;//Predicting position float damping = p.m_Damping; if (p.m_isCollide)//If collision occurs { damping += p.m_Friction;//Friction acts on damping if (damping > 1) { damping = 1; } p.m_isCollide = false;//Reset collision detection } p.m_Position += v * (1 - damping) + force + rmove;//Calculate the position of nodes after calculating inertia and external forces } else { p.m_PrevPosition = p.m_Position; p.m_Position = p.m_TransformPosition; } } }
voidUpdateParticles2(float timeVar) { for (int i = 0; i < m_ParticleTrees.Count; ++i) { UpdateParticles2(m_ParticleTrees[i], timeVar); } }
voidUpdateParticles2(ParticleTree pt, float timeVar) { var movePlane = new Plane();
for (int i = 1; i < pt.m_Particles.Count; ++i) { Particle p = pt.m_Particles[i];//Node Particle p0 = pt.m_Particles[p.m_ParentIndex];//Parent node
float restLen;//Length between parent node and child node if (p.m_TransformNotNull)//Normal node { restLen = (p0.m_TransformPosition - p.m_TransformPosition).magnitude; } else//Virtual tail node { restLen = p0.m_TransformLocalToWorldMatrix.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_TransformLocalToWorldMatrix;//Matrix from local coordinates of parent node to world coordinates m0.SetColumn(3, p0.m_Position);//m0 is the matrix from the local coordinates of the node to the world coordinates Vector3 restPos;//Regression coordinates if (p.m_TransformNotNull) { restPos = m0.MultiplyPoint3x4(p.m_TransformLocalPosition); } else { restPos = m0.MultiplyPoint3x4(p.m_EndOffset); }
Vector3 d = restPos - p.m_Position;//Regression vector p.m_Position += d * (p.m_Elasticity * timeVar);//Apply elasticity
if (stiffness > 0)//Rigid motion { d = restPos - p.m_Position; float len = d.magnitude; float maxlen = restLen * (1 - stiffness) * 2;//Calculate theoretical length if (len > maxlen) { p.m_Position += d * ((len - maxlen) / len);//Coordinate offset } } }
voidApplyParticlesToTransforms() { Vector3 ax = Vector3.right; Vector3 ay = Vector3.up; Vector3 az = Vector3.forward; bool nx = false, ny = false, nz = false;
#if !UNITY_5_4_OR_NEWER Vector3 lossyScale = transform.lossyScale; if (lossyScale.x < 0 || lossyScale.y < 0 || lossyScale.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 = 0; i < m_ParticleTrees.Count; ++i) { ApplyParticlesToTransforms(m_ParticleTrees[i], ax, ay, az, nx, ny, nz); } }
voidApplyParticlesToTransforms(ParticleTree pt,Vector3 ax,Vector3 ay,Vector3 az,bool nx,bool ny,bool nz) { for (int i = 1; i < pt.m_Particles.Count; ++i) { Particle p = pt.m_Particles[i]; Particle p0 = pt.m_Particles[p.m_ParentIndex];
if (p0.m_ChildCount <= 1)//Modify bone orientation when only a single child node is present { Vector3 localPos; if (p.m_TransformNotNull)//Normal node { localPos = p.m_Transform.localPosition; } else//Virtual tail node { localPos = p.m_EndOffset; } Vector3 v0 = p0.m_Transform.TransformDirection(localPos); Vector3 v1 = p.m_Position - p0.m_Position; #if !UNITY_5_4_OR_NEWER if (nx) v1 = MirrorVector(v1, ax); if (ny) v1 = MirrorVector(v1, ay); if (nz) v1 = MirrorVector(v1, az); #endif Quaternion rot = Quaternion.FromToRotation(v0, v1); p0.m_Transform.rotation = rot * p0.m_Transform.rotation;//Handle parent node rotation }
if (p.m_TransformNotNull)//Nomal child node { p.m_Transform.position = p.m_Position;//Apply simulation result } } }
Conclusion
The plugin was last updated to version 1.3.2 more than a year ago, so it should have been in development now. There are already many (but not new) articles analyzing Dynamic bone, so I am committed to presenting the entire basic flow function of the script (other flow functions that are not shown also call the sub functions mentioned earlier). I hope it can be helpful to readers.
Overall, the chain structure of the skeleton was first constructed, local coordinates were recorded, and then the application of inertia, friction, external forces, elasticity, and stiffness from root to tail was processed and updated at each level. If you still have doubts, you can also move to other articles on Zhihu. Detailed explanation of the core code: Dynamic Bone source code analysis Explanation of Simulation Algorithm: Detailed Explanation of Dynamic Bone Algorithm for Dynamic Bones.