use glam::*;
use crate::data::runtime::skeleton::{InheritMode, ShearMode};
pub fn build_affine2_0(
translate: [f32; 2],
rotation_deg: f32,
scale: [f32; 2],
shear_deg: [f32; 2],
) -> Affine2 {
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]));
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]))
}
pub fn build_affine2_1(
translate: [f32; 2],
rotation_deg: f32,
scale: [f32; 2],
shear_deg: [f32; 2],
) -> Affine2 {
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]));
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]))
}
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;
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]),
)
}
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();
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;
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)
}
pub fn decompose_rotation(affine: &Affine2) -> f32 {
let x_axis = affine.matrix2.x_axis;
x_axis.y.atan2(x_axis.x).to_degrees()
}
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)
}
}
}
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;
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();
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 => {
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);
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() {
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);
}
}