pub struct Geodesic {
pub start: StartHint,
pub bridge: u16,
}Expand description
Reveal along the “spine” of the art.
The ink forms a graph under 8-connectivity. We take its largest connected
component (so a stray fleck can never hijack the reveal), find the two ends
of that component’s longest geodesic, a double breadth-first sweep, the
standard graph-diameter trick, and rank each cell by geodesic distance from
the chosen start, normalised to 0..=1. A serpent therefore paints from one
tip to the other along its body, around every coil, with no per-art tuning.
Hand-drawn ASCII is usually many separate strokes, not one connected line.
When the ink is fragmented the spine bridges small gaps so the whole body
still traces as one path, head to tail; when it is already mostly connected it
is traced strictly, with no shortcuts. The switch is automatic (see
[Spine::solve] and Geodesic::bridge).
Detached ink (shading, flecks, a signature) does not dump at the end. Every cell inherits the rank of the nearest spine cell, a geodesic Voronoi computed by a multi-source flood, so detail reveals in step with the body it sits beside. Both behaviours fall out of one metric; neither is a special case, so imperfect hand-drawn art still reveals gracefully.
Fields§
§start: StartHintWhich tip of the spine the reveal begins from.
bridge: u16The largest gap, in blank cells, the spine may step across. Bridging only
engages when the art is actually fragmented (see [Spine::solve]), so it
stitches the separate strokes of hand-drawn ASCII into one body without ever
adding shortcuts to art that was already connected. 0 disables it.
Implementations§
Source§impl Geodesic
impl Geodesic
Sourcepub fn diagnose(&self, art: &Art) -> GeodesicReport
pub fn diagnose(&self, art: &Art) -> GeodesicReport
Inspect the art without building a full rank map.
Examples found in repository?
20fn main() {
21 let args: Vec<String> = std::env::args().skip(1).collect();
22 let snapshots = args.iter().any(|a| a == "--snapshots");
23
24 let art = match arg_value(&args, "--art") {
25 Some(path) => match std::fs::read_to_string(&path) {
26 Ok(text) => Art::parse(&text),
27 Err(e) => {
28 eprintln!("inkling: could not read {path}: {e}");
29 std::process::exit(1);
30 }
31 },
32 None => Art::parse(&serpent(64, 13)),
33 };
34
35 let ordering = Geodesic::default();
36 let GeodesicReport {
37 ink_cells,
38 connected_cells,
39 spine_length,
40 } = ordering.diagnose(&art);
41 let ranks = ordering.rank(&art);
42
43 eprintln!(
44 "inkling · {ink_cells} ink cells · {connected_cells} on the spine \
45 ({:.0}% connected) · spine length {spine_length}",
46 100.0 * connected_cells as f32 / ink_cells.max(1) as f32,
47 );
48
49 // Headless / piped / explicit: print staged text frames and exit.
50 if snapshots || !std::io::stdout().is_terminal() {
51 for p in [0.0, 0.2, 0.4, 0.6, 0.8, 1.0] {
52 println!("\n── progress {:>3.0}% {}", p * 100.0, "─".repeat(28));
53 print!("{}", frame::to_string(&art, &ranks, p));
54 }
55 return;
56 }
57
58 #[cfg(feature = "terminal")]
59 {
60 use inkling::{
61 easing::Easing,
62 render::{animate, Style},
63 };
64 use std::time::Duration;
65 if let Err(e) = animate(
66 &art,
67 &ranks,
68 Style::default(),
69 Duration::from_millis(3500),
70 Easing::EaseInOutCubic,
71 ) {
72 eprintln!("inkling: render error: {e}");
73 }
74 }
75}