use crate::plugins::skinning::SkinWeights;
use crate::plugins::skinning::{SkinnedMeshUpdate, SkinnedPoseUpdate};
use crate::resources::mesh::mesh_store::MeshId;
use crate::runtime::context::RuntimeStepContext;
use crate::runtime::plugin::{RuntimePlugin, phase};
use super::clip::AnimationClip;
use super::plugin::SkinningPath;
use super::skeleton::{JointMatrices, Pose, Skeleton, apply_skin};
pub struct SkinnedActorPart {
pub mesh_id: MeshId,
pub bind_positions: Vec<[f32; 3]>,
pub bind_normals: Vec<[f32; 3]>,
pub skin_weights: SkinWeights,
}
pub struct SkinnedActor {
pub parts: Vec<SkinnedActorPart>,
pub clip_index: usize,
pub playhead: f32,
pub speed: f32,
pub looping: bool,
pub playing: bool,
}
impl SkinnedActor {
pub fn new(parts: Vec<SkinnedActorPart>) -> Self {
Self {
parts,
clip_index: 0,
playhead: 0.0,
speed: 1.0,
looping: true,
playing: true,
}
}
pub fn with_clip(mut self, clip_index: usize) -> Self {
self.clip_index = clip_index;
self
}
pub fn with_playhead(mut self, playhead: f32) -> Self {
self.playhead = playhead;
self
}
pub fn with_speed(mut self, speed: f32) -> Self {
self.speed = speed;
self
}
}
pub struct SkinnedActorPlugin {
pub skeleton: Skeleton,
pub bind_pose: Pose,
pub clips: Vec<AnimationClip>,
pub actors: Vec<SkinnedActor>,
pub path: SkinningPath,
}
impl SkinnedActorPlugin {
pub fn new(skeleton: Skeleton, bind_pose: Pose, clips: Vec<AnimationClip>) -> Self {
Self {
skeleton,
bind_pose,
clips,
actors: Vec::new(),
path: SkinningPath::default(),
}
}
pub fn with_path(mut self, path: SkinningPath) -> Self {
self.path = path;
self
}
pub fn with_actor(mut self, actor: SkinnedActor) -> Self {
self.actors.push(actor);
self
}
pub fn with_actors(mut self, actors: impl IntoIterator<Item = SkinnedActor>) -> Self {
self.actors.extend(actors);
self
}
}
impl RuntimePlugin for SkinnedActorPlugin {
fn priority(&self) -> i32 {
phase::POST_SIM
}
fn step(&mut self, ctx: &mut RuntimeStepContext<'_>) {
for (actor_idx, actor) in self.actors.iter_mut().enumerate() {
let clip = match self.clips.get(actor.clip_index) {
Some(c) => c,
None => continue,
};
if actor.playing {
actor.playhead += ctx.dt * actor.speed;
if clip.duration > 0.0 {
if actor.looping {
actor.playhead = actor.playhead.rem_euclid(clip.duration);
} else {
actor.playhead = actor.playhead.clamp(0.0, clip.duration);
}
}
}
let mut pose = self.bind_pose.clone();
clip.sample_into(actor.playhead, &mut pose);
let matrices = JointMatrices::compute(&self.skeleton, &pose);
match self.path {
SkinningPath::Cpu => {
for part in &actor.parts {
let (positions, normals) = apply_skin(
&part.bind_positions,
&part.bind_normals,
&part.skin_weights,
&matrices,
);
ctx.output.events.emit(SkinnedMeshUpdate {
mesh_id: part.mesh_id,
positions,
normals,
});
}
}
SkinningPath::Gpu => {
let joint_matrices: Vec<glam::Mat4> = matrices
.as_slice()
.iter()
.map(|m| glam::Mat4::from(*m))
.collect();
for part in &actor.parts {
ctx.output.events.emit(SkinnedPoseUpdate {
mesh_id: part.mesh_id,
instance_id: actor_idx as u32,
joint_matrices: joint_matrices.clone(),
});
}
}
}
}
}
}