rust-galmon-web/src/ui/main/world_geo/clip.rs

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);
}
}
}