///|
/// Compact 2D affine matrix used when many sprites share one transform
/// hierarchy. The six values follow the conventional column-vector form.
pub struct Affine2D {
a : Double
b : Double
c : Double
d : Double
tx : Double
ty : Double
} derive(Debug)
///|
pub fn affine2d_identity() -> Affine2D {
{ a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0 }
}
///|
pub fn affine2d_translation(value : Point2) -> Affine2D {
{ a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: value.x(), ty: value.y() }
}
///|
pub fn affine2d_scale(value : Point2) -> Affine2D {
{ a: value.x(), b: 0.0, c: 0.0, d: value.y(), tx: 0.0, ty: 0.0 }
}
///|
pub fn affine2d_rotation(angle : Double) -> Affine2D {
let cosine = @math.cos(angle)
let sine = @math.sin(angle)
{ a: cosine, b: sine, c: -sine, d: cosine, tx: 0.0, ty: 0.0 }
}
///|
pub fn affine2d_skew(skew : Double) -> Affine2D {
{ a: 1.0, b: 0.0, c: @math.tan(skew), d: 1.0, tx: 0.0, ty: 0.0 }
}
///|
pub fn affine2d_from_transform(value : Transform2D) -> Affine2D {
affine2d_translation(value.position()).multiply(
affine2d_rotation(value.rotation()).multiply(
affine2d_skew(value.skew()).multiply(affine2d_scale(value.scale())),
),
)
}
///|
pub fn Affine2D::a(self : Affine2D) -> Double {
self.a
}
///|
pub fn Affine2D::b(self : Affine2D) -> Double {
self.b
}
///|
pub fn Affine2D::c(self : Affine2D) -> Double {
self.c
}
///|
pub fn Affine2D::d(self : Affine2D) -> Double {
self.d
}
///|
pub fn Affine2D::translation(self : Affine2D) -> Point2 {
point2(x=self.tx, y=self.ty)
}
///|
pub fn Affine2D::determinant(self : Affine2D) -> Double {
self.a * self.d - self.b * self.c
}
///|
pub fn Affine2D::multiply(self : Affine2D, other : Affine2D) -> Affine2D {
{
a: self.a * other.a + self.c * other.b,
b: self.b * other.a + self.d * other.b,
c: self.a * other.c + self.c * other.d,
d: self.b * other.c + self.d * other.d,
tx: self.a * other.tx + self.c * other.ty + self.tx,
ty: self.b * other.tx + self.d * other.ty + self.ty,
}
}
///|
pub fn Affine2D::apply(self : Affine2D, point : Point2) -> Point2 {
point2(
x=self.a * point.x() + self.c * point.y() + self.tx,
y=self.b * point.x() + self.d * point.y() + self.ty,
)
}
///|
pub fn Affine2D::inverse(self : Affine2D) -> Affine2D {
let determinant = self.determinant()
if determinant == 0.0 {
return affine2d_identity()
}
let inverse = 1.0 / determinant
{
a: self.d * inverse,
b: -self.b * inverse,
c: -self.c * inverse,
d: self.a * inverse,
tx: (self.c * self.ty - self.d * self.tx) * inverse,
ty: (self.b * self.tx - self.a * self.ty) * inverse,
}
}
///|
pub fn Affine2D::lerp(
self : Affine2D,
other : Affine2D,
ratio : Double,
) -> Affine2D {
let t = clamp01(ratio)
{
a: self.a + (other.a - self.a) * t,
b: self.b + (other.b - self.b) * t,
c: self.c + (other.c - self.c) * t,
d: self.d + (other.d - self.d) * t,
tx: self.tx + (other.tx - self.tx) * t,
ty: self.ty + (other.ty - self.ty) * t,
}
}
///|
pub struct AffineDecomposition2D {
position : Point2
scale : Point2
rotation : Double
skew : Double
determinant : Double
} derive(Debug)
///|
pub fn AffineDecomposition2D::position(self : AffineDecomposition2D) -> Point2 {
self.position
}
///|
pub fn AffineDecomposition2D::scale(self : AffineDecomposition2D) -> Point2 {
self.scale
}
///|
pub fn AffineDecomposition2D::rotation(self : AffineDecomposition2D) -> Double {
self.rotation
}
///|
pub fn AffineDecomposition2D::skew(self : AffineDecomposition2D) -> Double {
self.skew
}
///|
pub fn AffineDecomposition2D::determinant(
self : AffineDecomposition2D,
) -> Double {
self.determinant
}
///|
pub fn Affine2D::decompose(self : Affine2D) -> AffineDecomposition2D {
let scale_x = @math.hypot(self.a, self.b)
let rotation = if scale_x == 0.0 { 0.0 } else { @math.atan2(self.b, self.a) }
let normalized_a = if scale_x == 0.0 { 0.0 } else { self.a / scale_x }
let normalized_b = if scale_x == 0.0 { 0.0 } else { self.b / scale_x }
let shear = normalized_a * self.c + normalized_b * self.d
let orthogonal_c = self.c - normalized_a * shear
let orthogonal_d = self.d - normalized_b * shear
let scale_y = @math.hypot(orthogonal_c, orthogonal_d)
{
position: point2(x=self.tx, y=self.ty),
scale: point2(x=scale_x, y=scale_y),
rotation,
skew: if scale_y == 0.0 {
0.0
} else {
@math.atan(shear / scale_y)
},
determinant: self.determinant(),
}
}
///|
pub fn transform2d_from_affine(value : Affine2D) -> Transform2D {
let decomposition = value.decompose()
transform2d(
position=decomposition.position,
scale=decomposition.scale,
rotation=decomposition.rotation,
skew=decomposition.skew,
)
}
///|
pub fn transform2d_apply(value : Transform2D, point : Point2) -> Point2 {
affine2d_from_transform(value).apply(point)
}
///|
pub struct Transform2Sample {
time : Double
value : Transform2D
matrix : Affine2D
} derive(Debug)
///|
pub fn transform2_sample(
time : Double,
value : Transform2D,
) -> Transform2Sample {
{ time, value, matrix: affine2d_from_transform(value) }
}
///|
pub fn Transform2Sample::time(self : Transform2Sample) -> Double {
self.time
}
///|
pub fn Transform2Sample::value(self : Transform2Sample) -> Transform2D {
self.value
}
///|
pub fn Transform2Sample::matrix(self : Transform2Sample) -> Affine2D {
self.matrix
}
///|
pub fn Transform3D::translated(
self : Transform3D,
offset : Point3,
) -> Transform3D {
transform3d(
position=point3(
x=self.position().x() + offset.x(),
y=self.position().y() + offset.y(),
z=self.position().z() + offset.z(),
),
rotation=self.rotation(),
scale=self.scale(),
)
}
///|
pub fn Transform3D::scaled(
self : Transform3D,
multiplier : Point3,
) -> Transform3D {
transform3d(
position=self.position(),
rotation=self.rotation(),
scale=point3(
x=self.scale().x() * multiplier.x(),
y=self.scale().y() * multiplier.y(),
z=self.scale().z() * multiplier.z(),
),
)
}
///|
pub fn Transform3D::around(self : Transform3D, pivot : Point3) -> Transform3D {
let offset = point3(
x=self.position().x() - pivot.x(),
y=self.position().y() - pivot.y(),
z=self.position().z() - pivot.z(),
)
let rotated = self.rotation().rotate(offset)
transform3d(
position=point3(
x=pivot.x() + rotated.x(),
y=pivot.y() + rotated.y(),
z=pivot.z() + rotated.z(),
),
rotation=self.rotation(),
scale=self.scale(),
)
}
///|
pub fn Transform3D::is_identity(self : Transform3D) -> Bool {
self.position().x() == 0.0 &&
self.position().y() == 0.0 &&
self.position().z() == 0.0 &&
self.rotation().x() == 0.0 &&
self.rotation().y() == 0.0 &&
self.rotation().z() == 0.0 &&
self.rotation().w() == 1.0 &&
self.scale().x() == 1.0 &&
self.scale().y() == 1.0 &&
self.scale().z() == 1.0
}
///|
pub fn transform3d_average(
values : Array[Transform3D],
weights : Array[Double],
) -> Transform3D {
if values.length() == 0 {
return Transform3D::identity()
}
let count = values.length().min(weights.length())
if count == 0 {
return Transform3D::identity()
}
let normalized = normalize_weights(weights)
let mut result = values[0]
for index in 1..