362 lines
9.3 KiB
Rust
362 lines
9.3 KiB
Rust
use super::{
|
|
DrawPath,
|
|
RadianPoint,
|
|
PathTransformer,
|
|
polygon_contains_south,
|
|
};
|
|
|
|
struct RingState {
|
|
start_new_segment: bool,
|
|
segments: Vec<Vec<(RadianPoint, bool)>>,
|
|
}
|
|
|
|
pub struct ClipControl<S> {
|
|
sink: S,
|
|
ring: Option<RingState>,
|
|
}
|
|
|
|
impl<S> ClipControl<S>
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
{
|
|
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<S>(&mut self, control: &mut ClipControl<S>, to: RadianPoint, stroke: bool)
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
;
|
|
}
|
|
|
|
pub trait Clipper {
|
|
type PathClipper: PathClipper;
|
|
|
|
fn create_clipper(&self) -> Self::PathClipper;
|
|
|
|
fn interpolate<S>(&self, control: &mut ClipControl<S>, from_to: Option<(RadianPoint, RadianPoint)>, forward: bool)
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
;
|
|
}
|
|
|
|
pub struct Clip<S, C>
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
C: Clipper,
|
|
{
|
|
control: ClipControl<S>,
|
|
polygon: Vec<Vec<RadianPoint>>,
|
|
polygon_first_stroke: bool,
|
|
segments_with_splits: Vec<Vec<(RadianPoint, bool)>>,
|
|
clipper: C,
|
|
path_clipper: Option<C::PathClipper>,
|
|
}
|
|
|
|
impl<S, C> Clip<S, C>
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
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::<Intersection>()
|
|
};
|
|
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<S, C> PathTransformer for Clip<S, C>
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
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<S, C> Drop for Clip<S, C>
|
|
where
|
|
S: DrawPath<Point = RadianPoint>,
|
|
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);
|
|
}
|
|
}
|
|
}
|