pub struct Geodesic {
pub start: StartHint,
pub bridge: u16,
}Expand description
Reveal by tracing the art’s skeleton.
The ink is first thinned to a one-cell-wide skeleton (Zhang-Suen), the centerline a pen would draw. Each connected piece of that skeleton is traced tip to tip by geodesic distance, a double breadth-first sweep finding the two ends of its longest path, and the pieces are ordered along the art’s dominant axis. So a snake paints head to tail, a filled dragon paints down its spine, and a multi-letter logo paints letter by letter in reading order, with no per-art tuning.
Hand-drawn ASCII is usually many separate strokes, not one connected line, so the
trace bridges small gaps to stitch a broken stroke into one piece; art that is
already whole is traced strictly, with no shortcuts (see Geodesic::bridge).
The flesh around the skeleton inherits the value of its nearest centerline cell, a Voronoi flood, so detail reveals in step with the part of the spine it hangs from; where the skeleton is a mere dot, as in a solid blob, the fill radiates out from the middle. Finally the values are rank-transformed to evenly spaced ranks, so the reveal keeps its order yet tracks the progress bar with no dead zone at either end.
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}