mittens_engine/engine/ecs/system/
skinned_mesh_system.rs1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::World;
3use crate::engine::ecs::component::GLTFComponent;
4use crate::engine::ecs::component::RenderableComponent;
5use crate::engine::ecs::component::SkinnedMeshComponent;
6use crate::engine::ecs::component::TransformComponent;
7use crate::engine::ecs::system::{System, TransformSystem};
8use crate::engine::graphics::SkinId;
9use crate::engine::graphics::VisualWorld;
10use crate::engine::graphics::primitives::TransformMatrix;
11use crate::engine::user_input::InputState;
12use std::collections::{HashMap, HashSet};
13use std::sync::atomic::{AtomicUsize, Ordering};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16struct BindingKey {
17 mesh_transform: ComponentId,
18 gltf_component: ComponentId,
19 skin_id: SkinId,
20}
21
22#[derive(Debug, Default)]
32pub struct SkinnedMeshSystem {
33 joint_to_bindings: HashMap<ComponentId, Vec<BindingKey>>,
35 mesh_transform_to_bindings: HashMap<ComponentId, Vec<BindingKey>>,
37 dirty_bindings: HashSet<BindingKey>,
39 known_bindings: HashSet<BindingKey>,
41
42 instance_joints: HashMap<(ComponentId, SkinId), Vec<Option<ComponentId>>>,
46}
47
48impl SkinnedMeshSystem {
49 pub fn new() -> Self {
50 Self::default()
51 }
52
53 pub fn register_skin_instance_joints(
54 &mut self,
55 gltf_component: ComponentId,
56 skin_id: SkinId,
57 joints: Vec<Option<ComponentId>>,
58 ) {
59 self.instance_joints
60 .insert((gltf_component, skin_id), joints);
61
62 let affected: Vec<BindingKey> = self
64 .known_bindings
65 .iter()
66 .copied()
67 .filter(|b| b.gltf_component == gltf_component && b.skin_id == skin_id)
68 .collect();
69 for b in affected {
70 self.dirty_bindings.insert(b);
71 }
72 }
73
74 pub fn instance_joints_for_skin(
79 &self,
80 gltf_component: ComponentId,
81 skin_id: SkinId,
82 ) -> Option<&[Option<ComponentId>]> {
83 self.instance_joints
84 .get(&(gltf_component, skin_id))
85 .map(|v| v.as_slice())
86 }
87
88 fn mat4_identity() -> TransformMatrix {
89 [
90 [1.0, 0.0, 0.0, 0.0],
91 [0.0, 1.0, 0.0, 0.0],
92 [0.0, 0.0, 1.0, 0.0],
93 [0.0, 0.0, 0.0, 1.0],
94 ]
95 }
96
97 fn mat4_mul(a: TransformMatrix, b: TransformMatrix) -> TransformMatrix {
98 let mut out = [[0.0f32; 4]; 4];
100 for c in 0..4 {
101 for r in 0..4 {
102 out[c][r] =
103 a[0][r] * b[c][0] + a[1][r] * b[c][1] + a[2][r] * b[c][2] + a[3][r] * b[c][3];
104 }
105 }
106 out
107 }
108
109 fn update_binding(
110 &self,
111 world: &World,
112 visuals: &VisualWorld,
113 binding: BindingKey,
114 ) -> Option<Vec<TransformMatrix>> {
115 let mesh_world = TransformSystem::world_model(world, binding.mesh_transform)
116 .unwrap_or_else(Self::mat4_identity);
117
118 let inv_mesh_world =
119 crate::utils::math::mat4_inverse(mesh_world).unwrap_or_else(Self::mat4_identity);
120
121 let skin = visuals.skin(binding.skin_id)?;
122 let joints = self
123 .instance_joints
124 .get(&(binding.gltf_component, binding.skin_id))?;
125
126 let joint_count = skin.joint_count().min(joints.len());
127 let mut skin_mats: Vec<TransformMatrix> = Vec::with_capacity(joint_count);
128
129 for i in 0..joint_count {
130 let joint_world = match joints[i] {
131 Some(joint_cid) => TransformSystem::world_model(world, joint_cid)
132 .unwrap_or_else(Self::mat4_identity),
133 None => Self::mat4_identity(),
134 };
135 let ibm = skin.inverse_bind_matrices[i];
136
137 let skin_mat = Self::mat4_mul(Self::mat4_mul(inv_mesh_world, joint_world), ibm);
138 skin_mats.push(skin_mat);
139 }
140
141 Some(skin_mats)
142 }
143
144 fn find_parent_renderable(world: &World, mut cid: ComponentId) -> Option<ComponentId> {
145 while let Some(parent) = world.parent_of(cid) {
146 if world
147 .get_component_by_id_as::<RenderableComponent>(parent)
148 .is_some()
149 {
150 return Some(parent);
151 }
152 cid = parent;
153 }
154 None
155 }
156
157 fn find_parent_transform(world: &World, mut cid: ComponentId) -> Option<ComponentId> {
158 while let Some(parent) = world.parent_of(cid) {
159 if world
160 .get_component_by_id_as::<TransformComponent>(parent)
161 .is_some()
162 {
163 return Some(parent);
164 }
165 cid = parent;
166 }
167 None
168 }
169
170 fn find_nearest_gltf_component_for_skin(
177 &self,
178 world: &World,
179 mut cid: ComponentId,
180 skin_id: SkinId,
181 ) -> Option<ComponentId> {
182 let mut first_candidate: Option<ComponentId> = None;
183
184 loop {
185 if world.get_component_by_id_as::<GLTFComponent>(cid).is_some() {
187 if self.instance_joints.contains_key(&(cid, skin_id)) {
188 return Some(cid);
189 }
190 if first_candidate.is_none() {
191 first_candidate = Some(cid);
192 }
193 }
194
195 for &child in world.children_of(cid) {
197 if world
198 .get_component_by_id_as::<GLTFComponent>(child)
199 .is_some()
200 {
201 if self.instance_joints.contains_key(&(child, skin_id)) {
202 return Some(child);
203 }
204 if first_candidate.is_none() {
205 first_candidate = Some(child);
206 }
207 }
208 }
209
210 let parent = world.parent_of(cid);
211 match parent {
212 Some(p) => cid = p,
213 None => return first_candidate,
214 }
215 }
216 }
217
218 fn rebuild_indices(
219 &mut self,
220 world: &World,
221 skinned: &[ComponentId],
222 ) -> HashMap<BindingKey, Vec<ComponentId>> {
223 self.joint_to_bindings.clear();
224 self.mesh_transform_to_bindings.clear();
225
226 let mut binding_to_renderables: HashMap<BindingKey, Vec<ComponentId>> = HashMap::new();
227
228 for &skinned_cid in skinned {
229 let Some(sm) = world.get_component_by_id_as::<SkinnedMeshComponent>(skinned_cid) else {
230 continue;
231 };
232 let Some(skin_id) = sm.skin_id else {
233 continue;
234 };
235
236 let Some(renderable_cid) = Self::find_parent_renderable(world, skinned_cid) else {
237 continue;
238 };
239
240 let Some(gltf_component) =
241 self.find_nearest_gltf_component_for_skin(world, skinned_cid, skin_id)
242 else {
243 continue;
244 };
245
246 let Some(mesh_transform) = Self::find_parent_transform(world, renderable_cid) else {
247 continue;
248 };
249
250 let binding = BindingKey {
251 mesh_transform,
252 gltf_component,
253 skin_id,
254 };
255
256 binding_to_renderables
257 .entry(binding)
258 .or_default()
259 .push(renderable_cid);
260 }
261
262 for (&binding, _) in binding_to_renderables.iter() {
264 self.mesh_transform_to_bindings
265 .entry(binding.mesh_transform)
266 .or_default()
267 .push(binding);
268
269 let Some(joints) = self
270 .instance_joints
271 .get(&(binding.gltf_component, binding.skin_id))
272 else {
273 continue;
274 };
275
276 for &joint in joints.iter().flatten() {
277 self.joint_to_bindings
278 .entry(joint)
279 .or_default()
280 .push(binding);
281 }
282 }
283
284 binding_to_renderables
285 }
286
287 pub fn transform_subtree_changed(&mut self, world: &World, root: ComponentId) {
291 if self.joint_to_bindings.is_empty() && self.mesh_transform_to_bindings.is_empty() {
293 return;
294 }
295
296 let mut stack: Vec<ComponentId> = vec![root];
297 while let Some(node) = stack.pop() {
298 if let Some(bindings) = self.joint_to_bindings.get(&node) {
299 for &binding in bindings {
300 self.dirty_bindings.insert(binding);
301 }
302 }
303
304 if let Some(bindings) = self.mesh_transform_to_bindings.get(&node) {
305 for &binding in bindings {
306 self.dirty_bindings.insert(binding);
307 }
308 }
309
310 for &child in world.children_of(node) {
311 stack.push(child);
312 }
313 }
314 }
315}
316
317impl System for SkinnedMeshSystem {
318 fn tick(
319 &mut self,
320 world: &mut World,
321 visuals: &mut VisualWorld,
322 _input: &InputState,
323 _dt_sec: f32,
324 ) {
325 let debug_skin_apply = std::env::var("CAT_DEBUG_SKIN_APPLY")
326 .ok()
327 .map(|s| {
328 let s = s.trim().to_ascii_lowercase();
329 s == "1" || s == "true" || s == "on" || s == "yes"
330 })
331 .unwrap_or(false);
332
333 static APPLY_LOG_COUNT: AtomicUsize = AtomicUsize::new(0);
334
335 let skinned: Vec<ComponentId> = world
336 .all_components()
337 .filter(|&cid| {
338 world
339 .get_component_by_id_as::<SkinnedMeshComponent>(cid)
340 .is_some()
341 })
342 .collect();
343
344 let binding_to_renderables = self.rebuild_indices(&*world, &skinned);
345
346 for &binding in binding_to_renderables.keys() {
348 if self.known_bindings.insert(binding) {
349 self.dirty_bindings.insert(binding);
350 }
351 }
352
353 if self.dirty_bindings.is_empty() {
355 return;
356 }
357
358 let dirty: Vec<BindingKey> = self.dirty_bindings.iter().copied().collect();
359 self.dirty_bindings.clear();
360
361 for binding in dirty {
362 let skin_mats = match self.update_binding(&*world, visuals, binding) {
363 Some(v) => v,
364 None => {
365 if debug_skin_apply {
366 let n = APPLY_LOG_COUNT.fetch_add(1, Ordering::Relaxed);
367 if n < 16 {
368 let has_skin = visuals.skin(binding.skin_id).is_some();
369 let has_joints = self
370 .instance_joints
371 .contains_key(&(binding.gltf_component, binding.skin_id));
372 println!(
373 "[SkinnedMeshSystem] binding skipped: reason=missing_data has_skin={} has_instance_joints={} gltf_component={:?} mesh_transform={:?}",
374 has_skin,
375 has_joints,
376 binding.gltf_component,
377 binding.mesh_transform,
378 );
379 }
380 }
381 self.dirty_bindings.insert(binding);
383 continue;
384 }
385 };
386
387 let Some(renderables) = binding_to_renderables.get(&binding) else {
388 continue;
389 };
390
391 let mut missing_handle = false;
392 let mut applied = 0usize;
393 let mut failed_apply = 0usize;
394
395 for &renderable_cid in renderables {
396 let Some(renderable) =
397 world.get_component_by_id_as::<RenderableComponent>(renderable_cid)
398 else {
399 continue;
400 };
401 let Some(handle) = renderable.get_handle() else {
402 missing_handle = true;
403 continue;
404 };
405 let did = visuals.set_skin_matrices(handle, &skin_mats);
406 if did {
407 applied += 1;
408 } else {
409 failed_apply += 1;
410 }
411 }
412
413 if debug_skin_apply {
414 let n = APPLY_LOG_COUNT.fetch_add(1, Ordering::Relaxed);
416 if n < 16 {
417 println!(
418 "[SkinnedMeshSystem] binding applied: skin_mats={} renderables={} applied={} failed_apply={} missing_handle={} gltf_component={:?} mesh_transform={:?}",
419 skin_mats.len(),
420 renderables.len(),
421 applied,
422 failed_apply,
423 missing_handle,
424 binding.gltf_component,
425 binding.mesh_transform,
426 );
427 }
428 }
429
430 if missing_handle {
433 self.dirty_bindings.insert(binding);
434 }
435 }
436 }
437}