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 }