aada 0.1.1

纯 Rust 的 2D 骨骼动画引擎,兼容 Spine 4.x 导出格式(JSON + atlas)。求解到分层 Mesh 与各 Mesh 渲染设置为止,之后交给渲染器。
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
//! 骨骼:静态层级([`Skeleton`])与每帧可变姿态([`Pose`])。
//!
//! [`Pose`] 持有每骨骼的局部变换(`bone_data`),并提供世界变换/激活状态的惰性缓存:
//! 局部值修改后调用 [`Pose::invalidate_world`] 只失效受影响子树,首次访问按需重建;
//! 无法确定改动范围时设置 `Pose::dirty` 做全量失效。

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)
    }
    /// 重建子骨骼表(`children`)。手动构造骨骼后必须调用,约束的子树失效依赖它
    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);
            }
        }
    }
    /// 重建根骨骼表(`roots`)。手动构造骨骼后必须调用
    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 {
    /// 骨骼在局部空间中的平移(相对于父骨骼),默认值为 (0,0)
    pub translate_x: f32,
    pub translate_y: f32,

    /// 局部旋转(单位:度,正值 = 逆时针),默认值为 0
    pub rotation_deg: f32,

    /// 局部缩放,默认值为 (1,1)
    pub scale_x: f32,
    pub scale_y: f32,

    /// 剪切角度(单位:度),默认值为 (0,0)
    pub shear_x_deg: f32,
    pub shear_y_deg: f32,

    /// 骨骼长度(沿局部 X 轴),用于 IK / Path / Physics 等约束,默认值为 0
    pub length: f32,

    /// 变换继承模式(控制如何从父骨骼继承变换),默认值为 Normal
    pub inherit_mode: InheritMode,

    /// 是否默认激活(false 表示仅当皮肤包含时才启用),默认值为 true
    pub default_active: bool,

    /// 父骨骼索引(None 表示根骨骼),默认值为 None
    pub parent_index: Option<BoneIdx>,

    /// 调试显示颜色(RGBA,范围 \[0,1\]),仅用于编辑器调试显示,默认值为红色
    pub debug_color: [f32; 4],

    /// 剪切模式,默认值为 KeepLength
    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.),          // 0
        Vec2::new(l, 0.),           // 1
        Vec2::new(-d, d),           // 2
        Vec2::new(-d, -d),          // 3
        Vec2::new(2. * e, 2. * e),  // 4
        Vec2::new(2. * e, -2. * e), // 5
        Vec2::new(l - e, e),        // 6
        Vec2::new(l - e, -e),       // 7
        Vec2::new(l + d, d),        // 8
        Vec2::new(l + d, -d),       // 9
        Vec2::new(-2. * d, d),      // 10
        Vec2::new(-2. * d, -d),     // 11
        Vec2::new(0., d),           // 12
        Vec2::new(0., -d),          // 13
    ];
    let indices = [
        // 0, 2, 3, // 1
        4, 0, 5, // 2
        6, 4, 5, // 3
        6, 5, 7, // 4
        1, 6, 7, // 5
        8, 1, 9, // 6
        12, 10, 11, // 7
        12, 11, 13, // 8
    ];
    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 {
    /// 变换后,各轴在原始坐标轴上的投影长度保持为 1
    KeepProjectedLength,
    /// 变换后,各轴的单位向量长度保持为 1
    #[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,

    /// 骨骼长度(沿局部 X 轴),用于 IK / Path / Physics 等约束
    pub length: f32,

    /// 变换继承模式(控制如何从父骨骼继承变换)
    pub inherit_mode: InheritMode,

    /// 是否激活
    pub active: bool,

    /// 调试显示颜色(RGBA,范围 \[0,1\]),仅用于编辑器调试显示
    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);
    }
    /// 增量失效:仅清除某骨骼及其所有后代的世界变换缓存(局部值修改后调用)。
    ///
    /// 与 [Self::clear_cache](全量)相比,约束等批量修改场景下其它骨骼的缓存可继续复用,
    /// 避免每修改一次就全量失效
    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; // 每个骨骼在Z轴上的偏移,避免重叠
        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));
        }
    }
}