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(description,
"Solve a two-bone skeleton chain toward a target using a pole vector to control the bend.")
48 halp_meta(authors,
"ossia team")
51 "https://ossia.io/score-docs/processes/inverse-kinematics.html")
52 halp_meta(uuid,
"6e9f2a4c-1b85-4d3e-a7f6-8c2b4d5e9a0f")
58 halp_meta(name,
"Scene In");
59 ossia::scene_spec scene;
63 halp::lineedit<
"End joint name",
"hand_r"> end_joint;
65 halp::xyz_spinboxes_f32<
67 halp::range{-10000., 10000., 0.}>
69 halp::xyz_spinboxes_f32<
71 halp::range{-10000., 10000., 0.}>
74 halp::hslider_f32<
"Weight", halp::range{0., 1., 1.}> weight;
81 halp_meta(name,
"Scene Out");
82 ossia::scene_spec scene;
87 std::shared_ptr<ossia::scene_state> m_state;
90 static QVector3D toVec(
const float v[3]) {
return QVector3D(v[0], v[1], v[2]); }
91 static QQuaternion toQuat(
const float v[4])
93 return QQuaternion(v[3], v[0], v[1], v[2]);
95 static void fromQuat(
float v[4],
const QQuaternion& q)
97 v[0] = q.x(); v[1] = q.y(); v[2] = q.z(); v[3] = q.scalar();
102 static QVector3D worldJointPos(
103 const ossia::skeleton_component& skel, int32_t idx)
105 if(idx < 0 || idx >= (int32_t)skel.joints.size())
109 ossia::small_vector<int32_t, 16> chain;
110 for(int32_t i = idx; i >= 0; i = skel.joints[i].parent_index)
112 std::reverse(chain.begin(), chain.end());
115 for(int32_t i : chain)
117 const auto& j = skel.joints[i];
119 T.translate(j.translation[0], j.translation[1], j.translation[2]);
120 T.rotate(QQuaternion(
121 j.rotation[3], j.rotation[0], j.rotation[1], j.rotation[2]));
122 T.scale(j.scale[0], j.scale[1], j.scale[2]);
125 return M.map(QVector3D());
133 QQuaternion rootDelta;
134 QQuaternion midDelta;
137 QVector3D root, QVector3D mid, QVector3D end,
138 QVector3D target, QVector3D pole)
140 const float eps = 1e-6f;
141 QVector3D r2m = mid - root;
142 QVector3D m2e = end - mid;
143 QVector3D r2e = end - root;
144 QVector3D r2t = target - root;
146 const float lA = r2m.length();
147 const float lB = m2e.length();
148 const float lTgt = std::min(r2t.length(), lA + lB - eps);
149 if(lA < eps || lB < eps || lTgt < eps)
150 return {QQuaternion(), QQuaternion()};
154 const float cosNew = std::clamp(
155 (lA * lA + lB * lB - lTgt * lTgt) / (2.0f * lA * lB), -1.0f, 1.0f);
156 const float thetaNew = std::acos(cosNew);
159 const float cosCur = std::clamp(
160 QVector3D::dotProduct(-r2m.normalized(), m2e.normalized()),
162 const float thetaCur = std::acos(cosCur);
166 QVector3D planeNormal = QVector3D::crossProduct(r2m, m2e);
167 if(planeNormal.lengthSquared() < eps)
170 QVector3D poleDir = (pole - root).normalized();
171 planeNormal = QVector3D::crossProduct(r2e.normalized(), poleDir);
172 if(planeNormal.lengthSquared() < eps)
173 planeNormal = QVector3D(0, 1, 0);
175 planeNormal.normalize();
177 QQuaternion elbowDelta = QQuaternion::fromAxisAndAngle(
178 planeNormal, (thetaCur - thetaNew) * 180.0f /
float(M_PI));
188 QVector3D newEnd = mid + elbowDelta.rotatedVector(m2e);
189 QVector3D r2t_n = r2t.normalized();
190 QVector3D r2e_n = (newEnd - root).normalized();
191 QQuaternion rootDelta = QQuaternion::rotationTo(r2e_n, r2t_n);
193 return {rootDelta, elbowDelta};
198 const auto& in = inputs.scene_in.scene;
199 if(!in.state || !in.state->roots)
201 outputs.scene_out.scene.state.reset();
202 outputs.scene_out.dirty = 0;
207 const ossia::skeleton_component* srcSkel =
nullptr;
208 if(in.state->skeletons && !in.state->skeletons->empty())
209 srcSkel = (*in.state->skeletons)[0].get();
210 if(!srcSkel || srcSkel->joints.empty())
212 outputs.scene_out.scene = in;
213 outputs.scene_out.dirty = 0;
217 const std::string endName = inputs.end_joint.value;
218 int32_t endIdx = srcSkel->find_joint(endName);
219 if(endIdx < 0 || srcSkel->joints[endIdx].parent_index < 0)
221 outputs.scene_out.scene = in;
222 outputs.scene_out.dirty = 0;
225 const int32_t midIdx = srcSkel->joints[endIdx].parent_index;
226 if(srcSkel->joints[midIdx].parent_index < 0)
228 outputs.scene_out.scene = in;
229 outputs.scene_out.dirty = 0;
232 const int32_t rootIdx = srcSkel->joints[midIdx].parent_index;
235 QVector3D wRoot = worldJointPos(*srcSkel, rootIdx);
236 QVector3D wMid = worldJointPos(*srcSkel, midIdx);
237 QVector3D wEnd = worldJointPos(*srcSkel, endIdx);
240 inputs.target.value.x, inputs.target.value.y, inputs.target.value.z);
242 inputs.pole.value.x, inputs.pole.value.y, inputs.pole.value.z);
244 Solution sol = solve2Bone(wRoot, wMid, wEnd, target, pole);
247 const float w = std::clamp(inputs.weight.value, 0.0f, 1.0f);
250 outputs.scene_out.scene = in;
251 outputs.scene_out.dirty = 0;
254 QQuaternion rootDelta = QQuaternion::slerp(QQuaternion(), sol.rootDelta, w);
255 QQuaternion midDelta = QQuaternion::slerp(QQuaternion(), sol.midDelta, w);
259 auto newSkel = std::make_shared<ossia::skeleton_component>(*srcSkel);
263 auto worldRotOf = [&](int32_t idx) {
265 for(int32_t i = idx; i >= 0; i = srcSkel->joints[i].parent_index)
268 srcSkel->joints[i].rotation[3],
269 srcSkel->joints[i].rotation[0],
270 srcSkel->joints[i].rotation[1],
271 srcSkel->joints[i].rotation[2]);
276 QQuaternion parentRoot = srcSkel->joints[rootIdx].parent_index >= 0
277 ? worldRotOf(srcSkel->joints[rootIdx].parent_index)
279 QQuaternion parentMid = worldRotOf(rootIdx);
281 QQuaternion rootLocalNew
282 = parentRoot.inverted() * rootDelta * parentRoot
283 * toQuat(srcSkel->joints[rootIdx].rotation);
284 QQuaternion midLocalNew
285 = parentMid.inverted() * midDelta * parentMid
286 * toQuat(srcSkel->joints[midIdx].rotation);
288 fromQuat(newSkel->joints[rootIdx].rotation, rootLocalNew);
289 fromQuat(newSkel->joints[midIdx].rotation, midLocalNew);
290 newSkel->dirty_index++;
294 if(!m_state || m_state->version != in.state->version - 1)
295 m_state = std::make_shared<ossia::scene_state>(*in.state);
297 *m_state = *in.state;
299 auto skels = std::make_shared<std::vector<ossia::skeleton_component_ptr>>();
300 if(in.state->skeletons)
301 *skels = *in.state->skeletons;
303 skels->push_back(newSkel);
305 (*skels)[0] = newSkel;
306 m_state->skeletons = std::move(skels);
308 m_state->version = m_version;
310 outputs.scene_out.scene.state = m_state;
311 outputs.scene_out.dirty = ossia::scene_port::dirty_transform;