Skip to main content

cranpose_ui_graphics/
layer_transform.rs

1use crate::{
2    GraphicsLayer, Point, Rect,
3    space::{LayerSpace, Mat4, MeshSurface, Scene3d},
4    transform::{ProjectiveTransform, quad_bounds},
5};
6
7#[doc(hidden)]
8#[inline]
9pub fn layer_scale_x(layer: &GraphicsLayer) -> f32 {
10    layer.scale * layer.scale_x
11}
12
13#[doc(hidden)]
14#[inline]
15pub fn layer_scale_y(layer: &GraphicsLayer) -> f32 {
16    layer.scale * layer.scale_y
17}
18
19/// Returns the smaller of the layer's horizontal and vertical scales.
20pub fn layer_uniform_scale(layer: &GraphicsLayer) -> f32 {
21    layer_scale_x(layer).min(layer_scale_y(layer))
22}
23
24#[doc(hidden)]
25#[inline]
26pub fn layer_affine_is_identity(layer: &GraphicsLayer) -> bool {
27    layer.scale == 1.0
28        && layer.scale_x == 1.0
29        && layer.scale_y == 1.0
30        && layer.translation_x == 0.0
31        && layer.translation_y == 0.0
32}
33
34/// Scales and translates a rectangle around the layer's transform origin.
35pub fn apply_layer_affine_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
36    if layer_affine_is_identity(layer) {
37        return rect;
38    }
39    let scale_x = layer_scale_x(layer);
40    let scale_y = layer_scale_y(layer);
41    let (pivot_x, pivot_y) = layer_rotation_pivot(layer_bounds, layer);
42    Rect {
43        x: pivot_x + (rect.x - pivot_x) * scale_x + layer.translation_x,
44        y: pivot_y + (rect.y - pivot_y) * scale_y + layer.translation_y,
45        width: rect.width * scale_x,
46        height: rect.height * scale_y,
47    }
48}
49
50/// Point counterpart of [`apply_layer_affine_to_rect`]: scale about the layer
51/// pivot, then translate. Rotation/perspective are deliberately excluded — the
52/// affine space is the one SDF shapes are evaluated in, with rotation carried
53/// by the deformed quad instead.
54pub fn apply_layer_affine_to_point(
55    point: Point,
56    layer_bounds: Rect,
57    layer: &GraphicsLayer,
58) -> Point {
59    if layer_affine_is_identity(layer) {
60        return point;
61    }
62    let (pivot_x, pivot_y) = layer_rotation_pivot(layer_bounds, layer);
63    Point::new(
64        pivot_x + (point.x - pivot_x) * layer_scale_x(layer) + layer.translation_x,
65        pivot_y + (point.y - pivot_y) * layer_scale_y(layer) + layer.translation_y,
66    )
67}
68
69fn layer_rotation_pivot(layer_bounds: Rect, layer: &GraphicsLayer) -> (f32, f32) {
70    (
71        layer_bounds.x + layer_bounds.width * layer.transform_origin.pivot_fraction_x,
72        layer_bounds.y + layer_bounds.height * layer.transform_origin.pivot_fraction_y,
73    )
74}
75
76#[doc(hidden)]
77#[inline]
78pub fn layer_scales_or_rotates(layer: &GraphicsLayer) -> bool {
79    layer_scale_x(layer) != 1.0 || layer_scale_y(layer) != 1.0 || layer_has_rotation(layer)
80}
81
82#[inline]
83fn layer_has_rotation(layer: &GraphicsLayer) -> bool {
84    layer.rotation_x.abs() > f32::EPSILON
85        || layer.rotation_y.abs() > f32::EPSILON
86        || layer.rotation_z.abs() > f32::EPSILON
87}
88
89fn rotation_axes(layer: &GraphicsLayer) -> [[f32; 3]; 2] {
90    let (sin_x, cos_x) = layer.rotation_x.to_radians().sin_cos();
91    let (sin_y, cos_y) = layer.rotation_y.to_radians().sin_cos();
92    let (sin_z, cos_z) = layer.rotation_z.to_radians().sin_cos();
93    [
94        [cos_z * cos_y, sin_z * cos_y, -sin_y],
95        [
96            cos_z * sin_x * sin_y - sin_z * cos_x,
97            sin_z * sin_x * sin_y + cos_z * cos_x,
98            sin_x * cos_y,
99        ],
100    ]
101}
102
103fn camera_distance(layer: &GraphicsLayer) -> f32 {
104    const CAMERA_DISTANCE_SCALE: f32 = 72.0;
105    (layer.camera_distance * CAMERA_DISTANCE_SCALE).max(1.0)
106}
107
108fn apply_rotation_and_perspective(
109    point: [f32; 2],
110    pivot: (f32, f32),
111    layer: &GraphicsLayer,
112) -> [f32; 2] {
113    if !layer_has_rotation(layer) {
114        return point;
115    }
116    let (x, y) = (point[0] - pivot.0, point[1] - pivot.1);
117    let [x_axis, y_axis] = rotation_axes(layer);
118    let [turned_x, turned_y, turned_z] = [0, 1, 2].map(|axis| x * x_axis[axis] + y * y_axis[axis]);
119    let camera_distance = camera_distance(layer);
120    let perspective = camera_distance / (camera_distance - turned_z).max(1.0);
121    [
122        pivot.0 + turned_x * perspective,
123        pivot.1 + turned_y * perspective,
124    ]
125}
126
127fn apply_layer_to_point(point: [f32; 2], pivot: (f32, f32), layer: &GraphicsLayer) -> [f32; 2] {
128    let scaled = [
129        pivot.0 + (point[0] - pivot.0) * layer_scale_x(layer),
130        pivot.1 + (point[1] - pivot.1) * layer_scale_y(layer),
131    ];
132    let rotated = apply_rotation_and_perspective(scaled, pivot, layer);
133    [
134        rotated[0] + layer.translation_x,
135        rotated[1] + layer.translation_y,
136    ]
137}
138
139/// Transforms a rectangle's four corners through scale, rotation and perspective.
140#[inline]
141pub fn apply_layer_to_quad(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> [[f32; 2]; 4] {
142    let quad = [
143        [rect.x, rect.y],
144        [rect.x + rect.width, rect.y],
145        [rect.x, rect.y + rect.height],
146        [rect.x + rect.width, rect.y + rect.height],
147    ];
148    if layer_affine_is_identity(layer) && !layer_has_rotation(layer) {
149        return quad;
150    }
151    let pivot = layer_rotation_pivot(layer_bounds, layer);
152    quad.map(|point| apply_layer_to_point(point, pivot, layer))
153}
154
155/// Returns the axis-aligned bounds after the complete layer transform.
156#[inline]
157pub fn apply_layer_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
158    let Rect {
159        x,
160        y,
161        width,
162        height,
163    } = rect;
164    // A layer that leaves the corners where they are bounds them as
165    // `quad_bounds` would, from the far corner, for a finite rect that
166    // does not run backwards.
167    if layer_affine_is_identity(layer)
168        && !layer_has_rotation(layer)
169        && width >= 0.0
170        && height >= 0.0
171        && crate::float::all_finite([x, y, width, height])
172    {
173        return Rect {
174            x,
175            y,
176            width: (x + width) - x,
177            height: (y + height) - y,
178        };
179    }
180    quad_bounds(apply_layer_to_quad(rect, layer_bounds, layer))
181}
182
183/// Combines a graphics layer with the node's placement in its parent.
184pub fn layer_transform_to_parent(
185    local_bounds: Rect,
186    placement: Point,
187    layer: &GraphicsLayer,
188) -> ProjectiveTransform {
189    if !layer_scales_or_rotates(layer) {
190        return ProjectiveTransform::translation(
191            placement.x + layer.translation_x,
192            placement.y + layer.translation_y,
193        );
194    }
195    let (pivot_x, pivot_y) = layer_rotation_pivot(local_bounds, layer);
196    let [x_axis, y_axis] = rotation_axes(layer);
197    let (scale_x, scale_y) = (layer_scale_x(layer), layer_scale_y(layer));
198    let camera_distance = camera_distance(layer);
199    let turn = ProjectiveTransform::from_homogeneous([
200        [x_axis[0] * scale_x, y_axis[0] * scale_y, 0.0],
201        [x_axis[1] * scale_x, y_axis[1] * scale_y, 0.0],
202        [
203            -x_axis[2] * scale_x / camera_distance,
204            -y_axis[2] * scale_y / camera_distance,
205            1.0,
206        ],
207    ]);
208    let placed = ProjectiveTransform::translation(-pivot_x, -pivot_y)
209        .then(turn)
210        .then(ProjectiveTransform::translation(pivot_x, pivot_y))
211        .then(ProjectiveTransform::translation(
212            placement.x + layer.translation_x,
213            placement.y + layer.translation_y,
214        ));
215    ProjectiveTransform::from_homogeneous(placed.matrix())
216}
217
218/// Extends an ancestor mapping from untransformed layout coordinates to window
219/// coordinates with this node's graphics layer. `origin` and `bounds` describe
220/// the node before any ancestor transforms; `bounds` is relative to `origin`.
221#[inline]
222pub fn layer_transform_to_window(
223    parent: ProjectiveTransform,
224    origin: Point,
225    bounds: Rect,
226    layer: &GraphicsLayer,
227) -> ProjectiveTransform {
228    if layer_affine_is_identity(layer) && !layer_has_rotation(layer) {
229        return parent;
230    }
231    let bounds = Rect {
232        x: bounds.x + origin.x,
233        y: bounds.y + origin.y,
234        ..bounds
235    };
236    layer_transform_to_parent(bounds, Point::default(), layer).then(parent)
237}
238
239/// The scene a layer's graphics layer shows, or `None` for a layer that
240/// shows none.
241pub fn layer_scene(layer: &GraphicsLayer) -> Option<&Scene3d> {
242    match layer.space.as_deref()? {
243        LayerSpace::Scene(scene) => Some(scene),
244        LayerSpace::Surface(_) | LayerSpace::Transform(_) => None,
245    }
246}
247
248/// The mesh a layer's graphics layer draws its content on, or `None` for a
249/// layer drawn flat.
250pub fn layer_surface(layer: &GraphicsLayer) -> Option<&MeshSurface> {
251    match layer.space.as_deref()? {
252        LayerSpace::Surface(surface) => Some(surface),
253        LayerSpace::Scene(_) | LayerSpace::Transform(_) => None,
254    }
255}
256
257/// The move a layer's graphics layer gives the 3D frames beneath it, or
258/// `None` for none.
259pub fn layer_transform3d(layer: &GraphicsLayer) -> Option<&Mat4> {
260    match layer.space.as_deref()? {
261        LayerSpace::Transform(transform) => Some(transform),
262        LayerSpace::Scene(_) | LayerSpace::Surface(_) => None,
263    }
264}