aada 0.1.0

纯 Rust 的 2D 骨骼动画引擎,兼容 Spine 4.x 导出格式(JSON + atlas)。求解到分层 Mesh 与各 Mesh 渲染设置为止,之后交给渲染器。
Documentation
//! 2D 仿射变换计算:骨骼局部变换矩阵构造 / 分解 / 层级组合。
//!
//! - [`build_affine`]:由平移、旋转、缩放、剪切构造局部矩阵(两种剪切模式)。
//! - [`decompose_affine`] / [`decompose_rotation`]:矩阵分解,用于约束求解回写局部参数。
//! - [`cacl_affine`]:父子层级组合,移植 spine 4.2 `Bone.updateWorldTransform` 的全部继承模式。

use glam::*;

use crate::data::runtime::skeleton::{InheritMode, ShearMode};

/// 构造局部仿射矩阵,构造顺序 scale -> shear -> rotation -> translation。
///
/// 剪切角度按 `tan` 转换为切变因子。注意:该变体不会交换剪切轴的顺序,
/// 主要用于与其它变体对比验证
pub fn build_affine2_0(
    translate: [f32; 2],
    rotation_deg: f32,
    scale: [f32; 2],
    shear_deg: [f32; 2],
) -> Affine2 {
    // 先rotate对于2d动画来说不太合适
    // 构造顺序:scale -> shear -> rotation -> translation
    let angle = rotation_deg.to_radians();
    let rot = mat2(
        vec2(angle.cos(), angle.sin()),
        vec2(-angle.sin(), angle.cos()),
    );
    let scale = mat2(vec2(scale[0], 0.0), vec2(0.0, scale[1]));
    // 将 shear 角度视作角度并转换为切变因子(tan),如果你的 shear 值已经是切变因子则去掉 .tan()
    let shear_x = shear_deg[0].to_radians().tan();
    let shear_y = shear_deg[1].to_radians().tan();
    let shear = mat2(vec2(1.0, shear_y), vec2(shear_x, 1.0));
    let linear = rot * shear * scale;

    Affine2::from_mat2_translation(linear, Vec2::new(translate[0], translate[1]))
}
/// 构造局部仿射矩阵,构造顺序 scale -> shear -> rotation -> translation,并交换剪切轴顺序。
///
/// 对应 `ShearMode::KeepProjectedLength` 的语义(各轴在原始坐标轴上的投影长度保持为 1)
pub fn build_affine2_1(
    translate: [f32; 2],
    rotation_deg: f32,
    scale: [f32; 2],
    shear_deg: [f32; 2],
) -> Affine2 {
    // 构造顺序: -> scale -> shear -> rotation -> translation
    // 同时交换shear的xy轴
    let shear_deg = [-shear_deg[1], shear_deg[0]];
    let angle = rotation_deg.to_radians();
    let rot = mat2(
        vec2(angle.cos(), angle.sin()),
        vec2(-angle.sin(), angle.cos()),
    );
    let scale = mat2(vec2(scale[0], 0.0), vec2(0.0, scale[1]));
    // 将 shear 角度视作角度并转换为切变因子(tan),如果你的 shear 值已经是切变因子则去掉 .tan()
    let shear_x = shear_deg[0].to_radians().tan();
    let shear_y = shear_deg[1].to_radians().tan();
    let shear = mat2(vec2(1.0, shear_y), vec2(shear_x, 1.0));
    let linear = rot * shear * scale;

    Affine2::from_mat2_translation(linear, Vec2::new(translate[0], translate[1]))
}

/// 构造局部仿射矩阵:x 轴方向角 = rotation + shear_x,y 轴方向角 = rotation + 90 + shear_y。
///
/// 对应 `ShearMode::KeepLength` 的语义(各轴的单位向量长度保持为 1,spine 默认),
/// 为 [`build_affine`] 在默认剪切模式下的实现
pub fn build_affine2_2(
    translate: [f32; 2],
    rotation_deg: f32,
    scale: [f32; 2],
    shear_deg: [f32; 2],
) -> Affine2 {
    let rx = (rotation_deg + shear_deg[0]).to_radians();
    let ry = (rotation_deg + 90.0 + shear_deg[1]).to_radians();

    let sx = scale[0];
    let sy = scale[1];

    let x_axis_x = rx.cos() * sx;
    let x_axis_y = rx.sin() * sx;

    let y_axis_x = ry.cos() * sy;
    let y_axis_y = ry.sin() * sy;

    // 构造仿射矩阵:
    // [ x_axis_x  y_axis_x ]   → 对应 .a, .b
    // [ x_axis_y  y_axis_y ]   → 对应 .c, .d
    // 平移为 translate

    Affine2::from_mat2_translation(
        Mat2::from_cols(Vec2::new(x_axis_x, x_axis_y), Vec2::new(y_axis_x, y_axis_y)),
        Vec2::new(translate[0], translate[1]),
    )
}

/// 仿射矩阵分解(`build_affine2_2` 的逆过程),用于约束求解
///
/// 返回 (tx, ty, rotation_deg, scale_x, scale_y, shear_x_deg, shear_y_deg)
///
/// 说明:
/// - build_affine2_2 中 x 轴方向角 rx = rotation + shear_x,y 轴方向角 ry = rotation + 90 + shear_y
/// - rotation 与 shear_x 存在分解歧义,此处约定 shear_x = 0(约束求解中 shear 由 x/y 轴共同决定,
///   spine 4.2 的 transform 约束也只提供 mixShearY,无 mixShearX)
/// - 反射(det < 0,如 scaleX 为负):反射进 x 轴(scale_x 取负),旋转角补偿 180°,
///   保证 decompose -> build 严格还原矩阵(rotation 超 ±90° 或带反射的骨骼不损坏)
pub fn decompose_affine(affine: &Affine2) -> (f32, f32, f32, f32, f32, f32, f32) {
    let t = affine.translation;
    let x_axis = affine.matrix2.x_axis;
    let y_axis = affine.matrix2.y_axis;
    let det = x_axis.x * y_axis.y - x_axis.y * y_axis.x;
    let sx0 = x_axis.length();
    let sy = y_axis.length();
    let a = x_axis.y.atan2(x_axis.x).to_degrees();
    // det < 0:反射矩阵,无法用纯旋转 x 正缩放表示,把反射进 x 轴并旋转 180° 抵消
    let (sx, rx) = if det < 0.0 {
        (-sx0, a - 180.0)
    } else {
        (sx0, a)
    };
    let ry = y_axis.y.atan2(y_axis.x).to_degrees();
    let mut shear_y = ry - rx - 90.0;
    // 归一化到 (-180, 180],避免 ±360 数值抖动(build 用 cos/sin,等价)
    shear_y = ((shear_y + 180.0) % 360.0 + 360.0) % 360.0 - 180.0;
    (t.x, t.y, rx, sx, sy, 0.0, shear_y)
}

/// 从仿射矩阵提取旋转角(度):即 x 轴方向角
pub fn decompose_rotation(affine: &Affine2) -> f32 {
    let x_axis = affine.matrix2.x_axis;
    x_axis.y.atan2(x_axis.x).to_degrees()
}

/// 按剪切模式构造局部仿射矩阵
///
/// - `ShearMode::KeepLength`:spine 默认语义,各轴单位向量长度保持为 1([`build_affine2_2`])
/// - `ShearMode::KeepProjectedLength`:各轴在原始坐标轴上的投影长度保持为 1([`build_affine2_1`])
pub fn build_affine(
    translate: [f32; 2],
    rotation_deg: f32,
    scale: [f32; 2],
    shear_deg: [f32; 2],
    shear_mode: ShearMode,
) -> Affine2 {
    match shear_mode {
        ShearMode::KeepLength => build_affine2_2(translate, rotation_deg, scale, shear_deg),
        ShearMode::KeepProjectedLength => {
            build_affine2_1(translate, rotation_deg, scale, shear_deg)
        }
    }
}

/// 根据继承模式处理父级 affine 和子局部变换的组合
///
/// 移植 spine 4.2 Bone.updateWorldTransform 的全部继承模式(skeleton.scaleX/Y = 1):
/// - Normal:父矩阵 × 局部矩阵
/// - OnlyTranslation:只继承平移(线性部分 = 局部)
/// - NoRotationOrReflection:继承父旋转的缩放(|det| 归一),不继承旋转方向与反射
/// - NoScale / NoScaleOrReflection:不继承父 scale(父线性部分按角度分解 + 归一化)
pub fn cacl_affine(
    parent_affine: Affine2,
    local: &crate::data::runtime::skeleton::BoneData,
) -> Affine2 {
    let pa = parent_affine.matrix2.x_axis.x;
    let pb = parent_affine.matrix2.y_axis.x;
    let pc = parent_affine.matrix2.x_axis.y;
    let pd = parent_affine.matrix2.y_axis.y;
    let sx = local.scale_x;
    let sy = local.scale_y;
    // 局部 x 轴方向角 = rotation + shearX;y 轴方向角 = rotation + 90 + shearY
    let shear_x = (local.rotation_deg + local.shear_x_deg).to_radians();
    let shear_y = (local.rotation_deg + 90.0 + local.shear_y_deg).to_radians();
    let (a, b, c, d) = match local.inherit_mode {
        InheritMode::Normal => {
            let la = shear_x.cos() * sx;
            let lc = shear_x.sin() * sx;
            let lb = shear_y.cos() * sy;
            let ld = shear_y.sin() * sy;
            (
                pa * la + pb * lc,
                pa * lb + pb * ld,
                pc * la + pd * lc,
                pc * lb + pd * ld,
            )
        }
        InheritMode::OnlyTranslation => {
            let la = shear_x.cos() * sx;
            let lc = shear_x.sin() * sx;
            let lb = shear_y.cos() * sy;
            let ld = shear_y.sin() * sy;
            (la, lb, lc, ld)
        }
        InheritMode::NoRotationOrReflection => {
            let (mut pa, mut pb, mut pc, mut pd) = (pa, pb, pc, pd);
            let s = pa * pa + pc * pc;
            let prx;
            if s > 1e-4 {
                let s2 = (pa * pd - pb * pc).abs() / s;
                pb = pc * s2;
                pd = pa * s2;
                prx = pc.atan2(pa).to_degrees();
            } else {
                pa = 0.0;
                pc = 0.0;
                prx = 90.0 - pd.atan2(pb).to_degrees();
            }
            let prx_r = prx.to_radians();
            // 局部矩阵旋转 -prx(官方 la = cos(rotation + shearX - prx)·scaleX 等)
            let rx = shear_x - prx_r;
            let ry = shear_y - prx_r;
            let la2 = rx.cos() * sx;
            let lc2 = rx.sin() * sx;
            let lb2 = ry.cos() * sy;
            let ld2 = ry.sin() * sy;
            (
                pa * la2 - pb * lc2,
                pa * lb2 - pb * ld2,
                pc * la2 + pd * lc2,
                pc * lb2 + pd * ld2,
            )
        }
        InheritMode::NoScale | InheritMode::NoScaleOrReflection => {
            // 官方:父线性矩阵按局部旋转角度取列,归一化去掉 scale,再重建
            let rotation = local.rotation_deg.to_radians();
            let (cos, sin) = (rotation.cos(), rotation.sin());
            let (mut za, mut zc) = (pa * cos + pb * sin, pc * cos + pd * sin);
            let mut s = (za * za + zc * zc).sqrt();
            if s > 1e-5 {
                s = 1.0 / s;
            }
            za *= s;
            zc *= s;
            s = (za * za + zc * zc).sqrt();
            if local.inherit_mode == InheritMode::NoScale && (pa * pd - pb * pc < 0.0) {
                s = -s;
            }
            let rotation2 = std::f32::consts::FRAC_PI_2 + zc.atan2(za);
            let (zb, zd) = (rotation2.cos() * s, rotation2.sin() * s);
            // 官方 NoScale:局部列向量只用 shear(不含 rotation,rotation 已进 za/zc)
            let la = local.shear_x_deg.to_radians().cos() * sx;
            let lb = (90.0 + local.shear_y_deg).to_radians().cos() * sy;
            let lc = local.shear_x_deg.to_radians().sin() * sx;
            let ld = (90.0 + local.shear_y_deg).to_radians().sin() * sy;
            (
                za * la + zb * lc,
                za * lb + zb * ld,
                zc * la + zd * lc,
                zc * lb + zd * ld,
            )
        }
    };
    Affine2::from_mat2_translation(
        Mat2::from_cols(Vec2::new(a, c), Vec2::new(b, d)),
        parent_affine.translation
            + Vec2::new(
                pa * local.translate_x + pb * local.translate_y,
                pc * local.translate_x + pd * local.translate_y,
            ),
    )
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn decompose_roundtrip_preserves_matrix() {
        // 各种旋转/缩放/剪切组合下,decompose -> build 必须还原矩阵
        let cases: [[f32; 7]; 6] = [
            [0.0, 0.0, 10.0, 2.0, 3.0, 0.0, 0.0],
            [0.0, 0.0, 170.0, 1.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 120.0, 1.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, -150.0, 1.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 30.0, -2.0, 1.0, 0.0, 0.0],
            [0.0, 0.0, 20.0, 2.0, 3.0, 0.0, 15.0],
        ];
        for c in cases {
            let m = build_affine(
                [c[0], c[1]],
                c[2],
                [c[3], c[4]],
                [c[5], c[6]],
                ShearMode::KeepLength,
            );
            let (tx, ty, r, sx, sy, shx, shy) = decompose_affine(&m);
            let r2 = build_affine([tx, ty], r, [sx, sy], [shx, shy], ShearMode::KeepLength);
            let d = (m.matrix2 - r2.matrix2)
                .to_cols_array()
                .iter()
                .map(|v| v.abs())
                .fold(0.0f32, f32::max)
                + (m.translation - r2.translation).abs().max_element();
            assert!(
                d < 1e-3,
                "roundtrip mismatch {d} for case {c:?} -> ({tx},{ty},{r},{sx},{sy},{shx},{shy})"
            );
        }
    }

    #[test]
    fn test_build_affine2() {
        let translate = [10.0, 20.0];
        let rotation_deg = 45.0;
        let scale = [2.0, 3.0];
        let shear_deg = [10.0, 15.0];

        let affine = build_affine2_0(translate, rotation_deg, scale, shear_deg);
        let affine1 = build_affine2_1(translate, rotation_deg, scale, shear_deg);
        // 打印结果以供检查
        println!("Affine2 Matrix: {}", affine);
        println!("Affine2_1 Matrix: {}", affine1);
    }
}