Split B-quads whose curves' convex hulls intersect in most cases.

Avoids some rendering artefacts with Loop-Blinn, improving the tiger.
This commit is contained in:
Patrick Walton 2017-09-21 18:57:48 -07:00
parent a126e1248e
commit be1b100826
7 changed files with 310 additions and 98 deletions

View File

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

View File

@ -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<f32>],
@ -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<f32>]) -> Option<Curve> {
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)]

View File

@ -16,6 +16,7 @@ use std::f32;
use PathSegment;
use intersection::{Intersect, Side};
use line::Line;
pub struct Curve {
pub endpoints: [Point2D<f32>; 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<f32> {

View File

@ -63,7 +63,7 @@ pub(crate) trait Intersect {
impl Side for Line {
#[inline]
fn side(&self, point: &Point2D<f32>) -> f32 {
self.to_vector().cross(*point - self.endpoints[0])
Line::side(self, point)
}
}

View File

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

View File

@ -31,6 +31,11 @@ impl Line {
self.endpoints[0].lerp(self.endpoints[1], t)
}
#[inline]
pub fn side(&self, point: &Point2D<f32>) -> f32 {
self.to_vector().cross(*point - self.endpoints[0])
}
#[inline]
pub(crate) fn to_vector(&self) -> Vector2D<f32> {
self.endpoints[1] - self.endpoints[0]

32
path-utils/src/svg.rs Normal file
View File

@ -0,0 +1,32 @@
// pathfinder/path-utils/src/svg.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.
//! Utilities for converting paths to SVG representations.
use std::io::{self, Write};
use PathSegment;
pub fn to_svg_description<W, S>(output: &mut W, stream: S) -> io::Result<()>
where W: Write, S: Iterator<Item = PathSegment> {
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(())
}