From 93ae7d3548f2cb90cb21f4891aebeb2dbbcdb7b7 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 16 Jan 2019 16:53:10 -0800 Subject: [PATCH] Basic perspective support --- demo3/src/main.rs | 11 ++++-- geometry/src/outline.rs | 18 ++++++++- geometry/src/transform3d.rs | 74 ++++++++++++++++++++++++------------- renderer/src/scene.rs | 18 +++++++++ 4 files changed, 90 insertions(+), 31 deletions(-) diff --git a/demo3/src/main.rs b/demo3/src/main.rs index a51f8d92..647f614d 100644 --- a/demo3/src/main.rs +++ b/demo3/src/main.rs @@ -14,7 +14,7 @@ use gl::types::{GLchar, GLfloat, GLint, GLsizei, GLsizeiptr, GLuint, GLvoid}; use jemallocator; use pathfinder_geometry::point::Point2DF32; use pathfinder_geometry::transform::Transform2DF32; -use pathfinder_geometry::transform3d::Transform3DF32; +use pathfinder_geometry::transform3d::{Perspective, Transform3DF32}; use pathfinder_renderer::builder::SceneBuilder; use pathfinder_renderer::gpu_data::{Batch, BuiltScene, SolidTileScenePrimitive}; use pathfinder_renderer::paint::ObjectShader; @@ -26,6 +26,7 @@ use rayon::ThreadPoolBuilder; use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::video::GLProfile; +use std::f32::consts::FRAC_PI_4; use std::ffi::CString; use std::fs::File; use std::io::Read; @@ -84,12 +85,14 @@ fn main() { //let mut scale = 1.0; //let mut theta = 0.0; - let mut transform = Transform3DF32::from_perspective(f32::PI / 4.0, 4.0 / 3.0, 0.01, 1000.0); - transform = transform.pre_mul(Transform3DF32::from_translation(0.0, 0.0, -100.0)); + let mut transform = Transform3DF32::from_perspective(FRAC_PI_4, 4.0 / 3.0, 0.01, 100.0); + transform = transform.post_mul(&Transform3DF32::from_translation(0.0, 0.0, -6.0)); + let window_size = Size2D::new(MAIN_FRAMEBUFFER_WIDTH, MAIN_FRAMEBUFFER_HEIGHT); + let perspective = Perspective::new(&transform, &window_size); while !exit { let mut scene = base_scene.clone(); - scene.transform_3d(&transform); + scene.apply_perspective(&perspective); //scene.transform(&Transform2DF32::from_rotation(theta)); //scene.transform(&Transform2DF32::from_scale(&Point2DF32::splat(scale))); //theta += 0.001; diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 28d30727..4671ca4e 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -15,9 +15,9 @@ use crate::line_segment::LineSegmentF32; use crate::monotonic::MonotonicConversionIter; use crate::point::Point2DF32; use crate::segment::{Segment, SegmentFlags, SegmentKind}; +use crate::transform3d::Perspective; use crate::transform::Transform2DF32; use euclid::{Point2D, Rect, Size2D}; -use lyon_path::PathEvent; use std::fmt::{self, Debug, Formatter}; use std::mem; @@ -107,6 +107,12 @@ impl Outline { self.bounds = transform.transform_rect(&self.bounds); } + #[inline] + pub fn apply_perspective(&mut self, perspective: &Perspective) { + self.contours.iter_mut().for_each(|contour| contour.apply_perspective(perspective)); + self.bounds = perspective.transform_rect(&self.bounds); + } + #[inline] fn push_contour(&mut self, contour: Contour) { if self.contours.is_empty() { @@ -288,6 +294,16 @@ impl Contour { self.make_monotonic(); } + #[inline] + pub fn apply_perspective(&mut self, perspective: &Perspective) { + for (point_index, point) in self.points.iter_mut().enumerate() { + *point = perspective.transform_point(point); + union_rect(&mut self.bounds, *point, point_index == 0); + } + + self.make_monotonic(); + } + #[inline] pub fn make_monotonic(&mut self) { let contour = mem::replace(self, Contour::new()); diff --git a/geometry/src/transform3d.rs b/geometry/src/transform3d.rs index f4b6a5cd..54b612eb 100644 --- a/geometry/src/transform3d.rs +++ b/geometry/src/transform3d.rs @@ -13,7 +13,7 @@ use crate::point::{Point2DF32, Point4DF32}; use crate::segment::Segment; use crate::simd::F32x4; -use euclid::Size2D; +use euclid::{Point2D, Rect, Size2D}; /// An transform, optimized with SIMD. /// @@ -163,17 +163,52 @@ impl Transform3DF32 { } } -/// Transforms a path with a SIMD 3D transform. -pub struct Transform3DF32PathIter +#[derive(Clone, Copy, Debug)] +pub struct Perspective { + pub transform: Transform3DF32, + pub window_size: Size2D, +} + +impl Perspective { + #[inline] + pub fn new(transform: &Transform3DF32, window_size: &Size2D) -> Perspective { + Perspective { transform: *transform, window_size: *window_size } + } + + #[inline] + pub fn transform_point(&self, point: &Point2DF32) -> Point2DF32 { + let point = self.transform.transform_point(point.to_4d()).perspective_divide().to_2d(); + let window_size = self.window_size.to_f32(); + let size_scale = Point2DF32::new(window_size.width * 0.5, window_size.height * 0.5); + (point + Point2DF32::splat(1.0)) * size_scale + } + + // TODO(pcwalton): SIMD? + #[inline] + pub fn transform_rect(&self, rect: &Rect) -> Rect { + let upper_left = self.transform_point(&Point2DF32::from_euclid(rect.origin)); + let upper_right = self.transform_point(&Point2DF32::from_euclid(rect.top_right())); + let lower_left = self.transform_point(&Point2DF32::from_euclid(rect.bottom_left())); + let lower_right = self.transform_point(&Point2DF32::from_euclid(rect.bottom_right())); + let min_x = upper_left.x().min(upper_right.x()).min(lower_left.x()).min(lower_right.x()); + let min_y = upper_left.y().min(upper_right.y()).min(lower_left.y()).min(lower_right.y()); + let max_x = upper_left.x().max(upper_right.x()).max(lower_left.x()).max(lower_right.x()); + let max_y = upper_left.y().max(upper_right.y()).max(lower_left.y()).max(lower_right.y()); + let (width, height) = (max_x - min_x, max_y - min_y); + Rect::new(Point2D::new(min_x, min_y), Size2D::new(width, height)) + } +} + +/// Transforms a path with a perspective projection. +pub struct PerspectivePathIter where I: Iterator, { iter: I, - transform: Transform3DF32, - window_size: Size2D, + perspective: Perspective, } -impl Iterator for Transform3DF32PathIter +impl Iterator for PerspectivePathIter where I: Iterator, { @@ -183,12 +218,12 @@ where fn next(&mut self) -> Option { let mut segment = self.iter.next()?; if !segment.is_none() { - segment.baseline.set_from(&self.transform_point(&segment.baseline.from())); - segment.baseline.set_to(&self.transform_point(&segment.baseline.to())); + segment.baseline.set_from(&self.perspective.transform_point(&segment.baseline.from())); + segment.baseline.set_to(&self.perspective.transform_point(&segment.baseline.to())); if !segment.is_line() { - segment.ctrl.set_from(&self.transform_point(&segment.ctrl.from())); + segment.ctrl.set_from(&self.perspective.transform_point(&segment.ctrl.from())); if !segment.is_quadratic() { - segment.ctrl.set_to(&self.transform_point(&segment.ctrl.to())); + segment.ctrl.set_to(&self.perspective.transform_point(&segment.ctrl.to())); } } } @@ -196,26 +231,13 @@ where } } -impl Transform3DF32PathIter +impl PerspectivePathIter where I: Iterator, { #[inline] - pub fn new(iter: I, transform: &Transform3DF32, window_size: &Size2D) - -> Transform3DF32PathIter { - Transform3DF32PathIter { - iter, - transform: *transform, - window_size: *window_size, - } - } - - #[inline] - fn transform_point(&self, point: &Point2DF32) -> Point2DF32 { - let point = self.transform.transform_point(point.to_4d()).perspective_divide().to_2d(); - let window_size = self.window_size.to_f32(); - let size_scale = Point2DF32::new(window_size.width * 0.5, window_size.height * 0.5); - (point + Point2DF32::splat(1.0)) * size_scale + pub fn new(iter: I, perspective: &Perspective) -> PerspectivePathIter { + PerspectivePathIter { iter, perspective: *perspective } } } diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index e23bbe48..6f1b3766 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -17,6 +17,7 @@ use crate::z_buffer::ZBuffer; use euclid::Rect; use hashbrown::HashMap; use pathfinder_geometry::outline::Outline; +use pathfinder_geometry::transform3d::Perspective; use pathfinder_geometry::transform::Transform2DF32; use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator}; @@ -114,6 +115,23 @@ impl Scene { //println!("new bounds={:?}", bounds); self.bounds = bounds; } + + pub fn apply_perspective(&mut self, perspective: &Perspective) { + let mut bounds = Rect::zero(); + for (object_index, object) in self.objects.iter_mut().enumerate() { + object.outline.apply_perspective(perspective); + object.outline.clip_against_rect(&self.view_box); + + if object_index == 0 { + bounds = *object.outline.bounds(); + } else { + bounds = bounds.union(object.outline.bounds()); + } + } + + //println!("new bounds={:?}", bounds); + self.bounds = bounds; + } } #[derive(Clone, Debug)]