Implement curve-curve and curve-line intersection, untested as of yet

This commit is contained in:
Patrick Walton 2017-09-18 21:00:34 -07:00
parent dbd83d17ef
commit d644f1b6a6
6 changed files with 189 additions and 39 deletions

View File

@ -65,27 +65,6 @@ pub fn line_line_crossing_point(a_p0: &Point2D<f32>,
Some(p + r * t)
}
// TODO(pcwalton): Implement this.
pub fn line_quadratic_bezier_crossing_point(a_p0: &Point2D<f32>,
a_p1: &Point2D<f32>,
b_p0: &Point2D<f32>,
b_p1: &Point2D<f32>,
b_p2: &Point2D<f32>)
-> Option<Point2D<f32>> {
None
}
// TODO(pcwalton): Implement this.
pub fn quadratic_bezier_quadratic_bezier_crossing_point(_a_p0: &Point2D<f32>,
_a_p1: &Point2D<f32>,
_a_p2: &Point2D<f32>,
_b_p0: &Point2D<f32>,
_b_p1: &Point2D<f32>,
_b_p2: &Point2D<f32>)
-> Option<Point2D<f32>> {
None
}
pub fn solve_line_t_for_x(x: f32, a: &Point2D<f32>, b: &Point2D<f32>) -> f32 {
if b.x == a.x {
0.0

View File

@ -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)
})
}
}
}

View File

@ -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;

View File

@ -0,0 +1,116 @@
// pathfinder/path-utils/src/intersection.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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, B>(a: &A, b: &B) -> Option<Intersection> 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<f32>;
}
impl Intersect for Line {
#[inline]
fn sample(&self, t: f32) -> Point2D<f32> {
Line::sample(self, t)
}
}
impl Intersect for Curve {
#[inline]
fn sample(&self, t: f32) -> Point2D<f32> {
Curve::sample(self, t)
}
}

View File

@ -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<I> Iterator for Transform2DPathStream<I> where I: Iterator<Item = PathSegme
}
}
}
#[inline]
pub fn lerp(a: f32, b: f32, t: f32) -> f32 {
a + (b - a) * t
}

31
path-utils/src/line.rs Normal file
View File

@ -0,0 +1,31 @@
// pathfinder/path-utils/src/line.rs
//
// Copyright © 2017 The Pathfinder Project Developers.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<f32>; 2],
}
impl Line {
#[inline]
pub fn new(endpoint_0: &Point2D<f32>, endpoint_1: &Point2D<f32>) -> Line {
Line {
endpoints: [*endpoint_0, *endpoint_1],
}
}
#[inline]
pub fn sample(&self, t: f32) -> Point2D<f32> {
self.endpoints[0].lerp(self.endpoints[1], t)
}
}