cranpose_render_common/
layer_transform.rs1use cranpose_ui_graphics::{GraphicsLayer, Point, Rect};
2
3use crate::graph::{quad_bounds, ProjectiveTransform};
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 fn apply_layer_affine_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
18 let offset_x = rect.x - layer_bounds.x;
19 let offset_y = rect.y - layer_bounds.y;
20 let scale_x = layer_scale_x(layer);
21 let scale_y = layer_scale_y(layer);
22 Rect {
23 x: layer_bounds.x + offset_x * scale_x + layer.translation_x,
24 y: layer_bounds.y + offset_y * scale_y + layer.translation_y,
25 width: rect.width * scale_x,
26 height: rect.height * scale_y,
27 }
28}
29
30fn layer_rotation_pivot(layer_bounds: Rect, layer: &GraphicsLayer) -> (f32, f32) {
31 (
32 layer_bounds.x + layer_bounds.width * layer.transform_origin.pivot_fraction_x,
33 layer_bounds.y + layer_bounds.height * layer.transform_origin.pivot_fraction_y,
34 )
35}
36
37fn layer_has_rotation(layer: &GraphicsLayer) -> bool {
38 layer.rotation_x.abs() > f32::EPSILON
39 || layer.rotation_y.abs() > f32::EPSILON
40 || layer.rotation_z.abs() > f32::EPSILON
41}
42
43fn apply_rotation_and_perspective(
44 point: [f32; 2],
45 pivot: (f32, f32),
46 layer: &GraphicsLayer,
47) -> [f32; 2] {
48 if !layer_has_rotation(layer) {
49 return point;
50 }
51
52 let mut x = point[0] - pivot.0;
53 let mut y = point[1] - pivot.1;
54 let mut z = 0.0f32;
55
56 let (sin_x, cos_x) = layer.rotation_x.to_radians().sin_cos();
57 let (sin_y, cos_y) = layer.rotation_y.to_radians().sin_cos();
58 let (sin_z, cos_z) = layer.rotation_z.to_radians().sin_cos();
59
60 let y_rot_x = y * cos_x - z * sin_x;
61 let z_rot_x = y * sin_x + z * cos_x;
62 y = y_rot_x;
63 z = z_rot_x;
64
65 let x_rot_y = x * cos_y + z * sin_y;
66 let z_rot_y = -x * sin_y + z * cos_y;
67 x = x_rot_y;
68 z = z_rot_y;
69
70 let x_rot_z = x * cos_z - y * sin_z;
71 let y_rot_z = x * sin_z + y * cos_z;
72 x = x_rot_z;
73 y = y_rot_z;
74
75 const CAMERA_DISTANCE_SCALE: f32 = 72.0;
76 let camera_distance = (layer.camera_distance * CAMERA_DISTANCE_SCALE).max(1.0);
77 let denom = (camera_distance - z).max(1.0);
78 let perspective = camera_distance / denom;
79
80 [pivot.0 + x * perspective, pivot.1 + y * perspective]
81}
82
83pub fn apply_layer_to_quad(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> [[f32; 2]; 4] {
84 let affine_rect = apply_layer_affine_to_rect(rect, layer_bounds, layer);
85 let affine_layer_bounds = apply_layer_affine_to_rect(layer_bounds, layer_bounds, layer);
86 let pivot = layer_rotation_pivot(affine_layer_bounds, layer);
87 let quad = [
88 [affine_rect.x, affine_rect.y],
89 [affine_rect.x + affine_rect.width, affine_rect.y],
90 [affine_rect.x, affine_rect.y + affine_rect.height],
91 [
92 affine_rect.x + affine_rect.width,
93 affine_rect.y + affine_rect.height,
94 ],
95 ];
96
97 quad.map(|point| apply_rotation_and_perspective(point, pivot, layer))
98}
99
100pub fn apply_layer_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
101 quad_bounds(apply_layer_to_quad(rect, layer_bounds, layer))
102}
103
104pub fn layer_transform_to_parent(
105 local_bounds: Rect,
106 placement: Point,
107 layer: &GraphicsLayer,
108) -> ProjectiveTransform {
109 let placement_rect = Rect {
110 x: placement.x,
111 y: placement.y,
112 width: local_bounds.width,
113 height: local_bounds.height,
114 };
115 ProjectiveTransform::from_rect_to_quad(
116 local_bounds,
117 apply_layer_to_quad(placement_rect, placement_rect, layer),
118 )
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use cranpose_ui_graphics::{Rect, TransformOrigin};
125
126 #[test]
127 fn layer_transform_to_parent_maps_local_bounds_to_positioned_bounds() {
128 let local_bounds = Rect {
129 x: 0.0,
130 y: 0.0,
131 width: 30.0,
132 height: 18.0,
133 };
134 let placement = Point { x: 12.0, y: 9.0 };
135 let transform =
136 layer_transform_to_parent(local_bounds, placement, &GraphicsLayer::default());
137
138 assert_eq!(
139 transform.bounds_for_rect(local_bounds),
140 Rect {
141 x: 12.0,
142 y: 9.0,
143 width: 30.0,
144 height: 18.0,
145 }
146 );
147 }
148
149 #[test]
150 fn layer_transform_to_parent_applies_rotation_about_transform_origin() {
151 let local_bounds = Rect {
152 x: 0.0,
153 y: 0.0,
154 width: 100.0,
155 height: 40.0,
156 };
157 let layer = GraphicsLayer {
158 rotation_z: 90.0,
159 transform_origin: TransformOrigin::CENTER,
160 ..Default::default()
161 };
162
163 let transform = layer_transform_to_parent(local_bounds, Point::default(), &layer);
164 let mapped = transform.bounds_for_rect(local_bounds);
165
166 assert!((mapped.width - 40.0).abs() < 0.01);
167 assert!((mapped.height - 100.0).abs() < 0.01);
168 }
169}