use super::{ DrawPath, RadianPoint, PathTransformer, polygon_contains_south, }; struct RingState { start_new_segment: bool, segments: Vec>, } pub struct ClipControl { sink: S, ring: Option, } impl ClipControl where S: DrawPath, { pub fn line(&mut self, to: RadianPoint, stroke: bool) { if let Some(ring) = &mut self.ring { if ring.start_new_segment { ring.start_new_segment = false; if ring.segments.last().map(|l| l.len() <= 1).unwrap_or(false) { // last segment empty or only a single point; remove it ring.segments.pop(); } ring.segments.push(vec![(to, stroke)]); } else { ring.segments.last_mut().unwrap().push((to, stroke)); } } else { self.sink.line(to, stroke); } } pub fn split_path(&mut self) { if let Some(ring) = &mut self.ring { ring.start_new_segment = true; } else { self.sink.path_end(); self.sink.path_start(); } } fn send_ring_segment(&mut self, segment: Vec<(RadianPoint, bool)>) { self.sink.ring_start(); for (point, stroke) in segment { self.sink.line(point, stroke); } self.sink.ring_end(); } } pub trait PathClipper { fn line(&mut self, control: &mut ClipControl, to: RadianPoint, stroke: bool) where S: DrawPath, ; } pub trait Clipper { type PathClipper: PathClipper; fn create_clipper(&self) -> Self::PathClipper; fn interpolate(&self, control: &mut ClipControl, from_to: Option<(RadianPoint, RadianPoint)>, forward: bool) where S: DrawPath, ; } pub struct Clip where S: DrawPath, C: Clipper, { control: ClipControl, polygon: Vec>, polygon_first_stroke: bool, segments_with_splits: Vec>, clipper: C, path_clipper: Option, } impl Clip where S: DrawPath, C: Clipper, { pub fn new(sink: S, clipper: C) -> Self { Self { control: ClipControl { sink, ring: None, }, polygon: Vec::new(), polygon_first_stroke: false, segments_with_splits: Vec::new(), clipper, path_clipper: None, } } fn clip_rejoin(&mut self, contains_south: bool) { let segments = std::mem::replace(&mut self.segments_with_splits, Vec::new()); struct Intersection { point: RadianPoint, clip_next: usize, clip_prev: usize, entry: bool, visited: bool, } impl Intersection { fn new(point: RadianPoint) -> Self { Intersection { point, clip_next: 0, clip_prev: 0, entry: false, visited: false, } } fn compare(a: &&mut Self, b: &&mut Self) -> std::cmp::Ordering { let x = &a.point; let y = &b.point; // start with crossings on the west (left) side (going north), // then continue on the east side (going south) let x_is_west = x.lambda < 0.0; let y_is_west = y.lambda < 0.0; if x_is_west != y_is_west { return std::cmp::Ord::cmp(&x_is_west, &y_is_west); } // now both on the same side if x_is_west { // up is the natural "phi" ordering std::cmp::PartialOrd::partial_cmp(&x.phi, &y.phi).unwrap() } else { // down needs reverse std::cmp::PartialOrd::partial_cmp(&y.phi, &x.phi).unwrap() } } } let mut rem_segments = Vec::new(); let mut intersections = Vec::new(); for segment in segments { if segment.len() <= 1 { continue; } let first = segment[0].0; let last = segment[segment.len()-1].0; if segment.len() < 3 || first == last { self.control.send_ring_segment(segment); continue; } rem_segments.push(segment); // always two intersections entries correspond to the same segment intersections.push(Intersection::new(first)); intersections.push(Intersection::new(last)); } if rem_segments.is_empty() { // TODO: check winding order of exterior ring to add sphere? return; } { let ix_base = intersections.as_ptr(); let mut ix_sort: Vec<_> = intersections.iter_mut().collect(); ix_sort.sort_by(Intersection::compare); let last_ndx = ix_sort.len() - 1; let index_of = |ix: &Intersection| -> usize { let ix: *const Intersection = ix as _; (ix as usize - ix_base as usize) / std::mem::size_of::() }; ix_sort[0].clip_prev = index_of(ix_sort[last_ndx]); ix_sort[last_ndx].clip_next = index_of(ix_sort[0]); for ndx in 0..last_ndx { ix_sort[ndx].clip_next = index_of(ix_sort[ndx+1]); ix_sort[ndx+1].clip_prev = index_of(ix_sort[ndx]); } // if we don't contain south pole the first entry on the "bottom left" (south east) is an entry let mut entry = contains_south; for ix in ix_sort { entry = !entry; ix.entry = entry; } } let n = intersections.len(); loop { let mut current_ndx = match intersections.iter().position(|ix| !ix.visited) { Some(v) => v, None => break, // done }; self.control.sink.ring_start(); loop { // start with subject intersections[current_ndx].visited = true; if 0 == (current_ndx & 1) { let segment = &rem_segments[current_ndx / 2]; for (point, stroke) in segment.iter() { self.control.sink.line(*point, *stroke); } current_ndx = (current_ndx + 1) % n; } else { // this should be very unlikely unless the input is broken. let segment = &rem_segments[current_ndx / 2]; let mut next_stroke = segment[0].1; for (point, stroke) in segment.iter().rev() { self.control.sink.line(*point, next_stroke); next_stroke = *stroke; } current_ndx = (current_ndx + (n - 1)) % n; } if intersections[current_ndx].visited { break; } // now alternate to clip intersections[current_ndx].visited = true; if intersections[current_ndx].entry { let next_ndx = intersections[current_ndx].clip_next; let from = intersections[current_ndx].point; let to = intersections[next_ndx].point; self.clipper.interpolate(&mut self.control, Some((from, to)), true); current_ndx = next_ndx; } else { let next_ndx = intersections[current_ndx].clip_prev; let from = intersections[current_ndx].point; let to = intersections[next_ndx].point; self.clipper.interpolate(&mut self.control, Some((from, to)), false); current_ndx = next_ndx; } if intersections[current_ndx].visited { break; } } self.control.sink.ring_end(); } } // pub? trait? fn sphere(&mut self) { self.control.sink.ring_start(); self.clipper.interpolate(&mut self.control, None, true); self.control.sink.ring_end(); } } impl PathTransformer for Clip where S: DrawPath, C: Clipper, { type Point = RadianPoint; type Sink = S; fn sink(&mut self) -> &mut Self::Sink { &mut self.control.sink } fn transform_line(&mut self, to: Self::Point, stroke: bool) { let path_clipper = self.path_clipper.as_mut().expect("missing geometry state"); if self.control.ring.is_some() { let current_poly = self.polygon.last_mut().unwrap(); if current_poly.is_empty() { self.polygon_first_stroke = stroke; } // remember original polygon rings for contains check current_poly.push(to); } path_clipper.line(&mut self.control, to, stroke); } fn transform_ring_start(&mut self) { assert!(self.path_clipper.is_none()); assert!(self.control.ring.is_none()); self.path_clipper = Some(self.clipper.create_clipper()); self.control.ring = Some(RingState { start_new_segment: true, segments: Vec::new(), }); self.polygon.push(Vec::new()); } fn transform_ring_end(&mut self) { assert!(self.path_clipper.is_some()); assert!(self.control.ring.is_some()); let current_poly = self.polygon.last().unwrap(); if current_poly.len() < 1 { // no data or single point, skip self.control.ring = None; self.path_clipper = None; self.polygon.pop(); return; } // close ring in clip and reset clipper self.path_clipper.take().unwrap().line(&mut self.control, current_poly[0], self.polygon_first_stroke); if current_poly.len() < 2 { // doesn't describe an are, don't remember for contains check self.polygon.pop(); } let mut segments = self.control.ring.take().unwrap().segments; if segments.is_empty() { return; } if segments.len() == 1 { // no splits, just forward to sink let segment = segments.pop().unwrap(); self.control.send_ring_segment(segment); return; } // rejoin last and first segment, as they should touch { let mut first = segments.swap_remove(0); // now [0] contains previous last segments[0].append(&mut first); } // delay handling until we have all rings of polygon self.segments_with_splits.append(&mut segments); } fn transform_path_start(&mut self) { assert!(self.path_clipper.is_none()); assert!(self.control.ring.is_none()); self.path_clipper = Some(self.clipper.create_clipper()); self.control.sink.path_start(); } fn transform_path_end(&mut self) { assert!(self.path_clipper.take().is_some()); // reset path_clipper assert!(self.control.ring.is_none()); self.control.sink.path_end(); } } impl Drop for Clip where S: DrawPath, C: Clipper, { fn drop(&mut self) { let contains_south = polygon_contains_south(&self.polygon); if self.segments_with_splits.is_empty() { if contains_south { self.sphere(); } } else { jslog!("contains_south: {}", contains_south); self.clip_rejoin(contains_south); } } }