diff --git a/partitioner/src/geometry.rs b/partitioner/src/geometry.rs index 472b4e3e..1884a13c 100644 --- a/partitioner/src/geometry.rs +++ b/partitioner/src/geometry.rs @@ -65,27 +65,6 @@ pub fn line_line_crossing_point(a_p0: &Point2D, Some(p + r * t) } -// TODO(pcwalton): Implement this. -pub fn line_quadratic_bezier_crossing_point(a_p0: &Point2D, - a_p1: &Point2D, - b_p0: &Point2D, - b_p1: &Point2D, - b_p2: &Point2D) - -> Option> { - None -} - -// TODO(pcwalton): Implement this. -pub fn quadratic_bezier_quadratic_bezier_crossing_point(_a_p0: &Point2D, - _a_p1: &Point2D, - _a_p2: &Point2D, - _b_p0: &Point2D, - _b_p1: &Point2D, - _b_p2: &Point2D) - -> Option> { - None -} - pub fn solve_line_t_for_x(x: f32, a: &Point2D, b: &Point2D) -> f32 { if b.x == a.x { 0.0 diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index 2db4ab3f..f93fbafc 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -14,11 +14,14 @@ use geometry::{self, SubdividedQuadraticBezier}; use log::LogLevel; use pathfinder_path_utils::PathBuffer; use pathfinder_path_utils::curve::Curve; +use pathfinder_path_utils::intersection::Intersection; +use pathfinder_path_utils::line::Line; use std::collections::BinaryHeap; use std::cmp::Ordering; use std::f32; use std::iter; use std::u32; + use {BQuad, BVertexLoopBlinnData, BVertexKind, CurveIndices, Endpoint, FillRule}; use {LineIndices, Subpath}; @@ -811,36 +814,50 @@ impl<'a> Partitioner<'a> { lower_left_vertex_position, lower_right_endpoint_position) } + (upper_control_point_vertex_index, u32::MAX) => { let upper_control_point = &self.b_vertex_positions[upper_control_point_vertex_index as usize]; - geometry::line_quadratic_bezier_crossing_point(lower_left_vertex_position, - lower_right_endpoint_position, - upper_left_vertex_position, - upper_control_point, - upper_right_endpoint_position) + let upper_curve = Curve::new(&upper_left_vertex_position, + &upper_control_point, + &upper_right_endpoint_position); + let lower_line = Line::new(lower_left_vertex_position, + lower_right_endpoint_position); + + Intersection::calculate(&upper_curve, &lower_line).map(|intersection| { + lower_line.sample(intersection.t_b) + }) } + (u32::MAX, lower_control_point_vertex_index) => { let lower_control_point = &self.b_vertex_positions[lower_control_point_vertex_index as usize]; - geometry::line_quadratic_bezier_crossing_point(upper_left_vertex_position, - upper_right_endpoint_position, - lower_left_vertex_position, - lower_control_point, - lower_right_endpoint_position) + let lower_curve = Curve::new(&lower_left_vertex_position, + &lower_control_point, + &lower_right_endpoint_position); + let upper_line = Line::new(upper_left_vertex_position, + upper_right_endpoint_position); + + Intersection::calculate(&upper_line, &lower_curve).map(|intersection| { + upper_line.sample(intersection.t_a) + }) } + (upper_control_point_vertex_index, lower_control_point_vertex_index) => { let upper_control_point = &self.b_vertex_positions[upper_control_point_vertex_index as usize]; let lower_control_point = &self.b_vertex_positions[lower_control_point_vertex_index as usize]; - geometry::quadratic_bezier_quadratic_bezier_crossing_point( - upper_left_vertex_position, - upper_control_point, - upper_right_endpoint_position, - lower_left_vertex_position, - lower_control_point, - lower_right_endpoint_position) + let upper_curve = Curve::new(&upper_left_vertex_position, + &upper_control_point, + &upper_right_endpoint_position); + let lower_curve = Curve::new(&lower_left_vertex_position, + &lower_control_point, + &lower_right_endpoint_position); + + Intersection::calculate(&upper_curve, &lower_curve).map(|intersection| { + upper_curve.sample(intersection.t_a) + }) } } } diff --git a/path-utils/src/curve.rs b/path-utils/src/curve.rs index 253951a6..abe9e002 100644 --- a/path-utils/src/curve.rs +++ b/path-utils/src/curve.rs @@ -10,8 +10,8 @@ //! Geometry utilities for Bézier curves. -use euclid::Point2D; use euclid::approxeq::ApproxEq; +use euclid::Point2D; use std::f32; use PathSegment; diff --git a/path-utils/src/intersection.rs b/path-utils/src/intersection.rs new file mode 100644 index 00000000..23abab6e --- /dev/null +++ b/path-utils/src/intersection.rs @@ -0,0 +1,116 @@ +// pathfinder/path-utils/src/intersection.rs +// +// Copyright © 2017 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Intersections of two segments. + +use euclid::{Point2D, Rect}; + +use curve::Curve; +use line::Line; +use lerp; + +const SUBDIVISION_TOLERANCE: f32 = 0.0001; +const MAX_SUBDIVISIONS: u32 = 1000; + +pub struct Intersection { + pub t_a: f32, + pub t_b: f32, +} + +impl Intersection { + /// Requires that any curves be monotonic. (See the `monotonic` module for that.) + /// + /// This should work for line segments, but it is inefficient. + /// + /// See T.W. Sederberg, "Computer Aided Geometric Design Course Notes" § 7.6. + pub fn calculate(a: &A, b: &B) -> Option where A: Intersect, B: Intersect { + let (mut a_lower_t, mut a_upper_t) = (0.0, 1.0); + let (mut b_lower_t, mut b_upper_t) = (0.0, 1.0); + + for _ in 0..MAX_SUBDIVISIONS { + let a_lower_point = a.sample(a_lower_t); + let a_upper_point = a.sample(a_upper_t); + let b_lower_point = b.sample(b_lower_t); + let b_upper_point = b.sample(b_upper_t); + + let a_distance = (a_upper_point - a_lower_point).length(); + let b_distance = (b_upper_point - b_lower_point).length(); + + let need_to_subdivide_a = a_distance >= SUBDIVISION_TOLERANCE; + let need_to_subdivide_b = b_distance >= SUBDIVISION_TOLERANCE; + if !need_to_subdivide_b && !need_to_subdivide_a { + break + } + + let a_rect; + if need_to_subdivide_a { + let a_middle_t = lerp(a_lower_t, a_upper_t, 0.5); + let a_middle_point = a.sample(a_middle_t); + + let a_lower_rect = + Rect::from_points(&[a_lower_point, a_middle_point]); + let a_upper_rect = + Rect::from_points(&[a_middle_point, a_upper_point]); + let b_rect = Rect::from_points(&[b_lower_point, b_upper_point]); + + if a_lower_rect.intersects(&b_rect) { + a_upper_t = a_middle_t; + a_rect = a_lower_rect; + } else if a_upper_rect.intersects(&b_rect) { + a_lower_t = a_middle_t; + a_rect = a_upper_rect; + } else { + return None + } + } else { + a_rect = Rect::from_points(&[a_lower_point, a_upper_point]) + } + + if need_to_subdivide_b { + let b_middle_t = lerp(b_lower_t, b_upper_t, 0.5); + let b_middle_point = b.sample(b_middle_t); + + let b_lower_rect = Rect::from_points(&[b_lower_point, b_middle_point]); + let b_upper_rect = Rect::from_points(&[b_middle_point, b_upper_point]); + + if b_lower_rect.intersects(&a_rect) { + b_upper_t = b_middle_t + } else if b_upper_rect.intersects(&a_rect) { + b_lower_t = b_middle_t + } else { + return None + } + } + } + + Some(Intersection { + t_a: lerp(a_lower_t, a_upper_t, 0.5), + t_b: lerp(b_lower_t, b_upper_t, 0.5), + }) + } +} + +pub trait Intersect { + fn sample(&self, t: f32) -> Point2D; +} + +impl Intersect for Line { + #[inline] + fn sample(&self, t: f32) -> Point2D { + Line::sample(self, t) + } +} + +impl Intersect for Curve { + #[inline] + fn sample(&self, t: f32) -> Point2D { + Curve::sample(self, t) + } +} diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs index d7a6d449..4ab3d916 100644 --- a/path-utils/src/lib.rs +++ b/path-utils/src/lib.rs @@ -19,6 +19,8 @@ use std::u32; pub mod curve; pub mod freetype; +pub mod intersection; +pub mod line; pub mod monotonic; pub mod stroke; @@ -195,3 +197,8 @@ impl Iterator for Transform2DPathStream where I: Iterator f32 { + a + (b - a) * t +} diff --git a/path-utils/src/line.rs b/path-utils/src/line.rs new file mode 100644 index 00000000..0165f4ef --- /dev/null +++ b/path-utils/src/line.rs @@ -0,0 +1,31 @@ +// pathfinder/path-utils/src/line.rs +// +// Copyright © 2017 The Pathfinder Project Developers. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Geometry utilities for straight line segments. + +use euclid::Point2D; + +pub struct Line { + pub endpoints: [Point2D; 2], +} + +impl Line { + #[inline] + pub fn new(endpoint_0: &Point2D, endpoint_1: &Point2D) -> Line { + Line { + endpoints: [*endpoint_0, *endpoint_1], + } + } + + #[inline] + pub fn sample(&self, t: f32) -> Point2D { + self.endpoints[0].lerp(self.endpoints[1], t) + } +}