///|
/// A single transformation operation.
pub(all) enum TransformOp {
Scale((Double, Double), Double, Double)
Rotate((Double, Double), Double)
Translate(Double, Double)
ShearX((Double, Double), Double)
ShearY((Double, Double), Double)
} derive(Debug)
///|
/// A list of transformations, stored with the most recent op at the head.
pub struct Transform {
ops : Array[TransformOp]
} derive(Debug)
///|
/// A transformation matrix (a c e / b d f / 0 0 1).
pub struct TransformMatrix {
a : Double
b : Double
c : Double
d : Double
e : Double
f : Double
} derive(Debug)
///|
pub fn Transform::Transform(ops : Array[TransformOp]) -> Transform {
{ ops, }
}
///|
pub fn TransformMatrix::TransformMatrix(
a~ : Double,
b~ : Double,
c~ : Double,
d~ : Double,
e~ : Double,
f~ : Double,
) -> TransformMatrix {
{ a, b, c, d, e, f, }
}
///|
/// The identity transform.
pub fn Transform::identity() -> Transform {
Transform([])
}
///|
/// Create a transform from an array of ops.
pub fn Transform::new(ops : Array[TransformOp]) -> Transform {
{ ops, }
}
///|
/// The identity matrix.
pub fn TransformMatrix::identity() -> TransformMatrix {
TransformMatrix(a=1.0, b=0.0, c=0.0, d=1.0, e=0.0, f=0.0)
}
///|
fn point_eq(a : (Double, Double), b : (Double, Double)) -> Bool {
a.0 == b.0 && a.1 == b.1
}
///|
fn concat_ops(
a : Array[TransformOp],
b : Array[TransformOp],
) -> Array[TransformOp] {
let out = Array(capacity=a.length() + b.length())
for item in a {
out.push(item)
}
for item in b {
out.push(item)
}
out
}
///|
fn tail_ops(tr : Array[TransformOp]) -> Array[TransformOp] {
let out = Array(capacity=if tr.length() > 0 { tr.length() - 1 } else { 0 })
let mut i = 1
while i < tr.length() {
out.push(tr[i])
i = i + 1
}
out
}
///|
fn safe_float(value : Double) -> Double {
if value == 0.0 || value.is_nan() || value.is_inf() {
0.0
} else {
value
}
}
///|
fn pi() -> Double {
4.0 * @math.atan(1.0)
}
///|
fn string_of_op(op : TransformOp) -> String {
match op {
Scale((x, y), sx, sy) =>
"Scale about (\{x}, \{y}) by \{sx} in x and \{sy} in y\n"
Rotate((x, y), angle) => "Rotate by \{angle} about (\{x}, \{y})\n"
Translate(dx, dy) => "Translate by \{dx}, \{dy}\n"
ShearX((x, y), factor) =>
"Shear in X about (\{x}, \{y}), proportionality constant \{factor}\n"
ShearY((x, y), factor) =>
"Shear in Y about (\{x}, \{y}), proportionality constant \{factor}\n"
}
}
///|
/// Make a string of a transform for debug purposes.
pub fn Transform::to_string(self : Transform) -> String {
let sb = StringBuilder()
for op in self.ops[:].rev_iter() {
sb.write_string(string_of_op(op))
}
sb.to_string()
}
///|
/// String of a transformation matrix.
pub fn TransformMatrix::to_string(self : TransformMatrix) -> String {
"\{self.a}, \{self.b}, \{self.c}, \{self.d}, \{self.e}, \{self.f}"
}
///|
/// Compose a transformation operation onto an existing transform.
pub fn Transform::compose(self : Transform, op : TransformOp) -> Transform {
if self.ops.length() == 0 {
return Transform([op])
}
let head = self.ops[0]
match (head, op) {
(Translate(dx, dy), Translate(dx2, dy2)) =>
Transform(concat_ops([Translate(dx + dx2, dy + dy2)], tail_ops(self.ops)))
(Scale(p, sx, sy), Scale(p2, sx2, sy2)) =>
if point_eq(p, p2) {
Transform(
concat_ops([Scale(p, sx * sx2, sy * sy2)], tail_ops(self.ops)),
)
} else {
Transform(concat_ops([op], self.ops))
}
(Rotate(p, a), Rotate(p2, a2)) =>
if point_eq(p, p2) {
Transform(concat_ops([Rotate(p, a + a2)], tail_ops(self.ops)))
} else {
Transform(concat_ops([op], self.ops))
}
(ShearX(p, a), ShearX(p2, a2)) =>
if point_eq(p, p2) {
Transform(concat_ops([ShearX(p, a + a2)], tail_ops(self.ops)))
} else {
Transform(concat_ops([op], self.ops))
}
(ShearY(p, a), ShearY(p2, a2)) =>
if point_eq(p, p2) {
Transform(concat_ops([ShearY(p, a + a2)], tail_ops(self.ops)))
} else {
Transform(concat_ops([op], self.ops))
}
_ => Transform(concat_ops([op], self.ops))
}
}
///|
/// Append two transforms (perform b then a).
pub fn Transform::append(self : Transform, other : Transform) -> Transform {
Transform(concat_ops(self.ops, other.ops))
}
///|
/// Compose two matrices. Applying the result is equivalent to applying m then m'.
pub fn TransformMatrix::compose(
self : TransformMatrix,
m : TransformMatrix,
) -> TransformMatrix {
TransformMatrix(
a=self.a * m.a + self.c * m.b,
b=self.b * m.a + self.d * m.b,
c=self.a * m.c + self.c * m.d,
d=self.b * m.c + self.d * m.d,
e=self.a * m.e + self.c * m.f + self.e,
f=self.b * m.e + self.d * m.f + self.f,
)
}
///|
/// Matrix inversion. Raises NonInvertable if no inverse.
pub fn TransformMatrix::invert(
self : TransformMatrix,
) -> TransformMatrix raise NonInvertable {
let divisor = self.a * self.d - self.b * self.c
if divisor == 0.0 {
raise NonInvertable
}
let det = 1.0 / divisor
if det == 0.0 || det.is_nan() || det.is_inf() {
raise NonInvertable
}
TransformMatrix(
a=det * self.d,
b=det * -self.b,
c=det * -self.c,
d=det * self.a,
e=det * (self.c * self.f - self.d * self.e),
f=det * (self.b * self.e - self.a * self.f),
)
}
///|
/// Make a translation matrix.
pub fn TransformMatrix::translate(tx : Double, ty : Double) -> TransformMatrix {
TransformMatrix(a=1.0, b=0.0, c=0.0, d=1.0, e=tx, f=ty)
}
///|
/// Make a scale matrix about a center point.
pub fn TransformMatrix::scale(
center : (Double, Double),
sx : Double,
sy : Double,
) -> TransformMatrix {
let translate = TransformMatrix::translate(-center.0, -center.1)
let translate_back = TransformMatrix::translate(center.0, center.1)
let scale_matrix = TransformMatrix(a=sx, b=0.0, c=0.0, d=sy, e=0.0, f=0.0)
translate_back.compose(scale_matrix.compose(translate))
}
///|
/// Make a rotation matrix about a center point.
pub fn TransformMatrix::rotate(
center : (Double, Double),
angle : Double,
) -> TransformMatrix {
let translate = TransformMatrix::translate(-center.0, -center.1)
let translate_back = TransformMatrix::translate(center.0, center.1)
let rotation_matrix = TransformMatrix(
a=@math.cos(angle),
b=@math.sin(angle),
c=-@math.sin(angle),
d=@math.cos(angle),
e=0.0,
f=0.0,
)
translate_back.compose(rotation_matrix.compose(translate))
}
///|
/// Matrix to shear in x about a point.
pub fn TransformMatrix::shear_x(
center : (Double, Double),
factor : Double,
) -> TransformMatrix {
let translate = TransformMatrix::translate(-center.0, -center.1)
let translate_back = TransformMatrix::translate(center.0, center.1)
let shear_matrix = TransformMatrix(
a=1.0,
b=0.0,
c=factor,
d=1.0,
e=0.0,
f=0.0,
)
translate_back.compose(shear_matrix.compose(translate))
}
///|
/// Matrix to shear in y about a point.
pub fn TransformMatrix::shear_y(
center : (Double, Double),
factor : Double,
) -> TransformMatrix {
let translate = TransformMatrix::translate(-center.0, -center.1)
let translate_back = TransformMatrix::translate(center.0, center.1)
let shear_matrix = TransformMatrix(
a=1.0,
b=factor,
c=0.0,
d=1.0,
e=0.0,
f=0.0,
)
translate_back.compose(shear_matrix.compose(translate))
}
///|
/// Make a matrix from a single transformation operation.
pub fn TransformOp::to_matrix(self : TransformOp) -> TransformMatrix {
match self {
Scale(center, sx, sy) => TransformMatrix::scale(center, sx, sy)
Rotate(center, angle) => TransformMatrix::rotate(center, angle)
Translate(dx, dy) => TransformMatrix::translate(dx, dy)
ShearX(center, factor) => TransformMatrix::shear_x(center, factor)
ShearY(center, factor) => TransformMatrix::shear_y(center, factor)
}
}
///|
/// Make a matrix from a transform.
pub fn Transform::to_matrix(self : Transform) -> TransformMatrix {
let mut acc = TransformMatrix::identity()
for op in self.ops {
acc = op.to_matrix().compose(acc)
}
acc
}
///|
/// Transform a coordinate by a given transformation matrix.
pub fn TransformMatrix::apply(
self : TransformMatrix,
point : (Double, Double),
) -> (Double, Double) {
let x = point.0
let y = point.1
(x * self.a + y * self.c + self.e, x * self.b + y * self.d + self.f)
}
///|
/// Transform a coordinate by a given transform.
pub fn Transform::apply(
self : Transform,
point : (Double, Double),
) -> (Double, Double) {
let mut x = point.0
let mut y = point.1
for op in self.ops[:].rev_iter() {
match op {
Scale((cx, cy), sx, sy) => {
let dx = x - cx
let dy = y - cy
x = dx * sx + cx
y = dy * sy + cy
}
Rotate((cx, cy), angle) => {
let cosine = @math.cos(angle)
let sine = @math.sin(angle)
let dx = x - cx
let dy = y - cy
let nx = dx * cosine + dy * -sine
let ny = dx * sine + dy * cosine
x = nx + cx
y = ny + cy
}
Translate(dx, dy) => {
x = x + dx
y = y + dy
}
ShearX((cx, cy), factor) => {
let dx = x - cx
let dy = y - cy
x = dx + dy * factor + cx
y = dy + cy
}
ShearY((cx, cy), factor) => {
let dx = x - cx
let dy = y - cy
x = dx + cx
y = dx * factor + dy + cy
}
}
}
(x, y)
}
///|
/// Decompose a transformation matrix into scale, aspect, rotation, shear,
/// translation in x, translation in y.
pub fn TransformMatrix::decompose(
self : TransformMatrix,
) -> (Double, Double, Double, Double, Double, Double) {
let axb = self.a * self.d - self.c * self.b
let moda = (self.a * self.a + self.b * self.b).sqrt()
let modb = (self.c * self.c + self.d * self.d).sqrt()
let adotb = self.a * self.c + self.b * self.d
let scale = axb / moda
let abs_scale = scale.abs()
let aspect = if abs_scale == 0.0 { 1.0 } else { moda / abs_scale }
let rotation = @math.atan2(self.b, self.a)
let shear = if moda * modb == 0.0 {
0.0
} else {
pi() / 2.0 - @math.acos(adotb / (moda * modb))
}
(
safe_float(scale),
safe_float(aspect),
safe_float(rotation),
safe_float(shear),
safe_float(self.e),
safe_float(self.f),
)
}
///|
/// Recompose a matrix from components.
pub fn TransformMatrix::recompose(
scale : Double,
aspect : Double,
rotation : Double,
shear : Double,
tx : Double,
ty : Double,
) -> TransformMatrix {
let scale_aspect_shear = TransformMatrix(
a=scale.abs() * aspect,
b=0.0,
c=scale * @math.tan(shear),
d=scale,
e=0.0,
f=0.0,
)
let rotated = TransformMatrix::rotate((0.0, 0.0), rotation).compose(
scale_aspect_shear,
)
TransformMatrix::translate(tx, ty).compose(rotated)
}
///|
/// Exception raised if a matrix is non-invertible.
pub suberror NonInvertable {
NonInvertable
}
// Explicit method promotion for derived/implemented traits.
// (Previously implicit; see warning [0079] implicit_impl_as_method.)
///|
pub extend Transform with Debug::{to_repr}
///|
pub extend TransformMatrix with Debug::{to_repr}
///|
pub extend TransformOp with Debug::{to_repr}