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

131 lines
3.9 KiB
Rust

pub use super::*;
#[derive(Clone, Copy, Debug)]
struct SegmentPoint {
input: RadianPoint,
cartesian: Cartesian,
projected: ProjectedPoint,
}
impl SegmentPoint {
fn new<P>(projection: P, input: RadianPoint) -> Self
where
P: Projection,
{
SegmentPoint {
input,
cartesian: input.into(),
projected: projection.project(input),
}
}
}
fn resample_segment<P, S>(sink: &mut S, projection: &P, from: SegmentPoint, to: SegmentPoint, stroke: bool, recursion_limit: u32, resolution: f32)
where
P: Projection,
S: DrawPath<Point = ProjectedPoint>,
{
let cos_min_distance = 30.0f32.to_radians().cos();
let dx = to.projected.x - from.projected.x;
let dy = to.projected.y - from.projected.y;
let dist_square = dx*dx + dy*dy;
if dist_square > (4.0 * resolution) && recursion_limit > 1 {
let mid_cartesian = {
// normalize mid point between from and to (onto sphere)
let s = from.cartesian + to.cartesian; // normalizing anyway, drop `*0.5`
s / (s * s).sqrt()
};
let mid_lambda = if (mid_cartesian.c.abs() - 1.0).abs() < F32_PRECISION
|| (from.input.lambda - to.input.lambda).abs() < F32_PRECISION
{
// close to poles (a and b will be near zero, atan2 would fail)
// or from/to lambdas close together (atan2 should work though in this case, but it would convert -pi to pi)
(from.input.lambda + to.input.lambda) / 2.0
} else {
f32::atan2(mid_cartesian.b, mid_cartesian.a)
};
let mid_input = RadianPoint {
phi: mid_cartesian.c.asin(),
lambda: mid_lambda,
};
let mid = SegmentPoint {
input: mid_input,
cartesian: mid_cartesian,
projected: projection.project(mid_input),
};
let dx2 = mid.projected.x - from.projected.x;
let dy2 = mid.projected.y - from.projected.y;
// (dy, -dx): orthogonal vector to (dx, dy)
// norm(dy, -dx) * (dx2, dy2): (projected) distance of mid from line between from and to
let mid_line_dist_square = {
let d = dy*dx2 - dx*dy2;
(d * d) / dist_square
};
// norm(dx, dy) * (dx2, dy2): (projected) "progress" of mid *on* the line between from and to
// let mid_progress = (dx*dx2 + dy*dy2) / dist_square;
if mid_line_dist_square > resolution // perpendicular projected distance
// /* this is broken and probably not needed */ || (mid_progress - 0.5).abs() > 0.3 // midpoint close to an end
|| (to.cartesian * from.cartesian) < cos_min_distance // angular distance
{
resample_segment(sink, projection, from, mid, stroke, recursion_limit - 1, resolution);
sink.line(mid.projected, stroke);
resample_segment(sink, projection, mid, to, stroke, recursion_limit - 1, resolution);
}
}
}
pub struct ResamplePath<P: Projection, S> {
projection: P,
sink: S,
resolution: f32,
recursion_limit: u32,
start_prev: Option<(bool, SegmentPoint, SegmentPoint)>,
}
impl<P: Projection, S: DrawPath<Point=ProjectedPoint>> ResamplePath<P, S> {
pub fn new(projection: P, sink: S, resolution: f32) -> Self {
Self {
projection,
sink,
resolution,
recursion_limit: 16,
start_prev: None,
}
}
}
impl<P: Projection, S: DrawPath<Point=ProjectedPoint>> PathTransformer for ResamplePath<P, S> {
type Sink = S;
type Point = RadianPoint;
fn sink(&mut self) -> &mut Self::Sink {
&mut self.sink
}
fn transform_line(&mut self, to: Self::Point, stroke: bool) {
let to = SegmentPoint::new(&self.projection, to);
if let Some((_init_stroke, _start, prev)) = &mut self.start_prev {
resample_segment(&mut self.sink, &self.projection, *prev, to, stroke, self.recursion_limit, self.resolution);
self.sink.line(to.projected, stroke);
*prev = to;
} else {
self.sink.line(to.projected, stroke);
self.start_prev = Some((stroke, to, to));
}
}
fn transform_ring_end(&mut self) {
if let Some((init_stroke, start, prev)) = self.start_prev.take() {
resample_segment(&mut self.sink, &self.projection, prev, start, init_stroke, self.recursion_limit, self.resolution);
}
self.sink().ring_end();
}
fn transform_path_end(&mut self) {
self.start_prev = None;
self.sink().path_end();
}
}