git.lucas.co / cce-model
3D model viewer: STL and OBJ
git clone https://git.lucas.co/cce-model.git

src/units.rs (2.9K)

 1 //! Lengths as the HUD says them: in millimetres, or metres once they reach a
 2 //! metre, when the file's format says what its units are; as bare numbers
 3 //! when it does not (OBJ, PLY), since calling those millimetres would be a
 4 //! guess presented as a measurement.
 5 
 6 use cce_mesh_io::Unit;
 7 use glam::Vec3;
 8 
 9 /// The smallest of 1, 2 or 5 × 10ⁿ that is at least `raw`: a step a ruler
10 /// would use.
11 pub fn nice_step(raw: f32) -> f32 {
12     if !(raw > 0.0) || !raw.is_finite() {
13         return 1.0;
14     }
15     // In f64, so 5 × 10⁻² comes out as the f32 nearest 0.05, not a hair
16     // under it: the grid's lines are whole multiples of this.
17     let raw = raw as f64;
18     let decade = 10f64.powi(raw.log10().floor() as i32);
19     for m in [1.0, 2.0, 5.0] {
20         if m * decade >= raw * (1.0 - 1e-6) {
21             return (m * decade) as f32;
22         }
23     }
24     (10.0 * decade) as f32
25 }
26 
27 /// About three significant figures, without trailing zeros.
28 fn number(v: f32) -> String {
29     let s = match v.abs() {
30         a if a >= 100.0 => format!("{v:.0}"),
31         a if a >= 10.0 => format!("{v:.1}"),
32         a if a >= 1.0 => format!("{v:.2}"),
33         _ => format!("{v:.3}"),
34     };
35     if s.contains('.') {
36         s.trim_end_matches('0').trim_end_matches('.').to_string()
37     } else {
38         s
39     }
40 }
41 
42 /// The unit suffix and divisor for lengths up to `largest` file units.
43 fn scale(largest: f32, unit: Unit) -> (&'static str, f32) {
44     match unit.millimetres() {
45         Some(mm) if largest * mm >= 1000.0 => (" m", mm / 1000.0),
46         Some(mm) => (" mm", mm),
47         None => ("", 1.0),
48     }
49 }
50 
51 /// One length in file units, as said.
52 pub fn length(v: f32, unit: Unit) -> String {
53     let (suffix, k) = scale(v, unit);
54     format!("{}{suffix}", number(v * k))
55 }
56 
57 /// A box's width × height × depth, all in the unit its largest side needs.
58 pub fn dimensions(size: Vec3, unit: Unit) -> String {
59     let (suffix, k) = scale(size.max_element(), unit);
60     format!("{} × {} × {}{suffix}", number(size.x * k), number(size.y * k), number(size.z * k))
61 }
62 
63 #[cfg(test)]
64 mod tests {
65     use super::*;
66 
67     #[test]
68     fn steps_are_one_two_five() {
69         assert_eq!(nice_step(10.0), 10.0);
70         assert_eq!(nice_step(11.0), 20.0);
71         assert_eq!(nice_step(3.0), 5.0);
72         assert_eq!(nice_step(0.07), 0.1);
73         assert_eq!(nice_step(0.0), 1.0);
74     }
75 
76     #[test]
77     fn lengths_say_their_unit_only_when_the_format_does() {
78         assert_eq!(length(10.0, Unit::Millimetre), "10 mm");
79         assert_eq!(length(0.5, Unit::Metre), "500 mm");
80         assert_eq!(length(2.5, Unit::Metre), "2.5 m");
81         assert_eq!(length(0.25, Unit::Unspecified), "0.25");
82         assert_eq!(dimensions(Vec3::new(120.0, 45.26, 80.0), Unit::Millimetre), "120 × 45.3 × 80 mm");
83         assert_eq!(dimensions(Vec3::new(1.8, 0.75, 0.9), Unit::Metre), "1.8 × 0.75 × 0.9 m");
84         assert_eq!(dimensions(Vec3::new(2.0, 1.0, 0.5), Unit::Unspecified), "2 × 1 × 0.5");
85     }
86 }