diff --git a/demo/client/css/pathfinder.css b/demo/client/css/pathfinder.css index 940fc717..def4c88d 100644 --- a/demo/client/css/pathfinder.css +++ b/demo/client/css/pathfinder.css @@ -82,8 +82,8 @@ button > svg path { background: rgba(0, 0, 0, 0.75); width: 17em; } -#pf-svg { - visibility: hidden; +#pf-svg, #pf-svg * { + visibility: hidden !important; } .pf-spinner { position: absolute; diff --git a/partitioner/src/partitioner.rs b/partitioner/src/partitioner.rs index e4afebb5..0422dd83 100644 --- a/partitioner/src/partitioner.rs +++ b/partitioner/src/partitioner.rs @@ -9,6 +9,7 @@ // except according to those terms. use bit_vec::BitVec; +use euclid::approxeq::ApproxEq; use euclid::Point2D; use geometry::{self, SubdividedQuadraticBezier}; use log::LogLevel; @@ -24,6 +25,8 @@ use std::u32; use {BQuad, BVertexLoopBlinnData, BVertexKind, CurveIndices, Endpoint, FillRule}; use {LineIndices, Subpath}; +const MAX_B_QUAD_SUBDIVISIONS: u8 = 8; + pub struct Partitioner<'a> { endpoints: &'a [Endpoint], control_points: &'a [Point2D], @@ -188,7 +191,7 @@ impl<'a> Partitioner<'a> { let next_active_edge_index = self.find_point_between_active_edges(endpoint_index); let endpoint = &self.endpoints[endpoint_index as usize]; - self.emit_b_quad(next_active_edge_index, endpoint.position.x); + self.emit_b_quads_around_active_edge(next_active_edge_index, endpoint.position.x); self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index); @@ -206,7 +209,7 @@ impl<'a> Partitioner<'a> { debug!("... REGULAR point: active edge {}", active_edge_index); let endpoint = &self.endpoints[endpoint_index as usize]; - let bottom = self.emit_b_quad(active_edge_index, endpoint.position.x) == + let bottom = self.emit_b_quads_around_active_edge(active_edge_index, endpoint.position.x) == BQuadEmissionResult::BQuadEmittedAbove; let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); @@ -275,8 +278,8 @@ impl<'a> Partitioner<'a> { let endpoint = &self.endpoints[endpoint_index as usize]; - self.emit_b_quad(active_edge_indices[0], endpoint.position.x); - self.emit_b_quad(active_edge_indices[1], endpoint.position.x); + self.emit_b_quads_around_active_edge(active_edge_indices[0], endpoint.position.x); + self.emit_b_quads_around_active_edge(active_edge_indices[1], endpoint.position.x); self.heap.pop(); @@ -302,7 +305,11 @@ impl<'a> Partitioner<'a> { debug!("found SELF-INTERSECTION point for active edges {} & {}", upper_active_edge_index, lower_active_edge_index); - self.emit_b_quad(lower_active_edge_index, crossing_point.x); + self.emit_b_quads_around_active_edge(lower_active_edge_index, crossing_point.x); + } else { + debug!("warning: swapped active edges {} & {} without finding intersection", + upper_active_edge_index, + lower_active_edge_index); } self.active_edges.swap(upper_active_edge_index as usize, @@ -496,7 +503,8 @@ impl<'a> Partitioner<'a> { } } - fn emit_b_quad(&mut self, active_edge_index: u32, right_x: f32) -> BQuadEmissionResult { + fn emit_b_quads_around_active_edge(&mut self, active_edge_index: u32, right_x: f32) + -> BQuadEmissionResult { if (active_edge_index as usize) >= self.active_edges.len() { return BQuadEmissionResult::NoBQuadEmitted } @@ -521,48 +529,144 @@ impl<'a> Partitioner<'a> { SubdivisionType::Upper); for active_edge_index in (upper_active_edge_index + 1)..lower_active_edge_index { self.subdivide_active_edge_at(active_edge_index, right_x, SubdivisionType::Inside); + self.active_edges[active_edge_index as usize].toggle_parity(); } let lower_curve = self.subdivide_active_edge_at(lower_active_edge_index, right_x, SubdivisionType::Lower); - let upper_shape = upper_curve.shape(&self.b_vertex_loop_blinn_data); - let lower_shape = lower_curve.shape(&self.b_vertex_loop_blinn_data); + self.emit_b_quads(upper_active_edge_index, + lower_active_edge_index, + &upper_curve, + &lower_curve, + 0); + + emission_result + } + + /// Toggles parity at the end. + fn emit_b_quads(&mut self, + upper_active_edge_index: u32, + lower_active_edge_index: u32, + upper_subdivision: &SubdividedActiveEdge, + lower_subdivision: &SubdividedActiveEdge, + iteration: u8) { + let upper_shape = upper_subdivision.shape(&self.b_vertex_loop_blinn_data); + let lower_shape = lower_subdivision.shape(&self.b_vertex_loop_blinn_data); + + // Make sure the convex hulls of the two curves do not intersect. If they do, subdivide and + // recurse. + if iteration < MAX_B_QUAD_SUBDIVISIONS { + // TODO(pcwalton): Handle concave-line convex hull intersections. + if let (Some(upper_curve), Some(lower_curve)) = + (upper_subdivision.to_curve(&self.b_vertex_positions), + lower_subdivision.to_curve(&self.b_vertex_positions)) { + // TODO(pcwalton): Handle concave-concave convex hull intersections. + if upper_shape == Shape::Concave && + lower_curve.baseline().side(&upper_curve.control_point) > + f32::approx_epsilon() { + let (upper_left_subsubdivision, upper_right_subsubdivision) = + self.subdivide_active_edge_again_at_t(&upper_subdivision, + 0.5, + false); + let midpoint_x = self.b_vertex_positions[upper_left_subsubdivision.middle_point + as usize].x; + let (lower_left_subsubdivision, lower_right_subsubdivision) = + self.subdivide_active_edge_again_at_x(&lower_subdivision, + midpoint_x, + true); + + self.emit_b_quads(upper_active_edge_index, + lower_active_edge_index, + &upper_left_subsubdivision, + &lower_left_subsubdivision, + iteration + 1); + self.emit_b_quads(upper_active_edge_index, + lower_active_edge_index, + &upper_right_subsubdivision, + &lower_right_subsubdivision, + iteration + 1); + return; + } + + if lower_shape == Shape::Concave && + upper_curve.baseline().side(&lower_curve.control_point) < + -f32::approx_epsilon() { + let (lower_left_subsubdivision, lower_right_subsubdivision) = + self.subdivide_active_edge_again_at_t(&lower_subdivision, + 0.5, + true); + let midpoint_x = self.b_vertex_positions[lower_left_subsubdivision.middle_point + as usize].x; + let (upper_left_subsubdivision, upper_right_subsubdivision) = + self.subdivide_active_edge_again_at_x(&upper_subdivision, + midpoint_x, + false); + + self.emit_b_quads(upper_active_edge_index, + lower_active_edge_index, + &upper_left_subsubdivision, + &lower_left_subsubdivision, + iteration + 1); + self.emit_b_quads(upper_active_edge_index, + lower_active_edge_index, + &upper_right_subsubdivision, + &lower_right_subsubdivision, + iteration + 1); + return; + } + } + } match upper_shape { Shape::Flat => { self.edge_indices .upper_line_indices - .push(LineIndices::new(upper_curve.left_curve_left, upper_curve.middle_point)) + .push(LineIndices::new(upper_subdivision.left_curve_left, + upper_subdivision.middle_point)) } Shape::Convex | Shape::Concave => { self.edge_indices .upper_curve_indices - .push(CurveIndices::new(upper_curve.left_curve_left, - upper_curve.left_curve_control_point, - upper_curve.middle_point)) + .push(CurveIndices::new(upper_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point)) } } match lower_shape { Shape::Flat => { self.edge_indices .lower_line_indices - .push(LineIndices::new(lower_curve.left_curve_left, lower_curve.middle_point)) + .push(LineIndices::new(lower_subdivision.left_curve_left, + lower_subdivision.middle_point)) } Shape::Convex | Shape::Concave => { self.edge_indices .lower_curve_indices - .push(CurveIndices::new(lower_curve.left_curve_left, - lower_curve.left_curve_control_point, - lower_curve.middle_point)) + .push(CurveIndices::new(lower_subdivision.left_curve_left, + lower_subdivision.left_curve_control_point, + lower_subdivision.middle_point)) } } debug!("... emitting B-quad: UL {} BL {} UR {} BR {}", - upper_curve.left_curve_left, - lower_curve.left_curve_left, - upper_curve.middle_point, - lower_curve.middle_point); + upper_subdivision.left_curve_left, + lower_subdivision.left_curve_left, + upper_subdivision.middle_point, + lower_subdivision.middle_point); + + { + let upper_active_edge = &mut self.active_edges[upper_active_edge_index as usize]; + self.b_vertex_loop_blinn_data[upper_subdivision.middle_point as usize] = + BVertexLoopBlinnData::new(upper_active_edge.endpoint_kind()); + upper_active_edge.toggle_parity(); + } + { + let lower_active_edge = &mut self.active_edges[lower_active_edge_index as usize]; + self.b_vertex_loop_blinn_data[lower_subdivision.middle_point as usize] = + BVertexLoopBlinnData::new(lower_active_edge.endpoint_kind()); + lower_active_edge.toggle_parity(); + } match (upper_shape, lower_shape) { (Shape::Flat, Shape::Flat) | @@ -570,25 +674,25 @@ impl<'a> Partitioner<'a> { (Shape::Convex, Shape::Flat) | (Shape::Convex, Shape::Convex) => { self.cover_indices.interior_indices.extend([ - upper_curve.left_curve_left, - upper_curve.middle_point, - lower_curve.left_curve_left, - lower_curve.middle_point, - lower_curve.left_curve_left, - upper_curve.middle_point, + upper_subdivision.left_curve_left, + upper_subdivision.middle_point, + lower_subdivision.left_curve_left, + lower_subdivision.middle_point, + lower_subdivision.left_curve_left, + upper_subdivision.middle_point, ].into_iter()); if upper_shape != Shape::Flat { self.cover_indices.curve_indices.extend([ - upper_curve.left_curve_control_point, - upper_curve.middle_point, - upper_curve.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + upper_subdivision.left_curve_left, ].into_iter()) } if lower_shape != Shape::Flat { self.cover_indices.curve_indices.extend([ - lower_curve.left_curve_control_point, - lower_curve.left_curve_left, - lower_curve.middle_point, + lower_subdivision.left_curve_control_point, + lower_subdivision.left_curve_left, + lower_subdivision.middle_point, ].into_iter()) } } @@ -596,26 +700,26 @@ impl<'a> Partitioner<'a> { (Shape::Concave, Shape::Flat) | (Shape::Concave, Shape::Convex) => { self.cover_indices.interior_indices.extend([ - upper_curve.left_curve_left, - upper_curve.left_curve_control_point, - lower_curve.left_curve_left, - upper_curve.middle_point, - lower_curve.middle_point, - upper_curve.left_curve_control_point, - lower_curve.middle_point, - lower_curve.left_curve_left, - upper_curve.left_curve_control_point, + upper_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + lower_subdivision.left_curve_left, + upper_subdivision.middle_point, + lower_subdivision.middle_point, + upper_subdivision.left_curve_control_point, + lower_subdivision.middle_point, + lower_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, ].into_iter()); self.cover_indices.curve_indices.extend([ - upper_curve.left_curve_control_point, - upper_curve.left_curve_left, - upper_curve.middle_point, + upper_subdivision.left_curve_control_point, + upper_subdivision.left_curve_left, + upper_subdivision.middle_point, ].into_iter()); if lower_shape != Shape::Flat { self.cover_indices.curve_indices.extend([ - lower_curve.left_curve_control_point, - lower_curve.left_curve_left, - lower_curve.middle_point, + lower_subdivision.left_curve_control_point, + lower_subdivision.left_curve_left, + lower_subdivision.middle_point, ].into_iter()) } } @@ -623,64 +727,121 @@ impl<'a> Partitioner<'a> { (Shape::Flat, Shape::Concave) | (Shape::Convex, Shape::Concave) => { self.cover_indices.interior_indices.extend([ - upper_curve.left_curve_left, - upper_curve.middle_point, - lower_curve.left_curve_control_point, - upper_curve.middle_point, - lower_curve.middle_point, - lower_curve.left_curve_control_point, - upper_curve.left_curve_left, - lower_curve.left_curve_control_point, - lower_curve.left_curve_left, + upper_subdivision.left_curve_left, + upper_subdivision.middle_point, + lower_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + lower_subdivision.middle_point, + lower_subdivision.left_curve_control_point, + upper_subdivision.left_curve_left, + lower_subdivision.left_curve_control_point, + lower_subdivision.left_curve_left, ].into_iter()); self.cover_indices.curve_indices.extend([ - lower_curve.left_curve_control_point, - lower_curve.middle_point, - lower_curve.left_curve_left, + lower_subdivision.left_curve_control_point, + lower_subdivision.middle_point, + lower_subdivision.left_curve_left, ].into_iter()); if upper_shape != Shape::Flat { self.cover_indices.curve_indices.extend([ - upper_curve.left_curve_control_point, - upper_curve.middle_point, - upper_curve.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + upper_subdivision.left_curve_left, ].into_iter()) } } (Shape::Concave, Shape::Concave) => { self.cover_indices.interior_indices.extend([ - upper_curve.left_curve_left, - upper_curve.left_curve_control_point, - lower_curve.left_curve_left, - lower_curve.left_curve_left, - upper_curve.left_curve_control_point, - lower_curve.left_curve_control_point, - upper_curve.middle_point, - lower_curve.left_curve_control_point, - upper_curve.left_curve_control_point, - upper_curve.middle_point, - lower_curve.middle_point, - lower_curve.left_curve_control_point, + upper_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + lower_subdivision.left_curve_left, + lower_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + lower_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + lower_subdivision.left_curve_control_point, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + lower_subdivision.middle_point, + lower_subdivision.left_curve_control_point, ].into_iter()); self.cover_indices.curve_indices.extend([ - upper_curve.left_curve_control_point, - upper_curve.left_curve_left, - upper_curve.middle_point, - lower_curve.left_curve_control_point, - lower_curve.middle_point, - lower_curve.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.left_curve_left, + upper_subdivision.middle_point, + lower_subdivision.left_curve_control_point, + lower_subdivision.middle_point, + lower_subdivision.left_curve_left, ].into_iter()); } } - self.b_quads.push(BQuad::new(upper_curve.left_curve_left, - upper_curve.left_curve_control_point, - upper_curve.middle_point, - lower_curve.left_curve_left, - lower_curve.left_curve_control_point, - lower_curve.middle_point)); + self.b_quads.push(BQuad::new(upper_subdivision.left_curve_left, + upper_subdivision.left_curve_control_point, + upper_subdivision.middle_point, + lower_subdivision.left_curve_left, + lower_subdivision.left_curve_control_point, + lower_subdivision.middle_point)) + } - emission_result + fn subdivide_active_edge_again_at_t(&mut self, + subdivision: &SubdividedActiveEdge, + t: f32, + bottom: bool) + -> (SubdividedActiveEdge, SubdividedActiveEdge) { + let curve = subdivision.to_curve(&self.b_vertex_positions) + .expect("subdivide_active_edge_again_at_t(): not a curve!"); + let (left_curve, right_curve) = curve.subdivide(t); + + let left_control_point_index = self.b_vertex_positions.len() as u32; + let midpoint_index = left_control_point_index + 1; + let right_control_point_index = midpoint_index + 1; + self.b_vertex_positions.extend([ + left_curve.control_point, + left_curve.endpoints[1], + right_curve.control_point, + ].into_iter()); + + self.b_vertex_path_ids.extend(iter::repeat(self.path_id).take(3)); + + // Initially, assume that the parity is false. We will modify the Loop-Blinn data later if + // that is incorrect. + self.b_vertex_loop_blinn_data.extend([ + BVertexLoopBlinnData::control_point(&left_curve.endpoints[0], + &left_curve.control_point, + &left_curve.endpoints[1], + bottom), + BVertexLoopBlinnData::new(BVertexKind::Endpoint0), + BVertexLoopBlinnData::control_point(&right_curve.endpoints[0], + &right_curve.control_point, + &right_curve.endpoints[1], + bottom), + ].into_iter()); + + (SubdividedActiveEdge { + left_curve_left: subdivision.left_curve_left, + left_curve_control_point: left_control_point_index, + middle_point: midpoint_index, + }, SubdividedActiveEdge { + left_curve_left: midpoint_index, + left_curve_control_point: right_control_point_index, + middle_point: subdivision.middle_point, + }) + } + + fn subdivide_active_edge_again_at_x(&mut self, + subdivision: &SubdividedActiveEdge, + x: f32, + bottom: bool) + -> (SubdividedActiveEdge, SubdividedActiveEdge) { + let curve = subdivision.to_curve(&self.b_vertex_positions) + .expect("subdivide_active_edge_again_at_x(): not a curve!"); + let t = geometry::solve_quadratic_bezier_t_for_x(x, + &curve.endpoints[0], + &curve.control_point, + &curve.endpoints[1]); + self.subdivide_active_edge_again_at_t(subdivision, t, bottom) } fn already_visited_point(&self, point: &Point) -> bool { @@ -849,6 +1010,7 @@ impl<'a> Partitioner<'a> { } } + /// Does *not* toggle parity. You must do this after calling this function. fn subdivide_active_edge_at(&mut self, active_edge_index: u32, x: f32, @@ -876,8 +1038,6 @@ impl<'a> Partitioner<'a> { self.b_vertex_loop_blinn_data .push(BVertexLoopBlinnData::new(active_edge.endpoint_kind())); - active_edge.toggle_parity(); - left_curve_control_point_vertex_index = u32::MAX; } _ => { @@ -912,8 +1072,6 @@ impl<'a> Partitioner<'a> { &right_endpoint_position, bottom), ].into_iter()); - - active_edge.toggle_parity(); } } @@ -921,7 +1079,6 @@ impl<'a> Partitioner<'a> { left_curve_left: left_curve_left, left_curve_control_point: left_curve_control_point_vertex_index, middle_point: active_edge.left_vertex_index, - right_curve_control_point: active_edge.control_point_vertex_index, } } @@ -1128,7 +1285,6 @@ struct SubdividedActiveEdge { left_curve_left: u32, left_curve_control_point: u32, middle_point: u32, - right_curve_control_point: u32, } impl SubdividedActiveEdge { @@ -1141,6 +1297,16 @@ impl SubdividedActiveEdge { Shape::Concave } } + + fn to_curve(&self, b_vertex_positions: &[Point2D]) -> Option { + if self.left_curve_control_point == u32::MAX { + None + } else { + Some(Curve::new(&b_vertex_positions[self.left_curve_left as usize], + &b_vertex_positions[self.left_curve_control_point as usize], + &b_vertex_positions[self.middle_point as usize])) + } + } } #[derive(Debug, Clone, Copy)] diff --git a/path-utils/src/curve.rs b/path-utils/src/curve.rs index 04302f03..a701b8ca 100644 --- a/path-utils/src/curve.rs +++ b/path-utils/src/curve.rs @@ -16,6 +16,7 @@ use std::f32; use PathSegment; use intersection::{Intersect, Side}; +use line::Line; pub struct Curve { pub endpoints: [Point2D; 2], @@ -61,6 +62,11 @@ impl Curve { (inflection_point_x, inflection_point_y) } + #[inline] + pub fn baseline(&self) -> Line { + Line::new(&self.endpoints[0], &self.endpoints[1]) + } + #[inline] fn inflection_point_x(endpoint_x_0: f32, endpoint_x_1: f32, control_point_x: f32) -> Option { diff --git a/path-utils/src/intersection.rs b/path-utils/src/intersection.rs index 435551d2..6e61463d 100644 --- a/path-utils/src/intersection.rs +++ b/path-utils/src/intersection.rs @@ -63,7 +63,7 @@ pub(crate) trait Intersect { impl Side for Line { #[inline] fn side(&self, point: &Point2D) -> f32 { - self.to_vector().cross(*point - self.endpoints[0]) + Line::side(self, point) } } diff --git a/path-utils/src/lib.rs b/path-utils/src/lib.rs index cdff2d9b..982aa42c 100644 --- a/path-utils/src/lib.rs +++ b/path-utils/src/lib.rs @@ -23,6 +23,7 @@ pub mod intersection; pub mod line; pub mod monotonic; pub mod stroke; +pub mod svg; #[derive(Clone, Copy, Debug)] pub enum PathSegment { @@ -130,10 +131,12 @@ impl<'a> Iterator for PathBufferStream<'a> { return Some(PathSegment::ClosePath) } - let endpoint = &self.path_buffer.endpoints[self.endpoint_index as usize]; + let endpoint_index = self.endpoint_index; self.endpoint_index += 1; - if self.endpoint_index == subpath.first_endpoint_index { + let endpoint = &self.path_buffer.endpoints[endpoint_index as usize]; + + if endpoint_index == subpath.first_endpoint_index { return Some(PathSegment::MoveTo(endpoint.position)) } diff --git a/path-utils/src/line.rs b/path-utils/src/line.rs index d87a9c71..a8213f83 100644 --- a/path-utils/src/line.rs +++ b/path-utils/src/line.rs @@ -31,6 +31,11 @@ impl Line { self.endpoints[0].lerp(self.endpoints[1], t) } + #[inline] + pub fn side(&self, point: &Point2D) -> f32 { + self.to_vector().cross(*point - self.endpoints[0]) + } + #[inline] pub(crate) fn to_vector(&self) -> Vector2D { self.endpoints[1] - self.endpoints[0] diff --git a/path-utils/src/svg.rs b/path-utils/src/svg.rs new file mode 100644 index 00000000..8ec8377c --- /dev/null +++ b/path-utils/src/svg.rs @@ -0,0 +1,32 @@ +// pathfinder/path-utils/src/svg.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. + +//! Utilities for converting paths to SVG representations. + +use std::io::{self, Write}; + +use PathSegment; + +pub fn to_svg_description(output: &mut W, stream: S) -> io::Result<()> + where W: Write, S: Iterator { + for segment in stream { + match segment { + PathSegment::MoveTo(point) => try!(write!(output, "M{},{} ", point.x, point.y)), + PathSegment::LineTo(point) => try!(write!(output, "L{},{} ", point.x, point.y)), + PathSegment::CurveTo(control_point, endpoint) => { + try!(write!(output, "Q{},{} {},{} ", + control_point.x, control_point.y, + endpoint.x, endpoint.y)) + } + PathSegment::ClosePath => try!(output.write_all(b"z")), + } + } + Ok(()) +}