concinnity_core/components/
skinned_mesh.rs1use crate::components::{SkeletonJoint, SkinnedMesh};
11
12pub fn build_skeleton_from_joint_defs(defs: &[SkeletonJoint]) -> crate::gfx::skeleton::Skeleton {
19 use crate::gfx::skeleton as skinning;
20 let joints = defs
21 .iter()
22 .map(|jd| skinning::Joint {
23 name: jd.name.clone(),
24 parent: (jd.parent >= 0).then_some(jd.parent as usize),
25 bind: skinning::JointPose {
26 translation: jd.translation,
27 rotation_deg: jd.rotation_deg,
28 scale: jd.scale,
29 },
30 })
31 .collect();
32 skinning::Skeleton::new(joints)
33}
34
35pub trait SkinnedMeshGeometry {
40 fn model_matrix(&self) -> [[f32; 4]; 4];
42}
43
44impl SkinnedMeshGeometry for SkinnedMesh {
45 fn model_matrix(&self) -> [[f32; 4]; 4] {
48 crate::gfx::skeleton::JointPose {
49 translation: self.position,
50 rotation_deg: self.rotation_deg,
51 scale: self.scale,
52 }
53 .to_matrix()
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use crate::components::{CharacterCapsule, SkinnedVertexData};
61 use alloc::vec;
62
63 #[test]
64 fn build_skeleton_from_joint_defs_preserves_count_and_parent_links() {
65 let defs = vec![
66 SkeletonJoint {
67 name: "root".into(),
68 parent: -1,
69 translation: [0.0, 0.0, 0.0],
70 rotation_deg: [0.0, 0.0, 0.0],
71 scale: [1.0, 1.0, 1.0],
72 },
73 SkeletonJoint {
74 name: "tip".into(),
75 parent: 0,
76 translation: [0.0, 1.0, 0.0],
77 rotation_deg: [0.0, 0.0, 0.0],
78 scale: [1.0, 1.0, 1.0],
79 },
80 SkeletonJoint {
81 name: "tail".into(),
82 parent: 1,
83 translation: [0.0, 1.0, 0.0],
84 rotation_deg: [0.0, 0.0, 0.0],
85 scale: [1.0, 1.0, 1.0],
86 },
87 ];
88 let skel = build_skeleton_from_joint_defs(&defs);
89 assert_eq!(skel.len(), 3);
90 let joints = skel.joints();
91 assert_eq!(joints[0].parent, None);
92 assert_eq!(joints[1].parent, Some(0));
93 assert_eq!(joints[2].parent, Some(1));
94 }
95
96 #[test]
97 fn build_skeleton_from_joint_defs_treats_negative_parent_as_root() {
98 let defs = vec![SkeletonJoint {
102 name: "root".into(),
103 parent: -42,
104 translation: [1.0, 2.0, 3.0],
105 rotation_deg: [0.0, 0.0, 0.0],
106 scale: [1.0, 1.0, 1.0],
107 }];
108 let skel = build_skeleton_from_joint_defs(&defs);
109 assert_eq!(skel.joints()[0].parent, None);
110 }
111
112 #[test]
113 fn model_matrix_places_translation_in_last_column() {
114 let mesh = SkinnedMesh {
115 position: [2.0, 3.0, 4.0],
116 scale: [1.0, 1.0, 1.0],
117 ..SkinnedMesh::default()
118 };
119 let m = mesh.model_matrix();
120 assert_eq!([m[3][0], m[3][1], m[3][2]], [2.0, 3.0, 4.0]);
123 assert_eq!(m[3][3], 1.0);
124 assert_eq!(m[0][0], 1.0);
125 }
126
127 #[test]
128 fn skinned_vertex_defaults_fill_color_uv_and_weights() {
129 let v: SkinnedVertexData =
132 serde_json::from_value(serde_json::json!({"pos": [0.0, 0.0, 0.0]})).unwrap();
133 assert_eq!(v.color, [1.0, 1.0, 1.0]);
134 assert_eq!(v.uv, [0.0, 0.0]);
135 assert_eq!(v.weights, [1.0, 0.0, 0.0, 0.0]);
136 assert_eq!(v.joints, [0, 0, 0, 0]);
137 }
138
139 #[test]
140 fn capsule_joint_defaults() {
141 let cap = CharacterCapsule::default();
142 assert_eq!(cap.half_height, 0.5);
143 assert_eq!(cap.radius, 0.3);
144
145 let jd: SkeletonJoint = serde_json::from_value(serde_json::json!({})).unwrap();
146 assert_eq!(jd.parent, -1);
147 assert_eq!(jd.scale, [1.0, 1.0, 1.0]);
148 }
149}