Fix clipping
This commit is contained in:
parent
d98b244bda
commit
3ce60afb44
|
@ -11,6 +11,9 @@
|
|||
use crate::line_segment::LineSegmentF32;
|
||||
use crate::outline::{Contour, PointFlags};
|
||||
use crate::point::Point2DF32;
|
||||
use crate::segment::Segment;
|
||||
use crate::util::lerp;
|
||||
use arrayvec::ArrayVec;
|
||||
use euclid::{Point2D, Rect, Vector3D};
|
||||
use lyon_path::PathEvent;
|
||||
use std::mem;
|
||||
|
@ -92,7 +95,7 @@ impl<'a> RectClipper<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Edge {
|
||||
Left(f32),
|
||||
Top(f32),
|
||||
|
@ -103,13 +106,31 @@ enum Edge {
|
|||
impl Edge {
|
||||
fn point_is_inside(&self, point: &Point2DF32) -> bool {
|
||||
match *self {
|
||||
Edge::Left(x_edge) => point.x() >= x_edge,
|
||||
Edge::Top(y_edge) => point.y() >= y_edge,
|
||||
Edge::Right(x_edge) => point.x() <= x_edge,
|
||||
Edge::Bottom(y_edge) => point.y() <= y_edge,
|
||||
Edge::Left(x_edge) => point.x() > x_edge,
|
||||
Edge::Top(y_edge) => point.y() > y_edge,
|
||||
Edge::Right(x_edge) => point.x() < x_edge,
|
||||
Edge::Bottom(y_edge) => point.y() < y_edge,
|
||||
}
|
||||
}
|
||||
|
||||
fn trivially_test_segment(&self, segment: &Segment) -> EdgeRelativeLocation {
|
||||
let from_inside = self.point_is_inside(&segment.baseline.from());
|
||||
if from_inside != self.point_is_inside(&segment.baseline.to()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
if !segment.is_line() {
|
||||
if from_inside != self.point_is_inside(&segment.ctrl.from()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
if !segment.is_quadratic() {
|
||||
if from_inside != self.point_is_inside(&segment.ctrl.to()) {
|
||||
return EdgeRelativeLocation::Intersecting;
|
||||
}
|
||||
}
|
||||
}
|
||||
if from_inside { EdgeRelativeLocation::Inside } else { EdgeRelativeLocation::Outside }
|
||||
}
|
||||
|
||||
fn line_intersection(&self, line_segment: &LineSegmentF32) -> Point2DF32 {
|
||||
match *self {
|
||||
Edge::Left(x_edge) | Edge::Right(x_edge) => {
|
||||
|
@ -120,6 +141,105 @@ impl Edge {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn split_segment(&self, segment: &Segment) -> Option<(Segment, Segment)> {
|
||||
if segment.is_line() {
|
||||
return self.split_line_segment(segment);
|
||||
}
|
||||
|
||||
let mut segment = *segment;
|
||||
if segment.is_quadratic() {
|
||||
segment = segment.to_cubic();
|
||||
}
|
||||
|
||||
self.intersect_cubic_segment(&segment, 0.0, 1.0).map(|t| {
|
||||
self.fixup_clipped_segments(&segment.as_cubic_segment().split(t))
|
||||
})
|
||||
}
|
||||
|
||||
fn split_line_segment(&self, segment: &Segment) -> Option<(Segment, Segment)> {
|
||||
let intersection;
|
||||
match *self {
|
||||
Edge::Left(x_edge) | Edge::Right(x_edge) => {
|
||||
if (segment.baseline.from_x() <= x_edge && segment.baseline.to_x() <= x_edge) ||
|
||||
(segment.baseline.from_x() >= x_edge &&
|
||||
segment.baseline.to_x() >= x_edge) {
|
||||
return None
|
||||
}
|
||||
intersection = Point2DF32::new(x_edge, segment.baseline.solve_y_for_x(x_edge));
|
||||
}
|
||||
Edge::Top(y_edge) | Edge::Bottom(y_edge) => {
|
||||
if (segment.baseline.from_y() <= y_edge && segment.baseline.to_y() <= y_edge) ||
|
||||
(segment.baseline.from_y() >= y_edge &&
|
||||
segment.baseline.to_y() >= y_edge) {
|
||||
return None
|
||||
}
|
||||
intersection = Point2DF32::new(segment.baseline.solve_x_for_y(y_edge), y_edge);
|
||||
}
|
||||
};
|
||||
Some((Segment::line(&LineSegmentF32::new(&segment.baseline.from(), &intersection)),
|
||||
Segment::line(&LineSegmentF32::new(&intersection, &segment.baseline.to()))))
|
||||
}
|
||||
|
||||
fn intersect_cubic_segment(&self, segment: &Segment, t_min: f32, t_max: f32) -> Option<f32> {
|
||||
/*
|
||||
println!("... intersect_cubic_segment({:?}, {:?}, t=({}, {}))",
|
||||
self, segment, t_min, t_max);
|
||||
*/
|
||||
let t_mid = lerp(t_min, t_max, 0.5);
|
||||
if t_max - t_min < 0.001 {
|
||||
return Some(t_mid);
|
||||
}
|
||||
|
||||
let (prev_segment, next_segment) = segment.as_cubic_segment().split(t_mid);
|
||||
|
||||
let prev_cubic_segment = prev_segment.as_cubic_segment();
|
||||
let next_cubic_segment = next_segment.as_cubic_segment();
|
||||
|
||||
let (prev_min, prev_max, next_min, next_max, edge);
|
||||
match *self {
|
||||
Edge::Left(x) | Edge::Right(x) => {
|
||||
prev_min = prev_cubic_segment.min_x();
|
||||
prev_max = prev_cubic_segment.max_x();
|
||||
next_min = next_cubic_segment.min_x();
|
||||
next_max = next_cubic_segment.max_x();
|
||||
edge = x;
|
||||
}
|
||||
Edge::Top(y) | Edge::Bottom(y) => {
|
||||
prev_min = prev_cubic_segment.min_y();
|
||||
prev_max = prev_cubic_segment.max_y();
|
||||
next_min = next_cubic_segment.min_y();
|
||||
next_max = next_cubic_segment.max_y();
|
||||
edge = y;
|
||||
}
|
||||
}
|
||||
|
||||
if prev_min < edge && edge < prev_max {
|
||||
self.intersect_cubic_segment(segment, t_min, t_mid)
|
||||
} else if next_min < edge && edge < next_max {
|
||||
self.intersect_cubic_segment(segment, t_mid, t_max)
|
||||
} else if (prev_max == edge && next_min == edge) ||
|
||||
(prev_min == edge && next_max == edge) {
|
||||
Some(t_mid)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_clipped_segments(&self, segment: &(Segment, Segment)) -> (Segment, Segment) {
|
||||
let (mut before, mut after) = *segment;
|
||||
match *self {
|
||||
Edge::Left(x) | Edge::Right(x) => {
|
||||
before.baseline.set_to_x(x);
|
||||
after.baseline.set_from_x(x);
|
||||
}
|
||||
Edge::Top(y) | Edge::Bottom(y) => {
|
||||
before.baseline.set_to_y(y);
|
||||
after.baseline.set_from_y(y);
|
||||
}
|
||||
}
|
||||
(before, after)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ContourRectClipper {
|
||||
|
@ -146,27 +266,78 @@ impl ContourRectClipper {
|
|||
}
|
||||
|
||||
fn clip_against(&mut self, edge: Edge) {
|
||||
let mut first_point = false;
|
||||
let input = mem::replace(&mut self.contour, Contour::new());
|
||||
for event in input.iter() {
|
||||
let (from, to) = (event.baseline.from(), event.baseline.to());
|
||||
if edge.point_is_inside(&to) {
|
||||
if !edge.point_is_inside(&from) {
|
||||
//println!("clip: {:?} {:?}", from, to);
|
||||
let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to));
|
||||
add_line(&intersection, &mut self.contour, &mut first_point);
|
||||
for mut segment in input.iter() {
|
||||
// Easy cases.
|
||||
match edge.trivially_test_segment(&segment) {
|
||||
EdgeRelativeLocation::Outside => continue,
|
||||
EdgeRelativeLocation::Inside => {
|
||||
//println!("trivial test inside, pushing segment");
|
||||
push_segment(&mut self.contour, &segment, edge);
|
||||
continue;
|
||||
}
|
||||
add_line(&to, &mut self.contour, &mut first_point);
|
||||
} else if edge.point_is_inside(&from) {
|
||||
//println!("clip: {:?} {:?}", from, to);
|
||||
let intersection = edge.line_intersection(&LineSegmentF32::new(&from, &to));
|
||||
add_line(&intersection, &mut self.contour, &mut first_point);
|
||||
EdgeRelativeLocation::Intersecting => {}
|
||||
}
|
||||
|
||||
// We have a potential intersection.
|
||||
//println!("potential intersection: {:?} edge: {:?}", segment, edge);
|
||||
let mut starts_inside = edge.point_is_inside(&segment.baseline.from());
|
||||
while let Some((before_split, after_split)) = edge.split_segment(&segment) {
|
||||
// Push the split segment if appropriate.
|
||||
/*
|
||||
println!("... ... before_split={:?} after_split={:?} starts_inside={:?}",
|
||||
before_split,
|
||||
after_split,
|
||||
starts_inside);
|
||||
*/
|
||||
if starts_inside {
|
||||
//println!("... split segment case, pushing segment");
|
||||
push_segment(&mut self.contour, &before_split, edge);
|
||||
}
|
||||
|
||||
// We've now transitioned from inside to outside or vice versa.
|
||||
starts_inside = !starts_inside;
|
||||
segment = after_split;
|
||||
}
|
||||
|
||||
// No more intersections. Push the last segment if applicable.
|
||||
if starts_inside {
|
||||
//println!("... last segment case, pushing segment");
|
||||
push_segment(&mut self.contour, &segment, edge);
|
||||
}
|
||||
}
|
||||
|
||||
fn add_line(to: &Point2DF32, output: &mut Contour, first_point: &mut bool) {
|
||||
output.push_point(*to, PointFlags::empty());
|
||||
*first_point = false;
|
||||
fn push_segment(contour: &mut Contour, segment: &Segment, edge: Edge) {
|
||||
//println!("... push_segment({:?}, edge={:?}", segment, edge);
|
||||
if let Some(last_position) = contour.last_position() {
|
||||
if last_position != segment.baseline.from() {
|
||||
// Add a line to join up segments.
|
||||
//check_point(&segment.baseline.from(), edge);
|
||||
contour.push_point(segment.baseline.from(), PointFlags::empty());
|
||||
}
|
||||
}
|
||||
|
||||
//check_point(&segment.baseline.to(), edge);
|
||||
contour.push_segment(*segment);
|
||||
}
|
||||
|
||||
/*
|
||||
fn check_point(point: &Point2DF32, edge: Edge) {
|
||||
match edge {
|
||||
Edge::Left(x) if point.x() + 0.1 >= x => return,
|
||||
Edge::Top(y) if point.y() + 0.1 >= y => return,
|
||||
Edge::Right(x) if point.x() - 0.1 <= x => return,
|
||||
Edge::Bottom(y) if point.y() - 0.1 <= y => return,
|
||||
_ => {}
|
||||
}
|
||||
panic!("point {:?} outside edge {:?}", point, edge);
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
enum EdgeRelativeLocation {
|
||||
Intersecting,
|
||||
Inside,
|
||||
Outside,
|
||||
}
|
||||
|
|
|
@ -66,6 +66,26 @@ impl LineSegmentF32 {
|
|||
self.0[3]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from_x(&mut self, x: f32) {
|
||||
self.0[0] = x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_from_y(&mut self, y: f32) {
|
||||
self.0[1] = y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_to_x(&mut self, x: f32) {
|
||||
self.0[2] = x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_to_y(&mut self, y: f32) {
|
||||
self.0[3] = y
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scale(&self, factor: f32) -> LineSegmentF32 {
|
||||
LineSegmentF32(self.0 * F32x4::splat(factor))
|
||||
|
@ -176,6 +196,13 @@ impl LineSegmentF32 {
|
|||
self.reversed()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(pcwalton): Optimize with SIMD.
|
||||
#[inline]
|
||||
pub fn square_length(&self) -> f32 {
|
||||
let (dx, dy) = (self.to_x() - self.from_x(), self.to_y() - self.from_y());
|
||||
dx * dx + dy * dy
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<Point2DF32> for LineSegmentF32 {
|
||||
|
|
|
@ -29,9 +29,9 @@ pub struct Outline {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct Contour {
|
||||
points: Vec<Point2DF32>,
|
||||
flags: Vec<PointFlags>,
|
||||
bounds: Rect<f32>,
|
||||
pub(crate) points: Vec<Point2DF32>,
|
||||
pub(crate) flags: Vec<PointFlags>,
|
||||
pub(crate) bounds: Rect<f32>,
|
||||
}
|
||||
|
||||
bitflags! {
|
||||
|
@ -164,6 +164,11 @@ impl Contour {
|
|||
self.points[index as usize]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn last_position(&self) -> Option<Point2DF32> {
|
||||
self.points.last().cloned()
|
||||
}
|
||||
|
||||
// TODO(pcwalton): SIMD.
|
||||
#[inline]
|
||||
pub(crate) fn push_point(&mut self, point: Point2DF32, flags: PointFlags) {
|
||||
|
|
|
@ -239,6 +239,13 @@ impl<'s> CubicSegment<'s> {
|
|||
self.0.ctrl.from_y(),
|
||||
self.0.ctrl.to_y(),
|
||||
self.0.baseline.to_y());
|
||||
|
||||
// TODO(pcwalton): Optimize this.
|
||||
if p0p1p2p3[0] <= p0p1p2p3[1] && p0p1p2p3[0] <= p0p1p2p3[2] &&
|
||||
p0p1p2p3[1] <= p0p1p2p3[3] && p0p1p2p3[2] <= p0p1p2p3[3] {
|
||||
return (None, None);
|
||||
}
|
||||
|
||||
let pxp0p1p2 = p0p1p2p3.wxyz();
|
||||
let pxv0v1v2 = p0p1p2p3 - pxp0p1p2;
|
||||
let (v0, v1, v2) = (pxv0v1v2[1], pxv0v1v2[2], pxv0v1v2[3]);
|
||||
|
@ -262,6 +269,15 @@ impl<'s> CubicSegment<'s> {
|
|||
|
||||
const EPSILON: f32 = 0.001;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn min_x(&self) -> f32 { f32::min(self.0.baseline.min_x(), self.0.ctrl.min_x()) }
|
||||
#[inline]
|
||||
pub fn min_y(&self) -> f32 { f32::min(self.0.baseline.min_y(), self.0.ctrl.min_y()) }
|
||||
#[inline]
|
||||
pub fn max_x(&self) -> f32 { f32::max(self.0.baseline.max_x(), self.0.ctrl.max_x()) }
|
||||
#[inline]
|
||||
pub fn max_y(&self) -> f32 { f32::max(self.0.baseline.max_y(), self.0.ctrl.max_y()) }
|
||||
}
|
||||
|
||||
// Lyon interoperability
|
||||
|
|
|
@ -197,13 +197,11 @@ impl BuiltObject {
|
|||
|
||||
// TODO(pcwalton): Optimize this better with SIMD!
|
||||
pub fn generate_fill_primitives_for_line(&mut self, mut segment: LineSegmentF32, tile_y: i16) {
|
||||
/*
|
||||
println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})",
|
||||
/*println!("... generate_fill_primitives_for_line(): segment={:?} tile_y={} ({}-{})",
|
||||
segment,
|
||||
tile_y,
|
||||
tile_y as f32 * TILE_HEIGHT as f32,
|
||||
(tile_y + 1) as f32 * TILE_HEIGHT as f32);
|
||||
*/
|
||||
(tile_y + 1) as f32 * TILE_HEIGHT as f32);*/
|
||||
|
||||
let winding = segment.from_x() > segment.to_x();
|
||||
let (segment_left, segment_right) = if !winding {
|
||||
|
|
|
@ -449,6 +449,8 @@ impl ActiveEdge {
|
|||
tile_y: i16,
|
||||
) -> Option<LineSegmentF32> {
|
||||
let tile_bottom = ((i32::from(tile_y) + 1) * TILE_HEIGHT as i32) as f32;
|
||||
/*println!("process_line_segment({:?}, tile_y={}) tile_bottom={}",
|
||||
line_segment, tile_y, tile_bottom);*/
|
||||
if line_segment.max_y() <= tile_bottom {
|
||||
built_object.generate_fill_primitives_for_line(*line_segment, tile_y);
|
||||
return None;
|
||||
|
|
Loading…
Reference in New Issue