44 halp_meta(name,
"Inverse Kinematics (2-bone)")
45 halp_meta(category,
"Visuals/3D/Scene")
46 halp_meta(c_name,
"inverse_kinematics")
47 halp_meta(authors,
"ossia team")
50 "https://ossia.io/score-docs/processes/inverse-kinematics.html")
51 halp_meta(uuid,
"6e9f2a4c-1b85-4d3e-a7f6-8c2b4d5e9a0f")
57 halp_meta(name,
"Scene In");
58 ossia::scene_spec scene;
62 halp::lineedit<
"End joint name",
"hand_r"> end_joint;
64 halp::xyz_spinboxes_f32<
66 halp::range{-10000., 10000., 0.}>
68 halp::xyz_spinboxes_f32<
70 halp::range{-10000., 10000., 0.}>
73 halp::hslider_f32<
"Weight", halp::range{0., 1., 1.}> weight;
80 halp_meta(name,
"Scene Out");
81 ossia::scene_spec scene;
86 std::shared_ptr<ossia::scene_state> m_state;
89 static QVector3D toVec(
const float v[3]) {
return QVector3D(v[0], v[1], v[2]); }
90 static QQuaternion toQuat(
const float v[4])
92 return QQuaternion(v[3], v[0], v[1], v[2]);
94 static void fromQuat(
float v[4],
const QQuaternion& q)
96 v[0] = q.x(); v[1] = q.y(); v[2] = q.z(); v[3] = q.scalar();
101 static QVector3D worldJointPos(
102 const ossia::skeleton_component& skel, int32_t idx)
104 if(idx < 0 || idx >= (int32_t)skel.joints.size())
108 ossia::small_vector<int32_t, 16> chain;
109 for(int32_t i = idx; i >= 0; i = skel.joints[i].parent_index)
111 std::reverse(chain.begin(), chain.end());
114 for(int32_t i : chain)
116 const auto& j = skel.joints[i];
118 T.translate(j.translation[0], j.translation[1], j.translation[2]);
119 T.rotate(QQuaternion(
120 j.rotation[3], j.rotation[0], j.rotation[1], j.rotation[2]));
121 T.scale(j.scale[0], j.scale[1], j.scale[2]);
124 return M.map(QVector3D());
132 QQuaternion rootDelta;
133 QQuaternion midDelta;
136 QVector3D root, QVector3D mid, QVector3D end,
137 QVector3D target, QVector3D pole)
139 const float eps = 1e-6f;
140 QVector3D r2m = mid - root;
141 QVector3D m2e = end - mid;
142 QVector3D r2e = end - root;
143 QVector3D r2t = target - root;
145 const float lA = r2m.length();
146 const float lB = m2e.length();
147 const float lTgt = std::min(r2t.length(), lA + lB - eps);
148 if(lA < eps || lB < eps || lTgt < eps)
149 return {QQuaternion(), QQuaternion()};
153 const float cosNew = std::clamp(
154 (lA * lA + lB * lB - lTgt * lTgt) / (2.0f * lA * lB), -1.0f, 1.0f);
155 const float thetaNew = std::acos(cosNew);
158 const float cosCur = std::clamp(
159 QVector3D::dotProduct(-r2m.normalized(), m2e.normalized()),
161 const float thetaCur = std::acos(cosCur);
165 QVector3D planeNormal = QVector3D::crossProduct(r2m, m2e);
166 if(planeNormal.lengthSquared() < eps)
169 QVector3D poleDir = (pole - root).normalized();
170 planeNormal = QVector3D::crossProduct(r2e.normalized(), poleDir);
171 if(planeNormal.lengthSquared() < eps)
172 planeNormal = QVector3D(0, 1, 0);
174 planeNormal.normalize();
176 QQuaternion elbowDelta = QQuaternion::fromAxisAndAngle(
177 planeNormal, (thetaCur - thetaNew) * 180.0f /
float(M_PI));
187 QVector3D newEnd = mid + elbowDelta.rotatedVector(m2e);
188 QVector3D r2t_n = r2t.normalized();
189 QVector3D r2e_n = (newEnd - root).normalized();
190 QQuaternion rootDelta = QQuaternion::rotationTo(r2e_n, r2t_n);
192 return {rootDelta, elbowDelta};
197 const auto& in = inputs.scene_in.scene;
198 if(!in.state || !in.state->roots)
200 outputs.scene_out.scene.state.reset();
201 outputs.scene_out.dirty = 0;
206 const ossia::skeleton_component* srcSkel =
nullptr;
207 if(in.state->skeletons && !in.state->skeletons->empty())
208 srcSkel = (*in.state->skeletons)[0].get();
209 if(!srcSkel || srcSkel->joints.empty())
211 outputs.scene_out.scene = in;
212 outputs.scene_out.dirty = 0;
216 const std::string endName = inputs.end_joint.value;
217 int32_t endIdx = srcSkel->find_joint(endName);
218 if(endIdx < 0 || srcSkel->joints[endIdx].parent_index < 0)
220 outputs.scene_out.scene = in;
221 outputs.scene_out.dirty = 0;
224 const int32_t midIdx = srcSkel->joints[endIdx].parent_index;
225 if(srcSkel->joints[midIdx].parent_index < 0)
227 outputs.scene_out.scene = in;
228 outputs.scene_out.dirty = 0;
231 const int32_t rootIdx = srcSkel->joints[midIdx].parent_index;
234 QVector3D wRoot = worldJointPos(*srcSkel, rootIdx);
235 QVector3D wMid = worldJointPos(*srcSkel, midIdx);
236 QVector3D wEnd = worldJointPos(*srcSkel, endIdx);
239 inputs.target.value.x, inputs.target.value.y, inputs.target.value.z);
241 inputs.pole.value.x, inputs.pole.value.y, inputs.pole.value.z);
243 Solution sol = solve2Bone(wRoot, wMid, wEnd, target, pole);
246 const float w = std::clamp(inputs.weight.value, 0.0f, 1.0f);
249 outputs.scene_out.scene = in;
250 outputs.scene_out.dirty = 0;
253 QQuaternion rootDelta = QQuaternion::slerp(QQuaternion(), sol.rootDelta, w);
254 QQuaternion midDelta = QQuaternion::slerp(QQuaternion(), sol.midDelta, w);
258 auto newSkel = std::make_shared<ossia::skeleton_component>(*srcSkel);
262 auto worldRotOf = [&](int32_t idx) {
264 for(int32_t i = idx; i >= 0; i = srcSkel->joints[i].parent_index)
267 srcSkel->joints[i].rotation[3],
268 srcSkel->joints[i].rotation[0],
269 srcSkel->joints[i].rotation[1],
270 srcSkel->joints[i].rotation[2]);
275 QQuaternion parentRoot = srcSkel->joints[rootIdx].parent_index >= 0
276 ? worldRotOf(srcSkel->joints[rootIdx].parent_index)
278 QQuaternion parentMid = worldRotOf(rootIdx);
280 QQuaternion rootLocalNew
281 = parentRoot.inverted() * rootDelta * parentRoot
282 * toQuat(srcSkel->joints[rootIdx].rotation);
283 QQuaternion midLocalNew
284 = parentMid.inverted() * midDelta * parentMid
285 * toQuat(srcSkel->joints[midIdx].rotation);
287 fromQuat(newSkel->joints[rootIdx].rotation, rootLocalNew);
288 fromQuat(newSkel->joints[midIdx].rotation, midLocalNew);
289 newSkel->dirty_index++;
293 if(!m_state || m_state->version != in.state->version - 1)
294 m_state = std::make_shared<ossia::scene_state>(*in.state);
296 *m_state = *in.state;
298 auto skels = std::make_shared<std::vector<ossia::skeleton_component_ptr>>();
299 if(in.state->skeletons)
300 *skels = *in.state->skeletons;
302 skels->push_back(newSkel);
304 (*skels)[0] = newSkel;
305 m_state->skeletons = std::move(skels);
307 m_state->version = m_version;
309 outputs.scene_out.scene.state = m_state;
310 outputs.scene_out.dirty = ossia::scene_port::dirty_transform;