aada 0.1.0

纯 Rust 的 2D 骨骼动画引擎,兼容 Spine 4.x 导出格式(JSON + atlas)。求解到分层 Mesh 与各 Mesh 渲染设置为止,之后交给渲染器。
Documentation

aada

纯 Rust 的 2D 骨骼动画引擎库,兼容 Spine 4.x 导出格式(JSON + atlas)。只负责求解到"分层 Mesh + 各 Mesh 渲染设置"为止,之后的绘制交给渲染器。无渲染器依赖,仅依赖 glam / serde。

引入

[dependencies]
aada = "0.1"

默认不启用任何可选 feature。可选 bevy_reflect 使核心类型可被反射,供 bevy 等编辑器/调试工具直接检查与编辑:

aada = { version = "0.1", features = ["bevy_reflect"] }

快速开始

引擎数据从两个文件构建:xxx.json(骨架/皮肤/动画)+ xxx.atlas(图集布局),外加对应的图集纹理图片。

use aada::prelude::*;

// 1. 构建运行时(spine JSON + atlas 文本)-> (RT, 图集页列表)
let json = std::fs::read_to_string("assets/hero.skel.json")?;
let atlas_text = std::fs::read_to_string("assets/hero.atlas.txt")?;
let (rt, _pages) = build_rt(&json, &atlas_text)?;

// 2. 按名称取索引(之后全走索引,运行时零字符串查找)
let anim = rt.animates.get_index("walk").unwrap();
let skin = rt.skins.get_index("default").unwrap();

// 3. 每帧走分步管线(config 跨帧复用,连续时间采样 O(1) 加速),
//    中间可插入自定义逻辑(约束前操纵骨骼、覆盖约束参数)
let mut config = AnimateConfig::new();
for t in 0..60 {
    let sampled = rt.sample(anim, &mut config, t as f32 / 60.0);
    let mut pose = rt.pose_from_sample(&sampled); // 可在此修改 pose(约束前骨骼操纵)
    let state = rt.constraint_state(&sampled); // 可在此覆盖约束参数
    rt.apply_constraint_state(&mut pose, &state);
    let payloads = rt.build_payloads(&[skin], &sampled, &mut pose);
    // payloads: Vec<MeshPayload>,顺序即渲染顺序(靠后覆盖靠前)
}

无需在管线中间插入自定义逻辑时,RT::render_frame 提供一键式入口(内部即上述五步的顺序拼接):

let payloads = rt.render_frame(&[skin], anim, &mut config, t);

渲染负载

Vec<MeshPayload> 的顺序即渲染顺序,渲染层按序绘制即可。MeshPayload 字段:

字段 说明
usage Mesh(idx) / ClipStart{id} / ClipEnd{id}
slot 所属 slot 索引
positions 模型空间顶点(蒙皮变形后)
uvs 图集局部 UV
indices 三角形索引
tint_color 乘性顶点色(slot 颜色 × attachment 颜色,已含动画覆盖)
uv_transform UV 变换矩阵(当前恒为单位矩阵,预留)

同一 attachment 全局唯一 mesh_idx,渲染层可用它识别网格是否切换。

软件裁剪与位图渲染

Vec<MeshPayload> 直接实现 Clip 和 Render trait(无窗口渲染 / 离线导出):

use aada::data::runtime::clip::{AsBitmap, AsTexture, Clip, Render};

let mut payloads = rt.render_frame(&[skin], anim, &mut config, t);
payloads.clip_software(); // 消费 ClipStart/ClipEnd,把网格裁到多边形内(嵌套取交集)

// 光栅化到位图:half_extent 为每轴半范围(可不等长宽,如 Vec2::new(400.0, 200.0) 表示
// 以 offset 为中心、模型空间里 800×400 的矩形区域会被拉伸到整张 W×H 位图);
// offset 为视口中心在模型空间的坐标;
// 纹理采样与像素输出用闭包直接实现(functional_trait 宏)
let half_extent = Vec2::splat(400.0);
let mut px = vec![[0.0f32; 4]; W * H];
payloads.render_bitmap(W, H, half_extent, center, Some(&tex_closure), &mut |x, y, rgba| {
    px[y * W + x] = rgba;
});
// 或逐像素查询:let c = payloads.render_pixel(x, y, tex);
// 包围盒:      let (min, max) = payloads.aabb().unwrap();

// 输出剪影位图:Render::render_tint 不采样纹理,以 tint 纯色填充,配合 utils::write_bmp_rgba 写出 .bmp
let mut silhouette = vec![[0.0f32; 4]; W * H];
payloads.render_tint(W, H, half_extent, center, &mut |x, y, rgba| {
    silhouette[y * W + x] = rgba;
});
let rgba: Vec<u8> = silhouette
    .iter()
    .flat_map(|c| c.iter().map(|v| (v * 255.0).round() as u8))
    .collect();
let bmp = aada::utils::write_bmp_rgba(W as u32, H as u32, &rgba);
std::fs::write("silhouette.bmp", bmp)?;

动画支持矩阵

能力 状态
骨骼通道(translate/rotate/scale/shear,单轴/双轴拆分) ✅
曲线:linear / stepped / bezier(构建期预采样 + 运行时查表) ✅
slot 颜色(RGBA 逐分量独立曲线插值) ✅
slot attachment 切换(step 语义,支持隐藏) ✅
约束时间轴(ik / transform 参数覆盖) ✅
deform 顶点变形(加权 / 非加权) ✅
draw order 绘制顺序 ✅
裁剪 attachment(clipping)+ 软件裁剪 ✅
IK 约束(1/2 骨骼链,spine 4.2 移植)与 transform 约束(world 绝对模式) ✅
多动画混合 / crossfade ❌(单动画采样,可多次调用自行混合)
events / path 约束 / sequence 附件 ❌

采样语义遵循官方 spine(有测试对照):t 在时间轴首帧之前时间轴未生效(骨骼保持 setup、slot 保持默认),t 超出末帧后取末帧值。

已知限制

  • 只支持 spine 4.x JSON(.skel 二进制、3.x 旧格式不支持);atlas 只认 4.x bounds/offsets 字段。
  • 单纹理设计:所有 MeshPayload 共享同一纹理;多页 atlas 需渲染层自行处理(本库只输出 AtlasPage 列表)。
  • linkedmesh / boundingbox / 字体附件解析时静默跳过。

License

MIT OR Apache-2.0