use crate::{
custom_index::AsIndex,
data::runtime::{
animate::{
AnimIdx, AnimateConfig, AnimateSet, SampledAnimate, SampledAttachment,
SampledSlotValue, SlotChannel,
},
constraint::{ConstraintSet, ConstraintState, apply_constraints},
mesh::{MeshIdx, MeshSet},
payload::{MeshPayload, PayloadUsage},
skeleton::{Pose, Skeleton},
skin::{AttachmentSet, SkinIdx, SkinSet},
slot::{SlotIdx, SlotSet},
},
};
#[derive(Debug, Clone)]
pub struct RT {
pub skins: SkinSet,
pub meshs: MeshSet,
pub skeleton: Skeleton,
pub slots: SlotSet,
pub attachments: AttachmentSet,
pub animates: AnimateSet,
pub constraints: ConstraintSet,
}
impl RT {
pub fn sample(&self, anim: AnimIdx, config: &mut AnimateConfig, t: f32) -> SampledAnimate {
self.animates.sample(anim, config, t)
}
pub fn pose_from_sample(&self, sampled: &SampledAnimate) -> Pose {
let mut pose = self.skeleton.default_pose();
sampled.apply_pose(&mut pose);
pose
}
pub fn constraint_state(&self, sampled: &SampledAnimate) -> ConstraintState {
let mut state = self.constraints.new_state();
sampled.apply_constraints(&mut state);
state
}
pub fn apply_constraint_state(&self, pose: &mut Pose, state: &ConstraintState) {
apply_constraints(pose, &self.skeleton, &self.constraints, state);
}
pub fn build_payloads(
&self,
skins: &[SkinIdx],
sampled: &SampledAnimate,
pose: &mut Pose,
) -> Vec<MeshPayload> {
let mut wanted = self.skins.sample(skins, &self.slots, &self.attachments);
let mut slot_colors: Vec<Option<[f32; 4]>> = vec![None; self.slots.data.len()];
for (slot, channel, value) in &sampled.slot_values {
match (channel, value) {
(SlotChannel::Attachment, SampledSlotValue::Attachment(SampledAttachment::Set(name))) => {
let new_att = match name {
Some(name_idx) => {
let mut found = None;
for &sk in skins {
if let Some(att) = self.skins.data[sk].get(*slot, *name_idx) {
found = Some(att);
break;
}
}
found
}
None => None,
};
wanted.set(*slot, new_att);
}
(SlotChannel::Attachment, SampledSlotValue::Attachment(SampledAttachment::NotStarted)) => {}
(SlotChannel::Color, SampledSlotValue::Color(Some(c))) => {
slot_colors[slot.as_index()] = Some(*c);
}
_ => {}
}
}
let mut payloads = Vec::new();
let mut clip_stack: Vec<MeshIdx> = Vec::new();
let mut deform_map: Vec<Option<Vec<(MeshIdx, usize)>>> = Vec::new();
if !sampled.deform_values.is_empty() {
deform_map.resize(self.slots.data.len(), None);
for (i, (s, m, _)) in sampled.deform_values.iter().enumerate() {
deform_map[s.as_index()]
.get_or_insert_with(Vec::new)
.push((*m, i));
}
}
let wanted_iter: Vec<(
SlotIdx,
Option<crate::data::runtime::skin::AttachmentDataIdx>,
)> = match &sampled.draw_order {
Some(order) => wanted.iter_with_order(order),
None => wanted.iter().collect(),
};
for (slot, attachment) in wanted_iter {
while let Some(&clip_mesh) = clip_stack.last() {
let end = self.meshs.data_map[clip_mesh].clip_end_slot.unwrap();
if slot.as_index() >= end.as_index() {
clip_stack.pop();
payloads.push(MeshPayload::new(
PayloadUsage::ClipEnd { id: clip_mesh },
slot,
Vec::new(),
Vec::new(),
Vec::new(),
[1.0, 1.0, 1.0, 1.0],
glam::Affine2::IDENTITY,
));
} else {
break;
}
}
let Some(attachment) = attachment else {
continue;
};
let slot_bone = self.slots.get_bone(slot);
let Some(mesh_idx) = self.attachments.get_mesh(attachment) else {
continue;
};
let mesh_data = &self.meshs.data_map[mesh_idx];
let deform = deform_map
.get(slot.as_index())
.and_then(|v| v.as_ref())
.and_then(|list| list.iter().find(|(m, _)| *m == mesh_idx))
.map(|&(_, i)| sampled.deform_values[i].2.as_slice());
let positions = match deform {
Some(d) => mesh_data.deform_with_deform(d, pose, &self.skeleton, slot_bone),
None => mesh_data.deform(pose, &self.skeleton, slot_bone),
};
if let Some(_end_slot) = mesh_data.clip_end_slot {
payloads.push(MeshPayload::new(
PayloadUsage::ClipStart { id: mesh_idx },
slot,
positions,
Vec::new(),
Vec::new(),
[1.0, 1.0, 1.0, 1.0],
glam::Affine2::IDENTITY,
));
clip_stack.push(mesh_idx);
} else {
let slot_color = slot_colors[slot.as_index()]
.unwrap_or(self.slots.data[slot].color);
let att_color = self.attachments.data[attachment].color;
let tint = [
slot_color[0] * att_color[0],
slot_color[1] * att_color[1],
slot_color[2] * att_color[2],
slot_color[3] * att_color[3],
];
payloads.push(MeshPayload::new(
PayloadUsage::Mesh(mesh_idx),
slot,
positions,
mesh_data
.uvs
.chunks_exact(2)
.map(|c| glam::Vec2::new(c[0], c[1]))
.collect(),
mesh_data.triangles.iter().map(|&i| i as u32).collect(),
tint,
glam::Affine2::IDENTITY,
));
}
}
while let Some(clip_mesh) = clip_stack.pop() {
payloads.push(MeshPayload::new(
PayloadUsage::ClipEnd { id: clip_mesh },
unsafe { SlotIdx::from_index(0) },
Vec::new(),
Vec::new(),
Vec::new(),
[1.0, 1.0, 1.0, 1.0],
glam::Affine2::IDENTITY,
));
}
payloads
}
pub fn render_frame(
&self,
skins: &[SkinIdx],
anim: AnimIdx,
config: &mut AnimateConfig,
t: f32,
) -> Vec<MeshPayload> {
let sampled = self.sample(anim, config, t);
let mut pose = self.pose_from_sample(&sampled);
let state = self.constraint_state(&sampled);
self.apply_constraint_state(&mut pose, &state);
self.build_payloads(skins, &sampled, &mut pose)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
custom_index::AsIndex,
data::runtime::{
animate::{AnimateSet, AnimationRT, Curve, DeformFrame, DeformTimeline, Keyframe, SlotKeyframe, SlotTimeline, Timeline},
mesh::{MeshData, MeshIdx, MeshMetadata},
skeleton::{BoneIdx, Skeleton},
skin::{AttachmentData, AttachmentNames, Skin},
slot::{SlotData, SlotIdx},
},
utils::default,
};
fn build_rt() -> (RT, SkinIdx, AnimIdx) {
let mut skeleton = Skeleton::new();
skeleton.insert_bone("root", default()).unwrap();
let mut names = AttachmentNames::new(1);
let mut slots = SlotSet::new();
let slot_idx = slots.insert(
"s",
SlotData {
name: "s".to_string(),
bone: unsafe { BoneIdx::from_index(0) },
default_attachment: Some("a".to_string()),
default_attachment_idx: Some(names.intern(
unsafe { SlotIdx::from_index(0) },
"a",
)),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let _ = slot_idx;
let mut meshs = MeshSet::new();
let make_mesh = || MeshData {
meta: MeshMetadata::default(),
texture_name: "t".to_string(),
uvs: vec![0.0, 0.0],
vertex_count: 1,
triangles: vec![],
bones: None,
influence_counts: vec![1],
vertices_locals: vec![0.0, 0.0],
influence_bone_indices: vec![0],
influence_weights: vec![1.0],
clip_end_slot: None,
};
let mesh_a = meshs.insert_mesh("s_a".to_string(), make_mesh());
let mesh_b = meshs.insert_mesh("s_b".to_string(), make_mesh());
let mut attachments = AttachmentSet::new();
let att_a = attachments.insert(
"skin_a".to_string(),
AttachmentData {
name: "skin_a".to_string(),
mesh: Some(mesh_a),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let att_b = attachments.insert(
"skin_b".to_string(),
AttachmentData {
name: "skin_b".to_string(),
mesh: Some(mesh_b),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let mut skin = Skin::new();
skin.insert(slot_idx, names.intern(slot_idx, "a"), att_a);
skin.insert(slot_idx, names.intern(slot_idx, "b"), att_b);
let mut skins = SkinSet::new();
let skin_idx = skins.insert("skin", skin);
let mut animates = AnimateSet::new();
let anim_idx = animates.insert(
"walk",
AnimationRT {
name: "walk".to_string(),
constraint_timelines: vec![],
deform_timelines: vec![],
draw_order_timeline: None,
duration: 1.0,
timelines: vec![Timeline {
bone: unsafe { BoneIdx::from_index(0) },
channel: crate::data::runtime::animate::BoneChannel::TranslateX,
frames: vec![
Keyframe {
time: 0.0,
value: 0.0,
curve: Curve::Linear,
},
Keyframe {
time: 1.0,
value: 10.0,
curve: Curve::Linear,
},
],
}],
slot_timelines: vec![SlotTimeline {
slot: unsafe { SlotIdx::from_index(0) },
channel: SlotChannel::Attachment,
frames: vec![
SlotKeyframe::Attachment {
time: 0.0,
name: Some(names.intern(slot_idx, "a")),
},
SlotKeyframe::Attachment {
time: 0.5,
name: Some(names.intern(slot_idx, "b")),
},
],
}],
},
);
let rt = RT {
skins,
meshs,
skeleton,
slots,
attachments,
animates,
constraints: ConstraintSet::default(),
};
(rt, skin_idx, anim_idx)
}
#[test]
fn render_frame_delivers_payload_list() {
let (rt, skin_idx, anim_idx) = build_rt();
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads.len(), 1);
assert_eq!(
payloads[0].usage,
PayloadUsage::Mesh(unsafe { MeshIdx::from_index(0) })
);
assert_eq!(payloads[0].tint_color, [1.0, 1.0, 1.0, 1.0]);
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.5);
assert_eq!(payloads.len(), 1);
assert_eq!(
payloads[0].usage,
PayloadUsage::Mesh(unsafe { MeshIdx::from_index(1) })
);
assert!(
(payloads[0].positions[0].x - 5.0).abs() < 1e-5,
"x={}",
payloads[0].positions[0].x
);
}
#[test]
fn render_frame_slot_color_tint() {
let (rt, skin_idx, anim_idx) = build_rt();
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads.len(), 1);
assert_eq!(payloads[0].tint_color, [1.0, 1.0, 1.0, 1.0]);
assert_eq!(payloads[0].slot.as_index(), 0);
}
#[test]
fn render_frame_tint_slot_default_times_attachment() {
use crate::data::runtime::animate::{Curve, SlotKeyframe, SlotTimeline};
let (mut rt, skin_idx, anim_idx) = build_rt();
let slot_idx = unsafe { SlotIdx::from_index(0) };
rt.slots.data[slot_idx].color = [1.0, 0.5, 0.0, 1.0];
let mut names = AttachmentNames::new(rt.slots.data.len());
let att_a = rt.skins.data[skin_idx]
.get(slot_idx, names.intern(slot_idx, "a"))
.unwrap();
rt.attachments.data[att_a].color = [0.5, 1.0, 0.5, 0.8];
let att_b = rt.skins.data[skin_idx]
.get(slot_idx, names.intern(slot_idx, "b"))
.unwrap();
let _ = att_b;
rt.animates.slot_timelines_mut(anim_idx).push(SlotTimeline {
slot: slot_idx,
channel: crate::data::runtime::animate::SlotChannel::Color,
frames: vec![SlotKeyframe::Color {
time: 0.25,
rgba: [0.0, 1.0, 0.0, 1.0],
curves: [
Curve::Linear,
Curve::Linear,
Curve::Linear,
Curve::Linear,
],
}],
});
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads[0].tint_color, [0.5, 0.5, 0.0, 0.8]);
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.25);
assert_eq!(payloads[0].tint_color, [0.0, 1.0, 0.0, 0.8]);
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.5);
assert_eq!(payloads[0].tint_color, [0.0, 1.0, 0.0, 1.0]);
}
#[test]
fn render_frame_clip_stack() {
let mut skeleton = Skeleton::new();
skeleton.insert_bone("root", default()).unwrap();
let mut names = AttachmentNames::new(3);
let mut slots = SlotSet::new();
slots.insert(
"clip",
SlotData {
name: "clip".to_string(),
bone: unsafe { BoneIdx::from_index(0) },
default_attachment: Some("c".to_string()),
default_attachment_idx: Some(names.intern(
unsafe { SlotIdx::from_index(0) },
"c",
)),
color: [1.0, 1.0, 1.0, 1.0],
},
);
slots.insert(
"mid",
SlotData {
name: "mid".to_string(),
bone: unsafe { BoneIdx::from_index(0) },
default_attachment: Some("m".to_string()),
default_attachment_idx: Some(names.intern(
unsafe { SlotIdx::from_index(1) },
"m",
)),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let slot_end = slots.insert(
"end",
SlotData {
name: "end".to_string(),
bone: unsafe { BoneIdx::from_index(0) },
default_attachment: Some("e".to_string()),
default_attachment_idx: Some(names.intern(
unsafe { SlotIdx::from_index(2) },
"e",
)),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let mut meshs = MeshSet::new();
let make_mesh = || MeshData {
meta: MeshMetadata::default(),
texture_name: "t".to_string(),
uvs: vec![0.0, 0.0],
vertex_count: 1,
triangles: vec![],
bones: None,
influence_counts: vec![1],
vertices_locals: vec![0.0, 0.0],
influence_bone_indices: vec![0],
influence_weights: vec![1.0],
clip_end_slot: None,
};
let mesh_clip = meshs.insert_mesh(
"clip_c".to_string(),
MeshData {
meta: MeshMetadata::default(),
texture_name: "c".to_string(),
uvs: vec![],
vertex_count: 3,
triangles: vec![],
bones: None,
influence_counts: vec![1, 1, 1],
vertices_locals: vec![0.0, 0.0, 1.0, 0.0, 1.0, 1.0],
influence_bone_indices: vec![0, 0, 0],
influence_weights: vec![1.0, 1.0, 1.0],
clip_end_slot: Some(slot_end),
},
);
let mesh_mid = meshs.insert_mesh("mid_m".to_string(), make_mesh());
let mesh_end = meshs.insert_mesh("end_e".to_string(), make_mesh());
let mut attachments = AttachmentSet::new();
let att_c = attachments.insert(
"skin_c".to_string(),
AttachmentData {
name: "skin_c".to_string(),
mesh: Some(mesh_clip),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let att_m = attachments.insert(
"skin_m".to_string(),
AttachmentData {
name: "skin_m".to_string(),
mesh: Some(mesh_mid),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let att_e = attachments.insert(
"skin_e".to_string(),
AttachmentData {
name: "skin_e".to_string(),
mesh: Some(mesh_end),
color: [1.0, 1.0, 1.0, 1.0],
},
);
let mut skin = Skin::new();
skin.insert(
unsafe { SlotIdx::from_index(0) },
names.intern(unsafe { SlotIdx::from_index(0) }, "c"),
att_c,
);
skin.insert(
unsafe { SlotIdx::from_index(1) },
names.intern(unsafe { SlotIdx::from_index(1) }, "m"),
att_m,
);
skin.insert(
slot_end,
names.intern(slot_end, "e"),
att_e,
);
let mut skins = SkinSet::new();
let skin_idx = skins.insert("skin", skin);
let mut animates = AnimateSet::new();
let anim_idx = animates.insert(
"idle",
AnimationRT {
name: "idle".to_string(),
constraint_timelines: vec![],
deform_timelines: vec![],
draw_order_timeline: None,
duration: 1.0,
timelines: vec![],
slot_timelines: vec![],
},
);
let rt = RT {
skins,
meshs,
skeleton,
slots,
attachments,
animates,
constraints: ConstraintSet::default(),
};
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads.len(), 4);
assert_eq!(payloads[0].usage, PayloadUsage::ClipStart { id: mesh_clip });
assert_eq!(payloads[0].positions.len(), 3, "多边形顶点随 clip 交付");
assert_eq!(payloads[1].usage, PayloadUsage::Mesh(mesh_mid));
assert_eq!(payloads[2].usage, PayloadUsage::ClipEnd { id: mesh_clip });
assert_eq!(payloads[3].usage, PayloadUsage::Mesh(mesh_end));
}
#[test]
fn render_frame_deform_map_first_wins() {
let (mut rt, skin_idx, _) = build_rt();
let mut animates = AnimateSet::new();
let anim_idx = animates.insert(
"walk",
AnimationRT {
name: "walk".to_string(),
constraint_timelines: vec![],
deform_timelines: vec![
DeformTimeline {
slot: unsafe { SlotIdx::from_index(0) },
mesh: unsafe { MeshIdx::from_index(0) },
frames: vec![
DeformFrame {
time: 0.0,
vertices: vec![5.0, 7.0],
curve: Curve::Linear,
},
DeformFrame {
time: 1.0,
vertices: vec![9.0, 11.0],
curve: Curve::Linear,
},
],
},
DeformTimeline {
slot: unsafe { SlotIdx::from_index(0) },
mesh: unsafe { MeshIdx::from_index(0) },
frames: vec![
DeformFrame {
time: 0.0,
vertices: vec![9.0, 9.0],
curve: Curve::Linear,
},
DeformFrame {
time: 1.0,
vertices: vec![1.0, 1.0],
curve: Curve::Linear,
},
],
},
],
draw_order_timeline: None,
duration: 1.0,
timelines: vec![],
slot_timelines: vec![],
},
);
rt.animates = animates;
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads.len(), 1);
assert!(
(payloads[0].positions[0].x - 5.0).abs() < 1e-4
&& (payloads[0].positions[0].y - 7.0).abs() < 1e-4,
"got {:?}",
payloads[0].positions[0]
);
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.5);
assert!(
(payloads[0].positions[0].x - 7.0).abs() < 1e-4
&& (payloads[0].positions[0].y - 9.0).abs() < 1e-4,
"got {:?}",
payloads[0].positions[0]
);
}
#[test]
fn render_frame_deform_matches_mesh() {
let (mut rt, skin_idx, _) = build_rt();
let mut animates = AnimateSet::new();
let anim_idx = animates.insert(
"walk",
AnimationRT {
name: "walk".to_string(),
constraint_timelines: vec![],
deform_timelines: vec![
DeformTimeline {
slot: unsafe { SlotIdx::from_index(0) },
mesh: unsafe { MeshIdx::from_index(0) }, frames: vec![DeformFrame {
time: 0.0,
vertices: vec![5.0, 7.0],
curve: Curve::Linear,
}],
},
DeformTimeline {
slot: unsafe { SlotIdx::from_index(0) },
mesh: unsafe { MeshIdx::from_index(1) }, frames: vec![DeformFrame {
time: 0.0,
vertices: vec![30.0, 40.0],
curve: Curve::Linear,
}],
},
],
draw_order_timeline: None,
duration: 1.0,
timelines: vec![],
slot_timelines: vec![],
},
);
rt.animates = animates;
let mut config = AnimateConfig::new();
let payloads = rt.render_frame(&[skin_idx], anim_idx, &mut config, 0.0);
assert_eq!(payloads.len(), 1);
assert!(
(payloads[0].positions[0].x - 5.0).abs() < 1e-4
&& (payloads[0].positions[0].y - 7.0).abs() < 1e-4,
"got {:?}",
payloads[0].positions[0]
);
}
}