// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
/// 2D affine transform.
///
/// Ported from upstream `zeno/src/geometry.rs` (Apache-2.0 OR MIT).
pub struct Transform {
xx : Double
xy : Double
yx : Double
yy : Double
x : Double
y : Double
}
///|
pub fn Transform::new(
xx : Double,
xy : Double,
yx : Double,
yy : Double,
x : Double,
y : Double,
) -> Transform {
Transform::{ xx, xy, yx, yy, x, y }
}
///|
pub fn Transform::identity() -> Transform {
Transform::new(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
}
///|
pub fn Transform::translation(x : Double, y : Double) -> Transform {
Transform::new(1.0, 0.0, 0.0, 1.0, x, y)
}
///|
pub fn Transform::translate(x : Double, y : Double) -> Transform {
// Back-compat alias.
Transform::translation(x, y)
}
///|
pub fn Transform::rotation(angle : Angle) -> Transform {
let radians = angle.to_radians()
let sinv = @coremath.sin(radians)
let cosv = @coremath.cos(radians)
Transform::{ xx: cosv, xy: sinv, yx: -sinv, yy: cosv, x: 0.0, y: 0.0 }
}
///|
pub fn Transform::rotation_about(point : Point, angle : Angle) -> Transform {
Transform::translation(point.x(), point.y())
.then_rotate(angle)
.then_translate(-point.x(), -point.y())
}
///|
pub fn Transform::scale(x : Double, y : Double) -> Transform {
Transform::new(x, 0.0, 0.0, y, 0.0, 0.0)
}
///|
pub fn Transform::skew(x : Angle, y : Angle) -> Transform {
Transform::{
xx: 1.0,
xy: @coremath.tan(y.to_radians()),
yx: @coremath.tan(x.to_radians()),
yy: 1.0,
x: 0.0,
y: 0.0,
}
}
///|
fn combine(a : Transform, b : Transform) -> Transform {
let xx = a.xx * b.xx + a.yx * b.xy
let yx = a.xx * b.yx + a.yx * b.yy
let xy = a.xy * b.xx + a.yy * b.xy
let yy = a.xy * b.yx + a.yy * b.yy
let x = a.x * b.xx + a.y * b.xy + b.x
let y = a.x * b.yx + a.y * b.yy + b.y
Transform::{ xx, yx, xy, yy, x, y }
}
///|
/// Returns a new transform that represents the application of this transform
/// followed by other.
pub fn Transform::then(self : Transform, other : Transform) -> Transform {
combine(self, other)
}
///|
/// Returns a new transform that represents a translation followed by this transform.
pub fn Transform::pre_translate(
self : Transform,
x : Double,
y : Double,
) -> Transform {
combine(Transform::translation(x, y), self)
}
///|
/// Returns a new transform that represents this transform followed by a translation.
pub fn Transform::then_translate(
self : Transform,
x : Double,
y : Double,
) -> Transform {
Transform::{
xx: self.xx,
xy: self.xy,
yx: self.yx,
yy: self.yy,
x: self.x + x,
y: self.y + y,
}
}
///|
/// Returns a new transform that represents a rotation followed by this transform.
pub fn Transform::pre_rotate(self : Transform, angle : Angle) -> Transform {
combine(Transform::rotation(angle), self)
}
///|
/// Returns a new transform that represents this transform followed by a rotation.
pub fn Transform::then_rotate(self : Transform, angle : Angle) -> Transform {
combine(self, Transform::rotation(angle))
}
///|
/// Returns a new transform that represents a scale followed by this transform.
pub fn Transform::pre_scale(
self : Transform,
x : Double,
y : Double,
) -> Transform {
combine(Transform::scale(x, y), self)
}
///|
/// Returns a new transform that represents this transform followed by a scale.
pub fn Transform::then_scale(
self : Transform,
x : Double,
y : Double,
) -> Transform {
combine(self, Transform::scale(x, y))
}
///|
pub fn Transform::determinant(self : Transform) -> Double {
self.xx * self.yy - self.yx * self.xy
}
///|
pub fn Transform::invert(self : Transform) -> Transform? {
let det = self.determinant()
if det == 0.0 || det.is_nan() || det.is_inf() {
return None
}
let s = 1.0 / det
let a = self.xx
let b = self.xy
let c = self.yx
let d = self.yy
let x = self.x
let y = self.y
Some(Transform::{
xx: d * s,
xy: -b * s,
yx: -c * s,
yy: a * s,
x: (b * y - d * x) * s,
y: (c * x - a * y) * s,
})
}
///|
pub fn Transform::transform_point(self : Transform, point : Point) -> Point {
Vector::new(
point.x() * self.xx + point.y() * self.yx + self.x,
point.x() * self.xy + point.y() * self.yy + self.y,
)
}
///|
pub fn Transform::transform_vector(self : Transform, vector : Vector) -> Vector {
Vector::new(
vector.x() * self.xx + vector.y() * self.yx,
vector.x() * self.xy + vector.y() * self.yy,
)
}