From 1a8e1eaed1de540367bad377e72d228153d8cec6 Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sat, 18 Jul 2026 16:25:20 +0400 Subject: feat(animation): recover Node38 fallback hierarchy --- adapters/fparkan-render-vulkan/src/asset_mesh.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'adapters') diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs index 7e7bd26..959b51a 100644 --- a/adapters/fparkan-render-vulkan/src/asset_mesh.rs +++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs @@ -2,7 +2,7 @@ use crate::{VulkanStaticDrawRange, VulkanStaticMesh, VulkanStaticVertex}; use fparkan_msh::{ - draw_batches, node38_fallback_pose, selected_slot, Group, Lod, ModelAsset, NodeId, + draw_batches, node38_fallback_hierarchy, selected_slot, Group, Lod, ModelAsset, NodeId, }; use fparkan_render::{LegacyIron3dEulerTransform, LegacyPipelineState}; use fparkan_terrain_format::LandMeshDocument; @@ -261,11 +261,10 @@ pub fn project_msh_to_static_mesh_in_world_space_with_transform( /// Projects a standard `Node38` model using each selected node's decoded /// fallback pose before its recovered `Iron3D` placement transform. /// -/// This is deliberately not a skeleton evaluator: the source field at -/// `Node38 + 2` is still an unassigned link, so no parent transform is -/// inferred here. Each node's source fallback pose is nevertheless enough to -/// keep independently stored static components at their authored local pose. -/// Models without complete standard-node pose data retain the established +/// The source link at `Node38 + 2` is a parent index: `0xFFFF` marks a root +/// and non-root indices are parent-before-child. The bridge composes the +/// fallback poses through that hierarchy before applying the outer transform. +/// Models without complete standard-node hierarchy data retain the established /// unposed static bridge. /// /// # Errors @@ -289,6 +288,9 @@ pub fn project_msh_to_static_mesh_in_world_space_with_node_fallback_poses( if model.node_stride != 38 || model.node_count == 0 || model.animation.is_none() { return project_msh_to_static_mesh_in_world_space_with_transform(model, transform, scale); } + let Some(hierarchy) = node38_fallback_hierarchy(model) else { + return project_msh_to_static_mesh_in_world_space_with_transform(model, transform, scale); + }; let mut vertices = Vec::new(); let mut indices = Vec::new(); @@ -299,11 +301,7 @@ pub fn project_msh_to_static_mesh_in_world_space_with_node_fallback_poses( let Some(slot) = selected_slot(model, node, Lod(0), Group(0)) else { continue; }; - let Some(pose) = node38_fallback_pose(model, node) else { - return project_msh_to_static_mesh_in_world_space_with_transform( - model, transform, scale, - ); - }; + let pose = hierarchy.poses[node_index]; if !pose .translation .iter() @@ -893,7 +891,9 @@ mod tests { #[test] fn world_space_msh_applies_each_node_fallback_pose_before_root_transform() { let mut nodes = vec![0_u8; 76]; + nodes[2..4].copy_from_slice(&u16::MAX.to_le_bytes()); nodes[8..10].copy_from_slice(&0_u16.to_le_bytes()); + nodes[38 + 2..38 + 4].copy_from_slice(&0_u16.to_le_bytes()); nodes[38 + 6..38 + 8].copy_from_slice(&1_u16.to_le_bytes()); nodes[38 + 8..38 + 10].copy_from_slice(&1_u16.to_le_bytes()); let source = ModelAsset { -- cgit v1.2.3