Skip to main content

blizz_ui/components/mascot/
math.rs

1pub(super) const ANCHOR_ROW: f64 = 10.0;
2pub(super) const ANCHOR_COL: f64 = 19.0;
3pub(super) const CORE_ROW_OFFSET: f64 = 7.0;
4pub(super) const CHAR_ASPECT_RATIO: f64 = 0.5;
5
6pub(super) const CORE_GROW_SPEED: f64 = 0.2;
7pub(super) const CORE_MAX_RADIUS: f64 = 2.0;
8pub(super) const CORE_HOLD_TICKS: usize = 70;
9pub(super) const CROSSFADE_TICKS: usize = 20;
10
11#[derive(Clone, Debug)]
12pub struct DistanceMap {
13  pub(super) distances: Vec<Vec<f64>>,
14  pub max_distance: f64,
15}
16
17pub fn build_distance_map(lines: &[&str]) -> DistanceMap {
18  let mut max_distance: f64 = 0.0;
19  let distances: Vec<Vec<f64>> = lines
20    .iter()
21    .enumerate()
22    .map(|(row, line)| {
23      line
24        .chars()
25        .enumerate()
26        .map(|(col, ch)| char_distance(row, col, ch, &mut max_distance))
27        .collect()
28    })
29    .collect();
30
31  DistanceMap {
32    distances,
33    max_distance,
34  }
35}
36
37fn char_distance(row: usize, col: usize, ch: char, max_distance: &mut f64) -> f64 {
38  if ch == ' ' {
39    return f64::INFINITY;
40  }
41  let d = euclidean_distance(row as f64, col as f64);
42  if d > *max_distance {
43    *max_distance = d;
44  }
45  d
46}
47
48pub fn distance_at(map: &DistanceMap, row: usize, col: usize) -> f64 {
49  map.distances[row][col]
50}
51
52/// Weighted Euclidean distance from an arbitrary anchor point.
53///
54/// `deltas` is a slice of `(component_difference, weight)` pairs.
55/// Generalises to any number of dimensions — 2-D terminal grids today,
56/// 3-D projections or quaternion rotations tomorrow.
57pub(super) fn weighted_distance(deltas: &[(f64, f64)]) -> f64 {
58  deltas
59    .iter()
60    .map(|(d, w)| {
61      let scaled = d * w;
62      scaled * scaled
63    })
64    .sum::<f64>()
65    .sqrt()
66}
67
68pub(super) fn euclidean_distance(row: f64, col: f64) -> f64 {
69  weighted_distance(&[
70    (row - ANCHOR_ROW, 1.0),
71    ((col - ANCHOR_COL), CHAR_ASPECT_RATIO),
72  ])
73}
74
75pub(super) fn core_distance(row: f64, col: f64) -> f64 {
76  weighted_distance(&[
77    (row - (ANCHOR_ROW + CORE_ROW_OFFSET), 1.0),
78    ((col - ANCHOR_COL), CHAR_ASPECT_RATIO),
79  ])
80}
81
82pub(super) fn core_total_ticks() -> usize {
83  core_grow_ticks() + CORE_HOLD_TICKS
84}
85
86pub(super) fn core_grow_ticks() -> usize {
87  (CORE_MAX_RADIUS / CORE_GROW_SPEED).ceil() as usize
88}
89
90pub(super) fn core_radius(tick: usize) -> f64 {
91  let grow_ticks = core_grow_ticks();
92  if tick < grow_ticks {
93    tick as f64 * CORE_GROW_SPEED
94  } else {
95    CORE_MAX_RADIUS
96  }
97}
98
99#[cfg(test)]
100mod tests {
101  use super::*;
102
103  fn sample_lines() -> Vec<&'static str> {
104    vec!["  ▓▓▓  ", " ▓▓▓▓▓ ", "▓▓▓▓▓▓▓", " ▓▓▓▓▓ ", "  ▓▓▓  "]
105  }
106
107  #[test]
108  fn distance_map_assigns_infinity_to_spaces() {
109    let lines = sample_lines();
110    let map = build_distance_map(&lines);
111
112    assert_eq!(distance_at(&map, 0, 0), f64::INFINITY);
113    assert_eq!(distance_at(&map, 0, 1), f64::INFINITY);
114  }
115
116  #[test]
117  fn distance_map_computes_finite_for_visible_chars() {
118    let lines = sample_lines();
119    let map = build_distance_map(&lines);
120
121    assert!(distance_at(&map, 2, 3).is_finite());
122    assert!(map.max_distance > 0.0);
123  }
124
125  #[test]
126  fn euclidean_distance_at_anchor_is_zero() {
127    let d = euclidean_distance(ANCHOR_ROW, ANCHOR_COL);
128    assert!(d < 0.01);
129  }
130
131  #[test]
132  fn core_distance_is_finite_for_all_positions() {
133    assert!(core_distance(0.0, 0.0).is_finite());
134    assert!(core_distance(10.0, 19.0).is_finite());
135  }
136
137  #[test]
138  fn core_radius_grows_then_caps() {
139    assert_eq!(core_radius(0), 0.0);
140    let grow = core_grow_ticks();
141    assert!((core_radius(grow) - CORE_MAX_RADIUS).abs() < 0.01);
142    assert!((core_radius(grow + 100) - CORE_MAX_RADIUS).abs() < 0.01);
143  }
144
145  #[test]
146  fn core_total_ticks_is_sum_of_grow_and_hold() {
147    let total = core_total_ticks();
148    assert_eq!(total, core_grow_ticks() + CORE_HOLD_TICKS);
149  }
150}