Some tidying up
[opengl.git] / ik.cpp
1 #include "ik.hpp"
2 #include "util.hpp"
3
4 using namespace glm;
5
6 constexpr float tolerance = 0.001;
7
8 const std::vector<glm::vec3> fabrik(const glm::vec3 t,
9                 const std::vector<glm::vec3> jpsIn, // joint positions
10                 const std::vector<float> jds // distances between each joint
11                 ) {
12         size_t N = jpsIn.size();
13         assert(N == jds.size() + 1);
14         std::vector<glm::vec3> jps = jpsIn;
15
16         float dist = distance(jps[0], t);
17         float totalLength = 0;
18         for (int i = 0; i < N - 1; i++) totalLength += jds[i];
19         if (dist > totalLength) { // target is unreachable
20                 for (int i = 0; i < N - 1; i++) {
21                         float r = distance(t, jps[i]);
22                         float lambda = jds[i] / r;
23                         jps[i + 1] = (1.f - lambda) * jps[i] + lambda * t;
24                 }
25         } else { // target is reachable
26                 glm::vec3 b = jps[0];
27                 // distance between end effector and target
28                 float diff = distance(jps[N - 1], t);
29                 while (diff > tolerance) {
30                         // forward reaching
31                         jps[N - 1] = t; // set end effector to target
32                         for (int i = N - 2; i >= 0; i--) {
33                                 float r = distance(jps[i + 1], jps[i]);
34                                 float lambda = jds[i] / r;
35                                 jps[i] = (1.f - lambda) * jps[i + 1] + lambda * jps[i];
36                         }
37
38                         // backward reaching
39                         jps[0] = b; // reset root to initial pos
40                         for (int i = 0; i < N - 1; i++) {
41                                 float r = distance(jps[i + 1], jps[i]);
42                                 float lambda = jds[i] / r;
43                                 jps[i + 1] = (1 - lambda) * jps[i] + lambda * jps[i + 1];
44                         }
45
46                         float newDiff = distance(jps[N - 1], t);
47                         if (newDiff == diff) abort();
48                         diff = newDiff;
49                 }
50         }
51         return jps;
52 }
53
54 std::vector<Model::Node> allNodesTo(const Model::Node &from, const Model::Node &to) {
55         if (from == to) return { to };
56         assert(to.parent != nullptr);
57         auto res = allNodesTo(from, *to.parent);
58         res.push_back(to);
59         return res;
60 }
61
62 mat4 getAbsTrans(const Model::Node &root, const Model::Node &n) {
63         if (root.ai.mName == n.ai.mName)
64                 return mat4(1); //aiMatrixToMat4(n.ai.mTransformation);
65         assert(n.parent != nullptr);
66         return getAbsTrans(root, *n.parent) * aiMatrixToMat4(n.ai.mTransformation);
67 }
68
69 vec3 extractPos(mat4 trans) {
70         return vec3(trans[3]);
71 }
72
73 glm::mat4 absoluteToModelSpace(const Model::Node &root, const Model::Node &n, mat4 m) {
74         const Model::Node *parent = &n;
75         glm::mat4 res = m;
76         std::vector<mat4> trans;
77         while (&parent->ai != &root.ai) {
78                 trans.push_back(inverse(aiMatrixToMat4(parent->ai.mTransformation)));
79                 parent = parent->parent;
80         }
81         while (!trans.empty()) { res = trans.back() * res; trans.pop_back(); }
82         return res;
83 }
84
85 // Given points u and v on a sphere, calculate the transformation matrix
86 // to bring u -> v
87 // from https://math.stackexchange.com/a/2765250
88 mat4 getRotationToPoint(vec3 u, vec3 v, float dist) {
89         if (distance(u, v) < 0.00001) return mat4(1);
90         vec3 n = cross(u, v) / length(cross(u, v));
91         vec3 t = cross(n, u);
92         float alpha = atan2(dot(v, t), dot(v, u));
93         mat4 T = mat4(mat3(u, t, n));
94         mat4 R = rotate(mat4(1), alpha, {0, 0, 1}); // rotation in z axis
95         mat4 res = T * R * inverse(T);
96         return res;
97 }
98
99 float d = 0;
100
101 // Target is world position
102 void inverseKinematic(Model::Node &start, Model::Node &end, vec3 target) {
103         std::vector<Model::Node> chain = allNodesTo(start, end);
104         assert(!chain.empty());
105
106         const Model::Node *rootPtr = start.parent;
107         while (rootPtr->parent != nullptr)
108                 rootPtr = rootPtr->parent;
109         const Model::Node &root = *rootPtr;
110
111         std::vector<vec3> positions(chain.size()); std::vector<float> distances(chain.size() - 1);
112         for (size_t i = 0; i < chain.size(); i++) {
113                 mat4 absTrans = getAbsTrans(root, chain[i]);
114                 positions[i] = extractPos(absTrans);
115                 if (i > 0)
116                         distances[i - 1] = distance(positions[i], positions[i - 1]);
117         }
118
119         auto newPositions = fabrik(target, positions, distances);
120
121
122         
123         // Move all the nodes so that they are in the correct positions
124         // Don't need to move the root node - it's already in place
125         for (size_t i = 1; i < chain.size(); i++) {
126                 auto node = chain[i];
127                 mat4 absTrans = getAbsTrans(root, node);
128                 absTrans[3] = vec4(newPositions[i], absTrans[3][3]); // update position in transform
129
130                 /* vec3 oldRelPos = extractPos(aiMatrixToMat4(node.ai.mTransformation)); */
131                 /* vec3 newRelPos = extractPos(absoluteToModelSpace(root, *node.parent, absTrans)); */
132                 /* mat4 rot = getRotationToPoint(oldRelPos, newRelPos, distances[i - 1]); */
133                 /* node.ai.mTransformation = mat4ToaiMatrix(rot * aiMatrixToMat4(node.ai.mTransformation)); */
134
135                 absTrans[3] = vec4(newPositions[i], absTrans[3][3]); // update position in transform
136                 
137                 mat4 relTrans = absoluteToModelSpace(root, *node.parent, absTrans);
138                 node.ai.mTransformation = mat4ToaiMatrix(relTrans);
139         }
140
141         // Now rotate all the nodes so that they point towards each other
142         // Don't need to rotate the last node - it has nothing to point towards
143         // FIXME: This is numerically unstable and the transformation scales over time!!!
144         for (size_t i = 0; i < chain.size() - 1; i++) {
145                 auto node = chain[i]; auto nextNode = chain[i + 1];
146                 mat4 oldTrans = aiMatrixToMat4(node.ai.mTransformation);
147                 vec3 nextNodePos = extractPos(aiMatrixToMat4(nextNode.ai.mTransformation));
148
149
150                 vec3 up = {0, 1, 0};
151                 vec3 dir = -normalize(nextNodePos);
152
153                 vec3 v = cross(up, dir);
154                 mat3 sscpm = mat3(0, -v[2], v[1],
155                                                   v[2], 0, -v[0],
156                                                   -v[1], v[0], 0);
157                 mat4 rot = mat3(1) + sscpm + sscpm * sscpm * (1.f / 1.f + dot(up, dir));
158
159
160                 node.ai.mTransformation = mat4ToaiMatrix(oldTrans * rot);
161
162                 for (auto child: node.getChildren()) {
163                         child->ai.mTransformation = mat4ToaiMatrix(inverse(rot) * aiMatrixToMat4(child->ai.mTransformation));
164                 }
165
166         }
167
168 }
169