use std::collections::VecDeque;
use glam::{Affine2, Vec2, Vec3};
use crate::{
cacl::affine::{self, build_affine},
custom_index::{NameMap, TypedIndexVec},
error::Error,
new_index,
};
new_index!(BoneIdx);
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone)]
pub struct Skeleton {
pub data_static: NameMap<BoneIdx, BoneDataStatic>,
pub children: TypedIndexVec<BoneIdx, Vec<BoneIdx>>,
pub roots: Vec<BoneIdx>,
}
impl Default for Skeleton {
fn default() -> Self {
Self::new()
}
}
impl Skeleton {
pub fn new() -> Self {
Self {
data_static: NameMap::new(),
children: TypedIndexVec::new_invalid(),
roots: vec![],
}
}
pub fn insert_bone(
&mut self,
name: impl Into<String>,
data: BoneDataStatic,
) -> Result<BoneIdx, Error> {
let name = name.into();
if self.data_static.exist(&name) {
return Err(Error::InvalidData(format!(
"Bone name '{}' already exists",
name
)));
}
Ok(self.data_static.insert(name, data))
}
pub fn get_bone_index(&self, name: &str) -> Option<BoneIdx> {
self.data_static.get_index(name)
}
pub fn build_children_map(&mut self) {
self.children.invalidate();
self.children
.reconstruct_from_map(&self.data_static, |_, _| vec![]);
for (bone_idx, _name, bone_data) in self.data_static.iter() {
if let Some(parent_idx) = bone_data.parent_index {
self.children[parent_idx].push(bone_idx);
}
}
}
pub fn build_roots(&mut self) {
self.roots.clear();
for (bone_idx, _name, bone_data) in self.data_static.iter() {
if bone_data.parent_index.is_none() {
self.roots.push(bone_idx);
}
}
}
pub fn default_pose(&self) -> Pose {
let bones = self.data_static.map_to_vec(|_, bone| BoneData {
translate_x: bone.translate_x,
translate_y: bone.translate_y,
rotation_deg: bone.rotation_deg,
scale_x: bone.scale_x,
scale_y: bone.scale_y,
shear_x_deg: bone.shear_x_deg,
shear_y_deg: bone.shear_y_deg,
length: bone.length,
inherit_mode: bone.inherit_mode,
active: bone.default_active,
debug_color: bone.debug_color,
shear_mode: bone.shear_mode,
});
let world_transform_cache = self.data_static.map_to_vec(|_, _| None);
let active_cache = self.data_static.map_to_vec(|_, _| None);
Pose {
dirty: false, bone_data: bones,
world_transform_cache,
active_cache,
}
}
}
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone)]
pub struct BoneDataStatic {
pub translate_x: f32,
pub translate_y: f32,
pub rotation_deg: f32,
pub scale_x: f32,
pub scale_y: f32,
pub shear_x_deg: f32,
pub shear_y_deg: f32,
pub length: f32,
pub inherit_mode: InheritMode,
pub default_active: bool,
pub parent_index: Option<BoneIdx>,
pub debug_color: [f32; 4],
pub shear_mode: ShearMode,
}
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
pub enum InheritMode {
#[default]
Normal,
OnlyTranslation,
NoRotationOrReflection,
NoScale,
NoScaleOrReflection,
}
impl Default for BoneDataStatic {
fn default() -> Self {
Self {
translate_x: 0.0,
translate_y: 0.0,
rotation_deg: 0.0,
scale_x: 1.0,
scale_y: 1.0,
shear_x_deg: 0.0,
shear_y_deg: 0.0,
length: 0.0,
inherit_mode: InheritMode::Normal,
default_active: true,
parent_index: None,
debug_color: [1.0, 0.0, 0.0, 1.0],
shear_mode: ShearMode::KeepLength,
}
}
}
fn build_bone_debug_mesh(
l: f32,
d: f32,
z: f32,
base_idx: u32,
positions_out: &mut Vec<Vec3>,
indices_out: &mut Vec<u32>,
affine: Affine2,
) -> usize {
let e = f32::min(l / 4., d);
let positions = [
Vec2::new(0., 0.), Vec2::new(l, 0.), Vec2::new(-d, d), Vec2::new(-d, -d), Vec2::new(2. * e, 2. * e), Vec2::new(2. * e, -2. * e), Vec2::new(l - e, e), Vec2::new(l - e, -e), Vec2::new(l + d, d), Vec2::new(l + d, -d), Vec2::new(-2. * d, d), Vec2::new(-2. * d, -d), Vec2::new(0., d), Vec2::new(0., -d), ];
let indices = [
4, 0, 5, 6, 4, 5, 6, 5, 7, 1, 6, 7, 8, 1, 9, 12, 10, 11, 12, 11, 13, ];
positions_out.extend(
positions
.iter()
.map(|p| affine.transform_point2(*p))
.map(|p| Vec3::new(p.x, p.y, z)),
);
indices_out.extend(indices.iter().map(|i| base_idx + *i));
positions.len()
}
impl BoneDataStatic {
pub fn build_affine_local(&self) -> Affine2 {
build_affine(
[self.translate_x, self.translate_y],
self.rotation_deg,
[self.scale_x, self.scale_y],
[self.shear_x_deg, self.shear_y_deg],
self.shear_mode,
)
}
pub fn generate_debug_mesh_local(
&self,
base_idx: u32,
positions_out: &mut Vec<Vec3>,
indices_out: &mut Vec<u32>,
color_out: &mut Vec<[f32; 4]>,
depth_z: f32,
d: f32,
) {
let affine = self.build_affine_local();
let l = build_bone_debug_mesh(
self.length,
d,
depth_z,
base_idx,
positions_out,
indices_out,
affine,
);
let color = self.debug_color;
color_out.extend(std::iter::repeat_n(color, l));
}
}
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
pub enum ShearMode {
KeepProjectedLength,
#[default]
KeepLength,
}
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone)]
pub struct Pose {
pub dirty: bool,
pub bone_data: TypedIndexVec<BoneIdx, BoneData>,
pub world_transform_cache: TypedIndexVec<BoneIdx, Option<Affine2>>,
pub active_cache: TypedIndexVec<BoneIdx, Option<bool>>,
}
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
#[derive(Debug, Clone)]
pub struct BoneData {
pub translate_x: f32,
pub translate_y: f32,
pub rotation_deg: f32,
pub scale_x: f32,
pub scale_y: f32,
pub shear_x_deg: f32,
pub shear_y_deg: f32,
pub length: f32,
pub inherit_mode: InheritMode,
pub active: bool,
pub debug_color: [f32; 4],
pub shear_mode: ShearMode,
}
impl Pose {
pub fn check_dirty(&mut self) {
if self.dirty {
self.clear_cache();
self.dirty = false;
}
}
pub fn get_world_affine(&mut self, skeleton: &Skeleton, bone_idx: BoneIdx) -> Affine2 {
self.check_dirty();
if let Some(affine2) = self.world_transform_cache[bone_idx] {
return affine2;
}
let local_data = self.bone_data[bone_idx].clone();
let local_affine = build_affine(
[local_data.translate_x, local_data.translate_y],
local_data.rotation_deg,
[local_data.scale_x, local_data.scale_y],
[local_data.shear_x_deg, local_data.shear_y_deg],
local_data.shear_mode,
);
let world_affine = match skeleton.data_static[bone_idx].parent_index {
Some(parent_idx) => {
let parent_affine = self.get_world_affine(skeleton, parent_idx);
affine::cacl_affine(parent_affine, &local_data)
}
None => local_affine,
};
self.world_transform_cache[bone_idx] = Some(world_affine);
world_affine
}
pub fn get_active(&mut self, skeleton: &Skeleton, bone_idx: BoneIdx) -> bool {
self.check_dirty();
if let Some(active) = self.active_cache[bone_idx] {
return active;
}
let local_active = self.bone_data[bone_idx].active;
let parent = skeleton.data_static[bone_idx].parent_index;
let active = match parent {
Some(parent_idx) => {
let parent_active = self.get_active(skeleton, parent_idx);
local_active && parent_active
}
None => local_active,
};
self.active_cache[bone_idx] = Some(active);
active
}
pub fn clear_cache(&mut self) {
self.world_transform_cache
.iter_mut()
.for_each(|(_, c)| *c = None);
self.active_cache.iter_mut().for_each(|(_, c)| *c = None);
}
pub fn invalidate_world(&mut self, skeleton: &Skeleton, bone_idx: BoneIdx) {
let mut stack = vec![bone_idx];
while let Some(b) = stack.pop() {
self.world_transform_cache[b] = None;
stack.extend_from_slice(&skeleton.children[b]);
}
}
}
impl Pose {
pub fn build_debug_mesh(
&mut self,
skeleton: &Skeleton,
d: f32,
positions_out: &mut Vec<Vec3>,
indices_out: &mut Vec<u32>,
color_out: &mut Vec<[f32; 4]>,
) {
let mut bone_order = vec![];
for root in &skeleton.roots {
let mut queue = VecDeque::new();
queue.push_back(*root);
while let Some(bone_idx) = queue.pop_front() {
bone_order.push(bone_idx);
for &child_idx in &skeleton.children[bone_idx] {
queue.push_back(child_idx);
}
}
}
let z_offs = 0.1; for (i, bone_idx) in bone_order.iter().enumerate() {
let base_idx = positions_out.len() as u32;
let depth_z = i as f32 * z_offs;
let affine_world = self.get_world_affine(skeleton, *bone_idx);
let color = self.bone_data[*bone_idx].debug_color;
let length = self.bone_data[*bone_idx].length;
let new_vertexs = build_bone_debug_mesh(
length,
d,
depth_z,
base_idx,
positions_out,
indices_out,
affine_world,
);
color_out.extend(std::iter::repeat_n(color, new_vertexs));
}
}
}