Binary boolean operation between multi-island polygons A and B specified by contour ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Def: input polygons are A and B for binary ops and A for unary ops Def: canon: an unary operator on a single input polygon that resolves the self intersections and irregularities of that polygon Def: segment is a line or an arc Def: curve is an ordered list of segments with no intersection Def: face: a closed loop of curves (represented by an ordered list of curve IDs), without any curve crossing it; may embed other faces but the curves of those faces may not touch the curves of the outer face Def: output tree [of faces]: a tree of faces; each node is a face with child nodes that are smaller faces fully within 1. switch to topology 1.1. import all segments (from both A and B) into a 2D space, remembering which segment is referred by A or B; compute intersections of any two segments 1.2. split segments at intersections; NOTE: intersection coords should be integer which means spliting a segment in the middle can change the slope of the two new segments and introduce new interscetions. 1.3. merge fully overlapping segments, remembering how many times A and B segments participated in the overlap (same count field as in 1.1) 1.4. collect segments into curves between intersections 2. map faces - Algo: https://cp-algorithms.com/geometry/planar.html TODO: the actual algorithm is slightly different, document from scratch - Note: overlapping lines, e.g. in a bone, O--O case may result in having a curve that's not part of any face; prune such curves - Note: for curves of faces, each curve should have a list of faces that use the curve (this is at most two faces per curve) - Note: calculate the approximate area of each face to throw away the outer one that overlaps all - Note: throw away curves that do not have at least one face associated 3. mark input polarity: for each face 3.1. choose an internal point of the face 3.2. face->inA := 1 or 0 depending on if the point is inside input polygon A 3.3. face->inB := 1 or 0 depending on if the point is inside input polygon B 3.4. compute polarity of faces: for each face compute face->out from face->inA and face->inB, depending on the operator, as: - A union B: out = inA or inB - A sub B: out = inA and not inB - A intersect B: out = inA and inB - A xor B: out = inA xor inB - canon A: out = inA Note: in point 3.2. anmd 3.3. tests the search needs to use the new geometry created in step 1, not the original input vertices. The difference is that the new model may have some extra vertices that are integer-rounded changing the slope of some edges slightly so the "internal point" chosen using this model may not be an internal point in the original input. Note: for point 3.2. anmd 3.3. the algorithm is specified in point-in-poly.html; the direction vector is taken to point inside the triangle at the bottom of the right-most face corners. 4. figure which curves to keep: for each curve: - if the curve has two adjacent faces, mark the curve "pruned" if the ->out of those two faces are equal - if the curve has only one adjacent face, mark the curve "pruned" if face's ->out is 0 (not filled) 5. gather curves into output faces: Naive: Re-run step 2 but ignore curves marked "pruned" then re-run step 3. Optimized: remove faces that are either side of "pruned" curves and re-run step 3 only for these This step can be skipped if there was no pruning in step 4 (and the face list from step 2 can be used as output faces). The result is an unordered "list of output faces". 6. stack output faces, building the output tree of faces: 6.0. create an output tree with the root node being an infinitely large negative face 6.1. take the largest remaining output face "off the list of output faces" 6.2. check whether the output face is within any of the leaves - if yes: insert the output face as a child face of that leaf face, with polarity being the inverse of the leaf face - if not: when face->out==1 the output face is a new root in the output tree with positive polarity otherwise discard it Note: since there are no intersections, this is real cheap: start a ray from any point of the face and pick the first face it hits in odd number of times 6.3. go back to 7.1. if "list of output faces" is not empty The result is the output tree. (This is to figure the polarity of each output face) 7. flatten the tree: Iterate over each face in the output tree; for positive faces create a polygon island; for negative faces create a polygon hole within the island previously created for the parent face. 8. cleanup 8.1. optional: visit each output contour and look at each interesected vertex; if the two segments on the two sides can be converted into a single segment (e.g. coaxial lines or arcs on the same circle), merge them to reduce the number of vertices 8.2. discard and free the output tree (created in 7.) 8.3. discard and free the list of output face (created in 6., should be empty by now) 8.4. free all curves and faces used