flo_curves/bezier/path/arithmetic/
add.rs

1use super::ray_cast::*;
2use super::super::path::*;
3use super::super::graph_path::*;
4use super::super::super::super::geo::*;
5
6//
7// This uses a simple ray casting algorithm to perform the addition
8// 
9// Basic idea is to cast a ray at an edge which is currently uncategorised, and mark the edges it crosses as interior or
10// exterior depending on whether or not we consider it as crossing into or out of the final shape.
11//
12
13impl<Point: Coordinate+Coordinate2D> GraphPath<Point, PathLabel> {
14    ///
15    /// Given a labelled graph path, marks exterior edges by adding `PathSource::Path1` and `PathSource::Path2`
16    ///
17    /// This is used to implement the `path_add()` arithmetic function, and is generally called after using `collide()`
18    /// to combine the two input paths.
19    ///
20    pub fn set_exterior_by_adding(&mut self) {
21        // Use an even-odd winding rule (all edges are considered 'external')
22        self.set_edge_kinds_by_ray_casting(|path_crossings| (path_crossings[0]&1) != 0 || (path_crossings[1]&1) != 0);
23    }
24
25    ///
26    /// Given a path that intersects itself (ie, only contains SourcePath::Path1), discovers the 'true' exterior edge.
27    ///
28    pub fn set_exterior_by_removing_interior_points(&mut self) {
29        // All points inside the path are considered 'interior' (non-zero winding rule)
30        self.set_edge_kinds_by_ray_casting(|path_crossings| path_crossings[0] != 0 || path_crossings[1] != 0);
31    }
32}
33
34///
35/// Generates the path formed by adding two sets of paths
36///
37/// Each of the two paths passed into this function is assumed not to overlap themselves. IE, this does not perform self-intersection 
38/// on either `path1` or `path2`. This provides both a performance optimisation and finer control over how self-intersecting paths are
39/// handled. See `path_remove_interior_points()` and `path_remove_overlapped_points()` for a way to eliminate overlaps.
40/// 
41/// The input vectors represent the external edges of the path to add (a single BezierPath cannot have any holes in it, so a set of them
42/// effectively represents a path intended to be rendered with an even-odd winding rule)
43///
44pub fn path_add<POut>(path1: &Vec<impl BezierPath<Point=POut::Point>>, path2: &Vec<impl BezierPath<Point=POut::Point>>, accuracy: f64) -> Vec<POut>
45where
46    POut:           BezierPathFactory,
47    POut::Point:    Coordinate+Coordinate2D,
48{
49    // If either path is empty, short-circuit by returning the other
50    if path1.is_empty() {
51        return path2.iter()
52            .map(|path| POut::from_path(path))
53            .collect();
54    } else if path2.is_empty() {
55        return path1.iter()
56            .map(|path| POut::from_path(path))
57            .collect();
58    }
59
60    // Create the graph path from the source side
61    let mut merged_path = GraphPath::new();
62    merged_path         = merged_path.merge(GraphPath::from_merged_paths(path1.iter().map(|path| (path, PathLabel(0)))));
63
64    // Collide with the target side to generate a full path
65    merged_path         = merged_path.collide(GraphPath::from_merged_paths(path2.iter().map(|path| (path, PathLabel(1)))), accuracy);
66    merged_path.round(accuracy);
67
68    // Set the exterior edges using the 'add' algorithm
69    merged_path.set_exterior_by_adding();
70    merged_path.heal_exterior_gaps();
71
72    // Produce the final result
73    merged_path.exterior_paths()
74}
75
76///
77/// Generates the path formed by removing any interior points from an existing path. This considers only the outermost edges of the 
78/// path to be the true edges, so if there are sub-paths inside an outer path, they will be removed.
79///
80/// This is a strict version of the 'non-zero' winding rule. It's useful for things like a path that was generated from a brush stroke
81/// and might self-overlap: this can be passed a drawing of a loop made by overlapping the ends and it will output two non-overlapping 
82/// subpaths.
83///
84/// See `path_remove_overlapped_points()` for a version that considers all edges within the path to be exterior edges.
85///
86pub fn path_remove_interior_points<P1: BezierPath, POut: BezierPathFactory>(path: &Vec<P1>, accuracy: f64) -> Vec<POut>
87where
88    P1::Point:  Coordinate+Coordinate2D,
89    POut:       BezierPathFactory<Point=P1::Point>,
90{
91    // Create the graph path from the source side
92    let mut merged_path = GraphPath::new();
93    merged_path         = merged_path.merge(GraphPath::from_merged_paths(path.iter().map(|path| (path, PathLabel(0)))));
94
95    // Collide the path with itself to find the intersections
96    merged_path.self_collide(accuracy);
97    merged_path.round(accuracy);
98
99    // Set the exterior edges by considering all points inside an edge as 'interior'
100    merged_path.set_exterior_by_removing_interior_points();
101    merged_path.heal_exterior_gaps();
102
103    // Produce the final result
104    let result = merged_path.exterior_paths();
105    test_assert!(result.len() != 0 || path.is_empty());
106
107    result
108}
109
110///
111/// Generates the path formed by removing any interior points from an existing path. This considers all edges to be exterior edges
112/// and will remove those that are obscured by another part of the path.
113///
114/// This works like the 'even-odd' winding rule.
115///
116/// If a path self-intersects, this will leave a hole behind. See `path_remove_interior_points()` for a way to remove the interior
117/// parts of a path so that all points inside the boundary are filled. This function is useful for cleaning up paths from other
118/// sources, then the other arithmetic operations can be used to reshape the resulting path.
119///
120/// Note that calling 'subtract' is a more reliable way to cut a hole in an existing path than relying on a winding rule, as
121/// winding rules presuppose you can tell if a subpath is inside or outside of an existing path.
122///
123pub fn path_remove_overlapped_points<P1: BezierPath, POut: BezierPathFactory>(path: &Vec<P1>, accuracy: f64) -> Vec<POut>
124where
125    P1::Point:  Coordinate+Coordinate2D,
126    POut:       BezierPathFactory<Point=P1::Point>,
127{
128    // Create the graph path from the source side
129    let mut merged_path = GraphPath::new();
130    merged_path         = merged_path.merge(GraphPath::from_merged_paths(path.iter().map(|path| (path, PathLabel(0)))));
131
132    // Collide the path with itself to find the intersections
133    merged_path.self_collide(accuracy);
134    merged_path.round(accuracy);
135
136    // Set the exterior edges using the 'add' algorithm
137    merged_path.set_exterior_by_adding();
138    merged_path.heal_exterior_gaps();
139
140    // Produce the final result
141    let result = merged_path.exterior_paths();
142    test_assert!(result.len() != 0 || path.is_empty());
143
144    result
145}