cranpose_render_common/
layer_transform.rs1use cranpose_ui_graphics::{GraphicsLayer, Point, Rect};
2
3use crate::graph::{ProjectiveTransform, quad_bounds};
4
5pub(crate) fn layer_scale_x(layer: &GraphicsLayer) -> f32 {
6 layer.scale * layer.scale_x
7}
8
9pub(crate) fn layer_scale_y(layer: &GraphicsLayer) -> f32 {
10 layer.scale * layer.scale_y
11}
12
13pub fn layer_uniform_scale(layer: &GraphicsLayer) -> f32 {
14 layer_scale_x(layer).min(layer_scale_y(layer))
15}
16
17pub(crate) fn layer_affine_is_identity(layer: &GraphicsLayer) -> bool {
18 layer.scale == 1.0
19 && layer.scale_x == 1.0
20 && layer.scale_y == 1.0
21 && layer.translation_x == 0.0
22 && layer.translation_y == 0.0
23}
24
25pub fn apply_layer_affine_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
26 if layer_affine_is_identity(layer) {
27 return rect;
28 }
29 let scale_x = layer_scale_x(layer);
30 let scale_y = layer_scale_y(layer);
31 let (pivot_x, pivot_y) = layer_rotation_pivot(layer_bounds, layer);
32 Rect {
33 x: pivot_x + (rect.x - pivot_x) * scale_x + layer.translation_x,
34 y: pivot_y + (rect.y - pivot_y) * scale_y + layer.translation_y,
35 width: rect.width * scale_x,
36 height: rect.height * scale_y,
37 }
38}
39
40pub fn apply_layer_affine_to_point(
45 point: Point,
46 layer_bounds: Rect,
47 layer: &GraphicsLayer,
48) -> Point {
49 if layer_affine_is_identity(layer) {
50 return point;
51 }
52 let (pivot_x, pivot_y) = layer_rotation_pivot(layer_bounds, layer);
53 Point::new(
54 pivot_x + (point.x - pivot_x) * layer_scale_x(layer) + layer.translation_x,
55 pivot_y + (point.y - pivot_y) * layer_scale_y(layer) + layer.translation_y,
56 )
57}
58
59fn layer_rotation_pivot(layer_bounds: Rect, layer: &GraphicsLayer) -> (f32, f32) {
60 (
61 layer_bounds.x + layer_bounds.width * layer.transform_origin.pivot_fraction_x,
62 layer_bounds.y + layer_bounds.height * layer.transform_origin.pivot_fraction_y,
63 )
64}
65
66fn layer_has_rotation(layer: &GraphicsLayer) -> bool {
67 layer.rotation_x.abs() > f32::EPSILON
68 || layer.rotation_y.abs() > f32::EPSILON
69 || layer.rotation_z.abs() > f32::EPSILON
70}
71
72fn apply_rotation_and_perspective(
73 point: [f32; 2],
74 pivot: (f32, f32),
75 layer: &GraphicsLayer,
76) -> [f32; 2] {
77 if !layer_has_rotation(layer) {
78 return point;
79 }
80
81 let mut x = point[0] - pivot.0;
82 let mut y = point[1] - pivot.1;
83 let mut z = 0.0f32;
84
85 let (sin_x, cos_x) = layer.rotation_x.to_radians().sin_cos();
86 let (sin_y, cos_y) = layer.rotation_y.to_radians().sin_cos();
87 let (sin_z, cos_z) = layer.rotation_z.to_radians().sin_cos();
88
89 let y_rot_x = y * cos_x - z * sin_x;
90 let z_rot_x = y * sin_x + z * cos_x;
91 y = y_rot_x;
92 z = z_rot_x;
93
94 let x_rot_y = x * cos_y + z * sin_y;
95 let z_rot_y = -x * sin_y + z * cos_y;
96 x = x_rot_y;
97 z = z_rot_y;
98
99 let x_rot_z = x * cos_z - y * sin_z;
100 let y_rot_z = x * sin_z + y * cos_z;
101 x = x_rot_z;
102 y = y_rot_z;
103
104 const CAMERA_DISTANCE_SCALE: f32 = 72.0;
105 let camera_distance = (layer.camera_distance * CAMERA_DISTANCE_SCALE).max(1.0);
106 let denom = (camera_distance - z).max(1.0);
107 let perspective = camera_distance / denom;
108
109 [pivot.0 + x * perspective, pivot.1 + y * perspective]
110}
111
112fn apply_layer_to_point(point: [f32; 2], pivot: (f32, f32), layer: &GraphicsLayer) -> [f32; 2] {
113 let scaled = [
114 pivot.0 + (point[0] - pivot.0) * layer_scale_x(layer),
115 pivot.1 + (point[1] - pivot.1) * layer_scale_y(layer),
116 ];
117 let rotated = apply_rotation_and_perspective(scaled, pivot, layer);
118 [
119 rotated[0] + layer.translation_x,
120 rotated[1] + layer.translation_y,
121 ]
122}
123
124pub fn apply_layer_to_quad(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> [[f32; 2]; 4] {
125 let quad = [
126 [rect.x, rect.y],
127 [rect.x + rect.width, rect.y],
128 [rect.x, rect.y + rect.height],
129 [rect.x + rect.width, rect.y + rect.height],
130 ];
131 if layer_affine_is_identity(layer) && !layer_has_rotation(layer) {
132 return quad;
133 }
134 let pivot = layer_rotation_pivot(layer_bounds, layer);
135 quad.map(|point| apply_layer_to_point(point, pivot, layer))
136}
137
138pub fn apply_layer_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
139 quad_bounds(apply_layer_to_quad(rect, layer_bounds, layer))
140}
141
142pub fn layer_transform_to_parent(
143 local_bounds: Rect,
144 placement: Point,
145 layer: &GraphicsLayer,
146) -> ProjectiveTransform {
147 let placement_rect = Rect {
148 x: placement.x,
149 y: placement.y,
150 width: local_bounds.width,
151 height: local_bounds.height,
152 };
153 ProjectiveTransform::from_rect_to_quad(
154 local_bounds,
155 apply_layer_to_quad(placement_rect, placement_rect, layer),
156 )
157}
158
159#[cfg(test)]
160#[path = "tests/layer_transform_tests.rs"]
161mod tests;