use crate::data::{
ConstraintId, TransformConstraintData, TransformConstraintPose, TransformProperty,
};
use crate::math::util::{DEG_RAD, PI, PI_2, RAD_DEG};
use crate::skeleton::bone::{self, BonePose, Frame};
use crate::skeleton::constraint::Constraint;
use crate::skeleton::{Skeleton, UpdateCacheEntry};
impl Skeleton {
pub(crate) fn sort_transform(&mut self, id: ConstraintId, data: &TransformConstraintData) {
if !data.local_source {
self.sort_bone(data.source);
}
let world_target = !data.local_target;
if world_target {
for &b in &data.bones {
self.sort_bone(b);
}
}
self.update_cache.push(UpdateCacheEntry::Constraint(id));
for &b in &data.bones {
self.sort_reset(b);
self.constrain_bone(b);
}
for &b in &data.bones {
self.bones[b.index()].sorted = world_target;
}
}
pub(crate) fn update_transform(&mut self, id: ConstraintId, data: &TransformConstraintData) {
let Constraint::Transform(c) = &self.constraints[id.index()] else {
unreachable!()
};
let p = *c.posed.applied();
if p.mix_rotate == 0.0
&& p.mix_x == 0.0
&& p.mix_y == 0.0
&& p.mix_scale_x == 0.0
&& p.mix_scale_y == 0.0
&& p.mix_shear_y == 0.0
{
return;
}
let f = self.frame();
let (local_source, local_target) = (data.local_source, data.local_target);
if local_source {
bone::validate_local_transform(&mut self.bones, data.source.index(), &f);
}
for &b in &data.bones {
let bi = b.index();
if local_target {
bone::modify_local(&mut self.bones, bi, &f);
} else {
bone::modify_world(&mut self.bones, bi, &f);
}
let hoisted = *self.bones[data.source.index()].applied();
for from in &data.properties {
let source = if b == data.source {
*self.bones[bi].applied()
} else {
hoisted
};
let value = from_value(from.property, &f, &source, local_source, &data.offsets)
- from.offset;
for to in &from.to {
if to_mix(to.property, &p) != 0.0 {
let mut clamped = to.offset + value * to.scale;
if data.clamp {
clamped = if to.offset < to.max {
clamp(clamped, to.offset, to.max)
} else {
clamp(clamped, to.max, to.offset)
};
}
to_apply(
to.property,
&f,
&p,
self.bones[bi].applied_mut(),
clamped,
local_target,
data.additive,
);
}
}
}
}
}
}
#[inline]
fn clamp(x: f32, min: f32, max: f32) -> f32 {
if x < min {
min
} else if x > max {
max
} else {
x
}
}
pub(crate) fn from_value(
property: TransformProperty,
f: &Frame,
source: &BonePose,
local: bool,
offsets: &[f32; 6],
) -> f32 {
let (sx, sy) = (f.scale_x, f.scale_y);
let o = |p: TransformProperty| offsets[p.offset_index()];
match property {
TransformProperty::Rotate => {
if local {
return source.rotation + o(TransformProperty::Rotate);
}
let mut value = (source.c / sy).atan2(source.a / sx) * RAD_DEG
+ if (source.a * source.d - source.b * source.c) * sx * sy > 0.0 {
o(TransformProperty::Rotate)
} else {
-o(TransformProperty::Rotate)
};
if value < 0.0 {
value += 360.0;
}
value
}
TransformProperty::X => {
if local {
source.x + o(TransformProperty::X)
} else {
(o(TransformProperty::X) * source.a
+ o(TransformProperty::Y) * source.b
+ source.world_x)
/ sx
}
}
TransformProperty::Y => {
if local {
source.y + o(TransformProperty::Y)
} else {
(o(TransformProperty::X) * source.c
+ o(TransformProperty::Y) * source.d
+ source.world_y)
/ sy
}
}
TransformProperty::ScaleX => {
if local {
return source.scale_x + o(TransformProperty::ScaleX);
}
let a = source.a / sx;
let c = source.c / sy;
(a * a + c * c).sqrt() + o(TransformProperty::ScaleX)
}
TransformProperty::ScaleY => {
if local {
return source.scale_y + o(TransformProperty::ScaleY);
}
let b = source.b / sx;
let d = source.d / sy;
(b * b + d * d).sqrt() + o(TransformProperty::ScaleY)
}
TransformProperty::ShearY => {
if local {
return source.shear_y + o(TransformProperty::ShearY);
}
let ix = 1.0 / sx;
let iy = 1.0 / sy;
((source.d * iy).atan2(source.b * ix) - (source.c * iy).atan2(source.a * ix)) * RAD_DEG
- 90.0
+ o(TransformProperty::ShearY)
}
}
}
fn to_mix(property: TransformProperty, p: &TransformConstraintPose) -> f32 {
match property {
TransformProperty::Rotate => p.mix_rotate,
TransformProperty::X => p.mix_x,
TransformProperty::Y => p.mix_y,
TransformProperty::ScaleX => p.mix_scale_x,
TransformProperty::ScaleY => p.mix_scale_y,
TransformProperty::ShearY => p.mix_shear_y,
}
}
#[allow(clippy::many_single_char_names)]
fn to_apply(
property: TransformProperty,
f: &Frame,
p: &TransformConstraintPose,
bone: &mut BonePose,
mut value: f32,
local: bool,
additive: bool,
) {
let (sx, sy) = (f.scale_x, f.scale_y);
match property {
TransformProperty::Rotate => {
if local {
bone.rotation += (if additive {
value
} else {
value - bone.rotation
}) * p.mix_rotate;
} else {
let (ix, iy) = (1.0 / sx, 1.0 / sy);
let (a, b, c, d) = (bone.a * ix, bone.b * ix, bone.c * iy, bone.d * iy);
value *= DEG_RAD;
if !additive {
value -= c.atan2(a);
}
if value > PI {
value -= PI_2;
} else if value < -PI {
value += PI_2;
}
value *= p.mix_rotate;
let (cos, sin) = (value.cos(), value.sin());
bone.a = (cos * a - sin * c) * sx;
bone.b = (cos * b - sin * d) * sx;
bone.c = (sin * a + cos * c) * sy;
bone.d = (sin * b + cos * d) * sy;
}
}
TransformProperty::X => {
if local {
bone.x += (if additive { value } else { value - bone.x }) * p.mix_x;
} else {
if !additive {
value -= bone.world_x / sx;
}
bone.world_x += value * p.mix_x * sx;
}
}
TransformProperty::Y => {
if local {
bone.y += (if additive { value } else { value - bone.y }) * p.mix_y;
} else {
if !additive {
value -= bone.world_y / sy;
}
bone.world_y += value * p.mix_y * sy;
}
}
TransformProperty::ScaleX => {
if local {
if additive {
bone.scale_x *= 1.0 + (value - 1.0) * p.mix_scale_x;
} else if bone.scale_x != 0.0 {
bone.scale_x += (value - bone.scale_x) * p.mix_scale_x;
}
} else if additive {
let s = 1.0 + (value - 1.0) * p.mix_scale_x;
bone.a *= s;
bone.c *= s;
} else {
let a = bone.a / sx;
let c = bone.c / sy;
let mut s = (a * a + c * c).sqrt();
if s != 0.0 {
s = 1.0 + (value - s) * p.mix_scale_x / s;
bone.a *= s;
bone.c *= s;
}
}
}
TransformProperty::ScaleY => {
if local {
if additive {
bone.scale_y *= 1.0 + (value - 1.0) * p.mix_scale_y;
} else if bone.scale_y != 0.0 {
bone.scale_y += (value - bone.scale_y) * p.mix_scale_y;
}
} else if additive {
let s = 1.0 + (value - 1.0) * p.mix_scale_y;
bone.b *= s;
bone.d *= s;
} else {
let b = bone.b / sx;
let d = bone.d / sy;
let mut s = (b * b + d * d).sqrt();
if s != 0.0 {
s = 1.0 + (value - s) * p.mix_scale_y / s;
bone.b *= s;
bone.d *= s;
}
}
}
TransformProperty::ShearY => {
if local {
if !additive {
value -= bone.shear_y;
}
bone.shear_y += value * p.mix_shear_y;
} else {
let b = bone.b / sx;
let d = bone.d / sy;
let by = d.atan2(b);
value = (value + 90.0) * DEG_RAD;
if additive {
value -= PI / 2.0;
} else {
value -= by - (bone.c / sy).atan2(bone.a / sx);
if value > PI {
value -= PI_2;
} else if value < -PI {
value += PI_2;
}
}
value = by + value * p.mix_shear_y;
let s = (b * b + d * d).sqrt();
bone.b = value.cos() * s * sx;
bone.d = value.sin() * s * sy;
}
}
}
}