From f04c000caebfc2449f6a2cd7a583274d0571fd9a Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 9 Feb 2019 19:24:30 -0800 Subject: [PATCH] Add a quick check to skip clipping when an outline is entirely inside or outside the clip area --- geometry/src/clip.rs | 64 +++++++++++++++++++++++++++++++++++++++++ geometry/src/outline.rs | 15 +++++++++- renderer/src/scene.rs | 18 ++++++++---- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/geometry/src/clip.rs b/geometry/src/clip.rs index fab35255..013ee1a3 100644 --- a/geometry/src/clip.rs +++ b/geometry/src/clip.rs @@ -541,3 +541,67 @@ impl Edge3D { prev.lerp(next, alpha as f32) } } + +/// Coarse collision detection + +// Separating axis theorem. Requires that the polygon be convex. +pub(crate) fn rect_is_outside_polygon(rect: RectF32, polygon_points: &[Point2DF32]) -> bool { + let mut outcode = Outcode::all(); + for point in polygon_points { + if point.x() > rect.min_x() { outcode.remove(Outcode::LEFT); } + if point.x() < rect.max_x() { outcode.remove(Outcode::RIGHT); } + if point.y() > rect.min_y() { outcode.remove(Outcode::TOP); } + if point.y() < rect.max_y() { outcode.remove(Outcode::BOTTOM); } + } + if !outcode.is_empty() { + return true + } + + // FIXME(pcwalton): Check winding! + let rect_points = [rect.origin(), rect.upper_right(), rect.lower_left(), rect.lower_right()]; + for (next_point_index, &next) in polygon_points.iter().enumerate() { + let prev_point_index = if next_point_index == 0 { + polygon_points.len() - 1 + } else { + next_point_index - 1 + }; + let prev = polygon_points[prev_point_index]; + let polygon_edge_vector = next - prev; + if rect_points.iter().all(|&rect_point| polygon_edge_vector.det(rect_point - prev) < 0.0) { + return true + } + } + + false +} + +// Edge equation method. Requires that the polygon be convex. +pub(crate) fn rect_is_inside_polygon(rect: RectF32, polygon_points: &[Point2DF32]) -> bool { + // FIXME(pcwalton): Check winding! + let rect_points = [rect.origin(), rect.upper_right(), rect.lower_left(), rect.lower_right()]; + for (next_point_index, &next) in polygon_points.iter().enumerate() { + let prev_point_index = if next_point_index == 0 { + polygon_points.len() - 1 + } else { + next_point_index - 1 + }; + let prev = polygon_points[prev_point_index]; + let polygon_edge_vector = next - prev; + for &rect_point in &rect_points { + if polygon_edge_vector.det(rect_point - prev) < 0.0 { + return false; + } + } + } + + true +} + +bitflags! { + struct Outcode: u8 { + const LEFT = 0x01; + const RIGHT = 0x02; + const TOP = 0x04; + const BOTTOM = 0x08; + } +} diff --git a/geometry/src/outline.rs b/geometry/src/outline.rs index 95ffbb21..41caee30 100644 --- a/geometry/src/outline.rs +++ b/geometry/src/outline.rs @@ -15,7 +15,7 @@ use crate::basic::point::Point2DF32; use crate::basic::rect::RectF32; use crate::basic::transform2d::Transform2DF32; use crate::basic::transform3d::Perspective; -use crate::clip::{ContourPolygonClipper, ContourRectClipper}; +use crate::clip::{self, ContourPolygonClipper, ContourRectClipper}; use crate::dilation::ContourDilator; use crate::orientation::Orientation; use crate::segment::{Segment, SegmentFlags, SegmentKind}; @@ -138,7 +138,20 @@ impl Outline { self.bounds = self.bounds.intersection(view_box).unwrap_or_else(|| RectF32::default()); } + pub fn is_outside_polygon(&self, clip_polygon: &[Point2DF32]) -> bool { + clip::rect_is_outside_polygon(self.bounds, clip_polygon) + } + + fn is_inside_polygon(&self, clip_polygon: &[Point2DF32]) -> bool { + clip::rect_is_inside_polygon(self.bounds, clip_polygon) + } + pub fn clip_against_polygon(&mut self, clip_polygon: &[Point2DF32]) { + // Quick check. + if self.is_inside_polygon(clip_polygon) { + return; + } + let mut new_bounds = None; for contour in mem::replace(&mut self.contours, vec![]) { let contour = ContourPolygonClipper::new(clip_polygon, contour).clip(); diff --git a/renderer/src/scene.rs b/renderer/src/scene.rs index 7c2c67e9..98402e56 100644 --- a/renderer/src/scene.rs +++ b/renderer/src/scene.rs @@ -104,19 +104,27 @@ impl Scene { .collect() } - fn apply_render_options(&self, outline: &Outline, options: &PreparedRenderOptions) -> Outline { - // FIXME(pcwalton): Don't clone? - let mut outline = (*outline).clone(); + fn apply_render_options(&self, original_outline: &Outline, options: &PreparedRenderOptions) + -> Outline { + let mut outline; match options.transform { PreparedRenderTransform::Perspective { ref perspective, ref clip_polygon } => { - outline.clip_against_polygon(clip_polygon); - outline.apply_perspective(perspective); + if original_outline.is_outside_polygon(clip_polygon) { + outline = Outline::new(); + } else { + outline = (*original_outline).clone(); + outline.clip_against_polygon(clip_polygon); + outline.apply_perspective(perspective); + } } PreparedRenderTransform::Transform2D(ref transform) => { + // TODO(pcwalton): Short circuit. + outline = (*original_outline).clone(); outline.transform(transform); outline.clip_against_rect(self.view_box); } PreparedRenderTransform::None => { + outline = (*original_outline).clone(); outline.clip_against_rect(self.view_box); } }