From 98b0e1594f11b3e4e05d1a05d10693dfb2b04634 Mon Sep 17 00:00:00 2001 From: Dominik Strebel Date: Sun, 25 Oct 2026 19:27:46 +0100 Subject: [PATCH 1/2] fix(render): marks along a line on the globe follow it up to the horizon --- crates/render/src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index d7076f5c8..3a374e413 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -723,6 +723,33 @@ impl Ctx<'_> { fn marker_runs(&self, parts: &[Vec]) -> Vec { let ppp = self.view.pixels_per_point; let mut runs = Vec::new(); + if let Some(g) = &self.globe { + // On the globe, along the same bent, horizon-cut points as `globe_line` draws. + let h = g.horizon(); + let px = |v: V3| { + let (x, y) = g.project(v); + (x * ppp, y * ppp) + }; + for part in parts { + let mut run: Vec<(f64, f64)> = Vec::new(); + let mut prev: Option = None; + for v in Self::globe_vectors(g, part) { + let vis = v[2] > h; + match prev { + Some(p) if p[2] > h && !vis => { + run.push(px(g.horizon_crossing(p, v))); + runs.extend(line_markers::Run::new(std::mem::take(&mut run))); + } + Some(p) if p[2] <= h && vis => run.extend([px(g.horizon_crossing(p, v)), px(v)]), + _ if vis => run.push(px(v)), + _ => {} + } + prev = Some(v); + } + runs.extend(line_markers::Run::new(run)); + } + return runs; + } for part in parts { let pts: Vec> = part.iter().map(|c| self.screen(*c).map(|(x, y)| (x * ppp, y * ppp))).collect(); for run in pts.split(|p| p.is_none_or(|(x, y)| !x.is_finite() || !y.is_finite())) { @@ -5789,6 +5816,29 @@ mod tests { assert!((0..200).any(|x| r.pixmap.pixel(x, 100).unwrap().red() > 0), "marks show"); } + #[test] + fn markers_on_the_globe_follow_the_line_up_to_the_horizon() { + use rustgis_map::{LineMarkers, LineStroke}; + // A line from lon -30 to lon 120 seen over lon 10: its far end is behind the globe, but + // the part up to the horizon shows, so its marks do. + let mut m = Map::new("Map"); + m.basemap = rustgis_map::Basemap::Blank; + m.projection = rustgis_map::Projection::Globe; + let mut d = Dataset::new("rail", GeometryType::Polyline, vec![]); + d.push(Some(Geometry::Line(vec![vec![Coord::new(-30.0, 0.0), Coord::new(120.0, 0.0)]])), vec![]); + let id = m.add_dataset(d); + let red = Rgba::rgb(255, 0, 0); + let squares = + LineMarkers { mark: rustgis_map::LineMark::Marker(Marker::Square), size: 6.0, interval: 20.0, ..LineMarkers::default() }; + m.layer_mut(id).unwrap().line_strokes = vec![LineStroke { color: Some(red), markers: Some(squares), ..Default::default() }]; + let camera = Camera { center: lonlat_to_merc(Coord::new(10.0, 0.0)), res: 40_000.0, bearing: 0.0, pitch: 0.0 }; + let view = View { width: 400.0, height: 400.0, pixels_per_point: 1.0, camera, sky: false }; + let r = render(&mut m, &view).unwrap(); + let reds: Vec = + (0..400).filter(|x| (0..400).any(|y| r.pixmap.pixel(*x, y).is_some_and(|p| p.red() > 200 && p.green() < 60))).collect(); + assert!(reds.len() > 20, "marks along the visible part: {reds:?}"); + } + #[test] fn a_class_draws_its_own_stacked_strokes() { // Two lines of two Unique Values classes: "road" has a black casing and a red core of its From 7a097ce8de07dbd728f8ffba9b34029dd1547ace Mon Sep 17 00:00:00 2001 From: Dominik Strebel Date: Sun, 25 Oct 2026 19:53:34 +0100 Subject: [PATCH 2/2] test(render): the globe marker test starts and ends behind the globe --- crates/render/src/lib.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 3a374e413..e74be4a50 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -5819,13 +5819,14 @@ mod tests { #[test] fn markers_on_the_globe_follow_the_line_up_to_the_horizon() { use rustgis_map::{LineMarkers, LineStroke}; - // A line from lon -30 to lon 120 seen over lon 10: its far end is behind the globe, but - // the part up to the horizon shows, so its marks do. + // A line from lon -120 over lon 10 to lon 120, seen over lon 10: both its ends are behind + // the globe, its middle vertex alone shows, yet the parts up to the horizon on either side + // show too, so their marks do. let mut m = Map::new("Map"); m.basemap = rustgis_map::Basemap::Blank; m.projection = rustgis_map::Projection::Globe; let mut d = Dataset::new("rail", GeometryType::Polyline, vec![]); - d.push(Some(Geometry::Line(vec![vec![Coord::new(-30.0, 0.0), Coord::new(120.0, 0.0)]])), vec![]); + d.push(Some(Geometry::Line(vec![vec![Coord::new(-120.0, 0.0), Coord::new(10.0, 0.0), Coord::new(120.0, 0.0)]])), vec![]); let id = m.add_dataset(d); let red = Rgba::rgb(255, 0, 0); let squares = @@ -5837,6 +5838,8 @@ mod tests { let reds: Vec = (0..400).filter(|x| (0..400).any(|y| r.pixmap.pixel(*x, y).is_some_and(|p| p.red() > 200 && p.green() < 60))).collect(); assert!(reds.len() > 20, "marks along the visible part: {reds:?}"); + // On both sides of the middle, from the horizon in. + assert!(reds.iter().any(|x| *x < 150) && reds.iter().any(|x| *x > 250), "{reds:?}"); } #[test]