67 halp_meta(name,
"Humanoid Retarget")
68 halp_meta(category,
"Visuals/3D/Scene")
69 halp_meta(c_name,
"humanoid_retarget")
70 halp_meta(description,
"Transfer a humanoid pose stream to a calibrated character skeleton with optional root motion.")
71 halp_meta(authors,
"ossia team")
74 "https://ossia.io/score-docs/processes/humanoid-retarget.html")
75 halp_meta(uuid,
"7e1f4d8a-2c6b-4e7f-9a35-6c4b8d2e0f1a")
81 halp_meta(name,
"Scene In");
82 ossia::scene_spec scene;
91 halp_meta(name,
"Keypoints");
92 std::optional<keypoint_stream> value;
100 halp_meta(name,
"Trackers");
101 std::optional<tracker_bundle_6> value;
104 struct : halp::combobox_t<
"Source", HumanoidSourceType>
108 std::string_view values[5]{
109 "Off",
"BlazePose",
"COCO-17",
"RTMPose Whole",
"6DOF Trackers"};
117 self.m_calibrated =
false;
121 struct : halp::hslider_f32<
"Confidence", halp::range{0.f, 1.f, 0.5f}>
123 halp_meta(description,
"Per-keypoint confidence threshold");
124 } confidence_threshold;
126 struct : halp::combobox_t<
"Target rig", HumanoidRigPreset>
130 std::string_view values[3]{
"Mixamo",
"VRM",
"Unreal Mannequin"};
138 self.m_calibrated =
false;
142 halp::toggle<
"Root motion"> root_motion;
144 struct : halp::hslider_f32<
"Root scale", halp::range{0.01f, 10.f, 1.f}>
148 struct : halp::impulse_button<
"Capture rest pose">
158 halp_meta(name,
"Scene Out");
159 ossia::scene_spec scene;
166 const auto& in = inputs.scene_in.scene;
167 if(!in.state || !in.state->roots)
169 outputs.scene_out.scene.state.reset();
170 outputs.scene_out.dirty = 0;
177 std::optional<humanoid_pose> maybe_pose
178 = composeSourcePose(inputs.confidence_threshold.value);
181 outputs.scene_out.scene = in;
182 outputs.scene_out.dirty = 0;
186 const auto& pose = *maybe_pose;
189 if(!in.state->skeletons || in.state->skeletons->empty())
191 outputs.scene_out.scene = in;
192 outputs.scene_out.dirty = 0;
195 const auto& srcSkel = *(*in.state->skeletons)[0];
196 if(srcSkel.joints.empty())
198 outputs.scene_out.scene = in;
199 outputs.scene_out.dirty = 0;
206 if(m_need_calibrate || !m_calibrated)
208 calibrate(srcSkel, pose);
209 m_need_calibrate =
false;
214 auto newSkel = std::make_shared<ossia::skeleton_component>(srcSkel);
218 for(std::size_t b = 0; b < std::size_t(humanoid_bone_index::Count); ++b)
220 const int32_t tgt = m_target_joint_indices[b];
221 if(tgt < 0 || tgt >= int32_t(newSkel->joints.size()))
224 const auto& src_cur = pose.bones[b];
225 if(src_cur.validity < kValidityThreshold)
228 const float src_cur_q[4] = {
229 src_cur.qx, src_cur.qy, src_cur.qz, src_cur.qw};
230 float inv_src_rest[4];
231 quat_inv(m_source_rest[b], inv_src_rest);
234 quat_mul(inv_src_rest, src_cur_q, delta);
237 quat_mul(m_target_rest[b], delta, out);
239 auto& tgtJoint = newSkel->joints[tgt];
240 tgtJoint.rotation[0] = out[0];
241 tgtJoint.rotation[1] = out[1];
242 tgtJoint.rotation[2] = out[2];
243 tgtJoint.rotation[3] = out[3];
249 if(inputs.root_motion.value)
251 const int32_t hipsIdx
252 = m_target_joint_indices[std::size_t(humanoid_bone_index::Hips)];
253 if(hipsIdx >= 0 && hipsIdx < int32_t(newSkel->joints.size()))
255 const float s = inputs.root_scale.value;
256 auto& hip = newSkel->joints[hipsIdx];
258 = m_target_rest_hip_tr[0] + (pose.hip_x - m_source_rest_hip[0]) * s;
260 = m_target_rest_hip_tr[1] + (pose.hip_y - m_source_rest_hip[1]) * s;
262 = m_target_rest_hip_tr[2] + (pose.hip_z - m_source_rest_hip[2]) * s;
266 newSkel->dirty_index++;
270 auto state = std::make_shared<ossia::scene_state>(*in.state);
272 = std::make_shared<std::vector<ossia::skeleton_component_ptr>>();
273 skels->reserve(in.state->skeletons->size());
274 for(std::size_t i = 0; i < in.state->skeletons->size(); ++i)
276 i == 0 ? ossia::skeleton_component_ptr(newSkel)
277 : (*in.state->skeletons)[i]);
278 state->skeletons = std::move(skels);
279 state->version = ++m_version_counter;
280 state->dirty_index = in.state->dirty_index + 1;
282 m_state = std::move(state);
283 outputs.scene_out.scene.state = m_state;
284 outputs.scene_out.dirty = ossia::scene_port::dirty_transform;
291 static constexpr float kValidityThreshold = 0.5f;
294 static void quat_mul(
const float a[4],
const float b[4],
float out[4])
noexcept
296 const float x = a[3] * b[0] + a[0] * b[3] + a[1] * b[2] - a[2] * b[1];
297 const float y = a[3] * b[1] - a[0] * b[2] + a[1] * b[3] + a[2] * b[0];
298 const float z = a[3] * b[2] + a[0] * b[1] - a[1] * b[0] + a[2] * b[3];
299 const float w = a[3] * b[3] - a[0] * b[0] - a[1] * b[1] - a[2] * b[2];
309 static void quat_inv(
const float q[4],
float out[4])
noexcept
318 const ossia::skeleton_component& skel,
319 const humanoid_pose& pose)
noexcept
321 const auto& map = humanoidBoneMap(inputs.preset.value);
323 for(std::size_t b = 0; b < std::size_t(humanoid_bone_index::Count); ++b)
328 m_source_rest[b][0] = pose.bones[b].qx;
329 m_source_rest[b][1] = pose.bones[b].qy;
330 m_source_rest[b][2] = pose.bones[b].qz;
331 m_source_rest[b][3] = pose.bones[b].qw;
333 m_target_joint_indices[b] = -1;
337 const int32_t idx = skel.find_joint(map[b]);
340 m_target_joint_indices[b] = idx;
343 const auto& j = skel.joints[std::size_t(idx)];
344 m_target_rest[b][0] = j.rotation[0];
345 m_target_rest[b][1] = j.rotation[1];
346 m_target_rest[b][2] = j.rotation[2];
347 m_target_rest[b][3] = j.rotation[3];
349 if(b == std::size_t(humanoid_bone_index::Hips))
351 m_target_rest_hip_tr[0] = j.translation[0];
352 m_target_rest_hip_tr[1] = j.translation[1];
353 m_target_rest_hip_tr[2] = j.translation[2];
357 m_source_rest_hip[0] = pose.hip_x;
358 m_source_rest_hip[1] = pose.hip_y;
359 m_source_rest_hip[2] = pose.hip_z;
367 std::optional<humanoid_pose>
368 composeSourcePose(
float confidence_threshold)
noexcept
370 const auto src = inputs.source.value;
373 case HumanoidSourceType::Off:
374 case HumanoidSourceType::Count:
377 case HumanoidSourceType::BlazePose:
378 if(!inputs.keypoints_in.value
379 || inputs.keypoints_in.value->keypoints.empty())
381 return keypoints_to_humanoid_pose(
382 *inputs.keypoints_in.value, kBlazePoseMap, confidence_threshold);
384 case HumanoidSourceType::Coco17:
385 if(!inputs.keypoints_in.value
386 || inputs.keypoints_in.value->keypoints.empty())
388 return keypoints_to_humanoid_pose(
389 *inputs.keypoints_in.value, kCoco17Map, confidence_threshold);
391 case HumanoidSourceType::RTMPoseWhole:
392 if(!inputs.keypoints_in.value
393 || inputs.keypoints_in.value->keypoints.empty())
395 return keypoints_to_humanoid_pose(
396 *inputs.keypoints_in.value, kRTMPoseWholeMap,
397 confidence_threshold);
399 case HumanoidSourceType::Trackers6:
400 if(!inputs.trackers_in.value)
402 return trackers_to_humanoid_pose(*inputs.trackers_in.value);
409 bool m_calibrated{
false};
410 std::array<
float[4], std::size_t(humanoid_bone_index::Count)> m_source_rest{};
411 std::array<
float[4], std::size_t(humanoid_bone_index::Count)> m_target_rest{};
412 std::array<int32_t, std::size_t(humanoid_bone_index::Count)>
413 m_target_joint_indices{};
414 float m_target_rest_hip_tr[3]{0.f, 0.f, 0.f};
415 float m_source_rest_hip[3]{0.f, 0.f, 0.f};
418 bool m_need_calibrate{
false};
419 std::shared_ptr<ossia::scene_state> m_state;
420 int64_t m_version_counter{0};