From 2fde50113c2b32d606a461bd44628ce11f4bd2cd Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Wed, 28 Jun 2017 14:14:40 -0700 Subject: [PATCH] Initial commit of Pathfinder 2, still incomplete --- partitionfinder/.gitignore | 2 + partitionfinder/Cargo.toml | 8 + partitionfinder/src/capi.rs | 48 ++ partitionfinder/src/geometry.rs | 89 ++++ partitionfinder/src/lib.rs | 63 +++ partitionfinder/src/partitioner.rs | 674 ++++++++++++++++++++++++++ partitionfinder/src/partitionfinder.h | 84 ++++ 7 files changed, 968 insertions(+) create mode 100644 partitionfinder/.gitignore create mode 100644 partitionfinder/Cargo.toml create mode 100644 partitionfinder/src/capi.rs create mode 100644 partitionfinder/src/geometry.rs create mode 100644 partitionfinder/src/lib.rs create mode 100644 partitionfinder/src/partitioner.rs create mode 100644 partitionfinder/src/partitionfinder.h diff --git a/partitionfinder/.gitignore b/partitionfinder/.gitignore new file mode 100644 index 00000000..f2f9e58e --- /dev/null +++ b/partitionfinder/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock \ No newline at end of file diff --git a/partitionfinder/Cargo.toml b/partitionfinder/Cargo.toml new file mode 100644 index 00000000..6b5b08bd --- /dev/null +++ b/partitionfinder/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "partitionfinder" +version = "0.1.0" +authors = ["Patrick Walton "] + +[dependencies] +bit-vec = "0.4" +euclid = "0.15" diff --git a/partitionfinder/src/capi.rs b/partitionfinder/src/capi.rs new file mode 100644 index 00000000..d57abd9d --- /dev/null +++ b/partitionfinder/src/capi.rs @@ -0,0 +1,48 @@ +// partitionfinder/capi.rs + +use partitioner::Partitioner; +use std::mem; +use std::slice; +use {Bezieroid, ControlPoints, Endpoint, Path, Subpath}; + +#[no_mangle] +pub unsafe extern fn pf_partitioner_new(endpoints: *const Endpoint, + endpoint_count: u32, + control_points: *const ControlPoints, + control_points_count: u32, + subpaths: *const Subpath, + subpath_count: u32, + paths: *const Path, + path_count: u32) + -> *mut Partitioner<'static> { + let mut partitioner = + Box::new(Partitioner::new(slice::from_raw_parts(endpoints, endpoint_count as usize), + slice::from_raw_parts(control_points, + control_points_count as usize), + slice::from_raw_parts(subpaths, subpath_count as usize), + slice::from_raw_parts(paths, path_count as usize))); + let partitioner_ptr: *mut Partitioner<'static> = &mut *partitioner; + mem::forget(partitioner); + partitioner_ptr +} + +#[no_mangle] +pub unsafe extern fn pf_partitioner_destroy<'a>(partitioner: *mut Partitioner<'a>) { + drop(mem::transmute::<*mut Partitioner<'a>, Box>(partitioner)) +} + +#[no_mangle] +pub unsafe extern fn pf_partitioner_partition<'a>(partitioner: *mut Partitioner<'a>) { + (*partitioner).partition() +} + +#[no_mangle] +pub unsafe extern fn pf_partitioner_bezieroids<'a>(partitioner: *mut Partitioner<'a>, + out_bezieroid_count: *mut u32) + -> *const Bezieroid { + let bezieroids = (*partitioner).bezieroids(); + if !out_bezieroid_count.is_null() { + *out_bezieroid_count = bezieroids.len() as u32 + } + bezieroids.as_ptr() +} diff --git a/partitionfinder/src/geometry.rs b/partitionfinder/src/geometry.rs new file mode 100644 index 00000000..931117e5 --- /dev/null +++ b/partitionfinder/src/geometry.rs @@ -0,0 +1,89 @@ +// partitionfinder/geometry.rs + +use euclid::Point2D; +use euclid::approxeq::ApproxEq; + +// https://stackoverflow.com/a/565282 +pub fn line_line_crossing_point(a_p0: &Point2D, + a_p1: &Point2D, + b_p0: &Point2D, + b_p1: &Point2D) + -> Option> { + let (p, r) = (*a_p0, *a_p1 - *a_p0); + let (q, s) = (*b_p0, *b_p1 - *b_p0); + + let rs = r.cross(s); + if rs.approx_eq(&0.0) { + return None + } + + let t = (q - p).cross(s) / rs; + if t < f32::approx_epsilon() || t > 1.0f32 - f32::approx_epsilon() { + return None + } + + let u = (q - p).cross(r) / rs; + if u < f32::approx_epsilon() || u > 1.0f32 - f32::approx_epsilon() { + return None + } + + return Some(p + r * t) +} + +// TODO(pcwalton): Implement this. +pub fn line_cubic_bezier_crossing_point(_a_p0: &Point2D, + _a_p1: &Point2D, + _b_p0: &Point2D, + _b_p1: &Point2D, + _b_p2: &Point2D, + _b_p3: &Point2D) + -> Option> { + None +} + +// TODO(pcwalton): Implement this. +pub fn cubic_bezier_cubic_bezier_crossing_point(_a_p0: &Point2D, + _a_p1: &Point2D, + _a_p2: &Point2D, + _a_p3: &Point2D, + _b_p0: &Point2D, + _b_p1: &Point2D, + _b_p2: &Point2D, + _b_p3: &Point2D) + -> Option> { + None +} + +fn sample_cubic_bezier(t: f32, + p0: &Point2D, + p1: &Point2D, + p2: &Point2D, + p3: &Point2D) + -> Point2D { + // TODO(pcwalton) + Point2D::new(0.0, 0.0) +} + +pub fn solve_line_y_for_x(x: f32, a: &Point2D, b: &Point2D) -> f32 { + // TODO(pcwalton) + 0.0 +} + +pub fn solve_cubic_bezier_t_for_x(x: f32, + p0: &Point2D, + p1: &Point2D, + p2: &Point2D, + p3: &Point2D) + -> f32 { + // TODO(pcwalton) + 0.0 +} + +pub fn solve_cubic_bezier_y_for_x(x: f32, + p0: &Point2D, + p1: &Point2D, + p2: &Point2D, + p3: &Point2D) + -> f32 { + sample_cubic_bezier(solve_cubic_bezier_t_for_x(x, p0, p1, p2, p3), p0, p1, p2, p3).y +} diff --git a/partitionfinder/src/lib.rs b/partitionfinder/src/lib.rs new file mode 100644 index 00000000..870fac66 --- /dev/null +++ b/partitionfinder/src/lib.rs @@ -0,0 +1,63 @@ +// partitionfinder/lib.rs + +extern crate bit_vec; +extern crate euclid; + +use euclid::Point2D; +use std::u32; + +pub mod capi; +pub mod geometry; +pub mod partitioner; + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Bezieroid { + pub upper_prev_endpoint: u32, + pub upper_next_endpoint: u32, + pub lower_prev_endpoint: u32, + pub lower_next_endpoint: u32, + pub upper_left_time: f32, + pub upper_right_time: f32, + pub lower_left_time: f32, + pub lower_right_time: f32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Endpoint { + pub position: Point2D, + /// `u32::MAX` if not present. + pub control_points_index: u32, + pub subpath_index: u32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ControlPoints { + pub point1: Point2D, + pub point2: Point2D, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Subpath { + pub first_endpoint_index: u32, + pub path_index: u32, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct Path { + pub first_subpath_index: u32, + pub fill_color: ColorU8, +} + +#[repr(C)] +#[derive(Debug, Clone, Copy)] +pub struct ColorU8 { + pub r: u8, + pub g: u8, + pub b: u8, + pub a: u8, +} diff --git a/partitionfinder/src/partitioner.rs b/partitionfinder/src/partitioner.rs new file mode 100644 index 00000000..e43c586a --- /dev/null +++ b/partitionfinder/src/partitioner.rs @@ -0,0 +1,674 @@ +// partitionfinder/partitioner.rs + +use bit_vec::BitVec; +use euclid::Point2D; +use geometry; +use std::collections::BinaryHeap; +use std::cmp::{self, Ordering}; +use std::u32; +use {Bezieroid, ControlPoints, Endpoint, Path, Subpath}; + +pub struct Partitioner<'a> { + endpoints: &'a [Endpoint], + control_points: &'a [ControlPoints], + subpaths: &'a [Subpath], + paths: &'a [Path], + + bezieroids: Vec, + + heap: BinaryHeap, + visited_points: BitVec, + active_edges: Vec, +} + +impl<'a> Partitioner<'a> { + #[inline] + pub fn new<'b>(endpoints: &'b [Endpoint], + control_points: &'b [ControlPoints], + subpaths: &'b [Subpath], + paths: &'b [Path]) + -> Partitioner<'b> { + Partitioner { + endpoints: endpoints, + control_points: control_points, + subpaths: subpaths, + paths: paths, + + bezieroids: vec![], + + heap: BinaryHeap::new(), + visited_points: BitVec::new(), + active_edges: vec![], + } + } + + pub fn partition(&mut self) { + for path_index in (0..self.paths.len() as u32).rev() { + self.init_heap_for_path(path_index); + while self.process_next_point() {} + } + } + + #[inline] + pub fn bezieroids(&self) -> &[Bezieroid] { + &self.bezieroids + } + + fn process_next_point(&mut self) -> bool { + let point = match self.heap.peek() { + Some(point) => *point, + None => return false, + }; + + if self.already_visited_point(&point) { + self.heap.pop(); + return true + } + + self.mark_point_as_visited(&point); + + let matching_active_edges = self.find_right_point_in_active_edge_list(point.endpoint_index); + match point.point_type { + PointType::Endpoint => { + match matching_active_edges.count { + 0 => self.process_min_endpoint(point.endpoint_index), + 1 => { + self.process_regular_endpoint(point.endpoint_index, + matching_active_edges.indices[0]) + } + 2 => { + self.process_max_endpoint(point.endpoint_index, + matching_active_edges.indices) + } + _ => debug_assert!(false), + } + } + PointType::CrossingBelow => { + debug_assert!(matching_active_edges.count > 0); + self.process_crossing_point(point.position.x, matching_active_edges.indices[0]) + } + } + + true + } + + fn process_min_endpoint(&mut self, endpoint_index: u32) { + let next_active_edge_index = self.find_point_between_active_edges(endpoint_index); + + let endpoint = &self.endpoints[endpoint_index as usize]; + if self.should_fill_above_active_edge(next_active_edge_index) { + self.emit_bezieroid_above(next_active_edge_index, endpoint.position.x) + } + + self.add_new_edges_for_min_point(endpoint_index, next_active_edge_index); + + let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); + let next_endpoint_index = self.next_endpoint_of(endpoint_index); + let new_point = self.create_point_from_endpoint(next_endpoint_index); + *self.heap.peek_mut().unwrap() = new_point; + if next_endpoint_index != prev_endpoint_index { + let new_point = self.create_point_from_endpoint(prev_endpoint_index); + self.heap.push(new_point) + } + + self.add_crossings_to_heap_if_necessary(next_active_edge_index + 0, + next_active_edge_index + 2) + } + + fn process_regular_endpoint(&mut self, endpoint_index: u32, active_edge_index: u32) { + let endpoint = &self.endpoints[endpoint_index as usize]; + if self.should_fill_below_active_edge(active_edge_index) { + self.emit_bezieroid_below(active_edge_index, endpoint.position.x) + } + if self.should_fill_above_active_edge(active_edge_index) { + self.emit_bezieroid_above(active_edge_index, endpoint.position.x) + } + + let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); + let next_endpoint_index = self.next_endpoint_of(endpoint_index); + + { + let active_edge = &mut self.active_edges[active_edge_index as usize]; + active_edge.left_endpoint_index = active_edge.right_endpoint_index; + if active_edge.left_to_right { + active_edge.right_endpoint_index = next_endpoint_index; + active_edge.time = 0.0 + } else { + active_edge.right_endpoint_index = prev_endpoint_index; + active_edge.time = 1.0 + } + } + + let right_endpoint_index = self.active_edges[active_edge_index as usize] + .right_endpoint_index; + let new_point = self.create_point_from_endpoint(right_endpoint_index); + *self.heap.peek_mut().unwrap() = new_point; + + self.add_crossings_to_heap_if_necessary(active_edge_index + 0, active_edge_index + 2) + } + + fn process_max_endpoint(&mut self, endpoint_index: u32, active_edge_indices: [u32; 2]) { + debug_assert!(active_edge_indices[0] < active_edge_indices[1], + "Matching active edge indices in wrong order when processing MAX point"); + + let endpoint = &self.endpoints[endpoint_index as usize]; + + if self.should_fill_above_active_edge(active_edge_indices[0]) { + self.emit_bezieroid_above(active_edge_indices[0], endpoint.position.x) + } + if self.should_fill_above_active_edge(active_edge_indices[1]) { + self.emit_bezieroid_above(active_edge_indices[1], endpoint.position.x) + } + if self.should_fill_below_active_edge(active_edge_indices[1]) { + self.emit_bezieroid_below(active_edge_indices[1], endpoint.position.x) + } + + self.heap.pop(); + + // FIXME(pcwalton): This is twice as slow as it needs to be. + self.active_edges.remove(active_edge_indices[1] as usize); + self.active_edges.remove(active_edge_indices[0] as usize); + + self.add_crossings_to_heap_if_necessary(active_edge_indices[0], active_edge_indices[0] + 2) + } + + fn process_crossing_point(&mut self, x: f32, upper_active_edge_index: u32) { + if self.should_fill_above_active_edge(upper_active_edge_index) { + self.emit_bezieroid_above(upper_active_edge_index, x) + } + if self.should_fill_below_active_edge(upper_active_edge_index) { + self.emit_bezieroid_below(upper_active_edge_index, x) + } + + // Swap the two edges. + let lower_active_edge_index = upper_active_edge_index + 1; + self.active_edges.swap(upper_active_edge_index as usize, lower_active_edge_index as usize) + } + + fn add_new_edges_for_min_point(&mut self, endpoint_index: u32, next_active_edge_index: u32) { + // FIXME(pcwalton): This is twice as slow as it needs to be. + self.active_edges.insert(next_active_edge_index as usize, ActiveEdge::default()); + self.active_edges.insert(next_active_edge_index as usize, ActiveEdge::default()); + + let prev_endpoint_index = self.prev_endpoint_of(endpoint_index); + let next_endpoint_index = self.next_endpoint_of(endpoint_index); + + let new_active_edges = &mut self.active_edges[next_active_edge_index as usize.. + next_active_edge_index as usize + 2]; + + let endpoint = &self.endpoints[endpoint_index as usize]; + let prev_endpoint = &self.endpoints[prev_endpoint_index as usize]; + let next_endpoint = &self.endpoints[next_endpoint_index as usize]; + + // TODO(pcwalton): There's a faster way to do this with no divisions, almost certainly. + let prev_vector = (prev_endpoint.position - endpoint.position).normalize(); + let next_vector = (next_endpoint.position - endpoint.position).normalize(); + + if prev_vector.y <= next_vector.y { + new_active_edges[0].right_endpoint_index = prev_endpoint_index; + new_active_edges[1].right_endpoint_index = next_endpoint_index; + new_active_edges[0].left_to_right = false; + new_active_edges[1].left_to_right = true; + new_active_edges[0].time = 1.0; + new_active_edges[1].time = 0.0; + } else { + new_active_edges[0].right_endpoint_index = next_endpoint_index; + new_active_edges[1].right_endpoint_index = prev_endpoint_index; + new_active_edges[0].left_to_right = true; + new_active_edges[1].left_to_right = false; + new_active_edges[0].time = 0.0; + new_active_edges[1].time = 1.0; + } + } + + fn init_heap_for_path(&mut self, path_index: u32) { + let path = &self.paths[path_index as usize]; + let first_subpath_index = path.first_subpath_index; + let last_subpath_index = self.last_subpath_index_of_path(path_index); + for subpath_index in first_subpath_index..last_subpath_index { + let first_endpoint_index = self.subpaths[subpath_index as usize].first_endpoint_index; + let last_endpoint_index = self.last_endpoint_index_of_path(path_index); + for endpoint_index in first_endpoint_index..last_endpoint_index { + match self.classify_endpoint(endpoint_index) { + EndpointClass::Min => { + let new_point = self.create_point_from_endpoint(endpoint_index); + self.heap.push(new_point) + } + EndpointClass::Regular | EndpointClass::Max => {} + } + } + } + } + + fn should_fill_below_active_edge(&self, active_edge_index: u32) -> bool { + // TODO(pcwalton): Support the winding fill rule. + active_edge_index % 2 == 0 + } + + fn should_fill_above_active_edge(&self, active_edge_index: u32) -> bool { + // TODO(pcwalton): Support the winding fill rule. + active_edge_index % 2 == 1 + } + + fn emit_bezieroid_below(&mut self, upper_active_edge_index: u32, right_x: f32) { + self.emit_bezieroid_above(upper_active_edge_index + 1, right_x) + } + + fn emit_bezieroid_above(&mut self, lower_active_edge_index: u32, right_x: f32) { + // TODO(pcwalton): Assert that the green X position is the same on both edges. + debug_assert!(lower_active_edge_index > 0, + "Can't emit bezieroids above the top active edge"); + let upper_active_edge_index = lower_active_edge_index - 1; + + let new_bezieroid; + + { + let lower_active_edge = &self.active_edges[lower_active_edge_index as usize]; + let upper_active_edge = &self.active_edges[upper_active_edge_index as usize]; + + new_bezieroid = Bezieroid { + upper_prev_endpoint: upper_active_edge.prev_endpoint_index(), + upper_next_endpoint: upper_active_edge.next_endpoint_index(), + lower_prev_endpoint: lower_active_edge.prev_endpoint_index(), + lower_next_endpoint: lower_active_edge.next_endpoint_index(), + upper_left_time: upper_active_edge.time, + upper_right_time: self.solve_t_for_active_edge(upper_active_edge_index, right_x), + lower_left_time: lower_active_edge.time, + lower_right_time: self.solve_t_for_active_edge(lower_active_edge_index, right_x), + }; + + self.bezieroids.push(new_bezieroid); + } + + self.active_edges[upper_active_edge_index as usize].time = new_bezieroid.upper_right_time; + self.active_edges[lower_active_edge_index as usize].time = new_bezieroid.lower_right_time; + } + + fn already_visited_point(&self, point: &Point) -> bool { + // FIXME(pcwalton): This makes the visited vector too big. + let index = point.endpoint_index as usize * 2 + point.point_type as usize; + match self.visited_points.get(index) { + None => false, + Some(visited) => visited, + } + } + + fn mark_point_as_visited(&mut self, point: &Point) { + // FIXME(pcwalton): This makes the visited vector too big. + self.visited_points.set(point.endpoint_index as usize * 2 + point.point_type as usize, + true) + } + + fn find_right_point_in_active_edge_list(&self, endpoint_index: u32) -> MatchingActiveEdges { + let mut matching_active_edges = MatchingActiveEdges { + indices: [0, 0], + count: 0, + }; + + for (active_edge_index, active_edge) in self.active_edges.iter().enumerate() { + if active_edge.right_endpoint_index == endpoint_index { + matching_active_edges.indices[matching_active_edges.count as usize] = + active_edge_index as u32; + matching_active_edges.count += 1; + if matching_active_edges.count == 2 { + break + } + } + } + + matching_active_edges + } + + fn classify_endpoint(&self, endpoint_index: u32) -> EndpointClass { + // Create temporary points just for the comparison. + let point = self.create_point_from_endpoint(endpoint_index); + let prev_point = self.create_point_from_endpoint(self.prev_endpoint_of(endpoint_index)); + let next_point = self.create_point_from_endpoint(self.next_endpoint_of(endpoint_index)); + + match (prev_point.cmp(&point), next_point.cmp(&point)) { + (Ordering::Less, Ordering::Less) => EndpointClass::Max, + (Ordering::Less, _) | (_, Ordering::Less) => EndpointClass::Regular, + (_, _) => EndpointClass::Min, + } + } + + fn solve_t_for_active_edge(&self, active_edge_index: u32, x: f32) -> f32 { + let active_edge = &self.active_edges[active_edge_index as usize]; + let prev_endpoint_index = active_edge.prev_endpoint_index(); + let next_endpoint_index = active_edge.next_endpoint_index(); + let prev_endpoint = &self.endpoints[prev_endpoint_index as usize]; + let next_endpoint = &self.endpoints[next_endpoint_index as usize]; + match self.control_points_index(next_endpoint_index) { + None => { + let x_vector = next_endpoint.position.x - prev_endpoint.position.x; + (x - prev_endpoint.position.x) / x_vector + } + Some(control_points_index) => { + let control_points = &self.control_points[control_points_index as usize]; + geometry::solve_cubic_bezier_t_for_x(x, + &prev_endpoint.position, + &control_points.point1, + &control_points.point2, + &next_endpoint.position) + } + } + } + + fn find_point_between_active_edges(&self, endpoint_index: u32) -> u32 { + let endpoint = &self.endpoints[endpoint_index as usize]; + match self.active_edges.iter().position(|active_edge| { + self.solve_active_edge_y_for_x(endpoint.position.x, active_edge) > endpoint.position.y + }) { + Some(active_edge_index) => active_edge_index as u32, + None => self.active_edges.len() as u32, + } + } + + fn solve_active_edge_y_for_x(&self, x: f32, active_edge: &ActiveEdge) -> f32 { + let prev_endpoint_index = active_edge.prev_endpoint_index(); + let next_endpoint_index = active_edge.next_endpoint_index(); + if self.control_points_index(next_endpoint_index).is_none() { + self.solve_line_y_for_x(x, prev_endpoint_index, next_endpoint_index) + } else { + self.solve_cubic_bezier_y_for_x(x, prev_endpoint_index, next_endpoint_index) + } + } + + fn solve_line_y_for_x(&self, x: f32, prev_endpoint_index: u32, next_endpoint_index: u32) + -> f32 { + geometry::solve_line_y_for_x(x, + &self.endpoints[prev_endpoint_index as usize].position, + &self.endpoints[next_endpoint_index as usize].position) + } + + fn solve_cubic_bezier_y_for_x(&self, x: f32, prev_endpoint_index: u32, next_endpoint_index: u32) + -> f32 { + let prev_endpoint = &self.endpoints[prev_endpoint_index as usize]; + let next_endpoint = &self.endpoints[next_endpoint_index as usize]; + let control_points_index = self.control_points_index(next_endpoint_index) + .expect("Edge not a cubic bezier!"); + let control_points = &self.control_points[control_points_index as usize]; + geometry::solve_cubic_bezier_y_for_x(x, + &prev_endpoint.position, + &control_points.point1, + &control_points.point2, + &next_endpoint.position) + } + + fn control_points_index(&self, next_endpoint_index: u32) -> Option { + match self.endpoints[next_endpoint_index as usize].control_points_index { + u32::MAX => None, + control_points_index => Some(control_points_index), + } + } + + fn add_crossings_to_heap_if_necessary(&mut self, + mut first_active_edge_index: u32, + mut last_active_edge_index: u32) { + if self.active_edges.is_empty() { + return + } + + first_active_edge_index = first_active_edge_index.checked_sub(1) + .unwrap_or(first_active_edge_index); + last_active_edge_index = cmp::min(last_active_edge_index + 1, + self.active_edges.len() as u32); + + for (upper_active_edge_index, upper_active_edge) in + self.active_edges[(first_active_edge_index as usize).. + (last_active_edge_index as usize - 1)] + .iter() + .enumerate() { + let crossing_position = + match self.crossing_point_for_active_edge(upper_active_edge_index as u32) { + None => continue, + Some(crossing_point) => crossing_point, + }; + + let new_point = Point { + position: crossing_position, + endpoint_index: upper_active_edge.right_endpoint_index, + point_type: PointType::CrossingBelow, + }; + + self.heap.push(new_point); + } + } + + fn crossing_point_for_active_edge(&self, upper_active_edge_index: u32) + -> Option> { + let lower_active_edge_index = upper_active_edge_index + 1; + + let upper_active_edge = &self.active_edges[upper_active_edge_index as usize]; + let lower_active_edge = &self.active_edges[lower_active_edge_index as usize]; + if upper_active_edge.left_endpoint_index == lower_active_edge.left_endpoint_index || + upper_active_edge.right_endpoint_index == lower_active_edge.right_endpoint_index { + return None + } + + let prev_upper_endpoint_index = upper_active_edge.prev_endpoint_index(); + let next_upper_endpoint_index = upper_active_edge.next_endpoint_index(); + let prev_lower_endpoint_index = lower_active_edge.prev_endpoint_index(); + let next_lower_endpoint_index = lower_active_edge.next_endpoint_index(); + let upper_control_points_index = self.endpoints[next_upper_endpoint_index as usize] + .control_points_index; + let lower_control_points_index = self.endpoints[next_lower_endpoint_index as usize] + .control_points_index; + + match (lower_control_points_index, upper_control_points_index) { + (u32::MAX, u32::MAX) => { + self.line_line_crossing_point(prev_upper_endpoint_index, + next_upper_endpoint_index, + prev_lower_endpoint_index, + next_lower_endpoint_index) + } + (u32::MAX, _) => { + self.line_cubic_bezier_crossing_point(prev_upper_endpoint_index, + next_upper_endpoint_index, + next_lower_endpoint_index, + next_lower_endpoint_index) + } + (_, u32::MAX) => { + self.line_cubic_bezier_crossing_point(prev_lower_endpoint_index, + next_lower_endpoint_index, + next_upper_endpoint_index, + next_upper_endpoint_index) + } + (_, _) => { + self.cubic_bezier_cubic_bezier_crossing_point(prev_upper_endpoint_index, + next_upper_endpoint_index, + prev_lower_endpoint_index, + next_lower_endpoint_index) + } + } + } + + fn line_line_crossing_point(&self, + prev_upper_endpoint_index: u32, + next_upper_endpoint_index: u32, + prev_lower_endpoint_index: u32, + next_lower_endpoint_index: u32) + -> Option> { + let endpoints = &self.endpoints; + geometry::line_line_crossing_point(&endpoints[prev_upper_endpoint_index as usize].position, + &endpoints[next_upper_endpoint_index as usize].position, + &endpoints[prev_lower_endpoint_index as usize].position, + &endpoints[next_lower_endpoint_index as usize].position) + } + + fn line_cubic_bezier_crossing_point(&self, + prev_line_endpoint_index: u32, + next_line_endpoint_index: u32, + prev_bezier_endpoint_index: u32, + next_bezier_endpoint_index: u32) + -> Option> { + let control_points_index = self.control_points_index(next_bezier_endpoint_index) + .expect("Edge not a cubic Bezier!"); + let control_points = &self.control_points[control_points_index as usize]; + geometry::line_cubic_bezier_crossing_point( + &self.endpoints[prev_line_endpoint_index as usize].position, + &self.endpoints[next_line_endpoint_index as usize].position, + &self.endpoints[prev_bezier_endpoint_index as usize].position, + &control_points.point1, + &control_points.point2, + &self.endpoints[next_bezier_endpoint_index as usize].position) + } + + fn cubic_bezier_cubic_bezier_crossing_point(&self, + prev_upper_endpoint_index: u32, + next_upper_endpoint_index: u32, + prev_lower_endpoint_index: u32, + next_lower_endpoint_index: u32) + -> Option> { + let upper_control_points_index = self.control_points_index(next_upper_endpoint_index) + .expect("Upper edge not a cubic Bezier!"); + let upper_control_points = &self.control_points[upper_control_points_index as usize]; + let lower_control_points_index = self.control_points_index(next_lower_endpoint_index) + .expect("Lower edge not a cubic Bezier!"); + let lower_control_points = &self.control_points[lower_control_points_index as usize]; + geometry::cubic_bezier_cubic_bezier_crossing_point( + &self.endpoints[prev_upper_endpoint_index as usize].position, + &upper_control_points.point1, + &upper_control_points.point2, + &self.endpoints[next_upper_endpoint_index as usize].position, + &self.endpoints[prev_lower_endpoint_index as usize].position, + &lower_control_points.point1, + &lower_control_points.point2, + &self.endpoints[next_lower_endpoint_index as usize].position) + } + + fn prev_endpoint_of(&self, endpoint_index: u32) -> u32 { + let endpoint = &self.endpoints[endpoint_index as usize]; + let first_endpoint_index_of_subpath = self.subpaths[endpoint.subpath_index as usize] + .first_endpoint_index; + if endpoint_index > first_endpoint_index_of_subpath { + endpoint_index - 1 + } else { + self.last_endpoint_index_of_subpath(endpoint.subpath_index) - 1 + } + } + + fn next_endpoint_of(&self, endpoint_index: u32) -> u32 { + let endpoint = &self.endpoints[endpoint_index as usize]; + let last_endpoint_index_of_subpath = + self.last_endpoint_index_of_subpath(endpoint.subpath_index); + if endpoint_index + 1 < last_endpoint_index_of_subpath { + endpoint_index + 1 + } else { + self.subpaths[endpoint.subpath_index as usize].first_endpoint_index + } + } + + fn create_point_from_endpoint(&self, endpoint_index: u32) -> Point { + Point { + position: self.endpoints[endpoint_index as usize].position, + endpoint_index: endpoint_index, + point_type: PointType::Endpoint, + } + } + + fn last_endpoint_index_of_subpath(&self, subpath_index: u32) -> u32 { + match self.subpaths.get(subpath_index as usize + 1) { + Some(subpath) => subpath.first_endpoint_index, + None => self.endpoints.len() as u32, + } + } + + fn last_subpath_index_of_path(&self, path_index: u32) -> u32 { + match self.paths.get(path_index as usize + 1) { + Some(path) => path.first_subpath_index, + None => self.subpaths.len() as u32, + } + } + + fn last_endpoint_index_of_path(&self, path_index: u32) -> u32 { + self.last_endpoint_index_of_subpath(self.last_subpath_index_of_path(path_index)) + } +} + +#[derive(Debug, Clone, Copy)] +struct Point { + position: Point2D, + endpoint_index: u32, + point_type: PointType, +} + +impl PartialEq for Point { + #[inline] + fn eq(&self, other: &Point) -> bool { + self.position == other.position && self.endpoint_index == other.endpoint_index + } + #[inline] + fn ne(&self, other: &Point) -> bool { + self.position != other.position || self.endpoint_index != other.endpoint_index + } +} + +impl Eq for Point {} + +impl PartialOrd for Point { + #[inline] + fn partial_cmp(&self, other: &Point) -> Option { + match self.position.x.partial_cmp(&other.position.x) { + None | Some(Ordering::Equal) => {} + Some(ordering) => return Some(ordering), + } + match self.position.y.partial_cmp(&other.position.y) { + None | Some(Ordering::Equal) => {} + Some(ordering) => return Some(ordering), + } + self.endpoint_index.partial_cmp(&other.endpoint_index) + } +} + +impl Ord for Point { + #[inline] + fn cmp(&self, other: &Point) -> Ordering { + self.partial_cmp(other).unwrap_or(Ordering::Equal) + } +} + +#[derive(Debug, Clone, Copy, Default)] +struct ActiveEdge { + left_endpoint_index: u32, + right_endpoint_index: u32, + time: f32, + left_to_right: bool, +} + +impl ActiveEdge { + fn prev_endpoint_index(&self) -> u32 { + if self.left_to_right { + self.left_endpoint_index + } else { + self.right_endpoint_index + } + } + + fn next_endpoint_index(&self) -> u32 { + if self.left_to_right { + self.right_endpoint_index + } else { + self.left_endpoint_index + } + } +} + +#[derive(Debug, Clone, Copy)] +enum PointType { + Endpoint = 0, + CrossingBelow = 1, +} + +#[derive(Debug, Clone, Copy)] +enum EndpointClass { + Min, + Regular, + Max, +} + +#[derive(Debug, Clone, Copy)] +struct MatchingActiveEdges { + indices: [u32; 2], + count: u8, +} diff --git a/partitionfinder/src/partitionfinder.h b/partitionfinder/src/partitionfinder.h new file mode 100644 index 00000000..fb7e49c2 --- /dev/null +++ b/partitionfinder/src/partitionfinder.h @@ -0,0 +1,84 @@ +// partitionfinder/partitionfinder.h + +#ifndef PARTITIONFINDER_H +#define PARTITIONFINDER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct pf_point2d_f32 { + float x, y; +}; + +typedef struct pf_point2d_f32 pf_point2d_f32_t; + +struct pf_color_u8 { + uint8_t r, g, b, a; +}; + +typedef struct pf_color_u8 pf_color_u8_t; + +struct pf_bezieroid { + uint32_t upper_prev_endpoint, upper_next_endpoint; + uint32_t lower_prev_endpoint, lower_next_endpoint; + uint32_t upper_left_time, upper_right_time; + uint32_t lower_left_time, lower_right_time; +}; + +typedef struct pf_bezieroid pf_bezieroid_t; + +struct pf_endpoint { + pf_point2d_f32_t position; + uint32_t control_points_index; + uint32_t subpath_index; +}; + +typedef struct pf_endpoint pf_endpoint_t; + +struct pf_control_points { + pf_point2d_f32_t point1, point2; +}; + +typedef struct pf_control_points pf_control_points_t; + +struct pf_subpath { + uint32_t first_endpoint_index, path_index; +}; + +typedef struct pf_subpath pf_subpath_t; + +struct pf_path { + uint32_t first_subpath_index; + pf_color_u8_t fill_color; +}; + +typedef struct pf_path pf_path_t; + +struct pf_partitioner; + +typedef struct pf_partitioner pf_partitioner_t; + +pf_partitioner_t *pf_partitioner_new(const pf_endpoint_t *endpoints, + uint32_t endpoint_count, + const pf_control_points_t *control_points, + uint32_t control_points_count, + const pf_subpath_t *subpaths, + uint32_t subpath_count, + const pf_path_t *paths, + uint32_t path_count); + +void pf_partitioner_destroy(pf_partitioner_t *partitioner); + +void pf_partitioner_partition(pf_partitioner_t *partitioner); + +const pf_bezieroid_t *pf_partitioner_bezieroids(pf_partitioner_t *partitioner, + uint32_t *out_bezieroid_count); + +#ifdef __cplusplus +} +#endif + +#endif