1use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
30use std::collections::HashMap;
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
37pub struct BoneId(pub u32);
38
39impl BoneId {
40 pub const ROOT: BoneId = BoneId(0);
41 pub const NONE: BoneId = BoneId(u32::MAX);
42}
43
44impl Default for BoneId {
45 fn default() -> Self { BoneId::NONE }
46}
47
48impl std::fmt::Display for BoneId {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 if *self == BoneId::NONE { write!(f, "NONE") }
51 else { write!(f, "B{}", self.0) }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
61pub enum BoneRole {
62 Root,
63 Spine, Chest, Neck, Head,
64 ShoulderL, ShoulderR,
65 UpperArmL, UpperArmR,
66 ForearmL, ForearmR,
67 HandL, HandR,
68 FingerL(u8), FingerR(u8),
69 HipL, HipR,
70 ThighL, ThighR,
71 ShinL, ShinR,
72 FootL, FootR,
73 ToeL, ToeR,
74 Tail(u8),
75 Custom,
76}
77
78impl BoneRole {
79 pub fn label(self) -> String {
80 match self {
81 BoneRole::Root => "Root".into(),
82 BoneRole::Spine => "Spine".into(),
83 BoneRole::Chest => "Chest".into(),
84 BoneRole::Neck => "Neck".into(),
85 BoneRole::Head => "Head".into(),
86 BoneRole::ShoulderL => "Shoulder.L".into(),
87 BoneRole::ShoulderR => "Shoulder.R".into(),
88 BoneRole::UpperArmL => "UpperArm.L".into(),
89 BoneRole::UpperArmR => "UpperArm.R".into(),
90 BoneRole::ForearmL => "Forearm.L".into(),
91 BoneRole::ForearmR => "Forearm.R".into(),
92 BoneRole::HandL => "Hand.L".into(),
93 BoneRole::HandR => "Hand.R".into(),
94 BoneRole::FingerL(n) => format!("Finger.L.{n}"),
95 BoneRole::FingerR(n) => format!("Finger.R.{n}"),
96 BoneRole::HipL => "Hip.L".into(),
97 BoneRole::HipR => "Hip.R".into(),
98 BoneRole::ThighL => "Thigh.L".into(),
99 BoneRole::ThighR => "Thigh.R".into(),
100 BoneRole::ShinL => "Shin.L".into(),
101 BoneRole::ShinR => "Shin.R".into(),
102 BoneRole::FootL => "Foot.L".into(),
103 BoneRole::FootR => "Foot.R".into(),
104 BoneRole::ToeL => "Toe.L".into(),
105 BoneRole::ToeR => "Toe.R".into(),
106 BoneRole::Tail(n) => format!("Tail.{n}"),
107 BoneRole::Custom => "Custom".into(),
108 }
109 }
110
111 pub fn is_left(self) -> bool {
112 matches!(self,
113 BoneRole::ShoulderL | BoneRole::UpperArmL | BoneRole::ForearmL |
114 BoneRole::HandL | BoneRole::FingerL(_) | BoneRole::HipL |
115 BoneRole::ThighL | BoneRole::ShinL | BoneRole::FootL | BoneRole::ToeL
116 )
117 }
118
119 pub fn mirror(self) -> BoneRole {
120 match self {
121 BoneRole::ShoulderL => BoneRole::ShoulderR,
122 BoneRole::ShoulderR => BoneRole::ShoulderL,
123 BoneRole::UpperArmL => BoneRole::UpperArmR,
124 BoneRole::UpperArmR => BoneRole::UpperArmL,
125 BoneRole::ForearmL => BoneRole::ForearmR,
126 BoneRole::ForearmR => BoneRole::ForearmL,
127 BoneRole::HandL => BoneRole::HandR,
128 BoneRole::HandR => BoneRole::HandL,
129 BoneRole::FingerL(n) => BoneRole::FingerR(n),
130 BoneRole::FingerR(n) => BoneRole::FingerL(n),
131 BoneRole::HipL => BoneRole::HipR,
132 BoneRole::HipR => BoneRole::HipL,
133 BoneRole::ThighL => BoneRole::ThighR,
134 BoneRole::ThighR => BoneRole::ThighL,
135 BoneRole::ShinL => BoneRole::ShinR,
136 BoneRole::ShinR => BoneRole::ShinL,
137 BoneRole::FootL => BoneRole::FootR,
138 BoneRole::FootR => BoneRole::FootL,
139 BoneRole::ToeL => BoneRole::ToeR,
140 BoneRole::ToeR => BoneRole::ToeL,
141 other => other,
142 }
143 }
144}
145
146impl Default for BoneRole { fn default() -> Self { BoneRole::Custom } }
147
148#[derive(Debug, Clone, PartialEq)]
154pub struct BoneConstraint {
155 pub min_angles: Vec3,
157 pub max_angles: Vec3,
159 pub locked: [bool; 3],
161}
162
163impl Default for BoneConstraint {
164 fn default() -> Self {
165 let pi = std::f32::consts::PI;
166 Self {
167 min_angles: Vec3::new(-pi, -pi, -pi),
168 max_angles: Vec3::new( pi, pi, pi),
169 locked: [false; 3],
170 }
171 }
172}
173
174impl BoneConstraint {
175 pub fn hinge(axis: usize, min_deg: f32, max_deg: f32) -> Self {
176 let pi = std::f32::consts::PI;
177 let mut c = Self {
178 min_angles: Vec3::new(-pi, -pi, -pi),
179 max_angles: Vec3::new( pi, pi, pi),
180 locked: [true; 3],
181 };
182 c.locked[axis] = false;
183 let to_rad = |d: f32| d * std::f32::consts::PI / 180.0;
184 match axis {
185 0 => { c.min_angles.x = to_rad(min_deg); c.max_angles.x = to_rad(max_deg); }
186 1 => { c.min_angles.y = to_rad(min_deg); c.max_angles.y = to_rad(max_deg); }
187 2 => { c.min_angles.z = to_rad(min_deg); c.max_angles.z = to_rad(max_deg); }
188 _ => {}
189 }
190 c
191 }
192
193 pub fn ball_socket(half_angle_deg: f32) -> Self {
194 let r = half_angle_deg * std::f32::consts::PI / 180.0;
195 Self {
196 min_angles: Vec3::splat(-r),
197 max_angles: Vec3::splat( r),
198 locked: [false; 3],
199 }
200 }
201
202 pub fn clamp_rotation(&self, euler: Vec3) -> Vec3 {
203 let mut out = euler;
204 let mins = [self.min_angles.x, self.min_angles.y, self.min_angles.z];
205 let maxs = [self.max_angles.x, self.max_angles.y, self.max_angles.z];
206 let components = [&mut out.x, &mut out.y, &mut out.z];
207 for (i, v) in components.into_iter().enumerate() {
208 if self.locked[i] {
209 *v = 0.0;
210 } else {
211 *v = v.clamp(mins[i], maxs[i]);
212 }
213 }
214 out
215 }
216}
217
218#[derive(Debug, Clone)]
224pub struct BoneDesc {
225 pub id: BoneId,
226 pub name: String,
227 pub role: BoneRole,
228 pub parent: BoneId,
229 pub children: Vec<BoneId>,
230 pub head: Vec3,
232 pub tail: Vec3,
234 pub roll: f32,
236 pub envelope_radius: f32,
238 pub envelope_head_dist: f32,
240 pub envelope_tail_dist: f32,
241 pub rest_rotation: Quat,
243 pub pose_rotation: Quat,
245 pub pose_translate: Vec3,
247 pub constraint: BoneConstraint,
249 pub ik_enabled: bool,
251 pub ik_stretch: f32,
253 pub visible: bool,
254 pub locked: bool,
255}
256
257impl BoneDesc {
258 pub fn new(id: BoneId, name: impl Into<String>, head: Vec3, tail: Vec3) -> Self {
259 Self {
260 id,
261 name: name.into(),
262 role: BoneRole::Custom,
263 parent: BoneId::NONE,
264 children: Vec::new(),
265 head, tail,
266 roll: 0.0,
267 envelope_radius: 0.05,
268 envelope_head_dist: 0.0,
269 envelope_tail_dist: 0.0,
270 rest_rotation: Quat::IDENTITY,
271 pose_rotation: Quat::IDENTITY,
272 pose_translate: Vec3::ZERO,
273 constraint: BoneConstraint::default(),
274 ik_enabled: false,
275 ik_stretch: 0.0,
276 visible: true,
277 locked: false,
278 }
279 }
280
281 pub fn vector(&self) -> Vec3 { self.tail - self.head }
283
284 pub fn length(&self) -> f32 { self.vector().length() }
286
287 pub fn direction(&self) -> Vec3 { self.vector().normalize_or_zero() }
289
290 pub fn midpoint(&self) -> Vec3 { (self.head + self.tail) * 0.5 }
292
293 pub fn bone_matrix(&self) -> Mat4 {
295 let y = self.direction();
296 let ref_up = if y.dot(Vec3::Z).abs() < 0.99 { Vec3::Z } else { Vec3::Y };
297 let x = y.cross(ref_up).normalize_or_zero();
298 let z = x.cross(y);
299 Mat4::from_cols(
300 x.extend(0.0),
301 y.extend(0.0),
302 z.extend(0.0),
303 self.head.extend(1.0),
304 )
305 }
306
307 pub fn envelope_weight(&self, p: Vec3) -> f32 {
309 let len = self.length().max(1e-6);
310 let t = (p - self.head).dot(self.direction()) / len;
311 let t_clamped = t.clamp(0.0, 1.0);
312 let closest = self.head + self.direction() * (t_clamped * len);
313 let d = (p - closest).length();
314 let r = self.envelope_radius;
315 if d >= r { return 0.0; }
316 let q = d / r;
317 let w = 1.0 - q * q * (3.0 - 2.0 * q); w.max(0.0)
319 }
320
321 pub fn world_head(&self, parent_world: Mat4) -> Vec3 {
323 parent_world.transform_point3(self.head)
324 }
325
326 pub fn world_tail(&self, parent_world: Mat4) -> Vec3 {
328 parent_world.transform_point3(self.tail)
329 }
330}
331
332#[derive(Debug, Clone)]
338pub struct IkChain {
339 pub name: String,
340 pub bones: Vec<BoneId>,
342 pub target: Vec3,
343 pub pole: Option<Vec3>,
344 pub iterations: u32,
345 pub tolerance: f32,
346}
347
348impl IkChain {
349 pub fn new(name: impl Into<String>, bones: Vec<BoneId>, target: Vec3) -> Self {
350 Self {
351 name: name.into(),
352 bones,
353 target,
354 pole: None,
355 iterations: 10,
356 tolerance: 0.001,
357 }
358 }
359
360 pub fn solve_ccd(&self, skeleton: &mut BoneSkeleton) -> f32 {
363 for _iter in 0..self.iterations {
364 let tip_pos = self.tip_position(skeleton);
365 let dist = (tip_pos - self.target).length();
366 if dist < self.tolerance { return dist; }
367
368 for i in (0..self.bones.len().saturating_sub(1)).rev() {
370 let bone_id = self.bones[i];
371 if let Some(bone) = skeleton.get_mut(bone_id) {
372 if bone.locked { continue; }
373 }
374 let tip_pos = self.tip_position(skeleton);
375 let bone_head = skeleton.world_head(bone_id);
376 let to_tip = (tip_pos - bone_head).normalize_or_zero();
377 let to_target = (self.target - bone_head).normalize_or_zero();
378 let axis = to_tip.cross(to_target).normalize_or_zero();
379 let angle = to_tip.dot(to_target).clamp(-1.0, 1.0).acos();
380 if angle.abs() < 1e-5 { continue; }
381 let delta = Quat::from_axis_angle(axis, angle * 0.5);
382 if let Some(bone) = skeleton.get_mut(bone_id) {
383 let new_rot = delta * bone.pose_rotation;
384 let euler = new_rot.to_euler(glam::EulerRot::XYZ);
385 let euler = Vec3::new(euler.0, euler.1, euler.2);
386 let clamped = bone.constraint.clamp_rotation(euler);
387 bone.pose_rotation = Quat::from_euler(
388 glam::EulerRot::XYZ, clamped.x, clamped.y, clamped.z
389 );
390 }
391 }
392 }
393 let tip_pos = self.tip_position(skeleton);
394 (tip_pos - self.target).length()
395 }
396
397 fn tip_position(&self, skeleton: &BoneSkeleton) -> Vec3 {
398 if let Some(&tip_id) = self.bones.last() {
399 if let Some(bone) = skeleton.get(tip_id) {
400 return bone.tail;
401 }
402 }
403 Vec3::ZERO
404 }
405}
406
407#[derive(Debug, Clone, Default)]
413pub struct SkinWeight {
414 pub bones: [BoneId; 4],
415 pub weights: [f32; 4],
416}
417
418impl SkinWeight {
419 pub fn new_single(bone: BoneId, weight: f32) -> Self {
420 Self {
421 bones: [bone, BoneId::NONE, BoneId::NONE, BoneId::NONE],
422 weights: [weight, 0.0, 0.0, 0.0],
423 }
424 }
425
426 pub fn normalise(&mut self) {
428 let sum: f32 = self.weights.iter().sum();
429 if sum > 1e-6 {
430 for w in &mut self.weights { *w /= sum; }
431 }
432 }
433
434 pub fn from_unsorted(mut pairs: Vec<(BoneId, f32)>) -> Self {
436 pairs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
437 pairs.truncate(4);
438 let mut sw = SkinWeight::default();
439 for (i, (bone, w)) in pairs.into_iter().enumerate() {
440 sw.bones[i] = bone;
441 sw.weights[i] = w;
442 }
443 sw.normalise();
444 sw
445 }
446}
447
448#[derive(Debug)]
454pub struct BoneSkeleton {
455 bones: HashMap<BoneId, BoneDesc>,
456 next_id: u32,
457 pub root: BoneId,
458 pub ik_chains: Vec<IkChain>,
459 pub skin_weights: Vec<SkinWeight>,
461}
462
463impl BoneSkeleton {
464 pub fn new() -> Self {
465 Self {
466 bones: HashMap::new(),
467 next_id: 1,
468 root: BoneId::NONE,
469 ik_chains: Vec::new(),
470 skin_weights: Vec::new(),
471 }
472 }
473
474 fn alloc_id(&mut self) -> BoneId {
477 let id = BoneId(self.next_id);
478 self.next_id += 1;
479 id
480 }
481
482 pub fn add_bone(&mut self, name: impl Into<String>, head: Vec3, tail: Vec3) -> BoneId {
483 let id = self.alloc_id();
484 let bone = BoneDesc::new(id, name, head, tail);
485 if self.root == BoneId::NONE { self.root = id; }
486 self.bones.insert(id, bone);
487 id
488 }
489
490 pub fn remove_bone(&mut self, id: BoneId) -> Option<BoneDesc> {
491 let bone = self.bones.remove(&id)?;
492 if let Some(parent) = self.get_mut(bone.parent) {
494 parent.children.retain(|&c| c != id);
495 }
496 let children: Vec<_> = bone.children.clone();
498 for child_id in children {
499 if let Some(child) = self.get_mut(child_id) {
500 child.parent = BoneId::NONE;
501 }
502 }
503 Some(bone)
504 }
505
506 pub fn get(&self, id: BoneId) -> Option<&BoneDesc> { self.bones.get(&id) }
507 pub fn get_mut(&mut self, id: BoneId) -> Option<&mut BoneDesc> { self.bones.get_mut(&id) }
508
509 pub fn bones(&self) -> impl Iterator<Item = &BoneDesc> { self.bones.values() }
510 pub fn bone_count(&self) -> usize { self.bones.len() }
511
512 pub fn set_parent(&mut self, child: BoneId, parent: BoneId) {
515 if let Some(old_parent_id) = self.bones.get(&child).map(|b| b.parent) {
517 if let Some(old_parent) = self.bones.get_mut(&old_parent_id) {
518 old_parent.children.retain(|&c| c != child);
519 }
520 }
521 if let Some(bone) = self.bones.get_mut(&child) {
523 bone.parent = parent;
524 }
525 if let Some(parent_bone) = self.bones.get_mut(&parent) {
526 if !parent_bone.children.contains(&child) {
527 parent_bone.children.push(child);
528 }
529 }
530 }
531
532 pub fn children_of(&self, id: BoneId) -> &[BoneId] {
533 self.bones.get(&id).map(|b| b.children.as_slice()).unwrap_or(&[])
534 }
535
536 pub fn ancestors_of(&self, id: BoneId) -> Vec<BoneId> {
537 let mut result = Vec::new();
538 let mut current = id;
539 while let Some(bone) = self.bones.get(¤t) {
540 if bone.parent == BoneId::NONE { break; }
541 result.push(bone.parent);
542 current = bone.parent;
543 }
544 result
545 }
546
547 pub fn depth_of(&self, id: BoneId) -> usize {
548 self.ancestors_of(id).len()
549 }
550
551 pub fn world_head(&self, id: BoneId) -> Vec3 {
555 self.world_matrix(id).transform_point3(Vec3::ZERO)
556 }
557
558 pub fn world_matrix(&self, id: BoneId) -> Mat4 {
560 let mut mats = Vec::new();
561 let mut current = id;
562 loop {
563 let Some(bone) = self.bones.get(¤t) else { break; };
564 let local = Mat4::from_rotation_translation(bone.pose_rotation, bone.pose_translate);
565 mats.push(local);
566 if bone.parent == BoneId::NONE { break; }
567 current = bone.parent;
568 }
569 let mut result = Mat4::IDENTITY;
570 for m in mats.iter().rev() { result = result * *m; }
571 result
572 }
573
574 pub fn bind_weights(&mut self, surface_points: &[Vec3]) {
578 self.skin_weights.clear();
579 for &p in surface_points {
580 let mut pairs: Vec<(BoneId, f32)> = self.bones.values()
581 .map(|bone| (bone.id, bone.envelope_weight(p)))
582 .filter(|&(_, w)| w > 1e-4)
583 .collect();
584 if pairs.is_empty() {
585 if let Some(nearest) = self.bones.values()
587 .min_by(|a, b| {
588 let da = (a.midpoint() - p).length();
589 let db = (b.midpoint() - p).length();
590 da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
591 })
592 {
593 pairs.push((nearest.id, 1.0));
594 }
595 }
596 self.skin_weights.push(SkinWeight::from_unsorted(pairs));
597 }
598 }
599
600 pub fn build_humanoid(height: f32) -> Self {
604 let mut sk = BoneSkeleton::new();
605
606 let h = height;
608 let root_id = sk.add_bone("Root", Vec3::new(0.0, h * 0.52, 0.0), Vec3::new(0.0, h * 0.55, 0.0));
609 sk.bones.get_mut(&root_id).unwrap().role = BoneRole::Root;
610
611 let spine_id = sk.add_bone("Spine", Vec3::new(0.0, h * 0.55, 0.0), Vec3::new(0.0, h * 0.68, 0.0));
612 sk.bones.get_mut(&spine_id).unwrap().role = BoneRole::Spine;
613 sk.set_parent(spine_id, root_id);
614
615 let chest_id = sk.add_bone("Chest", Vec3::new(0.0, h * 0.68, 0.0), Vec3::new(0.0, h * 0.78, 0.0));
616 sk.bones.get_mut(&chest_id).unwrap().role = BoneRole::Chest;
617 sk.set_parent(chest_id, spine_id);
618
619 let neck_id = sk.add_bone("Neck", Vec3::new(0.0, h * 0.83, 0.0), Vec3::new(0.0, h * 0.88, 0.0));
620 sk.bones.get_mut(&neck_id).unwrap().role = BoneRole::Neck;
621 sk.set_parent(neck_id, chest_id);
622
623 let head_id = sk.add_bone("Head", Vec3::new(0.0, h * 0.88, 0.0), Vec3::new(0.0, h * 1.00, 0.0));
624 sk.bones.get_mut(&head_id).unwrap().role = BoneRole::Head;
625 sk.set_parent(head_id, neck_id);
626
627 let sh_l = sk.add_bone("Shoulder.L", Vec3::new(-h*0.14, h*0.78, 0.0), Vec3::new(-h*0.20, h*0.78, 0.0));
629 sk.bones.get_mut(&sh_l).unwrap().role = BoneRole::ShoulderL;
630 sk.set_parent(sh_l, chest_id);
631
632 let ua_l = sk.add_bone("UpperArm.L", Vec3::new(-h*0.20, h*0.78, 0.0), Vec3::new(-h*0.38, h*0.68, 0.0));
633 sk.bones.get_mut(&ua_l).unwrap().role = BoneRole::UpperArmL;
634 sk.set_parent(ua_l, sh_l);
635 let mut c = BoneConstraint::ball_socket(90.0); c.locked[2] = false;
636 sk.bones.get_mut(&ua_l).unwrap().constraint = c;
637
638 let fa_l = sk.add_bone("Forearm.L", Vec3::new(-h*0.38, h*0.68, 0.0), Vec3::new(-h*0.48, h*0.52, 0.0));
639 sk.bones.get_mut(&fa_l).unwrap().role = BoneRole::ForearmL;
640 sk.set_parent(fa_l, ua_l);
641 sk.bones.get_mut(&fa_l).unwrap().constraint = BoneConstraint::hinge(0, 0.0, 145.0);
642
643 let hand_l = sk.add_bone("Hand.L", Vec3::new(-h*0.48, h*0.52, 0.0), Vec3::new(-h*0.52, h*0.45, 0.0));
644 sk.bones.get_mut(&hand_l).unwrap().role = BoneRole::HandL;
645 sk.set_parent(hand_l, fa_l);
646
647 let sh_r = sk.add_bone("Shoulder.R", Vec3::new( h*0.14, h*0.78, 0.0), Vec3::new( h*0.20, h*0.78, 0.0));
648 sk.bones.get_mut(&sh_r).unwrap().role = BoneRole::ShoulderR;
649 sk.set_parent(sh_r, chest_id);
650
651 let ua_r = sk.add_bone("UpperArm.R", Vec3::new( h*0.20, h*0.78, 0.0), Vec3::new( h*0.38, h*0.68, 0.0));
652 sk.bones.get_mut(&ua_r).unwrap().role = BoneRole::UpperArmR;
653 sk.set_parent(ua_r, sh_r);
654
655 let fa_r = sk.add_bone("Forearm.R", Vec3::new( h*0.38, h*0.68, 0.0), Vec3::new( h*0.48, h*0.52, 0.0));
656 sk.bones.get_mut(&fa_r).unwrap().role = BoneRole::ForearmR;
657 sk.set_parent(fa_r, ua_r);
658 sk.bones.get_mut(&fa_r).unwrap().constraint = BoneConstraint::hinge(0, 0.0, 145.0);
659
660 let hand_r = sk.add_bone("Hand.R", Vec3::new( h*0.48, h*0.52, 0.0), Vec3::new( h*0.52, h*0.45, 0.0));
661 sk.bones.get_mut(&hand_r).unwrap().role = BoneRole::HandR;
662 sk.set_parent(hand_r, fa_r);
663
664 let hip_l = sk.add_bone("Hip.L", Vec3::new(-h*0.10, h*0.52, 0.0), Vec3::new(-h*0.12, h*0.50, 0.0));
666 sk.bones.get_mut(&hip_l).unwrap().role = BoneRole::HipL;
667 sk.set_parent(hip_l, root_id);
668
669 let thigh_l = sk.add_bone("Thigh.L", Vec3::new(-h*0.12, h*0.50, 0.0), Vec3::new(-h*0.14, h*0.28, 0.0));
670 sk.bones.get_mut(&thigh_l).unwrap().role = BoneRole::ThighL;
671 sk.set_parent(thigh_l, hip_l);
672
673 let shin_l = sk.add_bone("Shin.L", Vec3::new(-h*0.14, h*0.28, 0.0), Vec3::new(-h*0.14, h*0.06, 0.0));
674 sk.bones.get_mut(&shin_l).unwrap().role = BoneRole::ShinL;
675 sk.set_parent(shin_l, thigh_l);
676 sk.bones.get_mut(&shin_l).unwrap().constraint = BoneConstraint::hinge(0, -145.0, 0.0);
677
678 let foot_l = sk.add_bone("Foot.L", Vec3::new(-h*0.14, h*0.06, 0.0), Vec3::new(-h*0.14,-h*0.01, h*0.08));
679 sk.bones.get_mut(&foot_l).unwrap().role = BoneRole::FootL;
680 sk.set_parent(foot_l, shin_l);
681
682 let hip_r = sk.add_bone("Hip.R", Vec3::new( h*0.10, h*0.52, 0.0), Vec3::new( h*0.12, h*0.50, 0.0));
683 sk.bones.get_mut(&hip_r).unwrap().role = BoneRole::HipR;
684 sk.set_parent(hip_r, root_id);
685
686 let thigh_r = sk.add_bone("Thigh.R", Vec3::new( h*0.12, h*0.50, 0.0), Vec3::new( h*0.14, h*0.28, 0.0));
687 sk.bones.get_mut(&thigh_r).unwrap().role = BoneRole::ThighR;
688 sk.set_parent(thigh_r, hip_r);
689
690 let shin_r = sk.add_bone("Shin.R", Vec3::new( h*0.14, h*0.28, 0.0), Vec3::new( h*0.14, h*0.06, 0.0));
691 sk.bones.get_mut(&shin_r).unwrap().role = BoneRole::ShinR;
692 sk.set_parent(shin_r, thigh_r);
693 sk.bones.get_mut(&shin_r).unwrap().constraint = BoneConstraint::hinge(0, -145.0, 0.0);
694
695 let foot_r = sk.add_bone("Foot.R", Vec3::new( h*0.14, h*0.06, 0.0), Vec3::new( h*0.14,-h*0.01, h*0.08));
696 sk.bones.get_mut(&foot_r).unwrap().role = BoneRole::FootR;
697 sk.set_parent(foot_r, shin_r);
698
699 sk.ik_chains.push(IkChain::new("IK.Arm.L",
701 vec![ua_l, fa_l, hand_l],
702 sk.bones[&hand_l].tail,
703 ));
704 sk.ik_chains.push(IkChain::new("IK.Arm.R",
705 vec![ua_r, fa_r, hand_r],
706 sk.bones[&hand_r].tail,
707 ));
708 sk.ik_chains.push(IkChain::new("IK.Leg.L",
709 vec![thigh_l, shin_l, foot_l],
710 sk.bones[&foot_l].tail,
711 ));
712 sk.ik_chains.push(IkChain::new("IK.Leg.R",
713 vec![thigh_r, shin_r, foot_r],
714 sk.bones[&foot_r].tail,
715 ));
716
717 sk.root = root_id;
718 sk
719 }
720}
721
722impl Default for BoneSkeleton { fn default() -> Self { Self::new() } }
723
724#[derive(Debug, Clone, Copy, PartialEq)]
729pub enum RiggerMode {
730 Idle,
731 PlacingHead,
732 PlacingTail { head: [f32; 3] },
733 WeightPainting { bone: BoneId },
734 EditingConstraint { bone: BoneId },
735 IkSetup,
736}
737
738impl Default for RiggerMode { fn default() -> Self { RiggerMode::Idle } }
739
740#[derive(Debug)]
746pub struct BoneRigger {
747 pub skeleton: BoneSkeleton,
748 pub mode: RiggerMode,
749 pub selected: Option<BoneId>,
750 pub hovered: Option<BoneId>,
751 undo_stack: Vec<RiggerUndoEntry>,
752 redo_stack: Vec<RiggerUndoEntry>,
753 pub show_axes: bool,
755 pub show_envelopes: bool,
757 pub show_ik: bool,
759 pub show_weights: bool,
761 pub mirror_x: bool,
763 pub degrees_mode: bool,
765 pub pending_name: String,
767 pub pending_role: BoneRole,
769}
770
771#[derive(Debug, Clone)]
772enum RiggerUndoEntry {
773 AddBone(BoneId),
774 RemoveBone(BoneDesc),
775 SetParent { child: BoneId, old_parent: BoneId, new_parent: BoneId },
776 MoveBone { id: BoneId, old_head: Vec3, old_tail: Vec3, new_head: Vec3, new_tail: Vec3 },
777}
778
779impl BoneRigger {
780 pub fn new() -> Self {
781 Self {
782 skeleton: BoneSkeleton::new(),
783 mode: RiggerMode::Idle,
784 selected: None,
785 hovered: None,
786 undo_stack: Vec::new(),
787 redo_stack: Vec::new(),
788 show_axes: true,
789 show_envelopes: true,
790 show_ik: true,
791 show_weights: false,
792 mirror_x: false,
793 degrees_mode: true,
794 pending_name: "Bone".into(),
795 pending_role: BoneRole::Custom,
796 }
797 }
798
799 pub fn surface_click(&mut self, surface_p: Vec3) {
803 match self.mode {
804 RiggerMode::Idle => {
805 self.mode = RiggerMode::PlacingHead;
806 }
807 RiggerMode::PlacingHead => {
808 self.mode = RiggerMode::PlacingTail { head: surface_p.to_array() };
809 }
810 RiggerMode::PlacingTail { head } => {
811 let head_p = Vec3::from(head);
812 let id = self.skeleton.add_bone(
813 self.pending_name.clone(), head_p, surface_p,
814 );
815 if let Some(bone) = self.skeleton.get_mut(id) {
816 bone.role = self.pending_role;
817 }
818 if let Some(sel) = self.selected {
819 self.skeleton.set_parent(id, sel);
820 }
821 if self.mirror_x {
823 let mirrored_head = Vec3::new(-head_p.x, head_p.y, head_p.z);
824 let mirrored_tail = Vec3::new(-surface_p.x, surface_p.y, surface_p.z);
825 let mid = self.skeleton.add_bone(
826 format!("{}.mirror", self.pending_name),
827 mirrored_head, mirrored_tail,
828 );
829 if let Some(bone) = self.skeleton.get_mut(mid) {
830 bone.role = self.pending_role.mirror();
831 }
832 if let Some(sel) = self.selected {
833 self.skeleton.set_parent(mid, sel);
834 }
835 }
836 self.undo_stack.push(RiggerUndoEntry::AddBone(id));
837 self.redo_stack.clear();
838 self.selected = Some(id);
839 self.mode = RiggerMode::Idle;
840 self.pending_name = format!("Bone.{}", self.skeleton.bone_count());
841 }
842 _ => {}
843 }
844 }
845
846 pub fn select_nearest(&mut self, ray_origin: Vec3, ray_dir: Vec3) -> Option<BoneId> {
849 let mut best_id = None;
850 let mut best_dist = f32::MAX;
851 for bone in self.skeleton.bones() {
852 let ab = bone.tail - bone.head;
854 let ao = ray_origin - bone.head;
855 let ab_len = ab.length().max(1e-6);
856 let t = ao.dot(ab) / (ab_len * ab_len);
857 let closest = bone.head + ab * t.clamp(0.0, 1.0);
858 let d = (closest - ray_origin).length();
859 if d < best_dist {
860 best_dist = d;
861 best_id = Some(bone.id);
862 }
863 }
864 self.selected = best_id;
865 best_id
866 }
867
868 pub fn undo(&mut self) {
871 if let Some(entry) = self.undo_stack.pop() {
872 match entry {
873 RiggerUndoEntry::AddBone(id) => { self.skeleton.remove_bone(id); }
874 RiggerUndoEntry::RemoveBone(bone) => {
875 self.skeleton.bones.insert(bone.id, bone);
876 }
877 RiggerUndoEntry::MoveBone { id, old_head, old_tail, .. } => {
878 if let Some(bone) = self.skeleton.get_mut(id) {
879 bone.head = old_head;
880 bone.tail = old_tail;
881 }
882 }
883 RiggerUndoEntry::SetParent { child, old_parent, .. } => {
884 self.skeleton.set_parent(child, old_parent);
885 }
886 }
887 }
888 }
889
890 pub fn status_line(&self) -> String {
893 let mode = match self.mode {
894 RiggerMode::Idle => "Idle".to_string(),
895 RiggerMode::PlacingHead => "Click SDF surface for bone HEAD".to_string(),
896 RiggerMode::PlacingTail{..} => "Click SDF surface for bone TAIL".to_string(),
897 RiggerMode::WeightPainting{bone} => format!("Weight-painting bone {}", bone),
898 RiggerMode::EditingConstraint{bone} => format!("Editing constraint on {}", bone),
899 RiggerMode::IkSetup => "IK Setup".to_string(),
900 };
901 format!(
902 "Bone Rigger — {} | {} bones | sel {:?} | {}",
903 mode, self.skeleton.bone_count(), self.selected,
904 if self.mirror_x { "MIRROR" } else { "" }
905 )
906 }
907}
908
909impl Default for BoneRigger { fn default() -> Self { Self::new() } }
910
911#[cfg(test)]
916mod tests {
917 use super::*;
918
919 #[test]
920 fn humanoid_skeleton_builds() {
921 let sk = BoneSkeleton::build_humanoid(1.8);
922 assert!(sk.bone_count() > 15);
923 assert!(sk.root != BoneId::NONE);
924 }
925
926 #[test]
927 fn envelope_weight() {
928 let sk = BoneSkeleton::build_humanoid(1.8);
929 for bone in sk.bones() {
930 let mid = bone.midpoint();
931 let w = bone.envelope_weight(mid);
932 if bone.envelope_radius > 0.0 {
935 assert!(w >= 0.0);
936 }
937 }
938 }
939
940 #[test]
941 fn skin_weight_normalises() {
942 let mut sw = SkinWeight::from_unsorted(vec![
943 (BoneId(1), 2.0), (BoneId(2), 3.0), (BoneId(3), 1.0),
944 ]);
945 sw.normalise();
946 let sum: f32 = sw.weights.iter().sum();
947 assert!((sum - 1.0).abs() < 1e-5);
948 }
949
950 #[test]
951 fn constraint_hinge_clamps() {
952 let c = BoneConstraint::hinge(0, -90.0, 0.0);
953 let e = Vec3::new(2.0, 0.5, 0.1);
954 let clamped = c.clamp_rotation(e);
955 let max_x = 0.0f32;
956 assert!(clamped.x <= max_x + 1e-5);
957 assert!((clamped.y).abs() < 1e-5); assert!((clamped.z).abs() < 1e-5); }
960
961 #[test]
962 fn rigger_place_bone() {
963 let mut r = BoneRigger::new();
964 r.surface_click(Vec3::ZERO); r.surface_click(Vec3::Y); r.surface_click(Vec3::Y * 2.0); assert_eq!(r.skeleton.bone_count(), 1);
968 }
969
970 #[test]
971 fn rigger_undo() {
972 let mut r = BoneRigger::new();
973 r.surface_click(Vec3::ZERO);
974 r.surface_click(Vec3::Y);
975 r.surface_click(Vec3::Y * 2.0);
976 assert_eq!(r.skeleton.bone_count(), 1);
977 r.undo();
978 assert_eq!(r.skeleton.bone_count(), 0);
979 }
980}