1use crate::engine::ecs::component::ik_chain::TwoBoneIkDebugVisuals;
2use crate::engine::ecs::component::{
3 AvatarControlComponent, BoneRestPoseComponent, ColorComponent, EmissiveComponent,
4 IKChainComponent, IKSolver, OverlayComponent, QueryRootMode, RenderableComponent,
5 TransformComponent, resolve_component_ref,
6};
7use crate::engine::ecs::{ComponentId, IntentValue, SignalEmitter, World};
8use crate::utils::math::{
9 mat_to_quat, mat4_inverse, quat_conjugate, quat_from_axis_angle, quat_mul, quat_nlerp,
10 quat_rotate_vec3, quat_rotation_y, quat_to_axis_angle, shortest_arc_quat, vec3_add, vec3_cross,
11 vec3_dot, vec3_len, vec3_lerp, vec3_normalize, vec3_scale, vec3_sub,
12};
13use std::collections::HashSet;
14
15#[derive(Debug, Default)]
16pub struct IKSystem {
17 chains: HashSet<ComponentId>,
18}
19
20impl IKSystem {
21 pub fn new() -> Self {
22 Self::default()
23 }
24
25 pub fn tick(&mut self, world: &mut World, emit: &mut dyn SignalEmitter, _dt_sec: f32) {
26 let ids: Vec<_> = self.chains.iter().copied().collect();
27 for id in ids {
28 resolve_ik_chain_refs(world, id);
35 resolve_avc_ancestor(world, id);
36 tick_chain(id, world, emit);
37 }
38 }
39
40 pub fn register(&mut self, component: ComponentId) {
41 self.chains.insert(component);
42 }
43
44 pub fn remove(&mut self, component: ComponentId) {
45 self.chains.remove(&component);
46 }
47}
48
49fn resolve_ik_chain_refs(world: &mut World, id: ComponentId) {
55 use crate::engine::ecs::component::ComponentRef;
56 use slotmap::Key;
57 let (target_src, end_src, target_id, end_id) = {
58 let Some(ik) = world.get_component_by_id_as::<IKChainComponent>(id) else {
59 return;
60 };
61 (
62 ik.target_source.clone(),
63 ik.end_effector_source.clone(),
64 ik.target_id,
65 ik.end_effector_id,
66 )
67 };
68
69 let resolve = |src: &ComponentRef| -> Option<ComponentId> {
70 resolve_component_ref(world, src, Some(id), QueryRootMode::SelfSubtree)
71 };
72
73 let new_target = if target_id.is_null() {
74 target_src.as_ref().and_then(resolve)
75 } else {
76 None
77 };
78 let new_end = if end_id.is_null() {
79 end_src.as_ref().and_then(resolve)
80 } else {
81 None
82 };
83
84 if new_target.is_none() && new_end.is_none() {
85 return;
86 }
87 if let Some(ik) = world.get_component_by_id_as_mut::<IKChainComponent>(id) {
88 if let Some(t) = new_target {
89 ik.target_id = t;
90 }
91 if let Some(e) = new_end {
92 ik.end_effector_id = e;
93 }
94 }
95}
96
97fn resolve_avc_ancestor(world: &mut World, id: ComponentId) {
100 let needs_walk = world
101 .get_component_by_id_as::<IKChainComponent>(id)
102 .map(|c| c.avc_id.is_none())
103 .unwrap_or(false);
104 if !needs_walk {
105 return;
106 }
107 let found = find_avc_ancestor(world, id);
108 if let Some(ik) = world.get_component_by_id_as_mut::<IKChainComponent>(id) {
109 ik.avc_id = found;
110 }
111}
112
113fn find_avc_ancestor(world: &World, id: ComponentId) -> Option<ComponentId> {
117 let mut cur = id;
118 for _ in 0..32 {
119 let parent = world.parent_of(cur)?;
120 if world
121 .get_component_by_id_as::<AvatarControlComponent>(parent)
122 .is_some()
123 {
124 return Some(parent);
125 }
126 cur = parent;
127 }
128 None
129}
130
131fn tick_chain(id: ComponentId, world: &mut World, emit: &mut dyn SignalEmitter) {
132 let (solver, target_id, end_effector_id, weight, avc_id, xr_pose_driver) = {
133 let Some(c) = world.get_component_by_id_as::<IKChainComponent>(id) else {
134 return;
135 };
136 (
137 c.solver,
138 c.target_id,
139 c.end_effector_id,
140 c.weight,
141 c.avc_id,
142 c.xr_pose_driver,
143 )
144 };
145 if weight <= 0.0 {
146 return;
147 }
148 if let Some(driver) = xr_pose_driver {
149 let valid = world
150 .get_component_by_id_as::<crate::engine::ecs::component::InputXRComponent>(driver)
151 .map(|component| component.pose_valid)
152 .or_else(|| {
153 world
154 .get_component_by_id_as::<crate::engine::ecs::component::ControllerXRComponent>(
155 driver,
156 )
157 .map(|component| component.pose_valid)
158 })
159 .unwrap_or(false);
160 if !valid {
161 return;
162 }
163 }
164
165 let root_tc_opt = world.parent_of(id).filter(|&p| {
168 world
169 .get_component_by_id_as::<TransformComponent>(p)
170 .is_some()
171 });
172
173 match solver {
174 IKSolver::AimConstraint {
175 offset_yaw,
176 copy_position,
177 target_position_offset,
178 } => {
179 let Some(root_tc) = root_tc_opt else { return };
180 solve_aim(
181 world,
182 emit,
183 root_tc,
184 target_id,
185 offset_yaw,
186 copy_position,
187 target_position_offset,
188 weight,
189 );
190 }
191 IKSolver::TwoBoneIK {
192 root_joint_id,
193 mid_joint_id,
194 pole_direction,
195 copy_end_rotation,
196 } => {
197 use slotmap::Key;
200 if root_joint_id.is_null() || mid_joint_id.is_null() || end_effector_id.is_null() {
201 return;
202 }
203 if world
204 .get_component_by_id_as::<TransformComponent>(root_joint_id)
205 .is_none()
206 || world
207 .get_component_by_id_as::<TransformComponent>(mid_joint_id)
208 .is_none()
209 || world
210 .get_component_by_id_as::<TransformComponent>(end_effector_id)
211 .is_none()
212 {
213 return;
214 }
215 let chain = [root_joint_id, mid_joint_id, end_effector_id];
216 solve_two_bone(
217 world,
218 emit,
219 id,
220 &chain,
221 target_id,
222 pole_direction,
223 copy_end_rotation,
224 weight,
225 avc_id,
226 );
227 }
228 IKSolver::Fabrik {
229 max_iterations,
230 tolerance,
231 target_position_offset,
232 } => {
233 let Some(root_tc) = root_tc_opt else { return };
234 let chain = collect_tc_chain(world, root_tc, end_effector_id);
235 if chain.len() < 2 {
236 return;
237 }
238 solve_fabrik(
239 world,
240 emit,
241 &chain,
242 target_id,
243 target_position_offset,
244 max_iterations,
245 tolerance,
246 weight,
247 );
248 }
249 }
250}
251
252fn collect_tc_chain(world: &World, root: ComponentId, end_id: ComponentId) -> Vec<ComponentId> {
264 if root == end_id {
265 return vec![root];
266 }
267 let mut up: Vec<ComponentId> = vec![end_id];
268 let mut cur = end_id;
269 for _ in 0..32 {
270 let Some(parent) = world.parent_of(cur) else {
271 return Vec::new();
272 };
273 if world
274 .get_component_by_id_as::<TransformComponent>(parent)
275 .is_none()
276 {
277 return Vec::new();
278 }
279 up.push(parent);
280 if parent == root {
281 up.reverse();
282 return up;
283 }
284 cur = parent;
285 }
286 Vec::new()
287}
288
289fn solve_aim(
297 world: &World,
298 emit: &mut dyn SignalEmitter,
299 root_tc: ComponentId,
300 target_id: ComponentId,
301 offset_yaw: f32,
302 copy_position: bool,
303 target_position_offset: [f32; 3],
304 weight: f32,
305) {
306 let Some(target_tc) = world.get_component_by_id_as::<TransformComponent>(target_id) else {
307 return;
308 };
309 let target_world_rot = mat_to_quat(target_tc.transform.matrix_world);
310 let desired_world_rot = quat_mul(target_world_rot, quat_rotation_y(offset_yaw));
311 let target_local_offset_world = quat_rotate_vec3(target_world_rot, target_position_offset);
316 let target_world_pos = [
317 target_tc.transform.matrix_world[3][0] + target_local_offset_world[0],
318 target_tc.transform.matrix_world[3][1] + target_local_offset_world[1],
319 target_tc.transform.matrix_world[3][2] + target_local_offset_world[2],
320 ];
321
322 let parent_world_mat = world
323 .parent_of(root_tc)
324 .and_then(|p| world.get_component_by_id_as::<TransformComponent>(p))
325 .map(|t| t.transform.matrix_world)
326 .unwrap_or([
327 [1.0, 0.0, 0.0, 0.0],
328 [0.0, 1.0, 0.0, 0.0],
329 [0.0, 0.0, 1.0, 0.0],
330 [0.0, 0.0, 0.0, 1.0],
331 ]);
332 let parent_world_rot = mat_to_quat(parent_world_mat);
333
334 let full_local_rot = quat_mul(quat_conjugate(parent_world_rot), desired_world_rot);
335
336 let local_rot = if weight < 1.0 {
337 let cur = world
338 .get_component_by_id_as::<TransformComponent>(root_tc)
339 .map(|t| t.transform.rotation)
340 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
341 quat_nlerp(cur, full_local_rot, weight)
342 } else {
343 full_local_rot
344 };
345
346 let (cur_t, s) = world
347 .get_component_by_id_as::<TransformComponent>(root_tc)
348 .map(|tc| (tc.transform.translation, tc.transform.scale))
349 .unwrap_or(([0.0; 3], [1.0, 1.0, 1.0]));
350
351 let local_t = if copy_position {
352 let parent_pos = [
356 parent_world_mat[3][0],
357 parent_world_mat[3][1],
358 parent_world_mat[3][2],
359 ];
360 let delta = [
361 target_world_pos[0] - parent_pos[0],
362 target_world_pos[1] - parent_pos[1],
363 target_world_pos[2] - parent_pos[2],
364 ];
365 let inv_parent_rot = quat_conjugate(parent_world_rot);
367 let local_pos = quat_rotate_vec3(inv_parent_rot, delta);
368 if weight < 1.0 {
369 [
370 cur_t[0] + (local_pos[0] - cur_t[0]) * weight,
371 cur_t[1] + (local_pos[1] - cur_t[1]) * weight,
372 cur_t[2] + (local_pos[2] - cur_t[2]) * weight,
373 ]
374 } else {
375 local_pos
376 }
377 } else {
378 cur_t
379 };
380
381 emit.push_intent_now(
382 root_tc,
383 IntentValue::UpdateTransform {
384 component_ids: vec![root_tc],
385 translation: local_t,
386 rotation_quat_xyzw: local_rot,
387 scale: s,
388 },
389 );
390}
391
392fn solve_two_bone(
406 world: &mut World,
407 emit: &mut dyn SignalEmitter,
408 ik_chain_id: ComponentId,
409 chain: &[ComponentId],
410 target_id: ComponentId,
411 pole_direction: [f32; 3],
412 copy_end_rotation: bool,
413 weight: f32,
414 avc_id: Option<ComponentId>,
415) {
416 let (root_tc, mid_tc, end_tc) = (chain[0], chain[1], chain[2]);
417
418 let root_pos = tc_world_pos(world, root_tc);
420 let mid_pos = tc_world_pos(world, mid_tc);
421 let end_pos = tc_world_pos(world, end_tc);
422 let target_pos = tc_world_pos(world, target_id);
423
424 let root_world_rot = tc_world_rot(world, root_tc);
425 let mid_world_rot = tc_world_rot(world, mid_tc);
426
427 let upper_len = vec3_len(vec3_sub(mid_pos, root_pos)).max(1e-6);
428 let lower_len = vec3_len(vec3_sub(end_pos, mid_pos)).max(1e-6);
429
430 let root_parent_world_rot = world
431 .parent_of(root_tc)
432 .and_then(|p| world.get_component_by_id_as::<TransformComponent>(p))
433 .map(|t| mat_to_quat(t.transform.matrix_world))
434 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
435
436 let to_target = vec3_sub(target_pos, root_pos);
438 let raw_reach = vec3_len(to_target);
439 let reach = raw_reach.min(upper_len + lower_len - 1e-4).max(1e-6);
440 let reach_dir = if raw_reach > 1e-6 {
441 vec3_scale(to_target, 1.0 / raw_reach)
442 } else {
443 [0.0, 1.0, 0.0]
444 };
445
446 let cos_upper = ((upper_len * upper_len + reach * reach - lower_len * lower_len)
447 / (2.0 * upper_len * reach))
448 .clamp(-1.0, 1.0);
449 let upper_angle = cos_upper.acos();
450
451 let pole = match avc_id {
454 Some(avc) => world
455 .get_component_by_id_as::<AvatarControlComponent>(avc)
456 .and_then(|c| c.model_root_id)
457 .map(|root_id| {
458 let root_rot = tc_world_rot(world, root_id);
459 quat_rotate_vec3(root_rot, pole_direction)
460 })
461 .unwrap_or(pole_direction),
462 None => pole_direction,
463 };
464 let cross_tp = vec3_cross(to_target, pole);
465 let plane_normal = if vec3_len(cross_tp) > 1e-6 {
466 vec3_normalize(cross_tp)
467 } else {
468 let fallback = if reach_dir[0].abs() < 0.9 {
469 [1.0, 0.0, 0.0]
470 } else {
471 [0.0, 1.0, 0.0]
472 };
473 vec3_normalize(vec3_cross(to_target, fallback))
474 };
475 let perp = vec3_normalize(vec3_cross(plane_normal, reach_dir));
476
477 let elbow_dir = vec3_normalize(vec3_add(
478 vec3_scale(reach_dir, upper_angle.cos()),
479 vec3_scale(perp, upper_angle.sin()),
480 ));
481 let elbow_pos = vec3_add(root_pos, vec3_scale(elbow_dir, upper_len));
482
483 maybe_update_two_bone_debug(
484 world,
485 emit,
486 ik_chain_id,
487 avc_id,
488 root_pos,
489 target_pos,
490 pole,
491 plane_normal,
492 elbow_pos,
493 );
494
495 let old_upper_fwd = if vec3_len(vec3_sub(mid_pos, root_pos)) > 1e-6 {
497 vec3_normalize(vec3_sub(mid_pos, root_pos))
498 } else {
499 [0.0, 0.0, 1.0]
500 };
501 let delta_upper = shortest_arc_quat(old_upper_fwd, elbow_dir);
502 let new_upper_world_rot = quat_mul(delta_upper, root_world_rot);
503
504 let full_upper_local = quat_mul(quat_conjugate(root_parent_world_rot), new_upper_world_rot);
505 let upper_local = if weight < 1.0 {
506 let cur = world
507 .get_component_by_id_as::<TransformComponent>(root_tc)
508 .map(|t| t.transform.rotation)
509 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
510 quat_nlerp(cur, full_upper_local, weight)
511 } else {
512 full_upper_local
513 };
514
515 let old_lower_fwd = if vec3_len(vec3_sub(end_pos, mid_pos)) > 1e-6 {
517 vec3_normalize(vec3_sub(end_pos, mid_pos))
518 } else {
519 [0.0, 0.0, 1.0]
520 };
521 let lower_fwd_after_upper = quat_rotate_vec3(delta_upper, old_lower_fwd);
523 let new_lower_fwd = if vec3_len(vec3_sub(target_pos, elbow_pos)) > 1e-6 {
524 vec3_normalize(vec3_sub(target_pos, elbow_pos))
525 } else {
526 lower_fwd_after_upper
527 };
528 let delta_lower = shortest_arc_quat(lower_fwd_after_upper, new_lower_fwd);
529 let mut new_lower_world_rot = quat_mul(delta_lower, quat_mul(delta_upper, mid_world_rot));
530 let end_local_rest = read_bone_rest_rot(world, end_tc);
531 let solved_forearm_axis = if vec3_len(vec3_sub(target_pos, elbow_pos)) > 1e-6 {
532 vec3_normalize(vec3_sub(target_pos, elbow_pos))
533 } else {
534 new_lower_fwd
535 };
536 let solved_hand_world_rot = quat_mul(new_lower_world_rot, end_local_rest);
537 let target_world_rot = tc_world_rot(world, target_id);
538 let target_from_solved_hand = quat_mul(target_world_rot, quat_conjugate(solved_hand_world_rot));
539 let forearm_twist_world =
540 extract_twist_about_axis(target_from_solved_hand, solved_forearm_axis);
541 let (_, forearm_twist_angle) = quat_to_axis_angle(forearm_twist_world);
542 if forearm_twist_angle.abs() > 1e-4 {
543 new_lower_world_rot = quat_mul(forearm_twist_world, new_lower_world_rot);
544 }
545
546 let full_lower_local = quat_mul(quat_conjugate(new_upper_world_rot), new_lower_world_rot);
548 let lower_local = if weight < 1.0 {
549 let cur = world
550 .get_component_by_id_as::<TransformComponent>(mid_tc)
551 .map(|t| t.transform.rotation)
552 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
553 quat_nlerp(cur, full_lower_local, weight)
554 } else {
555 full_lower_local
556 };
557
558 let (rt, rs) = world
560 .get_component_by_id_as::<TransformComponent>(root_tc)
561 .map(|t| (t.transform.translation, t.transform.scale))
562 .unwrap_or(([0.0; 3], [1.0, 1.0, 1.0]));
563 emit.push_intent_now(
564 root_tc,
565 IntentValue::UpdateTransform {
566 component_ids: vec![root_tc],
567 translation: rt,
568 rotation_quat_xyzw: upper_local,
569 scale: rs,
570 },
571 );
572
573 let (mt, ms) = world
575 .get_component_by_id_as::<TransformComponent>(mid_tc)
576 .map(|t| (t.transform.translation, t.transform.scale))
577 .unwrap_or(([0.0; 3], [1.0, 1.0, 1.0]));
578 emit.push_intent_now(
579 mid_tc,
580 IntentValue::UpdateTransform {
581 component_ids: vec![mid_tc],
582 translation: mt,
583 rotation_quat_xyzw: lower_local,
584 scale: ms,
585 },
586 );
587
588 if copy_end_rotation {
590 let full_end_local = quat_mul(quat_conjugate(new_lower_world_rot), target_world_rot);
591 let end_local = if weight < 1.0 {
592 let cur = world
593 .get_component_by_id_as::<TransformComponent>(end_tc)
594 .map(|t| t.transform.rotation)
595 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
596 quat_nlerp(cur, full_end_local, weight)
597 } else {
598 full_end_local
599 };
600 let (et, es) = world
601 .get_component_by_id_as::<TransformComponent>(end_tc)
602 .map(|t| (t.transform.translation, t.transform.scale))
603 .unwrap_or(([0.0; 3], [1.0, 1.0, 1.0]));
604 emit.push_intent_now(
605 end_tc,
606 IntentValue::UpdateTransform {
607 component_ids: vec![end_tc],
608 translation: et,
609 rotation_quat_xyzw: end_local,
610 scale: es,
611 },
612 );
613 }
614}
615
616fn extract_twist_about_axis(delta_rot: [f32; 4], axis_world: [f32; 3]) -> [f32; 4] {
617 let axis = vec3_normalize(axis_world);
618 if vec3_len(axis) <= 1e-6 {
619 return [0.0, 0.0, 0.0, 1.0];
620 }
621 let projected = vec3_scale(
622 axis,
623 vec3_dot([delta_rot[0], delta_rot[1], delta_rot[2]], axis),
624 );
625 let twist = [projected[0], projected[1], projected[2], delta_rot[3]];
626 let len_sq =
627 twist[0] * twist[0] + twist[1] * twist[1] + twist[2] * twist[2] + twist[3] * twist[3];
628 if len_sq <= 1e-12 {
629 [0.0, 0.0, 0.0, 1.0]
630 } else {
631 let inv_len = len_sq.sqrt().recip();
632 [
633 twist[0] * inv_len,
634 twist[1] * inv_len,
635 twist[2] * inv_len,
636 twist[3] * inv_len,
637 ]
638 }
639}
640
641fn maybe_update_two_bone_debug(
642 world: &mut World,
643 emit: &mut dyn SignalEmitter,
644 ik_chain_id: ComponentId,
645 avc_id: Option<ComponentId>,
646 root_pos: [f32; 3],
647 target_pos: [f32; 3],
648 pole_world: [f32; 3],
649 plane_normal: [f32; 3],
650 elbow_pos: [f32; 3],
651) {
652 let enabled = avc_id
653 .and_then(|id| world.get_component_by_id_as::<AvatarControlComponent>(id))
654 .map(|avc| avc.ik_debug)
655 .unwrap_or(false);
656 if !enabled {
657 return;
658 }
659
660 let visuals = ensure_two_bone_debug_visuals(world, ik_chain_id);
661 let pole_tip = vec3_add(root_pos, vec3_scale(vec3_normalize_or_z(pole_world), 0.30));
662 let normal_tip = vec3_add(
663 root_pos,
664 vec3_scale(vec3_normalize_or_z(plane_normal), 0.22),
665 );
666
667 update_debug_segment(
668 world,
669 emit,
670 visuals.target_line,
671 root_pos,
672 target_pos,
673 0.010,
674 );
675 update_debug_segment(world, emit, visuals.pole_line, root_pos, pole_tip, 0.008);
676 update_debug_segment(
677 world,
678 emit,
679 visuals.plane_normal_line,
680 root_pos,
681 normal_tip,
682 0.008,
683 );
684 update_debug_segment(world, emit, visuals.elbow_line, root_pos, elbow_pos, 0.010);
685 update_debug_point(
686 world,
687 emit,
688 visuals.elbow_point,
689 elbow_pos,
690 [0.035, 0.035, 0.035],
691 );
692}
693
694fn ensure_two_bone_debug_visuals(
695 world: &mut World,
696 ik_chain_id: ComponentId,
697) -> TwoBoneIkDebugVisuals {
698 if let Some(existing) = world
699 .get_component_by_id_as::<IKChainComponent>(ik_chain_id)
700 .and_then(|ik| ik.two_bone_debug_visuals)
701 {
702 return existing;
703 }
704
705 let parent = world
706 .get_component_by_id_as::<IKChainComponent>(ik_chain_id)
707 .and_then(|ik| ik.avc_id)
708 .unwrap_or(ik_chain_id);
709
710 let visuals = TwoBoneIkDebugVisuals {
711 target_line: spawn_debug_cube(
712 world,
713 parent,
714 "ik_debug_target_line",
715 (1.0, 0.85, 0.15, 0.95),
716 ),
717 pole_line: spawn_debug_cube(world, parent, "ik_debug_pole_line", (0.10, 0.95, 1.0, 0.95)),
718 plane_normal_line: spawn_debug_cube(
719 world,
720 parent,
721 "ik_debug_plane_normal_line",
722 (1.0, 0.35, 0.80, 0.95),
723 ),
724 elbow_line: spawn_debug_cube(
725 world,
726 parent,
727 "ik_debug_elbow_line",
728 (0.20, 1.0, 0.35, 0.95),
729 ),
730 elbow_point: spawn_debug_cube(world, parent, "ik_debug_elbow_point", (1.0, 1.0, 1.0, 0.95)),
731 };
732
733 if let Some(ik) = world.get_component_by_id_as_mut::<IKChainComponent>(ik_chain_id) {
734 ik.two_bone_debug_visuals = Some(visuals);
735 }
736 visuals
737}
738
739fn spawn_debug_cube(
740 world: &mut World,
741 parent: ComponentId,
742 name: &str,
743 color: (f32, f32, f32, f32),
744) -> ComponentId {
745 let overlay = world.add_component(OverlayComponent::new());
746 let transform =
747 world.add_component_boxed_named(name.to_string(), Box::new(TransformComponent::new()));
748 let renderable = world.add_component(RenderableComponent::cube());
749 let color = world.add_component(ColorComponent::rgba(color.0, color.1, color.2, color.3));
750 let emissive = world.add_component(EmissiveComponent::on());
751 let _ = world.add_child(overlay, transform);
752 let _ = world.add_child(transform, renderable);
753 let _ = world.add_child(renderable, color);
754 let _ = world.add_child(renderable, emissive);
755 let _ = world.add_child(parent, overlay);
756 transform
757}
758
759fn update_debug_segment(
760 world: &World,
761 emit: &mut dyn SignalEmitter,
762 component_id: ComponentId,
763 start: [f32; 3],
764 end: [f32; 3],
765 thickness: f32,
766) {
767 let delta = vec3_sub(end, start);
768 let len = vec3_len(delta);
769 let dir = if len > 1e-6 {
770 vec3_scale(delta, 1.0 / len)
771 } else {
772 [0.0, 0.0, 1.0]
773 };
774 let mid = vec3_scale(vec3_add(start, end), 0.5);
775 let rot = shortest_arc_quat([0.0, 0.0, 1.0], dir);
776 let local_mid = world_point_to_local(world, component_id, mid);
777 let local_rot = world_quat_to_local(world, component_id, rot);
778 emit.push_intent_now(
779 component_id,
780 IntentValue::UpdateTransform {
781 component_ids: vec![component_id],
782 translation: local_mid,
783 rotation_quat_xyzw: local_rot,
784 scale: [thickness, thickness, len.max(thickness)],
785 },
786 );
787}
788
789fn update_debug_point(
790 world: &World,
791 emit: &mut dyn SignalEmitter,
792 component_id: ComponentId,
793 position: [f32; 3],
794 scale: [f32; 3],
795) {
796 let local_pos = world_point_to_local(world, component_id, position);
797 emit.push_intent_now(
798 component_id,
799 IntentValue::UpdateTransform {
800 component_ids: vec![component_id],
801 translation: local_pos,
802 rotation_quat_xyzw: [0.0, 0.0, 0.0, 1.0],
803 scale,
804 },
805 );
806}
807
808fn world_point_to_local(
809 world: &World,
810 component_id: ComponentId,
811 world_point: [f32; 3],
812) -> [f32; 3] {
813 let parent_world = nearest_ancestor_transform_world(world, component_id);
814 let Some(inv_parent) = parent_world.and_then(mat4_inverse) else {
815 return world_point;
816 };
817 let local = mat4_mul_vec4(
818 inv_parent,
819 [world_point[0], world_point[1], world_point[2], 1.0],
820 );
821 if local[3].abs() > 1e-6 {
822 [
823 local[0] / local[3],
824 local[1] / local[3],
825 local[2] / local[3],
826 ]
827 } else {
828 [local[0], local[1], local[2]]
829 }
830}
831
832fn world_quat_to_local(world: &World, component_id: ComponentId, world_rot: [f32; 4]) -> [f32; 4] {
833 let parent_world_rot = nearest_ancestor_transform_world(world, component_id)
834 .map(mat_to_quat)
835 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
836 quat_mul(quat_conjugate(parent_world_rot), world_rot)
837}
838
839fn nearest_ancestor_transform_world(
840 world: &World,
841 component_id: ComponentId,
842) -> Option<[[f32; 4]; 4]> {
843 let mut cur = component_id;
844 while let Some(parent) = world.parent_of(cur) {
845 if let Some(t) = world.get_component_by_id_as::<TransformComponent>(parent) {
846 return Some(t.transform.matrix_world);
847 }
848 cur = parent;
849 }
850 None
851}
852
853fn mat4_mul_vec4(m: [[f32; 4]; 4], v: [f32; 4]) -> [f32; 4] {
854 [
855 m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0] * v[3],
856 m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1] * v[3],
857 m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2] * v[3],
858 m[0][3] * v[0] + m[1][3] * v[1] + m[2][3] * v[2] + m[3][3] * v[3],
859 ]
860}
861
862fn vec3_normalize_or_z(v: [f32; 3]) -> [f32; 3] {
863 let len = vec3_len(v);
864 if len > 1e-6 {
865 vec3_scale(v, 1.0 / len)
866 } else {
867 [0.0, 0.0, 1.0]
868 }
869}
870
871fn solve_fabrik(
876 world: &World,
877 emit: &mut dyn SignalEmitter,
878 chain: &[ComponentId],
879 target_id: ComponentId,
880 target_position_offset: [f32; 3],
881 max_iterations: u32,
882 tolerance: f32,
883 weight: f32,
884) {
885 let n = chain.len();
886
887 let mut positions: Vec<[f32; 3]> = chain.iter().map(|&tc| tc_world_pos(world, tc)).collect();
888 let bone_lengths: Vec<f32> = (0..n - 1)
889 .map(|i| vec3_len(vec3_sub(positions[i + 1], positions[i])).max(1e-6))
890 .collect();
891 let root_pos = positions[0];
892 let target_pos = {
896 let base = tc_world_pos(world, target_id);
897 let target_rot = tc_world_rot(world, target_id);
898 let off = quat_rotate_vec3(target_rot, target_position_offset);
899 [base[0] + off[0], base[1] + off[1], base[2] + off[2]]
900 };
901
902 for _ in 0..max_iterations {
904 if vec3_len(vec3_sub(*positions.last().unwrap(), target_pos)) < tolerance {
905 break;
906 }
907 *positions.last_mut().unwrap() = target_pos;
909 for i in (0..n - 1).rev() {
910 let d = vec3_len(vec3_sub(positions[i], positions[i + 1]));
911 let t = if d > 1e-9 { bone_lengths[i] / d } else { 0.0 };
912 positions[i] = vec3_lerp(positions[i + 1], positions[i], t);
913 }
914 positions[0] = root_pos;
916 for i in 0..n - 1 {
917 let d = vec3_len(vec3_sub(positions[i + 1], positions[i]));
918 let t = if d > 1e-9 { bone_lengths[i] / d } else { 0.0 };
919 positions[i + 1] = vec3_lerp(positions[i], positions[i + 1], t);
920 }
921 }
922
923 let mut parent_world_rot = world
925 .parent_of(chain[0])
926 .and_then(|p| world.get_component_by_id_as::<TransformComponent>(p))
927 .map(|t| mat_to_quat(t.transform.matrix_world))
928 .unwrap_or([0.0, 0.0, 0.0, 1.0f32]);
929
930 let neck_index = chain.iter().position(|&id| {
933 world
934 .get_component_by_id_as::<TransformComponent>(id)
935 .and_then(|_| Some(()))
936 .is_some()
937 && world.children_of(id).iter().any(|&child| {
938 let is_head = world
940 .get_component_by_id_as::<TransformComponent>(child)
941 .and_then(|_| {
942 chain.iter().find(|&&c| c == child).map(|_| ())
944 })
945 .is_some();
946 is_head
947 })
948 });
949
950 for i in 0..n - 1 {
951 let tc = chain[i];
952 let cur_world_rot = tc_world_rot(world, tc);
953
954 let cur_fwd = {
955 let from = tc_world_pos(world, tc);
956 let to = tc_world_pos(world, chain[i + 1]);
957 let d = vec3_sub(to, from);
958 if vec3_len(d) > 1e-6 {
959 vec3_normalize(d)
960 } else {
961 [0.0, 0.0, 1.0]
962 }
963 };
964 let desired_fwd = {
965 let d = vec3_sub(positions[i + 1], positions[i]);
966 if vec3_len(d) > 1e-6 {
967 vec3_normalize(d)
968 } else {
969 cur_fwd
970 }
971 };
972
973 let delta = if neck_index == Some(i) {
977 let unconstrained_delta = shortest_arc_quat(cur_fwd, desired_fwd);
980 let (axis, angle) = quat_to_axis_angle(unconstrained_delta);
981 let max_neck_angle = 0.26f32; if angle.abs() > max_neck_angle {
983 let clamped_angle = angle.max(-max_neck_angle).min(max_neck_angle);
985 quat_from_axis_angle(axis, clamped_angle)
986 } else {
987 unconstrained_delta
988 }
989 } else {
990 shortest_arc_quat(cur_fwd, desired_fwd)
991 };
992
993 let new_world_rot = quat_mul(delta, cur_world_rot);
994 let full_local = quat_mul(quat_conjugate(parent_world_rot), new_world_rot);
995
996 let local_rot = if weight < 1.0 {
997 let cur = world
998 .get_component_by_id_as::<TransformComponent>(tc)
999 .map(|t| t.transform.rotation)
1000 .unwrap_or([0.0, 0.0, 0.0, 1.0]);
1001 quat_nlerp(cur, full_local, weight)
1002 } else {
1003 full_local
1004 };
1005
1006 let (t, s) = world
1007 .get_component_by_id_as::<TransformComponent>(tc)
1008 .map(|tc| (tc.transform.translation, tc.transform.scale))
1009 .unwrap_or(([0.0; 3], [1.0, 1.0, 1.0]));
1010 emit.push_intent_now(
1011 tc,
1012 IntentValue::UpdateTransform {
1013 component_ids: vec![tc],
1014 translation: t,
1015 rotation_quat_xyzw: local_rot,
1016 scale: s,
1017 },
1018 );
1019
1020 parent_world_rot = new_world_rot;
1021 }
1022}
1023
1024fn tc_world_pos(world: &World, id: ComponentId) -> [f32; 3] {
1029 world
1030 .get_component_by_id_as::<TransformComponent>(id)
1031 .map(|t| {
1032 let m = t.transform.matrix_world;
1033 [m[3][0], m[3][1], m[3][2]]
1034 })
1035 .unwrap_or([0.0; 3])
1036}
1037
1038fn tc_world_rot(world: &World, id: ComponentId) -> [f32; 4] {
1039 world
1040 .get_component_by_id_as::<TransformComponent>(id)
1041 .map(|t| mat_to_quat(t.transform.matrix_world))
1042 .unwrap_or([0.0, 0.0, 0.0, 1.0])
1043}
1044
1045fn read_bone_rest_rot(world: &World, bone_id: ComponentId) -> [f32; 4] {
1046 world
1047 .children_of(bone_id)
1048 .iter()
1049 .find_map(|&c| world.get_component_by_id_as::<BoneRestPoseComponent>(c))
1050 .map(|rest| rest.rotation)
1051 .or_else(|| {
1052 world
1053 .get_component_by_id_as::<TransformComponent>(bone_id)
1054 .map(|t| t.transform.rotation)
1055 })
1056 .unwrap_or([0.0, 0.0, 0.0, 1.0])
1057}
1058
1059#[cfg(test)]
1060mod tests {
1061 use super::*;
1062 use crate::engine::ecs::CommandQueue;
1063 use crate::engine::ecs::IntentValue;
1064 use crate::engine::ecs::component::{
1065 BoneRestPoseComponent, ComponentRef, IKChainComponent, IKSolver, TransformComponent,
1066 };
1067 use slotmap::Key;
1068
1069 #[derive(Default)]
1070 struct TestEmitter {
1071 intents: Vec<(ComponentId, IntentValue)>,
1072 }
1073
1074 impl SignalEmitter for TestEmitter {
1075 fn push_event(&mut self, _scope: ComponentId, _event: crate::engine::ecs::EventSignal) {}
1076
1077 fn push_intent(&mut self, scope: ComponentId, intent: crate::engine::ecs::IntentSignal) {
1078 self.intents.push((scope, intent.value));
1079 }
1080 }
1081
1082 #[test]
1083 fn xr_driven_chain_skips_until_pose_is_valid() {
1084 let mut world = World::default();
1085 let driver = world.add_component(crate::engine::ecs::component::InputXRComponent::on());
1086 let target = world.add_component(TransformComponent::new());
1087 let root = world.add_component(TransformComponent::new());
1088 world.add_child(driver, target).unwrap();
1089
1090 let mut chain = IKChainComponent::new(
1091 IKSolver::AimConstraint {
1092 offset_yaw: 0.0,
1093 copy_position: false,
1094 target_position_offset: [0.0; 3],
1095 },
1096 target,
1097 root,
1098 );
1099 chain.xr_pose_driver = Some(driver);
1100 let chain_id = world.add_component(chain);
1101 world.add_child(root, chain_id).unwrap();
1102
1103 let mut emit = TestEmitter::default();
1104 tick_chain(chain_id, &mut world, &mut emit);
1105 assert!(emit.intents.is_empty());
1106
1107 world
1108 .get_component_by_id_as_mut::<crate::engine::ecs::component::InputXRComponent>(driver)
1109 .unwrap()
1110 .pose_valid = true;
1111 tick_chain(chain_id, &mut world, &mut emit);
1112 assert!(!emit.intents.is_empty());
1113 }
1114
1115 #[cfg(any())]
1117 #[test]
1118 fn resolves_forward_reference_on_first_tick() {
1119 let mut w = World::default();
1120
1121 let ik_id = w.add_component(
1125 IKChainComponent::new(
1126 IKSolver::TwoBoneIK {
1127 root_joint_id: ComponentId::null(),
1128 mid_joint_id: ComponentId::null(),
1129 pole_direction: [0.0, 1.0, 0.0],
1130 copy_end_rotation: false,
1131 },
1132 ComponentId::null(),
1133 ComponentId::null(),
1134 )
1135 .with_target_source(ComponentRef::Query("#hand".to_string()))
1136 .with_end_effector_source(ComponentRef::Query("#elbow".to_string())),
1137 );
1138
1139 let hand = w.add_component_boxed_named("hand", Box::new(TransformComponent::new()));
1141 let elbow = w.add_component_boxed_named("elbow", Box::new(TransformComponent::new()));
1142
1143 {
1145 let ik = w.get_component_by_id_as::<IKChainComponent>(ik_id).unwrap();
1146 assert!(ik.target_id.is_null());
1147 assert!(ik.end_effector_id.is_null());
1148 }
1149
1150 let mut emit = CommandQueue::new();
1152 let mut sys = IKSystem::new();
1153 sys.tick(&mut w, &mut emit, 0.016);
1154
1155 let ik = w.get_component_by_id_as::<IKChainComponent>(ik_id).unwrap();
1156 assert_eq!(ik.target_id, hand);
1157 assert_eq!(ik.end_effector_id, elbow);
1158 }
1159
1160 #[test]
1161 fn does_not_overwrite_already_resolved_ids() {
1162 let mut w = World::default();
1163 let pre_target = w.add_component(TransformComponent::new());
1164 let pre_ee = w.add_component(TransformComponent::new());
1165 let unrelated = w.add_component_boxed_named("hand", Box::new(TransformComponent::new()));
1166
1167 let ik_id = w.add_component(
1168 IKChainComponent::new(
1169 IKSolver::AimConstraint {
1170 offset_yaw: 0.0,
1171 copy_position: false,
1172 target_position_offset: [0.0, 0.0, 0.0],
1173 },
1174 pre_target,
1175 pre_ee,
1176 )
1177 .with_target_source(ComponentRef::Query("#hand".to_string())),
1181 );
1182
1183 let mut emit = CommandQueue::new();
1184 let mut sys = IKSystem::new();
1185 sys.tick(&mut w, &mut emit, 0.016);
1186
1187 let ik = w.get_component_by_id_as::<IKChainComponent>(ik_id).unwrap();
1188 assert_eq!(ik.target_id, pre_target);
1189 assert_ne!(ik.target_id, unrelated);
1190 assert_eq!(ik.end_effector_id, pre_ee);
1191 }
1192
1193 #[test]
1194 fn resolves_relative_parent_prefixed_sources() {
1195 let mut w = World::default();
1196 let root = w.add_component(TransformComponent::new());
1197 let hand = w.add_component_boxed_named("hand", Box::new(TransformComponent::new()));
1198 let elbow = w.add_component_boxed_named("elbow", Box::new(TransformComponent::new()));
1199 w.add_child(root, hand).unwrap();
1200 w.add_child(root, elbow).unwrap();
1201
1202 let ik_id = w.add_component(
1203 IKChainComponent::new(
1204 IKSolver::TwoBoneIK {
1205 root_joint_id: ComponentId::null(),
1206 mid_joint_id: ComponentId::null(),
1207 pole_direction: [0.0, 1.0, 0.0],
1208 copy_end_rotation: false,
1209 },
1210 ComponentId::null(),
1211 ComponentId::null(),
1212 )
1213 .with_target_source(ComponentRef::Query("../#hand".to_string()))
1214 .with_end_effector_source(ComponentRef::Query("../#elbow".to_string())),
1215 );
1216 w.add_child(root, ik_id).unwrap();
1217
1218 let mut emit = CommandQueue::new();
1219 let mut sys = IKSystem::new();
1220 sys.tick(&mut w, &mut emit, 0.016);
1221
1222 let ik = w.get_component_by_id_as::<IKChainComponent>(ik_id).unwrap();
1223 assert_eq!(ik.target_id, hand);
1224 assert_eq!(ik.end_effector_id, elbow);
1225 }
1226
1227 #[test]
1228 fn resolves_bare_sources_from_local_scope() {
1229 let mut w = World::default();
1230
1231 let unrelated_root = w.add_component(TransformComponent::new());
1232 let unrelated_hand =
1233 w.add_component_boxed_named("hand", Box::new(TransformComponent::new()));
1234 w.add_child(unrelated_root, unrelated_hand).unwrap();
1235
1236 let root = w.add_component(TransformComponent::new());
1237
1238 let ik_id = w.add_component(
1239 IKChainComponent::new(
1240 IKSolver::TwoBoneIK {
1241 root_joint_id: ComponentId::null(),
1242 mid_joint_id: ComponentId::null(),
1243 pole_direction: [0.0, 1.0, 0.0],
1244 copy_end_rotation: false,
1245 },
1246 ComponentId::null(),
1247 ComponentId::null(),
1248 )
1249 .with_target_source(ComponentRef::Query("#hand".to_string()))
1250 .with_end_effector_source(ComponentRef::Query("#elbow".to_string())),
1251 );
1252 w.add_child(root, ik_id).unwrap();
1253 let hand = w.add_component_boxed_named("hand", Box::new(TransformComponent::new()));
1254 let elbow = w.add_component_boxed_named("elbow", Box::new(TransformComponent::new()));
1255 w.add_child(ik_id, hand).unwrap();
1256 w.add_child(ik_id, elbow).unwrap();
1257
1258 let mut emit = CommandQueue::new();
1259 let mut sys = IKSystem::new();
1260 sys.tick(&mut w, &mut emit, 0.016);
1261
1262 let ik = w.get_component_by_id_as::<IKChainComponent>(ik_id).unwrap();
1263 assert_eq!(ik.target_id, hand);
1264 assert_ne!(ik.target_id, unrelated_hand);
1265 assert_eq!(ik.end_effector_id, elbow);
1266 }
1267
1268 #[test]
1269 fn two_bone_forearm_twist_uses_hand_rest_rotation_not_live_hand_local_rotation() {
1270 let mut w = World::default();
1271 let root = w.add_component(TransformComponent::new().with_position(0.0, 0.0, 0.0));
1272 let mid = w.add_component(TransformComponent::new().with_position(1.0, 0.0, 0.0));
1273 let hand = w.add_component(
1274 TransformComponent::new()
1275 .with_position(2.0, 0.0, 0.0)
1276 .with_rotation_quat(quat_from_axis_angle(
1277 [1.0, 0.0, 0.0],
1278 std::f32::consts::FRAC_PI_2,
1279 )),
1280 );
1281 let target = w.add_component(TransformComponent::new().with_position(2.0, 0.0, 0.0));
1282 let hand_rest = w.add_component(BoneRestPoseComponent::new(
1283 [0.0, 0.0, 0.0],
1284 [0.0, 0.0, 0.0, 1.0],
1285 [1.0, 1.0, 1.0],
1286 ));
1287 w.add_child(hand, hand_rest).unwrap();
1288
1289 let mut emit = TestEmitter::default();
1290 solve_two_bone(
1291 &mut w,
1292 &mut emit,
1293 ComponentId::null(),
1294 &[root, mid, hand],
1295 target,
1296 [0.0, 1.0, 0.0],
1297 true,
1298 1.0,
1299 None,
1300 );
1301
1302 let lower_update = emit
1303 .intents
1304 .iter()
1305 .find_map(|(scope, value)| match (scope, value) {
1306 (
1307 scope_id,
1308 IntentValue::UpdateTransform {
1309 rotation_quat_xyzw, ..
1310 },
1311 ) if *scope_id == mid => Some(*rotation_quat_xyzw),
1312 _ => None,
1313 })
1314 .expect("lower arm update intent");
1315
1316 let (_, lower_angle) = quat_to_axis_angle(lower_update);
1317 assert!(
1318 lower_angle.abs() < 1e-4,
1319 "expected no forearm twist from live hand local rotation, got {lower_angle}"
1320 );
1321 }
1322}