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(¤tAngles, &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, ¤tAngles);
}
return needsClamp;
}
|