zng_unit/
transform.rs

1use serde::{Deserialize, Serialize};
2
3use std::marker::PhantomData;
4
5use crate::{Px, PxBox, PxPoint, PxVector};
6
7/// A transform in device pixels.
8#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
9pub enum PxTransform {
10    /// Simple offset.
11    Offset(euclid::Vector2D<f32, Px>),
12    /// Full transform.
13    #[serde(with = "serde_px_transform3d")]
14    Transform(euclid::Transform3D<f32, Px, Px>),
15}
16
17impl PartialEq for PxTransform {
18    fn eq(&self, other: &Self) -> bool {
19        match (self, other) {
20            (Self::Offset(l0), Self::Offset(r0)) => l0 == r0,
21            (Self::Transform(l0), Self::Transform(r0)) => l0 == r0,
22            (a, b) => a.is_identity() && b.is_identity() || a.to_transform() == b.to_transform(),
23        }
24    }
25}
26impl Default for PxTransform {
27    /// Identity.
28    fn default() -> Self {
29        Self::identity()
30    }
31}
32impl PxTransform {
33    /// Identity transform.
34    pub fn identity() -> Self {
35        PxTransform::Offset(euclid::vec2(0.0, 0.0))
36    }
37
38    /// New simple 2D translation.
39    pub fn translation(x: f32, y: f32) -> Self {
40        PxTransform::Offset(euclid::vec2(x, y))
41    }
42
43    /// New 3D translation.
44    pub fn translation_3d(x: f32, y: f32, z: f32) -> Self {
45        PxTransform::Transform(euclid::Transform3D::translation(x, y, z))
46    }
47
48    /// New 2D rotation.
49    pub fn rotation(x: f32, y: f32, theta: euclid::Angle<f32>) -> Self {
50        Self::rotation_3d(x, y, 1.0, theta)
51    }
52
53    /// New 3D rotation.
54    pub fn rotation_3d(x: f32, y: f32, z: f32, theta: euclid::Angle<f32>) -> Self {
55        let [x, y, z] = euclid::vec3::<_, ()>(x, y, z).normalize().to_array();
56        PxTransform::Transform(euclid::Transform3D::rotation(x, y, z, theta))
57    }
58
59    /// New 2D skew.
60    pub fn skew(alpha: euclid::Angle<f32>, beta: euclid::Angle<f32>) -> Self {
61        PxTransform::Transform(euclid::Transform3D::skew(alpha, beta))
62    }
63
64    /// New 2D scale.
65    pub fn scale(x: f32, y: f32) -> Self {
66        PxTransform::Transform(euclid::Transform3D::scale(x, y, 1.0))
67    }
68
69    /// New 3D scale.
70    pub fn scale_3d(x: f32, y: f32, z: f32) -> Self {
71        PxTransform::Transform(euclid::Transform3D::scale(x, y, z))
72    }
73
74    /// New 3D perspective distance.
75    pub fn perspective(d: f32) -> Self {
76        PxTransform::Transform(euclid::Transform3D::perspective(d))
77    }
78
79    /// To full transform.
80    pub fn to_transform(self) -> euclid::Transform3D<f32, Px, Px> {
81        match self {
82            PxTransform::Offset(v) => euclid::Transform3D::translation(v.x, v.y, 0.0),
83            PxTransform::Transform(t) => t,
84        }
85    }
86
87    /// Returns `true` it is the identity transform.
88    pub fn is_identity(&self) -> bool {
89        match self {
90            PxTransform::Offset(offset) => offset == &euclid::Vector2D::zero(),
91            PxTransform::Transform(transform) => transform == &euclid::Transform3D::identity(),
92        }
93    }
94
95    /// Returns the multiplication of the two matrices such that mat's transformation
96    /// applies after self's transformation.
97    #[must_use]
98    pub fn then(&self, other: &PxTransform) -> PxTransform {
99        match (self, other) {
100            (PxTransform::Offset(a), PxTransform::Offset(b)) => PxTransform::Offset(*a + *b),
101            (PxTransform::Offset(a), PxTransform::Transform(b)) => {
102                PxTransform::Transform(euclid::Transform3D::translation(a.x, a.y, 0.0).then(b))
103            }
104            (PxTransform::Transform(a), PxTransform::Offset(b)) => PxTransform::Transform(a.then_translate(b.to_3d())),
105            (PxTransform::Transform(a), PxTransform::Transform(b)) => PxTransform::Transform(a.then(b)),
106        }
107    }
108
109    /// Returns a transform with a translation applied after self's transformation.
110    #[must_use]
111    pub fn then_translate(&self, offset: euclid::Vector2D<f32, Px>) -> PxTransform {
112        match self {
113            PxTransform::Offset(a) => PxTransform::Offset(*a + offset),
114            PxTransform::Transform(a) => PxTransform::Transform(a.then_translate(offset.to_3d())),
115        }
116    }
117
118    /// Returns a transform with a translation applied before self's transformation.
119    #[must_use]
120    pub fn pre_translate(&self, offset: euclid::Vector2D<f32, Px>) -> PxTransform {
121        match self {
122            PxTransform::Offset(b) => PxTransform::Offset(offset + *b),
123            PxTransform::Transform(b) => PxTransform::Transform(euclid::Transform3D::translation(offset.x, offset.y, 0.0).then(b)),
124        }
125    }
126
127    /// Returns whether it is possible to compute the inverse transform.
128    pub fn is_invertible(&self) -> bool {
129        match self {
130            PxTransform::Offset(_) => true,
131            PxTransform::Transform(t) => t.is_invertible(),
132        }
133    }
134
135    /// Returns the inverse transform if possible.
136    pub fn inverse(&self) -> Option<PxTransform> {
137        match self {
138            PxTransform::Offset(v) => Some(PxTransform::Offset(-*v)),
139            PxTransform::Transform(t) => t.inverse().map(PxTransform::Transform),
140        }
141    }
142
143    /// Returns `true` if this transform can be represented with a `Transform2D`.
144    pub fn is_2d(&self) -> bool {
145        match self {
146            PxTransform::Offset(_) => true,
147            PxTransform::Transform(t) => t.is_2d(),
148        }
149    }
150
151    /// Transform the pixel point.
152    ///
153    /// Note that if the transform is 3D the point will be transformed with z=0, you can
154    /// use [`project_point`] to find the 2D point in the 3D z-plane represented by the 3D
155    /// transform.
156    ///
157    /// [`project_point`]: Self::project_point
158    pub fn transform_point(&self, point: PxPoint) -> Option<PxPoint> {
159        self.transform_point_f32(point.cast()).map(|p| p.cast())
160    }
161
162    /// Transform the pixel point.
163    ///
164    /// Note that if the transform is 3D the point will be transformed with z=0, you can
165    /// use [`project_point_f32`] to find the 2D point in the 3D z-plane represented by the 3D
166    /// transform.
167    ///
168    /// [`project_point_f32`]: Self::project_point_f32
169    pub fn transform_point_f32(&self, point: euclid::Point2D<f32, Px>) -> Option<euclid::Point2D<f32, Px>> {
170        match self {
171            PxTransform::Offset(v) => Some(point + *v),
172            PxTransform::Transform(t) => t.transform_point2d(point),
173        }
174    }
175
176    /// Transform the pixel vector.
177    pub fn transform_vector(&self, vector: PxVector) -> PxVector {
178        self.transform_vector_f32(vector.cast()).cast()
179    }
180
181    /// Transform the pixel vector.
182    pub fn transform_vector_f32(&self, vector: euclid::Vector2D<f32, Px>) -> euclid::Vector2D<f32, Px> {
183        match self {
184            PxTransform::Offset(v) => vector + *v,
185            PxTransform::Transform(t) => t.transform_vector2d(vector),
186        }
187    }
188
189    /// Project the 2D point onto the transform Z-plane.
190    pub fn project_point(&self, point: PxPoint) -> Option<PxPoint> {
191        self.project_point_f32(point.cast()).map(|p| p.cast())
192    }
193
194    /// Project the 2D point onto the transform Z-plane.
195    pub fn project_point_f32(&self, point: euclid::Point2D<f32, Px>) -> Option<euclid::Point2D<f32, Px>> {
196        match self {
197            PxTransform::Offset(v) => Some(point + *v),
198            PxTransform::Transform(t) => {
199                // source: https://github.com/servo/webrender/blob/master/webrender/src/util.rs#L1181
200
201                // Find a value for z that will transform to 0.
202
203                // The transformed value of z is computed as:
204                // z' = point.x * self.m13 + point.y * self.m23 + z * self.m33 + self.m43
205
206                // Solving for z when z' = 0 gives us:
207                let z = -(point.x * t.m13 + point.y * t.m23 + t.m43) / t.m33;
208
209                t.transform_point3d(euclid::point3(point.x, point.y, z))
210                    .map(|p3| euclid::point2(p3.x, p3.y))
211            }
212        }
213    }
214
215    /// Returns a 2D box that encompasses the result of transforming the given box by this
216    /// transform, if the transform makes sense for it, or `None` otherwise.
217    pub fn outer_transformed(&self, px_box: PxBox) -> Option<PxBox> {
218        self.outer_transformed_f32(px_box.cast()).map(|p| p.cast())
219    }
220
221    /// Returns a 2D box that encompasses the result of transforming the given box by this
222    /// transform, if the transform makes sense for it, or `None` otherwise.
223    pub fn outer_transformed_f32(&self, px_box: euclid::Box2D<f32, Px>) -> Option<euclid::Box2D<f32, Px>> {
224        match self {
225            PxTransform::Offset(v) => {
226                let v = *v;
227                let mut r = px_box;
228                r.min += v;
229                r.max += v;
230                Some(r)
231            }
232            PxTransform::Transform(t) => t.outer_transformed_box2d(&px_box),
233        }
234    }
235}
236
237impl From<euclid::Vector2D<f32, Px>> for PxTransform {
238    fn from(offset: euclid::Vector2D<f32, Px>) -> Self {
239        PxTransform::Offset(offset)
240    }
241}
242impl From<PxVector> for PxTransform {
243    fn from(offset: PxVector) -> Self {
244        PxTransform::Offset(offset.cast())
245    }
246}
247impl From<euclid::Transform3D<f32, Px, Px>> for PxTransform {
248    fn from(transform: euclid::Transform3D<f32, Px, Px>) -> Self {
249        PxTransform::Transform(transform)
250    }
251}
252
253/// euclid does skip the _unit
254mod serde_px_transform3d {
255    use crate::Px;
256
257    use super::*;
258    use serde::*;
259
260    #[derive(Serialize, Deserialize)]
261    struct SerdeTransform3D {
262        pub m11: f32,
263        pub m12: f32,
264        pub m13: f32,
265        pub m14: f32,
266        pub m21: f32,
267        pub m22: f32,
268        pub m23: f32,
269        pub m24: f32,
270        pub m31: f32,
271        pub m32: f32,
272        pub m33: f32,
273        pub m34: f32,
274        pub m41: f32,
275        pub m42: f32,
276        pub m43: f32,
277        pub m44: f32,
278    }
279
280    pub fn serialize<S: Serializer>(t: &euclid::Transform3D<f32, Px, Px>, serializer: S) -> Result<S::Ok, S::Error> {
281        SerdeTransform3D {
282            m11: t.m11,
283            m12: t.m12,
284            m13: t.m13,
285            m14: t.m14,
286            m21: t.m21,
287            m22: t.m22,
288            m23: t.m23,
289            m24: t.m24,
290            m31: t.m31,
291            m32: t.m32,
292            m33: t.m33,
293            m34: t.m34,
294            m41: t.m41,
295            m42: t.m42,
296            m43: t.m43,
297            m44: t.m44,
298        }
299        .serialize(serializer)
300    }
301
302    pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<euclid::Transform3D<f32, Px, Px>, D::Error> {
303        let t = SerdeTransform3D::deserialize(deserializer)?;
304        Ok(euclid::Transform3D {
305            m11: t.m11,
306            m12: t.m12,
307            m13: t.m13,
308            m14: t.m14,
309            m21: t.m21,
310            m22: t.m22,
311            m23: t.m23,
312            m24: t.m24,
313            m31: t.m31,
314            m32: t.m32,
315            m33: t.m33,
316            m34: t.m34,
317            m41: t.m41,
318            m42: t.m42,
319            m43: t.m43,
320            m44: t.m44,
321            _unit: PhantomData,
322        })
323    }
324}