1use 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
66pub(crate) fn layer_scales_or_rotates(layer: &GraphicsLayer) -> bool {
67 layer_scale_x(layer) != 1.0 || layer_scale_y(layer) != 1.0 || layer_has_rotation(layer)
68}
69
70fn layer_has_rotation(layer: &GraphicsLayer) -> bool {
71 layer.rotation_x.abs() > f32::EPSILON
72 || layer.rotation_y.abs() > f32::EPSILON
73 || layer.rotation_z.abs() > f32::EPSILON
74}
75
76fn rotation_axes(layer: &GraphicsLayer) -> [[f32; 3]; 2] {
79 let (sin_x, cos_x) = layer.rotation_x.to_radians().sin_cos();
80 let (sin_y, cos_y) = layer.rotation_y.to_radians().sin_cos();
81 let (sin_z, cos_z) = layer.rotation_z.to_radians().sin_cos();
82 [
83 [cos_z * cos_y, sin_z * cos_y, -sin_y],
84 [
85 cos_z * sin_x * sin_y - sin_z * cos_x,
86 sin_z * sin_x * sin_y + cos_z * cos_x,
87 sin_x * cos_y,
88 ],
89 ]
90}
91
92fn camera_distance(layer: &GraphicsLayer) -> f32 {
94 const CAMERA_DISTANCE_SCALE: f32 = 72.0;
95 (layer.camera_distance * CAMERA_DISTANCE_SCALE).max(1.0)
96}
97
98fn apply_rotation_and_perspective(
99 point: [f32; 2],
100 pivot: (f32, f32),
101 layer: &GraphicsLayer,
102) -> [f32; 2] {
103 if !layer_has_rotation(layer) {
104 return point;
105 }
106 let (x, y) = (point[0] - pivot.0, point[1] - pivot.1);
107 let [x_axis, y_axis] = rotation_axes(layer);
108 let [turned_x, turned_y, turned_z] = [0, 1, 2].map(|axis| x * x_axis[axis] + y * y_axis[axis]);
109 let camera_distance = camera_distance(layer);
110 let perspective = camera_distance / (camera_distance - turned_z).max(1.0);
111 [
112 pivot.0 + turned_x * perspective,
113 pivot.1 + turned_y * perspective,
114 ]
115}
116
117fn apply_layer_to_point(point: [f32; 2], pivot: (f32, f32), layer: &GraphicsLayer) -> [f32; 2] {
118 let scaled = [
119 pivot.0 + (point[0] - pivot.0) * layer_scale_x(layer),
120 pivot.1 + (point[1] - pivot.1) * layer_scale_y(layer),
121 ];
122 let rotated = apply_rotation_and_perspective(scaled, pivot, layer);
123 [
124 rotated[0] + layer.translation_x,
125 rotated[1] + layer.translation_y,
126 ]
127}
128
129pub fn apply_layer_to_quad(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> [[f32; 2]; 4] {
130 let quad = [
131 [rect.x, rect.y],
132 [rect.x + rect.width, rect.y],
133 [rect.x, rect.y + rect.height],
134 [rect.x + rect.width, rect.y + rect.height],
135 ];
136 if layer_affine_is_identity(layer) && !layer_has_rotation(layer) {
137 return quad;
138 }
139 let pivot = layer_rotation_pivot(layer_bounds, layer);
140 quad.map(|point| apply_layer_to_point(point, pivot, layer))
141}
142
143pub fn apply_layer_to_rect(rect: Rect, layer_bounds: Rect, layer: &GraphicsLayer) -> Rect {
144 quad_bounds(apply_layer_to_quad(rect, layer_bounds, layer))
145}
146
147pub fn layer_transform_to_parent(
148 local_bounds: Rect,
149 placement: Point,
150 layer: &GraphicsLayer,
151) -> ProjectiveTransform {
152 let (pivot_x, pivot_y) = layer_rotation_pivot(local_bounds, layer);
161 let [x_axis, y_axis] = rotation_axes(layer);
162 let (scale_x, scale_y) = (layer_scale_x(layer), layer_scale_y(layer));
163 let camera_distance = camera_distance(layer);
164 let turn = ProjectiveTransform::from_homogeneous([
165 [x_axis[0] * scale_x, y_axis[0] * scale_y, 0.0],
166 [x_axis[1] * scale_x, y_axis[1] * scale_y, 0.0],
167 [
168 -x_axis[2] * scale_x / camera_distance,
169 -y_axis[2] * scale_y / camera_distance,
170 1.0,
171 ],
172 ]);
173 let placed = ProjectiveTransform::translation(-pivot_x, -pivot_y)
174 .then(turn)
175 .then(ProjectiveTransform::translation(pivot_x, pivot_y))
176 .then(ProjectiveTransform::translation(
177 placement.x + layer.translation_x,
178 placement.y + layer.translation_y,
179 ));
180 ProjectiveTransform::from_homogeneous(placed.matrix())
181}
182
183#[cfg(test)]
184#[path = "tests/layer_transform_tests.rs"]
185mod tests;