Written before

  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.

Maintext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    public Transform m_Root = null;//Cutting reference
public List<Transform> m_Roots = null;//Cutting distance

public float m_UpdateRate = 60.0f;

public enum UpdateMode
{
Normal,
AnimatePhysics,
UnscaledTime,
Default
}
public UpdateMode m_UpdateMode = UpdateMode.Default;

[Range(0, 1)]
public float m_Damping = 0.1f;
public AnimationCurve m_DampingDistrib = null;

[Range(0, 1)]
public float m_Elasticity = 0.1f;
public AnimationCurve m_ElasticityDistrib = null;

[Range(0, 1)]
public float m_Stiffness = 0.1f;
public AnimationCurve m_StiffnessDistrib = null;

[Range(0, 1)]
public float m_Inert = 0;
public AnimationCurve m_InertDistrib = null;

public float m_Friction = 0;
public AnimationCurve m_FrictionDistrib = null;

public float m_Radius = 0;
public AnimationCurve m_RadiusDistrib = null;

public float m_EndLength = 0;//Virtual tail node length

public Vector3 m_EndOffset = Vector3.zero;//Virtual tail node bias

public Vector3 m_Gravity = Vector3.zero;//Gravity

public Vector3 m_Force = Vector3.zero;//External force

[Range(0, 1)]
public float m_BlendWeight = 1.0f;//Blend Weight

public List<DynamicBoneColliderBase> m_Colliders = null;//Collision itemset

public List<Transform> m_Exclusions = null;//Exclusion itemset

public enum FreezeAxis//Locked Axis
{
None, X, Y, Z
}
public FreezeAxis m_FreezeAxis = FreezeAxis.None;

public bool m_DistantDisable = false;//Cutting by distance or not
public Transform m_ReferenceObject = null;//Reference object
public float m_DistanceToObject = 20;//Reference distance

[HideInInspector]
public bool 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

class Particle//Bone nodes
{
public Transform m_Transform;
public int m_ParentIndex;
public int m_ChildCount;
public float m_Damping;
public float m_Elasticity;
public float m_Stiffness;
public float m_Inert;
public float m_Friction;
public float m_Radius;
public float m_BoneLength;
public bool m_isCollide;
public bool m_TransformNotNull;

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

class ParticleTree//Single bone
{
public Transform m_Root;
public Vector3 m_LocalGravity;
public Matrix4x4 m_RootWorldToLocalMatrix;
public float 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 ENABLE_MULTITHREAD
bool m_WorkAdded = false;
static List<DynamicBone> s_PendingWorks = new List<DynamicBone>();
static List<DynamicBone> s_EffectiveWorks = new List<DynamicBone>();
static AutoResetEvent s_AllWorksDoneEvent;
static int s_RemainWorkCount;
static Semaphore s_WorkQueueSemaphore;
static int s_WorkQueueIndex;
#endif

static int s_UpdateCount;
static int s_PrepareFrame;

Start()

1
2
3
4
void Start()
{
SetupParticles();
}
SetupParticles()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public void SetupParticles()
{
m_ParticleTrees.Clear();

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
}

UpdateParameters();//Update nodes
}
AppendParticleTree()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
void AppendParticleTree(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
}

void AppendParticles(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 (b != null)//Initialize normal nodes
{
p.m_Position = p.m_PrevPosition = b.position;
p.m_InitLocalPosition = b.localPosition;
p.m_InitLocalRotation = b.localRotation;//Save local position
}
else//Create virtual tail node
{
Transform pb = pt.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);
p.m_InitLocalPosition = Vector3.zero;
p.m_InitLocalRotation = Quaternion.identity;
}

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);
}
else if (m_EndLength > 0 || m_EndOffset != Vector3.zero)//Excluded, generate virtual tail nodes
{
AppendParticles(pt, null, index, boneLength);
}
}

if (b.childCount == 0 && (m_EndLength > 0 || m_EndOffset != Vector3.zero))//Generate virtual tail nodes
{
AppendParticles(pt, null, index, boneLength);
}
}
}
UpdateParameters()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public void UpdateParameters()
{
SetWeight(m_BlendWeight);//Set animation physics blending parameters to subsequently affect node stiffness

for (int i = 0; i < m_ParticleTrees.Count; ++i)
{
UpdateParameters(m_ParticleTrees[i]);
}
}

void UpdateParameters(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
void Update()
{
if (m_UpdateMode != UpdateMode.AnimatePhysics)
{
PreUpdate();//Initialize local spatial information of bones
}

#if ENABLE_MULTITHREAD //If multithreading is enabled
if (m_PreUpdateCount > 0 && m_Multithread) //Requires multithreaded updates
{
AddPendingWork(this);
m_WorkAdded = true;
}
#endif
++s_UpdateCount;
}
PreUpdate()
1
2
3
4
5
6
7
8
void PreUpdate()
{
if (IsNeedUpdate())
{
InitTransforms();//Reset bone coordinate information
}
++m_PreUpdateCount;
}
InitTransforms()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void InitTransforms()
{
for (int i = 0; i < m_ParticleTrees.Count; ++i)
{
InitTransforms(m_ParticleTrees[i]);
}
}


void InitTransforms(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
static void AddPendingWork(DynamicBone db)
{
s_PendingWorks.Add(db); //Add bones to the waiting queue
}

LateUpdate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void LateUpdate()
{
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
public void SetWeight(float w)
{
if (m_Weight != w)
{
if (w == 0)
{
InitTransforms();//Reset bone coordinate information
}
else if (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
}
}
ResetParticlesPosition()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void ResetParticlesPosition()
{
for (int i = 0; i < m_ParticleTrees.Count; ++i)
{
ResetParticlesPosition(m_ParticleTrees[i]);
}

m_ObjectPrevPosition = transform.position;//Retain previous frame information
}

void ResetParticlesPosition(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;
}
}
CheckDistance()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void CheckDistance()
{
if (!m_DistantDisable)
return;

Transform rt = m_ReferenceObject;
if (rt == null && Camera.main != null)
{
rt = Camera.main.transform;//Set as camera Trans if there is no reference
}

if (rt != null)
{
float d2 = (rt.position - transform.position).sqrMagnitude;
bool disable = d2 > m_DistanceToObject * m_DistanceToObject;//Is it beyond the distance
if (disable != m_DistantDisabled)
{
if (!disable)
{
ResetParticlesPosition();//Reset current position coordinates
}
m_DistantDisabled = disable;
}
}
}
IsNeedUpdate()
1
2
3
4
bool IsNeedUpdate()
{
return m_Weight > 0 && !(m_DistantDisable && m_DistantDisabled);
}
Prepare()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    void Prepare()
{
m_DeltaTime = Time.deltaTime;
#if UNITY_5_3_OR_NEWER
if (m_UpdateMode == UpdateMode.UnscaledTime)
{
m_DeltaTime = Time.unscaledDeltaTime;
}
else if (m_UpdateMode == UpdateMode.AnimatePhysics)
{
m_DeltaTime = Time.fixedDeltaTime * m_PreUpdateCount;
}
#endif

m_ObjectScale = Mathf.Abs(transform.lossyScale.x);
m_ObjectMove = transform.position - m_ObjectPrevPosition;//Displacement
m_ObjectPrevPosition = transform.position;//Previous frame position

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;
}
}
}
}
}
UpdateParticles()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void UpdateParticles()
{
if (m_ParticleTrees.Count <= 0)
return;

int loop = 1;
float timeVar = 1;
float dt = m_DeltaTime;

if (m_UpdateMode == UpdateMode.Default)//Frame rate processing
{
if (m_UpdateRate > 0)
{
timeVar = dt * m_UpdateRate;
}
}
else
{
if (m_UpdateRate > 0)//Control of simulation time steps
{
float frameTime = 1.0f / m_UpdateRate;
m_Time += dt;
loop = 0;

while (m_Time >= frameTime)
{
m_Time -= frameTime;
if (++loop >= 3)
{
m_Time = 0;
break;
}
}
}
}

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
}
}
UpdateParticles1()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
void UpdateParticles1(float timeVar, int loopIndex)
{
for (int i = 0; i < m_ParticleTrees.Count; ++i)
{
UpdateParticles1(m_ParticleTrees[i], timeVar, loopIndex);
}
}

void UpdateParticles1(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;
}
}
}
UpdateParticles2()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
void UpdateParticles2(float timeVar)
{
for (int i = 0; i < m_ParticleTrees.Count; ++i)
{
UpdateParticles2(m_ParticleTrees[i], timeVar);
}
}

void UpdateParticles2(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
}
}
}

//Collision
if (m_EffectiveColliders != null)
{
float particleRadius = p.m_Radius * m_ObjectScale;
for (int j = 0; j < m_EffectiveColliders.Count; ++j)
{
DynamicBoneColliderBase c = m_EffectiveColliders[j];
p.m_isCollide |= c.Collide(ref p.m_Position, particleRadius);
}
}

//Handle locked axis
if (m_FreezeAxis != FreezeAxis.None)
{
Vector3 planeNormal=p0.m_TransformLocalToWorldMatrix.GetColumn((int)m_FreezeAxis-1).normalized;
movePlane.SetNormalAndPosition(planeNormal, p0.m_Position);
p.m_Position -= movePlane.normal * movePlane.GetDistanceToPoint(p.m_Position);
}

//Keep length
Vector3 dd = p0.m_Position - p.m_Position;//The bone direction after updating child nodes
float leng = dd.magnitude;//The bone length after updating child nodes
if (leng > 0)
{
p.m_Position += dd * ((leng - restLen) / leng);//Update coordinates to ensure consistent bone length
}
}
}
ApplyParticlesToTransforms()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    void ApplyParticlesToTransforms()
{
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);
}
}

void ApplyParticlesToTransforms(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.