makepad/libs/render/tests/skin_augmentation_roundtrip.rs
Admin 55dde7b02a Land the 3D renderer and leftover sim/math from rik2.
libs/render (Renderer, SceneDraws, preview play) plus the Arcade
sim/math it still draws through. Gameplay crates stay out.
2026-08-18 14:23:59 +02:00

102 lines
3.2 KiB
Rust

//! Lossless SkinTokens augmentation -> engine parser/deformation gate.
use makepad_render::skin::{PoseBuffer, SkinnedModel, SKIN_VERTEX_FLOATS};
use makepad_gltf::{
augment_glb_skin, replace_glb_node_animations, write_glb_mesh, GlbAnimPath,
GlbNodeAnimChannel, GlbNodeAnimClip, GlbPrimitiveSkin, GlbSkinJoint,
};
fn positions(model: &SkinnedModel, clip: Option<(usize, f32)>) -> Vec<[f32; 3]> {
let pose = match clip {
Some((clip, time)) => {
let mut pose = PoseBuffer::new();
model.sample_clip(clip, time, &mut pose);
pose
}
None => model.rest_pose(),
};
let mut palette = Vec::new();
model.palette(&pose, &mut palette);
let mut packed = Vec::new();
model.skin_to_packed(&palette, &mut packed);
packed
.chunks_exact(SKIN_VERTEX_FLOATS)
.map(|vertex| [vertex[0], vertex[1], vertex[2]])
.collect()
}
fn assert_point(actual: [f32; 3], expected: [f32; 3]) {
for axis in 0..3 {
assert!(
(actual[axis] - expected[axis]).abs() < 2.0e-5,
"actual={actual:?}, expected={expected:?}"
);
}
}
#[test]
fn augmented_mesh_is_identity_at_bind_and_deforms_about_child_head() {
let source_positions = [
[0.0f32, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 2.0, 0.0],
];
let source = write_glb_mesh(&source_positions, &[0, 1, 2, 1, 3, 2]);
let joints = [
GlbSkinJoint {
name: "root".into(),
parent: None,
global_translation: [0.0, 0.0, 0.0],
},
GlbSkinJoint {
name: "child".into(),
parent: Some(0),
global_translation: [0.0, 1.0, 0.0],
},
];
let rigged = augment_glb_skin(
&source,
&joints,
&[GlbPrimitiveSkin {
node: 0,
primitive: 0,
weights: vec![
1.0, 0.0, // v0 root
1.0, 0.0, // v1 root
0.0, 1.0, // v2 child
0.0, 1.0, // v3 child
],
}],
)
.unwrap();
let child_node = 2;
let animated = replace_glb_node_animations(
&rigged,
&[GlbNodeAnimClip {
name: "walk".into(),
channels: vec![GlbNodeAnimChannel {
node: child_node,
path: GlbAnimPath::Rotation,
times: vec![0.0, 1.0, 2.0],
values: vec![
0.0, 0.0, 0.0, 1.0,
0.0, 0.0, std::f32::consts::FRAC_1_SQRT_2,
std::f32::consts::FRAC_1_SQRT_2,
0.0, 0.0, 0.0, 1.0,
],
}],
}],
)
.unwrap();
let model = SkinnedModel::parse_glb(&animated).expect("engine accepts augmented character");
assert_eq!(model.joint_count(), 2);
assert_eq!(model.vertex_count(), 4);
for (actual, expected) in positions(&model, None).into_iter().zip(source_positions) {
assert_point(actual, expected);
}
let posed = positions(&model, Some((0, 1.0)));
assert_point(posed[0], [0.0, 0.0, 0.0]);
assert_point(posed[2], [0.0, 1.0, 0.0]);
assert_point(posed[3], [-1.0, 1.0, 0.0]);
}