forked from bepu/bepuphysics1
-
Notifications
You must be signed in to change notification settings - Fork 0
/
IKSolver.cs
300 lines (255 loc) · 12.4 KB
/
IKSolver.cs
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
using System;
using System.Collections.Generic;
using BEPUutilities;
namespace BEPUik
{
/// <summary>
/// <para>
/// This is a little experimental project designed to iteratively converge to a decent solution
/// to full body inverse kinematics subject to a variety of constraints.
/// </para>
/// <para>
/// It's currently separated from the rest of BEPUphysics library internals because the immediate goal is to test out
/// features to be potentially integrated into a blender content pipeline. BEPUphysics interactions with this system
/// will have to go through the interfaces like everything else for now.
/// </para>
/// </summary>
public class IKSolver : IDisposable
{
/// <summary>
/// Gets the active joint set associated with the solver.
/// </summary>
public ActiveSet ActiveSet { get; private set; }
/// <summary>
/// Gets or sets the number of solver iterations to perform in an attempt to reach specified goals.
/// </summary>
public int ControlIterationCount { get; set; }
/// <summary>
/// Gets or sets the number of solter iterations to perform after the control iterations in an attempt to minimize
/// errors introduced by unreachable goals.
/// </summary>
public int FixerIterationCount { get; set; }
/// <summary>
/// Gets or sets the number of velocity iterations to perform per control or fixer iteration.
/// </summary>
public int VelocitySubiterationCount { get; set; }
/// <summary>
/// Gets or sets whether or not to scale control impulses such that they fit well with the mass of objects.
/// </summary>
public bool AutoscaleControlImpulses { get; set; }
/// <summary>
/// Gets or sets the maximum impulse the controls will try to push bones with when AutoscaleControlImpulses is enabled.
/// </summary>
public float AutoscaleControlMaximumForce { get; set; }
private float timeStepDuration = 1f;
/// <summary>
/// Gets or sets the time step duration elapsed by each position iteration.
/// </summary>
public float TimeStepDuration
{
get { return timeStepDuration; }
set
{
if (value <= 0)
throw new ArgumentException("Time step duration must be positive.");
timeStepDuration = value;
}
}
private PermutationMapper permutationMapper = new PermutationMapper();
/// <summary>
/// Constructs a new IKSolver.
/// </summary>
public IKSolver()
{
ActiveSet = new ActiveSet();
ControlIterationCount = 50;
FixerIterationCount = 70;
VelocitySubiterationCount = 2;
}
/// <summary>
/// Updates the positions of bones acted upon by the joints given to this solver.
/// This variant of the solver can be used when there are no goal-driven controls in the simulation.
/// This amounts to running just the 'fixer iterations' of a normal control-driven solve.
/// </summary>
public void Solve(List<IKJoint> joints)
{
ActiveSet.UpdateActiveSet(joints);
//Reset the permutation index; every solve should proceed in exactly the same order.
permutationMapper.PermutationIndex = 0;
float updateRate = 1 / TimeStepDuration;
foreach (var joint in ActiveSet.joints)
{
joint.Preupdate(TimeStepDuration, updateRate);
}
for (int i = 0; i < FixerIterationCount; i++)
{
//Update the world inertia tensors of objects for the latest position.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdateInertiaTensor();
}
//Update the per-constraint jacobians and effective mass for the current bone orientations and positions.
foreach (IKJoint joint in ActiveSet.joints)
{
joint.UpdateJacobiansAndVelocityBias();
joint.ComputeEffectiveMass();
joint.WarmStart();
}
for (int j = 0; j < VelocitySubiterationCount; j++)
{
//A permuted version of the indices is used. The randomization tends to avoid issues with solving order in corner cases.
for (int jointIndex = 0; jointIndex < ActiveSet.joints.Count; ++jointIndex)
{
int remappedIndex = permutationMapper.GetMappedIndex(jointIndex, ActiveSet.joints.Count);
ActiveSet.joints[remappedIndex].SolveVelocityIteration();
}
//Increment to use the next permutation.
++permutationMapper.PermutationIndex;
}
//Integrate the positions of the bones forward.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdatePosition();
}
}
//Clear out accumulated impulses; they should not persist through to another solving round because the state could be arbitrarily different.
for (int j = 0; j < ActiveSet.joints.Count; j++)
{
ActiveSet.joints[j].ClearAccumulatedImpulses();
}
}
/// <summary>
/// Updates the positions of bones acted upon by the controls given to this solver.
/// </summary>
/// <param name="controls">List of currently active controls.</param>
public void Solve(List<Control> controls)
{
//Update the list of active joints.
ActiveSet.UpdateActiveSet(controls);
if (AutoscaleControlImpulses)
{
//Update the control strengths to match the mass of the target bones and the desired maximum force.
foreach (var control in controls)
{
control.MaximumForce = control.TargetBone.Mass * AutoscaleControlMaximumForce;
}
}
//Reset the permutation index; every solve should proceed in exactly the same order.
permutationMapper.PermutationIndex = 0;
float updateRate = 1 / TimeStepDuration;
foreach (var joint in ActiveSet.joints)
{
joint.Preupdate(TimeStepDuration, updateRate);
}
foreach (var control in controls)
{
control.Preupdate(TimeStepDuration, updateRate);
}
//Go through the set of controls and active joints, updating the state of bones.
for (int i = 0; i < ControlIterationCount; i++)
{
//Update the world inertia tensors of objects for the latest position.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdateInertiaTensor();
}
//Update the per-constraint jacobians and effective mass for the current bone orientations and positions.
foreach (IKJoint joint in ActiveSet.joints)
{
joint.UpdateJacobiansAndVelocityBias();
joint.ComputeEffectiveMass();
joint.WarmStart();
}
foreach (var control in controls)
{
if (control.TargetBone.Pinned)
throw new InvalidOperationException("Pinned objects cannot be moved by controls.");
control.UpdateJacobiansAndVelocityBias();
control.ComputeEffectiveMass();
control.WarmStart();
}
for (int j = 0; j < VelocitySubiterationCount; j++)
{
//Controls are updated first.
foreach (Control control in controls)
{
control.SolveVelocityIteration();
}
//A permuted version of the indices is used. The randomization tends to avoid issues with solving order in corner cases.
for (int jointIndex = 0; jointIndex < ActiveSet.joints.Count; ++jointIndex)
{
int remappedIndex = permutationMapper.GetMappedIndex(jointIndex, ActiveSet.joints.Count);
ActiveSet.joints[remappedIndex].SolveVelocityIteration();
}
//Increment to use the next permutation.
++permutationMapper.PermutationIndex;
}
//Integrate the positions of the bones forward.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdatePosition();
}
}
//Clear out the control iteration accumulated impulses; they should not persist through to the fixer iterations since the stresses are (potentially) totally different.
//This just helps stability in some corner cases. Without clearing this, previous high stress would prime the fixer iterations with bad guesses,
//making the system harder to solve (i.e. introducing instability and requiring more iterations).
for (int j = 0; j < ActiveSet.joints.Count; j++)
{
ActiveSet.joints[j].ClearAccumulatedImpulses();
}
//The previous loop may still have significant errors in the active joints due to
//unreachable targets. Run a secondary pass without the influence of the controls to
//fix the errors without interference from impossible goals
//This can potentially cause the bones to move away from the control targets, but with a sufficient
//number of control iterations, the result is generally a good approximation.
for (int i = 0; i < FixerIterationCount; i++)
{
//Update the world inertia tensors of objects for the latest position.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdateInertiaTensor();
}
//Update the per-constraint jacobians and effective mass for the current bone orientations and positions.
foreach (IKJoint joint in ActiveSet.joints)
{
joint.UpdateJacobiansAndVelocityBias();
joint.ComputeEffectiveMass();
joint.WarmStart();
}
for (int j = 0; j < VelocitySubiterationCount; j++)
{
//A permuted version of the indices is used. The randomization tends to avoid issues with solving order in corner cases.
for (int jointIndex = 0; jointIndex < ActiveSet.joints.Count; ++jointIndex)
{
int remappedIndex = permutationMapper.GetMappedIndex(jointIndex, ActiveSet.joints.Count);
ActiveSet.joints[remappedIndex].SolveVelocityIteration();
}
//Increment to use the next permutation.
++permutationMapper.PermutationIndex;
}
//Integrate the positions of the bones forward.
foreach (Bone bone in ActiveSet.bones)
{
bone.UpdatePosition();
}
}
//Clear out accumulated impulses; they should not persist through to another solving round because the state could be arbitrarily different.
for (int j = 0; j < ActiveSet.joints.Count; j++)
{
ActiveSet.joints[j].ClearAccumulatedImpulses();
}
foreach (Control control in controls)
{
control.ClearAccumulatedImpulses();
}
}
~IKSolver()
{
Dispose();
}
public void Dispose()
{
ActiveSet.Dispose();
}
}
}