67 halp_meta(name,
"Humanoid Retarget")
68 halp_meta(category,
"Visuals/3D/Scene")
69 halp_meta(c_name,
"humanoid_retarget")
70 halp_meta(authors,
"ossia team")
73 "https://ossia.io/score-docs/processes/humanoid-retarget.html")
74 halp_meta(uuid,
"7e1f4d8a-2c6b-4e7f-9a35-6c4b8d2e0f1a")
80 halp_meta(name,
"Scene In");
81 ossia::scene_spec scene;
90 halp_meta(name,
"Keypoints");
91 std::optional<keypoint_stream> value;
99 halp_meta(name,
"Trackers");
100 std::optional<tracker_bundle_6> value;
103 struct : halp::combobox_t<
"Source", HumanoidSourceType>
107 std::string_view values[5]{
108 "Off",
"BlazePose",
"COCO-17",
"RTMPose Whole",
"6DOF Trackers"};
116 self.m_calibrated =
false;
120 struct : halp::hslider_f32<
"Confidence", halp::range{0.f, 1.f, 0.5f}>
122 halp_meta(description,
"Per-keypoint confidence threshold");
123 } confidence_threshold;
125 struct : halp::combobox_t<
"Target rig", HumanoidRigPreset>
129 std::string_view values[3]{
"Mixamo",
"VRM",
"Unreal Mannequin"};
137 self.m_calibrated =
false;
141 halp::toggle<
"Root motion"> root_motion;
143 struct : halp::hslider_f32<
"Root scale", halp::range{0.01f, 10.f, 1.f}>
147 struct : halp::impulse_button<
"Capture rest pose">
157 halp_meta(name,
"Scene Out");
158 ossia::scene_spec scene;
165 const auto& in = inputs.scene_in.scene;
166 if(!in.state || !in.state->roots)
168 outputs.scene_out.scene.state.reset();
169 outputs.scene_out.dirty = 0;
176 std::optional<humanoid_pose> maybe_pose
177 = composeSourcePose(inputs.confidence_threshold.value);
180 outputs.scene_out.scene = in;
181 outputs.scene_out.dirty = 0;
185 const auto& pose = *maybe_pose;
188 if(!in.state->skeletons || in.state->skeletons->empty())
190 outputs.scene_out.scene = in;
191 outputs.scene_out.dirty = 0;
194 const auto& srcSkel = *(*in.state->skeletons)[0];
195 if(srcSkel.joints.empty())
197 outputs.scene_out.scene = in;
198 outputs.scene_out.dirty = 0;
205 if(m_need_calibrate || !m_calibrated)
207 calibrate(srcSkel, pose);
208 m_need_calibrate =
false;
213 auto newSkel = std::make_shared<ossia::skeleton_component>(srcSkel);
217 for(std::size_t b = 0; b < std::size_t(humanoid_bone_index::Count); ++b)
219 const int32_t tgt = m_target_joint_indices[b];
220 if(tgt < 0 || tgt >= int32_t(newSkel->joints.size()))
223 const auto& src_cur = pose.bones[b];
224 if(src_cur.validity < kValidityThreshold)
227 const float src_cur_q[4] = {
228 src_cur.qx, src_cur.qy, src_cur.qz, src_cur.qw};
229 float inv_src_rest[4];
230 quat_inv(m_source_rest[b], inv_src_rest);
233 quat_mul(inv_src_rest, src_cur_q, delta);
236 quat_mul(m_target_rest[b], delta, out);
238 auto& tgtJoint = newSkel->joints[tgt];
239 tgtJoint.rotation[0] = out[0];
240 tgtJoint.rotation[1] = out[1];
241 tgtJoint.rotation[2] = out[2];
242 tgtJoint.rotation[3] = out[3];
248 if(inputs.root_motion.value)
250 const int32_t hipsIdx
251 = m_target_joint_indices[std::size_t(humanoid_bone_index::Hips)];
252 if(hipsIdx >= 0 && hipsIdx < int32_t(newSkel->joints.size()))
254 const float s = inputs.root_scale.value;
255 auto& hip = newSkel->joints[hipsIdx];
257 = m_target_rest_hip_tr[0] + (pose.hip_x - m_source_rest_hip[0]) * s;
259 = m_target_rest_hip_tr[1] + (pose.hip_y - m_source_rest_hip[1]) * s;
261 = m_target_rest_hip_tr[2] + (pose.hip_z - m_source_rest_hip[2]) * s;
265 newSkel->dirty_index++;
269 auto state = std::make_shared<ossia::scene_state>(*in.state);
271 = std::make_shared<std::vector<ossia::skeleton_component_ptr>>();
272 skels->reserve(in.state->skeletons->size());
273 for(std::size_t i = 0; i < in.state->skeletons->size(); ++i)
275 i == 0 ? ossia::skeleton_component_ptr(newSkel)
276 : (*in.state->skeletons)[i]);
277 state->skeletons = std::move(skels);
278 state->version = ++m_version_counter;
279 state->dirty_index = in.state->dirty_index + 1;
281 m_state = std::move(state);
282 outputs.scene_out.scene.state = m_state;
283 outputs.scene_out.dirty = ossia::scene_port::dirty_transform;
290 static constexpr float kValidityThreshold = 0.5f;
293 static void quat_mul(
const float a[4],
const float b[4],
float out[4])
noexcept
295 const float x = a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1];
296 const float y = a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0];
297 const float z = a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3];
298 const float w = a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2];
308 static void quat_inv(
const float q[4],
float out[4])
noexcept
317 const ossia::skeleton_component& skel,
318 const humanoid_pose& pose)
noexcept
320 const auto& map = humanoidBoneMap(inputs.preset.value);
322 for(std::size_t b = 0; b < std::size_t(humanoid_bone_index::Count); ++b)
327 m_source_rest[b][0] = pose.bones[b].qx;
328 m_source_rest[b][1] = pose.bones[b].qy;
329 m_source_rest[b][2] = pose.bones[b].qz;
330 m_source_rest[b][3] = pose.bones[b].qw;
332 m_target_joint_indices[b] = -1;
336 const int32_t idx = skel.find_joint(map[b]);
339 m_target_joint_indices[b] = idx;
342 const auto& j = skel.joints[std::size_t(idx)];
343 m_target_rest[b][0] = j.rotation[0];
344 m_target_rest[b][1] = j.rotation[1];
345 m_target_rest[b][2] = j.rotation[2];
346 m_target_rest[b][3] = j.rotation[3];
348 if(b == std::size_t(humanoid_bone_index::Hips))
350 m_target_rest_hip_tr[0] = j.translation[0];
351 m_target_rest_hip_tr[1] = j.translation[1];
352 m_target_rest_hip_tr[2] = j.translation[2];
356 m_source_rest_hip[0] = pose.hip_x;
357 m_source_rest_hip[1] = pose.hip_y;
358 m_source_rest_hip[2] = pose.hip_z;
366 std::optional<humanoid_pose>
367 composeSourcePose(
float confidence_threshold)
noexcept
369 const auto src = inputs.source.value;
372 case HumanoidSourceType::Off:
373 case HumanoidSourceType::Count:
376 case HumanoidSourceType::BlazePose:
377 if(!inputs.keypoints_in.value
378 || inputs.keypoints_in.value->keypoints.empty())
380 return keypoints_to_humanoid_pose(
381 *inputs.keypoints_in.value, kBlazePoseMap, confidence_threshold);
383 case HumanoidSourceType::Coco17:
384 if(!inputs.keypoints_in.value
385 || inputs.keypoints_in.value->keypoints.empty())
387 return keypoints_to_humanoid_pose(
388 *inputs.keypoints_in.value, kCoco17Map, confidence_threshold);
390 case HumanoidSourceType::RTMPoseWhole:
391 if(!inputs.keypoints_in.value
392 || inputs.keypoints_in.value->keypoints.empty())
394 return keypoints_to_humanoid_pose(
395 *inputs.keypoints_in.value, kRTMPoseWholeMap,
396 confidence_threshold);
398 case HumanoidSourceType::Trackers6:
399 if(!inputs.trackers_in.value)
401 return trackers_to_humanoid_pose(*inputs.trackers_in.value);
408 bool m_calibrated{
false};
409 std::array<
float[4], std::size_t(humanoid_bone_index::Count)> m_source_rest{};
410 std::array<
float[4], std::size_t(humanoid_bone_index::Count)> m_target_rest{};
411 std::array<int32_t, std::size_t(humanoid_bone_index::Count)>
412 m_target_joint_indices{};
413 float m_target_rest_hip_tr[3]{0.f, 0.f, 0.f};
414 float m_source_rest_hip[3]{0.f, 0.f, 0.f};
417 bool m_need_calibrate{
false};
418 std::shared_ptr<ossia::scene_state> m_state;
419 int64_t m_version_counter{0};