File: ik_solver.cpp

package info (click to toggle)
freespace2 24.0.2%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: trixie
  • size: 43,188 kB
  • sloc: cpp: 583,107; ansic: 21,729; python: 1,174; sh: 464; makefile: 248; xml: 181
file content (220 lines) | stat: -rw-r--r-- 8,281 bytes parent folder | download
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
#include "math/ik_solver.h"

ik_solver_fabrik::ik_solver_fabrik(unsigned int maxIterations, float targetDistance, float minProgress)
	: m_maxIterations(maxIterations), m_targetDistance(targetDistance), m_minProgress(minProgress) { }

void ik_solver_fabrik::solve(const vec3d& targetPos, const matrix* targetOrient) {
	//Implementation of the paper "FABRIK: A fast iterative solver for the Inverse Kinematics problem"
	//http://andreasaristidou.com/publications/papers/FABRIK.pdf
	
	Assertion(m_nodes.size() > 1, "Node-list for IK does not contain at least two nodes.");
	
	//Calculate distances and set up nodes
	for(size_t i = 1; i < m_nodes.size(); ++i){
		//This assumes that m_nodes[n] is _always_ a direct child of m_nodes[n-1]
		m_nodes[i - 1].distance = vm_vec_mag(&m_nodes[i].submodel->offset);
		m_nodes[i].calculatedPos = m_nodes[i - 1].calculatedPos + m_nodes[i].submodel->offset;
	}
	
	//Abort condition counter
	unsigned int iterationCounter = 0;
	float endEffectorDistance = FLT_MAX;
	float endEffectorDistanceDelta = FLT_MAX;
	
	while(iterationCounter++ < m_maxIterations && endEffectorDistance > m_targetDistance && endEffectorDistanceDelta > m_minProgress) {
		endEffectorDistanceDelta = endEffectorDistance;

		for(auto& node : m_nodes)
			node.lastPos = node.calculatedPos;
		
		m_nodes.back().calculatedPos = targetPos;
		
		//Backwards pass
		for(size_t i = m_nodes.size() - 2; i != static_cast<size_t>(-1); --i) {
			auto& child = m_nodes[i + 1];
			auto& parent = m_nodes[i];
			
			// Find new position for parent
			float localDistance = vm_vec_dist(&parent.calculatedPos, &child.calculatedPos);
			float lambda = parent.distance / localDistance;

			parent.calculatedPos *= lambda;
			parent.calculatedPos += child.calculatedPos * (1.0f - lambda);

			// Find the parent's global rotation based on parent-child combination
			parent.calculateGlobalRotation(child);
			
			if(i == m_nodes.size() - 2){
				//Now that we know where the first parent sits, we can update the end effector rotation.
				//If we have a target rotation, point to it, otherwise take its parent's rotation
				child.calculatedRot = targetOrient == nullptr ? parent.calculatedRot : *targetOrient;
			}
			
			// Find local rotation of child for constraint
			matrix localRot;
			vm_copy_transpose(&localRot, &parent.calculatedRot);
			vm_matrix_x_matrix(&localRot, &child.calculatedRot, &localRot);

			// Check if constraint requires change of local rotation
			if(child.constraint->constrain(localRot, true)){
				// Reposition parent to fulfill child local rotation constraint by changing parent global rotation
				
				// Convert new local rotation of child and global rotation of child to new parent global rotation
				vm_transpose(&localRot);
				vm_matrix_x_matrix(&parent.calculatedRot, &localRot, &child.calculatedRot);

				// Find new parent position from new parent global rotation, child offset and child position
				vm_vec_unrotate(&parent.calculatedPos, &child.submodel->offset, &parent.calculatedRot);
				parent.calculatedPos *= -1;
				parent.calculatedPos += child.calculatedPos;
			}
		}
		
		//Technically the base node would now need to be rotation constrained, but since the base position is reset anyways for the forwards pass with a fixed base, this has no effect
		
		m_nodes.front().calculatedPos = ZERO_VECTOR;
		m_nodes.front().calculatedRot = IDENTITY_MATRIX;

		//Forwards pass
		for(size_t i = 0; i < m_nodes.size() - 1; ++i) {
			auto& child = m_nodes[i + 1];
			auto& parent = m_nodes[i];
			
			// Find new position for child
			float localDistance = vm_vec_dist(&parent.calculatedPos, &child.calculatedPos);
			float lambda = parent.distance / localDistance;

			child.calculatedPos *= lambda;
			child.calculatedPos += parent.calculatedPos * (1.0f - lambda);
			
			// Find the parent's global rotation based on parent-child combination
			parent.calculateGlobalRotation(child);
			
			// Find local rotation of parent for constraint
			const matrix& parentRot = i == 0 ? vmd_identity_matrix : m_nodes[i - 1].calculatedRot;
			matrix localRot;
			vm_copy_transpose(&localRot, &parentRot);
			vm_matrix_x_matrix(&localRot, &parent.calculatedRot, &localRot);
			
			// Check if constraint requires change of local rotation
			if(parent.constraint->constrain(localRot)){
				// Reposition child to fulfill parent local rotation constraint by changing parent global rotation
				
				// Convert new local rotation and global rotation of tha parent's parent to new parent global rotation
				vm_matrix_x_matrix(&parent.calculatedRot, &localRot, &parentRot);
				
				// Find new child position from new parent global rotation, child offset and parent position
				vm_vec_unrotate(&child.calculatedPos, &child.submodel->offset, &parent.calculatedRot);
				child.calculatedPos += parent.calculatedPos;
			}
		}
		
		//After positioning the end effector, it now needs to be oriented correctly. If we have a target orientation use it, otherwise assume its parents rotation. Then constrain.
		{
			const matrix& parentRot = m_nodes[m_nodes.size() - 2].calculatedRot;
			// Find end effector target
			m_nodes.back().calculatedRot = targetOrient == nullptr ? parentRot : *targetOrient;
			
			// Calculate end effector local rotation
			matrix localRot;
			vm_copy_transpose(&localRot, &parentRot);
			vm_matrix_x_matrix(&localRot, &m_nodes.back().calculatedRot, &localRot);
			
			// Constrain
			if(m_nodes.back().constraint->constrain(localRot)) {
				// Calculate new global end effector rotation
				vm_matrix_x_matrix(&localRot, &localRot, &parentRot);
				m_nodes.back().calculatedRot = localRot;
			}
		}
		
		// Calculate abort conditions
		endEffectorDistance = vm_vec_dist(&targetPos, &m_nodes.back().calculatedPos);
		endEffectorDistanceDelta -= endEffectorDistance;
	}
	
	if (endEffectorDistanceDelta < 0.0f) {
		// Solution got worse, likely a singularity from hinges or something. Restore last good solution
		for(auto& node : m_nodes)
			node.calculatedPos = node.lastPos;

		for(size_t i = 0; i < m_nodes.size() - 1; ++i) {
			m_nodes[i].calculateGlobalRotation(m_nodes[i + 1]);
		}
	}
	
	//Localize rotations
	for(size_t i = m_nodes.size() - 1; i > 0; --i) {
		matrix parent;
		vm_copy_transpose(&parent, &m_nodes[i - 1].calculatedRot);
		vm_matrix_x_matrix(&m_nodes[i].calculatedRot, &parent, &m_nodes[i].calculatedRot);
	}
}

void ik_node::calculateGlobalRotation(const ik_node& child) {
	vec3d rotax;
	vec3d n_off;
	vec3d n_diff = child.calculatedPos - calculatedPos;
	vm_vec_copy_normalize(&n_off, &child.submodel->offset);
	vm_vec_normalize(&n_diff);
	
	vec3d checkSafeTransform = n_diff - n_off;
	if(vm_vec_mag(&checkSafeTransform) <= 0.001){
		calculatedRot = IDENTITY_MATRIX;
		return;
	}
	
	vm_vec_cross(&rotax, &n_off, &n_diff);
	vm_vec_normalize(&rotax);

	vm_quaternion_rotate(&calculatedRot, acosf(vm_vec_dot(&n_diff, &n_off)), &rotax);
}

static constexpr float angles::*pbh[] = { &angles::p, &angles::b, &angles::h };

ik_constraint_hinge::ik_constraint_hinge(const vec3d& _axis) : axis(_axis) { }

bool ik_constraint_hinge::constrain(matrix& localRot, bool /*backwardsPass*/) const {
	float angle;
	
	float distance = vm_closest_angle_to_matrix(&localRot, &axis, &angle);
	
	// Tolerance for off-axis.
	if(distance < fl_radians(0.5f)){
		return false;
	}

	/*if(backwardsPass){
		 TODO: If Hinge joints cause singularities in FABRIK, this might need random perturbations on the backwards pass
	}*/
	
	vm_quaternion_rotate(&localRot, angle, &axis);
	
	return true;
}

ik_constraint_window::ik_constraint_window(const angles& _absLimit) : absLimit(_absLimit) { }

bool ik_constraint_window::constrain(matrix& localRot, bool /*backwardsPass*/) const {
	//Convert to angles
	angles currentAngles;
	vm_extract_angles_matrix_alternate(&currentAngles, &localRot);
	
	bool needsClamp = false;
	
	//Clamp absolute value of individual angles to window
	for (float angles::* i : pbh) {
		const float absAngle = abs(currentAngles.*i);
		if(absAngle > absLimit.*i){
			needsClamp = true;
			currentAngles.*i = copysignf(std::min(absAngle, absLimit.*i), currentAngles.*i);
		}
	}
	
	//If any axis needed clamping, recalc the local rotation
	if(needsClamp){
		vm_angles_2_matrix(&localRot, &currentAngles);
	}
	
	return needsClamp;
}