diff --git a/render-wasm/src/math.rs b/render-wasm/src/math.rs index 67c1025db..dc2e8914e 100644 --- a/render-wasm/src/math.rs +++ b/render-wasm/src/math.rs @@ -122,65 +122,145 @@ impl Bounds { pub fn left(&self, p: Point) -> f32 { let hr = Ray::new(p, self.horizontal_vec()); let vr = Ray::new(self.nw, self.vertical_vec()); - if let Some(project_point) = intersect_rays(&hr, &vr) { - if vr.is_positive_side(&p) { - -Point::distance(project_point, p) - } else { - Point::distance(project_point, p) - } + + let mut result = if let Some(project_point) = intersect_rays(&hr, &vr) { + Point::distance(project_point, p) } else { - // This should not happen. All points should have a proyection so the - // intersection ray should always exist 0.0 + }; + + if vr.is_positive_side(&p) { + result = -result; } + + if self.flip_y() { + result = -result; + } + + if self.flip_x() { + result = -result; + } + + result } pub fn right(&self, p: Point) -> f32 { let hr = Ray::new(p, self.horizontal_vec()); let vr = Ray::new(self.ne, self.vertical_vec()); - if let Some(project_point) = intersect_rays(&hr, &vr) { - if vr.is_positive_side(&p) { - Point::distance(project_point, p) - } else { - -Point::distance(project_point, p) - } + + let mut result = if let Some(project_point) = intersect_rays(&hr, &vr) { + Point::distance(project_point, p) } else { - // This should not happen. All points should have a proyection so the - // intersection ray should always exist 0.0 + }; + + if !vr.is_positive_side(&p) { + result = -result; } + + if self.flip_y() { + result = -result; + } + + if self.flip_x() { + result = -result; + } + + result } pub fn top(&self, p: Point) -> f32 { let vr = Ray::new(p, self.vertical_vec()); let hr = Ray::new(self.nw, self.horizontal_vec()); - if let Some(project_point) = intersect_rays(&vr, &hr) { - if hr.is_positive_side(&p) { - Point::distance(project_point, p) - } else { - -Point::distance(project_point, p) - } + + let mut result = if let Some(project_point) = intersect_rays(&vr, &hr) { + Point::distance(project_point, p) } else { - // This should not happen. All points should have a proyection so the - // intersection ray should always exist 0.0 + }; + + if !hr.is_positive_side(&p) { + result = -result; } + + if self.flip_y() { + result = -result; + } + + if self.flip_x() { + result = -result; + } + + result } pub fn bottom(&self, p: Point) -> f32 { let vr = Ray::new(p, self.vertical_vec()); let hr = Ray::new(self.sw, self.horizontal_vec()); - if let Some(project_point) = intersect_rays(&vr, &hr) { - if hr.is_positive_side(&p) { - -Point::distance(project_point, p) - } else { - Point::distance(project_point, p) - } + + let mut result = if let Some(project_point) = intersect_rays(&vr, &hr) { + Point::distance(project_point, p) } else { - // This should not happen. All points should have a proyection so the - // intersection ray should always exist 0.0 + }; + + if hr.is_positive_side(&p) { + result = -result; } + + if self.flip_y() { + result = -result; + } + + if self.flip_x() { + result = -result; + } + + result + } + + pub fn transform_matrix(&self) -> Option { + let w2 = self.width() / 2.0; + let h2 = self.height() / 2.0; + + let s1x = -w2; + let s1y = -h2; + let s2x = w2; + let s2y = -h2; + let s4x = -w2; + let s4y = h2; + + let d1x = self.nw.x; + let d1y = self.nw.y; + let d2x = self.ne.x; + let d2y = self.ne.y; + let d4x = self.sw.x; + let d4y = self.sw.y; + + // TODO: Check how fast is to calculate here the invert matrix + let mut target_points_matrix = Matrix::new_all(d1x, d2x, d4x, d1y, d2y, d4y, 1.0, 1.0, 1.0); + let source_points_matrix = Matrix::new_all(s1x, s2x, s4x, s1y, s2y, s4y, 1.0, 1.0, 1.0); + + let source_points_matrix_inv = source_points_matrix.invert()?; + target_points_matrix.pre_concat(&source_points_matrix_inv); + + // Ignore translations + target_points_matrix.set_translate_x(0.0); + target_points_matrix.set_translate_y(0.0); + + Some(target_points_matrix) + } + + // TODO: Probably we can improve performance here removing the access + pub fn flip_x(&self) -> bool { + let m = self.transform_matrix().unwrap_or(Matrix::default()); + m.scale_x() < 0.0 + } + + // TODO: Probably we can improve performance here removing the access + pub fn flip_y(&self) -> bool { + let m = self.transform_matrix().unwrap_or(Matrix::default()); + m.scale_y() < 0.0 } } @@ -317,14 +397,46 @@ mod tests { #[test] fn test_bounds_distances() { let b1 = Bounds::new( - Point::new(1.0, 10.0), - Point::new(8.0, 10.0), - Point::new(8.0, 1.0), Point::new(1.0, 1.0), + Point::new(8.0, 1.0), + Point::new(8.0, 10.0), + Point::new(1.0, 10.0), ); - assert_eq!(b1.left(Point::new(4.0, 8.0)), -3.0); - assert_eq!(b1.top(Point::new(4.0, 8.0)), -2.0); - assert_eq!(b1.right(Point::new(7.0, 6.0),), -1.0); - assert_eq!(b1.bottom(Point::new(7.0, 6.0),), -5.0); + assert_eq!(b1.left(Point::new(4.0, 8.0)), 3.0); + assert_eq!(b1.top(Point::new(4.0, 8.0)), 7.0); + assert_eq!(b1.right(Point::new(7.0, 6.0),), 1.0); + assert_eq!(b1.bottom(Point::new(7.0, 6.0),), 4.0); + } + + #[test] + fn test_transform_matrix() { + let b = Bounds::new( + Point::new(0.0, 0.0), + Point::new(50.0, 0.0), + Point::new(50.0, 50.0), + Point::new(0.0, 50.0), + ); + + assert_eq!(b.width(), 50.0); + assert_eq!(b.height(), 50.0); + assert_eq!(b.transform_matrix().unwrap(), Matrix::default()); + + let b = Bounds::new( + Point::new(-25.0, 1.0), + Point::new(1.0, -34.5), + Point::new(27.0, 1.0), + Point::new(1.0, 36.5), + ); + + assert!((b.width() - 44.0).abs() <= 0.1); + assert!((b.height() - 44.0).abs() <= 0.1); + + let m = b.transform_matrix().unwrap(); + assert!((m.scale_x() - 0.59).abs() <= 0.1); + assert!((m.skew_y() - -0.81).abs() <= 0.1); + assert!((m.skew_x() - 0.59).abs() <= 0.1); + assert!((m.scale_y() - 0.81).abs() <= 0.1); + assert!((m.translate_x() - 0.0).abs() <= 0.1); + assert!((m.translate_y() - 0.0).abs() <= 0.1); } } diff --git a/render-wasm/src/shapes.rs b/render-wasm/src/shapes.rs index 1e12bdaba..cd8b84b16 100644 --- a/render-wasm/src/shapes.rs +++ b/render-wasm/src/shapes.rs @@ -72,7 +72,7 @@ pub enum Kind { Group(Group), } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum ConstraintH { Left, Right, @@ -94,7 +94,7 @@ impl ConstraintH { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Copy)] pub enum ConstraintV { Top, Bottom, diff --git a/render-wasm/src/shapes/modifiers.rs b/render-wasm/src/shapes/modifiers.rs index c4d28215b..30c104277 100644 --- a/render-wasm/src/shapes/modifiers.rs +++ b/render-wasm/src/shapes/modifiers.rs @@ -10,79 +10,100 @@ use crate::math::Bounds; use crate::shapes::{ConstraintH, ConstraintV, Shape, TransformEntry}; use crate::state::State; -fn calculate_new_bounds( +fn calculate_resize( constraint_h: ConstraintH, constraint_v: ConstraintV, parent_before: &Bounds, parent_after: &Bounds, - child_bounds: &Bounds, -) -> (f32, f32, f32, f32) { - let (delta_left, scale_width) = match constraint_h { - ConstraintH::Scale => { - let width_scale = parent_after.width() / parent_before.width(); - let target_left = parent_before.left(child_bounds.nw) * width_scale; - let current_left = parent_after.left(child_bounds.nw); - (target_left - current_left, width_scale) - } - ConstraintH::Left => { - let target_left = parent_before.left(child_bounds.nw); - let current_left = parent_after.left(child_bounds.nw); - (target_left - current_left, 1.0) - } - ConstraintH::Right => { - let target_right = parent_before.right(child_bounds.ne); - let current_right = parent_after.right(child_bounds.ne); - (current_right - target_right, 1.0) + child_before: &Bounds, + child_after: &Bounds, +) -> Option<(f32, f32)> { + let scale_width = match constraint_h { + ConstraintH::Left | ConstraintH::Right | ConstraintH::Center => { + parent_before.width() / parent_after.width() } ConstraintH::LeftRight => { - let target_left = parent_before.left(child_bounds.nw); - let target_right = parent_before.right(child_bounds.ne); - let current_left = parent_after.left(child_bounds.nw); - let new_width = parent_after.width() - target_left - target_right; - let width_scale = new_width / child_bounds.width(); - (target_left - current_left, width_scale) + let left = parent_before.left(child_before.nw); + let right = parent_before.right(child_before.ne); + let target_width = parent_after.width() - left - right; + target_width / child_after.width() + } + _ => 1.0, + }; + + let scale_height = match constraint_v { + ConstraintV::Top | ConstraintV::Bottom | ConstraintV::Center => { + parent_before.height() / parent_after.height() + } + ConstraintV::TopBottom => { + let top = parent_before.top(child_before.nw); + let bottom = parent_before.bottom(child_before.sw); + let target_height = parent_after.height() - top - bottom; + target_height / child_after.height() + } + _ => 1.0, + }; + + if (scale_width - 1.0).abs() < f32::EPSILON && (scale_height - 1.0).abs() < f32::EPSILON { + None + } else { + Some((scale_width, scale_height)) + } +} + +fn calculate_displacement( + constraint_h: ConstraintH, + constraint_v: ConstraintV, + parent_before: &Bounds, + parent_after: &Bounds, + child_before: &Bounds, + child_after: &Bounds, +) -> Option<(f32, f32)> { + let delta_x = match constraint_h { + ConstraintH::Left | ConstraintH::LeftRight => { + let target_left = parent_before.left(child_before.nw); + let current_left = parent_after.left(child_after.nw); + target_left - current_left + } + ConstraintH::Right => { + let target_right = parent_before.right(child_before.ne); + let current_right = parent_after.right(child_after.ne); + current_right - target_right } ConstraintH::Center => { let delta_width = parent_after.width() - parent_before.width(); - let delta_left = delta_width / 2.0; - (delta_left, 1.0) + let target_left = parent_before.left(child_before.nw); + let current_left = parent_after.left(child_after.nw); + target_left - current_left + delta_width / 2.0 } + _ => 0.0, }; - let (delta_top, scale_height) = match constraint_v { - ConstraintV::Scale => { - let height_scale = parent_after.height() / parent_before.height(); - let target_top = parent_before.top(child_bounds.nw) * height_scale; - let current_top = parent_after.top(child_bounds.nw); - (target_top - current_top, height_scale) - } - ConstraintV::Top => { - let height_scale = 1.0; - let target_top = parent_before.top(child_bounds.nw); - let current_top = parent_after.top(child_bounds.nw); - (target_top - current_top, height_scale) + let delta_y = match constraint_v { + ConstraintV::Top | ConstraintV::TopBottom => { + let target_top = parent_before.top(child_before.nw); + let current_top = parent_after.top(child_after.nw); + target_top - current_top } ConstraintV::Bottom => { - let target_bottom = parent_before.bottom(child_bounds.sw); - let current_bottom = parent_after.bottom(child_bounds.sw); - (current_bottom - target_bottom, 1.0) - } - ConstraintV::TopBottom => { - let target_top = parent_before.top(child_bounds.nw); - let target_bottom = parent_before.bottom(child_bounds.sw); - let current_top = parent_after.top(child_bounds.nw); - let new_height = parent_after.height() - target_top - target_bottom; - let height_scale = new_height / child_bounds.height(); - (target_top - current_top, height_scale) + let target_bottom = parent_before.bottom(child_before.ne); + let current_bottom = parent_after.bottom(child_after.ne); + current_bottom - target_bottom } ConstraintV::Center => { let delta_height = parent_after.height() - parent_before.height(); - let delta_top = delta_height / 2.0; - (delta_top, 1.0) + let target_top = parent_before.top(child_before.nw); + let current_top = parent_after.top(child_after.nw); + target_top - current_top + delta_height / 2.0 } + _ => 0.0, }; - (delta_left, delta_top, scale_width, scale_height) + if delta_x.abs() < f32::EPSILON && delta_y.abs() < f32::EPSILON { + None + } else { + Some((delta_x, delta_y)) + } } fn propagate_shape( @@ -121,36 +142,53 @@ fn propagate_shape( } if let Some(child_bounds_before) = parent_bounds_before.box_bounds(&child.bounds()) { - let (delta_left, delta_top, scale_width, scale_height) = calculate_new_bounds( + let mut transform = transform; + let mut child_bounds_after = child_bounds_before.transform(&transform); + + // Scale shape + if let Some((scale_width, scale_height)) = calculate_resize( constraint_h, constraint_v, &parent_bounds_before, &parent_bounds_after, &child_bounds_before, - ); + &child_bounds_after, + ) { + let center = child.center(); + + let mut parent_transform = parent_bounds_after + .transform_matrix() + .unwrap_or(Matrix::default()); + parent_transform.post_translate(center); + parent_transform.pre_translate(-center); + + let parent_transform_inv = &parent_transform.invert().unwrap(); + let origin = parent_transform_inv.map_point(child_bounds_after.nw); + + let mut scale = Matrix::scale((scale_width, scale_height)); + scale.post_translate(origin); + scale.post_concat(&parent_transform); + scale.pre_translate(-origin); + scale.pre_concat(&parent_transform_inv); + + child_bounds_after.transform_mut(&scale); + transform.post_concat(&scale); + } // Translate position - let th = parent_bounds_after.hv(delta_left); - let tv = parent_bounds_after.vv(delta_top); - let mut transform = Matrix::translate(th + tv); - let child_bounds = child_bounds_before.transform(&transform); + if let Some((delta_x, delta_y)) = calculate_displacement( + constraint_h, + constraint_v, + &parent_bounds_before, + &parent_bounds_after, + &child_bounds_before, + &child_bounds_after, + ) { + let th = parent_bounds_after.hv(delta_x); + let tv = parent_bounds_after.vv(delta_y); + transform.post_concat(&Matrix::translate(th + tv)); + } - // Scale shape - let center = child.center(); - let mut parent_transform = shape.transform; - parent_transform.post_translate(center); - parent_transform.pre_translate(-center); - - let parent_transform_inv = &parent_transform.invert().unwrap(); - let origin = parent_transform_inv.map_point(child_bounds.nw); - - let mut scale = Matrix::scale((scale_width, scale_height)); - scale.post_translate(origin); - scale.post_concat(&parent_transform); - scale.pre_translate(-origin); - scale.pre_concat(&parent_transform_inv); - - transform.post_concat(&scale); result.push(TransformEntry::new(child_id.clone(), transform)); } }