Blender V5.0
IK_QJacobianSolver.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2001-2002 NaN Holding BV. All rights reserved.
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include <algorithm>
10#include <cstdio>
11
12#include "IK_QJacobianSolver.h"
13
14// #include "analyze.h"
16{
17 m_poleconstraint = false;
18 m_getpoleangle = false;
19 m_rootmatrix.setIdentity();
20}
21
22double IK_QJacobianSolver::ComputeScale()
23{
24 std::vector<IK_QSegment *>::iterator seg;
25 double length = 0.0f;
26
27 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
28 length += (*seg)->MaxExtension();
29 }
30
31 if (length == 0.0) {
32 return 1.0;
33 }
34 return 1.0 / length;
35}
36
37void IK_QJacobianSolver::Scale(double scale, std::list<IK_QTask *> &tasks)
38{
39 std::list<IK_QTask *>::iterator task;
40 std::vector<IK_QSegment *>::iterator seg;
41
42 for (task = tasks.begin(); task != tasks.end(); task++) {
43 (*task)->Scale(scale);
44 }
45
46 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
47 (*seg)->Scale(scale);
48 }
49
50 m_rootmatrix.translation() *= scale;
51 m_goal *= scale;
52 m_polegoal *= scale;
53}
54
55void IK_QJacobianSolver::AddSegmentList(IK_QSegment *seg)
56{
57 m_segments.push_back(seg);
58
59 IK_QSegment *child;
60 for (child = seg->Child(); child; child = child->Sibling()) {
61 AddSegmentList(child);
62 }
63}
64
65bool IK_QJacobianSolver::Setup(IK_QSegment *root, std::list<IK_QTask *> &tasks)
66{
67 m_segments.clear();
68 AddSegmentList(root);
69
70 // assign each segment a unique id for the jacobian
71 std::vector<IK_QSegment *>::iterator seg;
72 int num_dof = 0;
73
74 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
75 (*seg)->SetDoFId(num_dof);
76 num_dof += (*seg)->NumberOfDoF();
77 }
78
79 if (num_dof == 0) {
80 return false;
81 }
82
83 // compute task ids and assign weights to task
84 int primary_size = 0;
85 int secondary_size = 0, secondary = 0;
86 double primary_weight = 0.0, secondary_weight = 0.0;
87 std::list<IK_QTask *>::iterator task;
88
89 for (task = tasks.begin(); task != tasks.end(); task++) {
90 IK_QTask *qtask = *task;
91
92 if (qtask->Primary()) {
93 qtask->SetId(primary_size);
94 primary_size += qtask->Size();
95 primary_weight += qtask->Weight();
96 }
97 else {
98 qtask->SetId(secondary_size);
99 secondary_size += qtask->Size();
100 secondary_weight += qtask->Weight();
101 secondary++;
102 }
103 }
104
105 if (primary_size == 0 || FuzzyZero(primary_weight)) {
106 return false;
107 }
108
109 m_secondary_enabled = (secondary > 0);
110
111 // rescale weights of tasks to sum up to 1
112 double primary_rescale = 1.0 / primary_weight;
113 double secondary_rescale;
114 if (FuzzyZero(secondary_weight)) {
115 secondary_rescale = 0.0;
116 }
117 else {
118 secondary_rescale = 1.0 / secondary_weight;
119 }
120
121 for (task = tasks.begin(); task != tasks.end(); task++) {
122 IK_QTask *qtask = *task;
123
124 if (qtask->Primary()) {
125 qtask->SetWeight(qtask->Weight() * primary_rescale);
126 }
127 else {
128 qtask->SetWeight(qtask->Weight() * secondary_rescale);
129 }
130 }
131
132 // set matrix sizes
133 m_jacobian.ArmMatrices(num_dof, primary_size);
134 if (secondary > 0) {
135 m_jacobian_sub.ArmMatrices(num_dof, secondary_size);
136 }
137
138 // set dof weights
139 int i;
140
141 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
142 for (i = 0; i < (*seg)->NumberOfDoF(); i++) {
143 m_jacobian.SetDoFWeight((*seg)->DoFId() + i, (*seg)->Weight(i));
144 }
145 }
146
147 return true;
148}
149
151 IK_QSegment *tip, Vector3d &goal, Vector3d &polegoal, float poleangle, bool getangle)
152{
153 m_poleconstraint = true;
154 m_poletip = tip;
155 m_goal = goal;
156 m_polegoal = polegoal;
157 m_poleangle = (getangle) ? 0.0f : poleangle;
158 m_getpoleangle = getangle;
159}
160
161void IK_QJacobianSolver::ConstrainPoleVector(IK_QSegment *root, std::list<IK_QTask *> &tasks)
162{
163 // this function will be called before and after solving. calling it before
164 // solving gives predictable solutions by rotating towards the solution,
165 // and calling it afterwards ensures the solution is exact.
166
167 if (!m_poleconstraint) {
168 return;
169 }
170
171 // disable pole vector constraint in case of multiple position tasks
172 std::list<IK_QTask *>::iterator task;
173 int positiontasks = 0;
174
175 for (task = tasks.begin(); task != tasks.end(); task++) {
176 if ((*task)->PositionTask()) {
177 positiontasks++;
178 }
179 }
180
181 if (positiontasks >= 2) {
182 m_poleconstraint = false;
183 return;
184 }
185
186 // get positions and rotations
187 root->UpdateTransform(m_rootmatrix);
188
189 const Vector3d rootpos = root->GlobalStart();
190 const Vector3d endpos = m_poletip->GlobalEnd();
191 const Matrix3d &rootbasis = root->GlobalTransform().linear();
192
193 // construct "lookat" matrices (like gluLookAt), based on a direction and
194 // an up vector, with the direction going from the root to the end effector
195 // and the up vector going from the root to the pole constraint position.
196 Vector3d dir = normalize(endpos - rootpos);
197 Vector3d rootx = rootbasis.col(0);
198 Vector3d rootz = rootbasis.col(2);
199 Vector3d up = rootx * cos(m_poleangle) + rootz * sin(m_poleangle);
200
201 // in post, don't rotate towards the goal but only correct the pole up
202 Vector3d poledir = (m_getpoleangle) ? dir : normalize(m_goal - rootpos);
203 Vector3d poleup = normalize(m_polegoal - rootpos);
204
205 Matrix3d mat, polemat;
206
207 mat.row(0) = normalize(dir.cross(up));
208 mat.row(1) = mat.row(0).cross(dir);
209 mat.row(2) = -dir;
210
211 polemat.row(0) = normalize(poledir.cross(poleup));
212 polemat.row(1) = polemat.row(0).cross(poledir);
213 polemat.row(2) = -poledir;
214
215 if (m_getpoleangle) {
216 // we compute the pole angle that to rotate towards the target
217 m_poleangle = angle(mat.row(1), polemat.row(1));
218
219 double dt = rootz.dot(mat.row(1) * cos(m_poleangle) + mat.row(0) * sin(m_poleangle));
220 if (dt > 0.0) {
221 m_poleangle = -m_poleangle;
222 }
223
224 // solve again, with the pole angle we just computed
225 m_getpoleangle = false;
226 ConstrainPoleVector(root, tasks);
227 }
228 else {
229 // now we set as root matrix the difference between the current and
230 // desired rotation based on the pole vector constraint. we use
231 // transpose instead of inverse because we have orthogonal matrices
232 // anyway, and in case of a singular matrix we don't get NaN's.
233 Affine3d trans;
234 trans.linear() = polemat.transpose() * mat;
235 trans.translation() = Vector3d(0, 0, 0);
236 m_rootmatrix = trans * m_rootmatrix;
237 }
238}
239
240bool IK_QJacobianSolver::UpdateAngles(double &norm)
241{
242 // assign each segment a unique id for the jacobian
243 std::vector<IK_QSegment *>::iterator seg;
244 IK_QSegment *qseg, *minseg = nullptr;
245 double minabsdelta = 1e10, absdelta;
246 Vector3d delta, mindelta;
247 bool locked = false, clamp[3];
248 int i, mindof = 0;
249
250 // here we check if any angle limits were violated. angles whose clamped
251 // position is the same as it was before, are locked immediate. of the
252 // other violation angles the most violating angle is rememberd
253 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
254 qseg = *seg;
255 if (qseg->UpdateAngle(m_jacobian, delta, clamp)) {
256 for (i = 0; i < qseg->NumberOfDoF(); i++) {
257 if (clamp[i] && !qseg->Locked(i)) {
258 absdelta = fabs(delta[i]);
259
260 if (absdelta < IK_EPSILON) {
261 qseg->Lock(i, m_jacobian, delta);
262 locked = true;
263 }
264 else if (absdelta < minabsdelta) {
265 minabsdelta = absdelta;
266 mindelta = delta;
267 minseg = qseg;
268 mindof = i;
269 }
270 }
271 }
272 }
273 }
274
275 // lock most violating angle
276 if (minseg) {
277 minseg->Lock(mindof, m_jacobian, mindelta);
278 locked = true;
279
280 norm = std::max(minabsdelta, norm);
281 }
282
283 if (locked == false) {
284 // no locking done, last inner iteration, apply the angles
285 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
286 (*seg)->UnLock();
287 (*seg)->UpdateAngleApply();
288 }
289 }
290
291 // signal if another inner iteration is needed
292 return locked;
293}
294
296 std::list<IK_QTask *> tasks,
297 const double /*tolerance*/,
298 const int max_iterations)
299{
300 float scale = ComputeScale();
301 bool solved = false;
302 // double dt = analyze_time();
303
304 Scale(scale, tasks);
305
306 ConstrainPoleVector(root, tasks);
307
308 root->UpdateTransform(m_rootmatrix);
309
310 // iterate
311 for (int iterations = 0; iterations < max_iterations; iterations++) {
312 // update transform
313 root->UpdateTransform(m_rootmatrix);
314
315 std::list<IK_QTask *>::iterator task;
316
317 // compute jacobian
318 for (task = tasks.begin(); task != tasks.end(); task++) {
319 if ((*task)->Primary()) {
320 (*task)->ComputeJacobian(m_jacobian);
321 }
322 else {
323 (*task)->ComputeJacobian(m_jacobian_sub);
324 }
325 }
326
327 double norm = 0.0;
328
329 do {
330 // invert jacobian
331 try {
332 m_jacobian.Invert();
333 if (m_secondary_enabled) {
334 m_jacobian.SubTask(m_jacobian_sub);
335 }
336 }
337 catch (...) {
338 fprintf(stderr, "IK Exception\n");
339 return false;
340 }
341
342 // update angles and check limits
343 } while (UpdateAngles(norm));
344
345 // unlock segments again after locking in clamping loop
346 std::vector<IK_QSegment *>::iterator seg;
347 for (seg = m_segments.begin(); seg != m_segments.end(); seg++) {
348 (*seg)->UnLock();
349 }
350
351 // compute angle update norm
352 double maxnorm = m_jacobian.AngleUpdateNorm();
353 norm = std::max(maxnorm, norm);
354
355 // check for convergence
357 solved = true;
358 break;
359 }
360 }
361
362 if (m_poleconstraint) {
363 root->PrependBasis(m_rootmatrix.linear());
364 }
365
366 Scale(1.0f / scale, tasks);
367
368 // analyze_add_run(max_iterations, analyze_time()-dt);
369
370 return solved;
371}
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
static const double IK_EPSILON
Definition IK_Math.h:23
static bool FuzzyZero(double x)
Definition IK_Math.h:25
SIMD_FORCE_INLINE btScalar norm() const
Return the norm (length) of the vector.
Definition btVector3.h:263
SIMD_FORCE_INLINE btScalar length() const
Return the length of the vector.
Definition btVector3.h:257
void SetPoleVectorConstraint(IK_QSegment *tip, Vector3d &goal, Vector3d &polegoal, float poleangle, bool getangle)
bool Solve(IK_QSegment *root, std::list< IK_QTask * > tasks, const double tolerance, const int max_iterations)
bool Setup(IK_QSegment *root, std::list< IK_QTask * > &tasks)
int NumberOfDoF() const
Definition IK_QSegment.h:75
const Affine3d & GlobalTransform() const
virtual void Lock(int, IK_QJacobian &, Vector3d &)
virtual bool UpdateAngle(const IK_QJacobian &, Vector3d &, bool *)=0
bool Locked(int dof) const
void UpdateTransform(const Affine3d &global)
Vector3d GlobalStart() const
void PrependBasis(const Matrix3d &mat)
IK_QSegment * Child() const
Definition IK_QSegment.h:51
IK_QSegment * Sibling() const
Definition IK_QSegment.h:56
void SetId(int id)
Definition IK_QTask.h:25
void SetWeight(double weight)
Definition IK_QTask.h:50
double Weight() const
Definition IK_QTask.h:45
bool Primary() const
Definition IK_QTask.h:35
int Size() const
Definition IK_QTask.h:30
#define sin
#define cos
VecBase< float, D > normalize(VecOp< float, D >) RET
constexpr T clamp(T, U, U) RET
float length(VecOp< float, D >) RET
ccl_device_inline float2 fabs(const float2 a)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
i
Definition text_draw.cc:230