From 1eb28a55393fab68b0b938438415c9f7aad49c3e Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Fri, 12 Jul 2019 11:26:09 -0700 Subject: [PATCH] Fix incorrect definition of 2D transform multiplication --- canvas/src/lib.rs | 13 +++--- content/src/outline.rs | 9 ++--- content/src/stroke.rs | 6 +-- demo/common/src/camera.rs | 3 +- demo/common/src/lib.rs | 33 +++++++-------- examples/canvas_minimal/src/main.rs | 7 ++-- geometry/src/transform2d.rs | 63 +++++++++++++++++------------ geometry/src/transform3d.rs | 10 ++--- renderer/src/scene.rs | 3 +- svg/src/lib.rs | 2 +- text/src/lib.rs | 5 ++- 11 files changed, 83 insertions(+), 71 deletions(-) diff --git a/canvas/src/lib.rs b/canvas/src/lib.rs index b79af1ed..0ccd3317 100644 --- a/canvas/src/lib.rs +++ b/canvas/src/lib.rs @@ -115,8 +115,7 @@ impl CanvasRenderingContext2D { TextAlign::Center => position.set_x(position.x() - layout.width() * 0.5), } - let transform = Transform2F::from_translation(position).post_mul(&self.current_state - .transform); + let transform = self.current_state.transform * Transform2F::from_translation(position); // TODO(pcwalton): Report errors. drop(self.scene.push_layout(&layout, @@ -443,7 +442,7 @@ impl Path2D { end_angle: f32, direction: ArcDirection) { let mut transform = Transform2F::from_scale(Vector2F::splat(radius)); - transform = transform.post_mul(&Transform2F::from_translation(center)); + transform = Transform2F::from_translation(center) * transform; self.current_contour.push_arc(&transform, start_angle, end_angle, direction); } @@ -458,7 +457,7 @@ impl Path2D { let center = ctrl + bisector.scale(hypot / bisector.length()); let mut transform = Transform2F::from_scale(Vector2F::splat(radius)); - transform = transform.post_mul(&Transform2F::from_translation(center)); + transform = Transform2F::from_translation(center) * transform; let chord = LineSegment2F::new(vu0.yx().scale_xy(Vector2F::new(-1.0, 1.0)), vu1.yx().scale_xy(Vector2F::new(1.0, -1.0))); @@ -484,9 +483,9 @@ impl Path2D { end_angle: f32) { self.flush_current_contour(); - let mut transform = Transform2F::from_rotation(rotation); - transform = transform.post_mul(&Transform2F::from_scale(axes)); - transform = transform.post_mul(&Transform2F::from_translation(center)); + let mut transform = Transform2F::from_translation(center); + transform *= Transform2F::from_rotation(rotation); + transform *= Transform2F::from_scale(axes); self.current_contour.push_arc(&transform, start_angle, end_angle, ArcDirection::CW); if end_angle - start_angle >= 2.0 * PI { diff --git a/content/src/outline.rs b/content/src/outline.rs index 6e90cc3c..5dd7f709 100644 --- a/content/src/outline.rs +++ b/content/src/outline.rs @@ -393,8 +393,7 @@ impl Contour { let half_sweep_vector = sweep_vector.halve_angle(); let rotation = Transform2F::from_rotation_vector(half_sweep_vector.rotate_by(vector)); - segment = segment.transform(&direction_transform.post_mul(&rotation) - .post_mul(&transform)); + segment = segment.transform(&(*transform * rotation * direction_transform)); let mut push_segment_flags = PushSegmentFlags::UPDATE_BOUNDS; if first_segment { @@ -419,13 +418,13 @@ impl Contour { self.push_segment(&segment.transform(transform), PushSegmentFlags::UPDATE_BOUNDS | PushSegmentFlags::INCLUDE_FROM_POINT); rotation = Transform2F::from_rotation_vector(UnitVector(Vector2F::new( 0.0, 1.0))); - self.push_segment(&segment.transform(&rotation.post_mul(&transform)), + self.push_segment(&segment.transform(&(*transform * rotation)), PushSegmentFlags::UPDATE_BOUNDS); rotation = Transform2F::from_rotation_vector(UnitVector(Vector2F::new(-1.0, 0.0))); - self.push_segment(&segment.transform(&rotation.post_mul(&transform)), + self.push_segment(&segment.transform(&(*transform * rotation)), PushSegmentFlags::UPDATE_BOUNDS); rotation = Transform2F::from_rotation_vector(UnitVector(Vector2F::new( 0.0, -1.0))); - self.push_segment(&segment.transform(&rotation.post_mul(&transform)), + self.push_segment(&segment.transform(&(*transform * rotation)), PushSegmentFlags::UPDATE_BOUNDS); } diff --git a/content/src/stroke.rs b/content/src/stroke.rs index 17bbb243..4e8a26ac 100644 --- a/content/src/stroke.rs +++ b/content/src/stroke.rs @@ -140,7 +140,7 @@ impl<'a> OutlineStrokeToFill<'a> { let offset = gradient.yx().scale_xy(Vector2F::new(-1.0, 1.0)); let mut transform = Transform2F::from_scale(scale); let translation = p1 + offset.scale(width * 0.5); - transform = transform.post_mul(&Transform2F::from_translation(translation)); + transform = Transform2F::from_translation(translation) * transform; let chord = LineSegment2F::new(-offset, offset); contour.push_arc_from_unit_chord(&transform, chord, ArcDirection::CW); } @@ -374,8 +374,8 @@ impl Contour { } LineJoin::Round => { let scale = Vector2F::splat(distance.abs()); - let mut transform = Transform2F::from_scale(scale); - transform = transform.post_mul(&Transform2F::from_translation(join_point)); + let mut transform = Transform2F::from_translation(join_point); + transform *= Transform2F::from_scale(scale); let chord_from = (prev_tangent.to() - join_point).normalize(); let chord_to = (next_tangent.to() - join_point).normalize(); let chord = LineSegment2F::new(chord_from, chord_to); diff --git a/demo/common/src/camera.rs b/demo/common/src/camera.rs index 15ad0554..d108c21b 100644 --- a/demo/common/src/camera.rs +++ b/demo/common/src/camera.rs @@ -56,7 +56,8 @@ impl Camera { let scale = i32::min(viewport_size.x(), viewport_size.y()) as f32 * scale_factor_for_view_box(view_box); let origin = viewport_size.to_f32().scale(0.5) - view_box.size().scale(scale * 0.5); - Camera::TwoD(Transform2F::from_scale(Vector2F::splat(scale)).post_translate(origin)) + Camera::TwoD(Transform2F::from_translation(origin) * + Transform2F::from_uniform_scale(scale)) } fn new_3d(mode: Mode, view_box: RectF, viewport_size: Vector2I) -> Camera { diff --git a/demo/common/src/lib.rs b/demo/common/src/lib.rs index 1f8aa8a9..3b0626ae 100644 --- a/demo/common/src/lib.rs +++ b/demo/common/src/lib.rs @@ -327,10 +327,10 @@ impl DemoApp where W: Window { if let Camera::TwoD(ref mut transform) = self.camera { let backing_scale_factor = self.window_size.backing_scale_factor; let position = position.to_f32().scale(backing_scale_factor); - *transform = transform.post_translate(-position); + *transform = Transform2F::from_translation(-position) * *transform; let scale_delta = 1.0 + d_dist * CAMERA_SCALE_SPEED_2D; - *transform = transform.post_scale(Vector2F::splat(scale_delta)); - *transform = transform.post_translate(position); + *transform = Transform2F::from_uniform_scale(scale_delta) * *transform; + *transform = Transform2F::from_translation(position) * *transform; } } Event::Look { pitch, yaw } => { @@ -587,7 +587,8 @@ impl DemoApp where W: Window { } UIEvent::MouseDragged(position) => { if let Camera::TwoD(ref mut transform) = self.camera { - *transform = transform.post_translate(position.relative.to_f32()); + *transform = Transform2F::from_translation(position.relative.to_f32()) * + *transform; } } _ => {} @@ -607,10 +608,10 @@ impl DemoApp where W: Window { if let Camera::TwoD(ref mut transform) = self.camera { let scale = Vector2F::splat(1.0 + CAMERA_ZOOM_AMOUNT_2D); let center = center_of_window(&self.window_size); - *transform = transform - .post_translate(-center) - .post_scale(scale) - .post_translate(center); + *transform = Transform2F::from_translation(center) * + Transform2F::from_scale(scale) * + Transform2F::from_translation(-center) * + *transform; self.dirty = true; } } @@ -618,10 +619,10 @@ impl DemoApp where W: Window { if let Camera::TwoD(ref mut transform) = self.camera { let scale = Vector2F::splat(1.0 - CAMERA_ZOOM_AMOUNT_2D); let center = center_of_window(&self.window_size); - *transform = transform - .post_translate(-center) - .post_scale(scale) - .post_translate(center); + *transform = Transform2F::from_translation(center) * + Transform2F::from_scale(scale) * + Transform2F::from_translation(-center) * + *transform; self.dirty = true; } } @@ -635,10 +636,10 @@ impl DemoApp where W: Window { if let Camera::TwoD(ref mut transform) = self.camera { let old_rotation = transform.rotation(); let center = center_of_window(&self.window_size); - *transform = transform - .post_translate(-center) - .post_rotate(*theta - old_rotation) - .post_translate(center); + *transform = Transform2F::from_translation(-center) * + Transform2F::from_rotation(*theta - old_rotation) * + Transform2F::from_translation(center) * + *transform; } } } diff --git a/examples/canvas_minimal/src/main.rs b/examples/canvas_minimal/src/main.rs index dce34e5d..26b7e655 100644 --- a/examples/canvas_minimal/src/main.rs +++ b/examples/canvas_minimal/src/main.rs @@ -8,10 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, Path2D}; -use pathfinder_geometry::vector::{Vector2F, Vector2I}; -use pathfinder_geometry::rect::RectF; +use pathfinder_canvas::{CanvasFontContext, CanvasRenderingContext2D, LineJoin, Path2D}; use pathfinder_content::color::ColorF; +use pathfinder_geometry::rect::RectF; +use pathfinder_geometry::vector::{Vector2F, Vector2I}; use pathfinder_gl::{GLDevice, GLVersion}; use pathfinder_gpu::resources::FilesystemResourceLoader; use pathfinder_renderer::concurrent::rayon::RayonExecutor; @@ -57,6 +57,7 @@ fn main() { // Set line width. canvas.set_line_width(10.0); + canvas.set_line_join(LineJoin::Round); // Draw walls. canvas.stroke_rect(RectF::new(Vector2F::new(75.0, 140.0), Vector2F::new(150.0, 110.0))); diff --git a/geometry/src/transform2d.rs b/geometry/src/transform2d.rs index f2c72857..4a68e619 100644 --- a/geometry/src/transform2d.rs +++ b/geometry/src/transform2d.rs @@ -16,7 +16,7 @@ use crate::rect::RectF; use crate::transform3d::Transform4F; use crate::unit_vector::UnitVector; use pathfinder_simd::default::F32x4; -use std::ops::Sub; +use std::ops::{Mul, MulAssign, Sub}; /// A 2x2 matrix, optimized with SIMD, in column-major order. #[derive(Clone, Copy, Debug, PartialEq)] @@ -46,18 +46,8 @@ impl Matrix2x2F { } #[inline] - pub fn row_major(m11: f32, m12: f32, m21: f32, m22: f32) -> Matrix2x2F { - Matrix2x2F(F32x4::new(m11, m21, m12, m22)) - } - - #[inline] - pub fn post_mul(&self, other: &Matrix2x2F) -> Matrix2x2F { - Matrix2x2F(self.0.xyxy() * other.0.xxzz() + self.0.zwzw() * other.0.yyww()) - } - - #[inline] - pub fn pre_mul(&self, other: &Matrix2x2F) -> Matrix2x2F { - other.post_mul(self) + pub fn row_major(m00: f32, m01: f32, m10: f32, m11: f32) -> Matrix2x2F { + Matrix2x2F(F32x4::new(m00, m10, m01, m11)) } #[inline] @@ -112,6 +102,14 @@ impl Sub for Matrix2x2F { } } +impl Mul for Matrix2x2F { + type Output = Matrix2x2F; + #[inline] + fn mul(self, other: Matrix2x2F) -> Matrix2x2F { + Matrix2x2F(self.0.xyxy() * other.0.xxzz() + self.0.zwzw() * other.0.yyww()) + } +} + /// An affine transform, optimized with SIMD. #[derive(Clone, Copy, Debug, PartialEq)] pub struct Transform2F { @@ -136,6 +134,11 @@ impl Transform2F { } } + #[inline] + pub fn from_uniform_scale(scale: f32) -> Transform2F { + Transform2F::from_scale(Vector2F::splat(scale)) + } + #[inline] pub fn from_rotation(theta: f32) -> Transform2F { Transform2F { @@ -165,7 +168,7 @@ impl Transform2F { ) -> Transform2F { let rotation = Transform2F::from_rotation(theta); let translation = Transform2F::from_translation(translation); - Transform2F::from_scale(scale).post_mul(&rotation).post_mul(&translation) + Transform2F::from_scale(scale) * rotation * translation } #[inline] @@ -198,18 +201,6 @@ impl Transform2F { RectF::from_points(min_point, max_point) } - #[inline] - pub fn post_mul(&self, other: &Transform2F) -> Transform2F { - let matrix = self.matrix.post_mul(&other.matrix); - let vector = other.transform_point(self.vector); - Transform2F { matrix, vector } - } - - #[inline] - pub fn pre_mul(&self, other: &Transform2F) -> Transform2F { - other.post_mul(self) - } - // TODO(pcwalton): Optimize better with SIMD. #[inline] pub fn to_3d(&self) -> Transform4F { @@ -255,6 +246,7 @@ impl Transform2F { self.matrix.m22() } + /* #[inline] pub fn post_translate(&self, vector: Vector2F) -> Transform2F { self.post_mul(&Transform2F::from_translation(vector)) @@ -269,6 +261,7 @@ impl Transform2F { pub fn post_scale(&self, scale: Vector2F) -> Transform2F { self.post_mul(&Transform2F::from_scale(scale)) } + */ /// Returns the translation part of this matrix. /// @@ -294,3 +287,21 @@ impl Transform2F { Vector2F(self.matrix.0.zw()).length() } } + +impl Mul for Transform2F { + type Output = Transform2F; + #[inline] + fn mul(self, other: Transform2F) -> Transform2F { + Transform2F { + matrix: self.matrix * other.matrix, + vector: self.transform_point(other.vector), + } + } +} + +impl MulAssign for Transform2F { + #[inline] + fn mul_assign(&mut self, other: Transform2F) { + *self = *self * other + } +} diff --git a/geometry/src/transform3d.rs b/geometry/src/transform3d.rs index ee78b4d5..c892d52d 100644 --- a/geometry/src/transform3d.rs +++ b/geometry/src/transform3d.rs @@ -280,13 +280,13 @@ impl Transform4F { // Compute temporary matrices. let a_inv = a.inverse(); - let x = c.post_mul(&a_inv); - let y = (d - x.post_mul(&b)).inverse(); - let z = a_inv.post_mul(&b); + let x = c * a_inv; + let y = (d - x * b).inverse(); + let z = a_inv * b; // Compute new submatrices. - let (a_new, b_new) = (a_inv + z.post_mul(&y).post_mul(&x), (-z).post_mul(&y)); - let (c_new, d_new) = ((-y).post_mul(&x), y); + let (a_new, b_new) = (a_inv + z * y * x, -z * y); + let (c_new, d_new) = (-y * x, y); // Construct inverse. Transform4F::from_submatrices(a_new, b_new, c_new, d_new) diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 0d779a21..6d2d9d36 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -119,8 +119,7 @@ impl Scene { PreparedRenderTransform::Perspective { .. } => unreachable!(), }; if options.subpixel_aa_enabled { - transform = transform - .post_mul(&Transform2F::from_scale(Vector2F::new(3.0, 1.0))) + transform *= Transform2F::from_scale(Vector2F::new(3.0, 1.0)) } outline.transform(&transform); } diff --git a/svg/src/lib.rs b/svg/src/lib.rs index 28bdfcae..11d1f4cf 100644 --- a/svg/src/lib.rs +++ b/svg/src/lib.rs @@ -89,7 +89,7 @@ impl BuiltSVG { fn process_node(&mut self, node: &Node, transform: &Transform2F) { let node_transform = usvg_transform_to_transform_2d(&node.transform()); - let transform = transform.pre_mul(&node_transform); + let transform = node_transform * *transform; match *node.borrow() { NodeKind::Group(ref group) => { diff --git a/text/src/lib.rs b/text/src/lib.rs index 1d7b985d..4433b07c 100644 --- a/text/src/lib.rs +++ b/text/src/lib.rs @@ -93,8 +93,9 @@ impl SceneExt for Scene { // FIXME(pcwalton): Cache this! let scale = style.size / (font.metrics().units_per_em as f32); let scale = Vector2F::new(scale, -scale); - let transform = - Transform2F::from_scale(scale).post_translate(offset).post_mul(transform); + let transform = *transform * + Transform2F::from_translation(offset) * + Transform2F::from_scale(scale); self.push_glyph(font, glyph.glyph_id, &transform,