Skip to main content

wows_core/
units.rs

1//! Distance newtypes and their unit conversions.
2//!
3//! The game mixes several distance units: real meters, BigWorld engine units
4//! (1 BW unit = 30 m), ship-model units (used by armor/hull geometry), plus
5//! kilometers and millimeters. These newtypes keep units honest at the type
6//! level; cross-unit arithmetic and comparison convert to a common unit.
7
8use std::fmt;
9use std::ops::Add;
10use std::ops::Mul;
11use std::ops::Sub;
12
13/// Conversion factor: 1 BigWorld unit = 30 meters.
14const BW_TO_METERS: f32 = 30.0;
15
16/// Conversion factor: 1 BigWorld unit = 15 ship-model units.
17/// Ship geometry (armor meshes, hull models) uses this coordinate space
18/// where 1 ship-model unit = 2 real meters (= 30 / 15).
19const BW_TO_SHIP: f32 = 15.0;
20
21/// Distance in meters.
22#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
24#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
25pub struct Meters(f32);
26
27/// Distance in BigWorld coordinate units (1 BW unit = 30 meters).
28#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
31pub struct BigWorldDistance(f32);
32
33/// Distance in ship-model coordinate units (1 unit = 2 meters).
34/// Ship geometry (armor meshes, hull visual models) uses this coordinate space.
35/// The game defines BW_TO_SHIP = 15, meaning 1 BigWorld unit = 15 ship-model units,
36/// so 1 ship-model unit = BW_TO_METERS / BW_TO_SHIP = 30 / 15 = 2 meters.
37#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
40pub struct ShipModelDistance(f32);
41
42/// Distance in kilometers.
43#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
44#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
45#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
46pub struct Km(f32);
47
48/// Distance in millimeters.
49#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
50#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
51#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
52pub struct Millimeters(f32);
53
54/// Speed in meters per second (shell muzzle velocity).
55#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
56#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
57#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
58pub struct MetersPerSecond(f32);
59
60impl From<f32> for Meters {
61    fn from(v: f32) -> Self {
62        Self(v)
63    }
64}
65impl From<i32> for Meters {
66    fn from(v: i32) -> Self {
67        Self(v as f32)
68    }
69}
70
71impl From<f32> for BigWorldDistance {
72    fn from(v: f32) -> Self {
73        Self(v)
74    }
75}
76impl From<i32> for BigWorldDistance {
77    fn from(v: i32) -> Self {
78        Self(v as f32)
79    }
80}
81
82impl From<f32> for Km {
83    fn from(v: f32) -> Self {
84        Self(v)
85    }
86}
87impl From<i32> for Km {
88    fn from(v: i32) -> Self {
89        Self(v as f32)
90    }
91}
92
93impl From<f32> for Millimeters {
94    fn from(v: f32) -> Self {
95        Self(v)
96    }
97}
98impl From<i32> for Millimeters {
99    fn from(v: i32) -> Self {
100        Self(v as f32)
101    }
102}
103
104impl From<f32> for MetersPerSecond {
105    fn from(v: f32) -> Self {
106        Self(v)
107    }
108}
109impl From<i32> for MetersPerSecond {
110    fn from(v: i32) -> Self {
111        Self(v as f32)
112    }
113}
114
115impl Meters {
116    /// Const constructor for use in static/const contexts.
117    pub const fn new(v: f32) -> Self {
118        Self(v)
119    }
120
121    pub fn value(self) -> f32 {
122        self.0
123    }
124    pub fn to_bigworld(self) -> BigWorldDistance {
125        BigWorldDistance(self.0 / BW_TO_METERS)
126    }
127    /// Convert to ship-model units (1 unit = 2 meters).
128    /// Use this for distances that will be compared against ship geometry
129    /// (armor meshes, hull models), which are in ship-model coordinates.
130    pub fn to_ship_model(self) -> ShipModelDistance {
131        ShipModelDistance(self.0 * BW_TO_SHIP / BW_TO_METERS)
132    }
133    pub fn to_km(self) -> Km {
134        Km(self.0 / 1000.0)
135    }
136    pub fn to_mm(self) -> Millimeters {
137        Millimeters(self.0 * 1000.0)
138    }
139}
140
141impl BigWorldDistance {
142    pub fn value(self) -> f32 {
143        self.0
144    }
145    pub fn to_meters(self) -> Meters {
146        Meters(self.0 * BW_TO_METERS)
147    }
148    pub fn to_km(self) -> Km {
149        self.to_meters().to_km()
150    }
151}
152
153impl ShipModelDistance {
154    pub fn value(self) -> f32 {
155        self.0
156    }
157    pub fn to_meters(self) -> Meters {
158        Meters(self.0 * BW_TO_METERS / BW_TO_SHIP)
159    }
160    pub fn to_bigworld(self) -> BigWorldDistance {
161        BigWorldDistance(self.0 / BW_TO_SHIP)
162    }
163}
164
165impl Km {
166    /// Const constructor for use in static/const contexts.
167    pub const fn new(v: f32) -> Self {
168        Self(v)
169    }
170
171    pub fn value(self) -> f32 {
172        self.0
173    }
174    pub fn to_meters(self) -> Meters {
175        Meters(self.0 * 1000.0)
176    }
177    pub fn to_bigworld(self) -> BigWorldDistance {
178        self.to_meters().to_bigworld()
179    }
180}
181
182impl Millimeters {
183    /// Const constructor for use in static/const contexts.
184    pub const fn new(v: f32) -> Self {
185        Self(v)
186    }
187
188    pub fn value(self) -> f32 {
189        self.0
190    }
191    pub fn to_meters(self) -> Meters {
192        Meters(self.0 / 1000.0)
193    }
194    pub fn to_bigworld(self) -> BigWorldDistance {
195        self.to_meters().to_bigworld()
196    }
197}
198
199impl MetersPerSecond {
200    /// Const constructor for use in static/const contexts.
201    pub const fn new(v: f32) -> Self {
202        Self(v)
203    }
204
205    pub fn value(self) -> f32 {
206        self.0
207    }
208}
209
210/// Conventional English-port rounding: whole meters.
211impl fmt::Display for Meters {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        write!(f, "{:.0} m", self.0)
214    }
215}
216
217/// Conventional English-port rounding: one decimal place.
218impl fmt::Display for Km {
219    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220        write!(f, "{:.1} km", self.0)
221    }
222}
223
224/// Conventional English-port rounding: whole millimeters.
225impl fmt::Display for Millimeters {
226    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227        write!(f, "{:.0} mm", self.0)
228    }
229}
230
231/// Conventional English-port rounding: whole meters per second.
232impl fmt::Display for MetersPerSecond {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        write!(f, "{:.0} m/s", self.0)
235    }
236}
237
238impl Mul<f32> for Meters {
239    type Output = Meters;
240    fn mul(self, rhs: f32) -> Meters {
241        Meters(self.0 * rhs)
242    }
243}
244
245impl Mul<f32> for BigWorldDistance {
246    type Output = BigWorldDistance;
247    fn mul(self, rhs: f32) -> BigWorldDistance {
248        BigWorldDistance(self.0 * rhs)
249    }
250}
251
252impl Mul<f32> for Km {
253    type Output = Km;
254    fn mul(self, rhs: f32) -> Km {
255        Km(self.0 * rhs)
256    }
257}
258
259impl Mul<f32> for Millimeters {
260    type Output = Millimeters;
261    fn mul(self, rhs: f32) -> Millimeters {
262        Millimeters(self.0 * rhs)
263    }
264}
265
266impl Add for Meters {
267    type Output = Meters;
268    fn add(self, rhs: Meters) -> Meters {
269        Meters(self.0 + rhs.0)
270    }
271}
272impl Sub for Meters {
273    type Output = Meters;
274    fn sub(self, rhs: Meters) -> Meters {
275        Meters(self.0 - rhs.0)
276    }
277}
278
279impl Add for BigWorldDistance {
280    type Output = BigWorldDistance;
281    fn add(self, rhs: BigWorldDistance) -> BigWorldDistance {
282        BigWorldDistance(self.0 + rhs.0)
283    }
284}
285impl Sub for BigWorldDistance {
286    type Output = BigWorldDistance;
287    fn sub(self, rhs: BigWorldDistance) -> BigWorldDistance {
288        BigWorldDistance(self.0 - rhs.0)
289    }
290}
291
292impl Add for Km {
293    type Output = Km;
294    fn add(self, rhs: Km) -> Km {
295        Km(self.0 + rhs.0)
296    }
297}
298impl Sub for Km {
299    type Output = Km;
300    fn sub(self, rhs: Km) -> Km {
301        Km(self.0 - rhs.0)
302    }
303}
304
305impl Add for Millimeters {
306    type Output = Millimeters;
307    fn add(self, rhs: Millimeters) -> Millimeters {
308        Millimeters(self.0 + rhs.0)
309    }
310}
311impl Sub for Millimeters {
312    type Output = Millimeters;
313    fn sub(self, rhs: Millimeters) -> Millimeters {
314        Millimeters(self.0 - rhs.0)
315    }
316}
317
318impl Add<BigWorldDistance> for Meters {
319    type Output = Meters;
320    fn add(self, rhs: BigWorldDistance) -> Meters {
321        Meters(self.0 + rhs.to_meters().0)
322    }
323}
324impl Sub<BigWorldDistance> for Meters {
325    type Output = Meters;
326    fn sub(self, rhs: BigWorldDistance) -> Meters {
327        Meters(self.0 - rhs.to_meters().0)
328    }
329}
330
331impl Add<Meters> for BigWorldDistance {
332    type Output = BigWorldDistance;
333    fn add(self, rhs: Meters) -> BigWorldDistance {
334        BigWorldDistance(self.0 + rhs.to_bigworld().0)
335    }
336}
337impl Sub<Meters> for BigWorldDistance {
338    type Output = BigWorldDistance;
339    fn sub(self, rhs: Meters) -> BigWorldDistance {
340        BigWorldDistance(self.0 - rhs.to_bigworld().0)
341    }
342}
343
344impl Add<Km> for Meters {
345    type Output = Meters;
346    fn add(self, rhs: Km) -> Meters {
347        Meters(self.0 + rhs.to_meters().0)
348    }
349}
350impl Sub<Km> for Meters {
351    type Output = Meters;
352    fn sub(self, rhs: Km) -> Meters {
353        Meters(self.0 - rhs.to_meters().0)
354    }
355}
356
357impl Add<Meters> for Km {
358    type Output = Km;
359    fn add(self, rhs: Meters) -> Km {
360        Km(self.0 + rhs.to_km().0)
361    }
362}
363impl Sub<Meters> for Km {
364    type Output = Km;
365    fn sub(self, rhs: Meters) -> Km {
366        Km(self.0 - rhs.to_km().0)
367    }
368}
369
370impl std::ops::Div<f32> for Meters {
371    type Output = Meters;
372    fn div(self, rhs: f32) -> Meters {
373        Meters(self.0 / rhs)
374    }
375}
376
377impl std::ops::Div<f32> for BigWorldDistance {
378    type Output = BigWorldDistance;
379    fn div(self, rhs: f32) -> BigWorldDistance {
380        BigWorldDistance(self.0 / rhs)
381    }
382}
383
384impl std::ops::Div<f32> for Km {
385    type Output = Km;
386    fn div(self, rhs: f32) -> Km {
387        Km(self.0 / rhs)
388    }
389}
390
391impl std::ops::Div<f32> for Millimeters {
392    type Output = Millimeters;
393    fn div(self, rhs: f32) -> Millimeters {
394        Millimeters(self.0 / rhs)
395    }
396}
397
398impl std::iter::Sum for Meters {
399    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
400        Meters(iter.map(|m| m.0).sum())
401    }
402}
403
404impl std::iter::Sum for BigWorldDistance {
405    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
406        BigWorldDistance(iter.map(|d| d.0).sum())
407    }
408}
409
410impl std::iter::Sum for Km {
411    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
412        Km(iter.map(|k| k.0).sum())
413    }
414}
415
416impl std::iter::Sum for Millimeters {
417    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
418        Millimeters(iter.map(|m| m.0).sum())
419    }
420}
421
422impl PartialEq<BigWorldDistance> for Meters {
423    fn eq(&self, other: &BigWorldDistance) -> bool {
424        self.0 == other.to_meters().0
425    }
426}
427impl PartialOrd<BigWorldDistance> for Meters {
428    fn partial_cmp(&self, other: &BigWorldDistance) -> Option<std::cmp::Ordering> {
429        self.0.partial_cmp(&other.to_meters().0)
430    }
431}
432
433impl PartialEq<Meters> for BigWorldDistance {
434    fn eq(&self, other: &Meters) -> bool {
435        self.0 == other.to_bigworld().0
436    }
437}
438impl PartialOrd<Meters> for BigWorldDistance {
439    fn partial_cmp(&self, other: &Meters) -> Option<std::cmp::Ordering> {
440        self.0.partial_cmp(&other.to_bigworld().0)
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    #[test]
449    fn meters_display_rounds_to_whole() {
450        assert_eq!(Meters::from(740.6).to_string(), "741 m");
451    }
452
453    #[test]
454    fn km_display_one_decimal() {
455        assert_eq!(Km::from(10.5).to_string(), "10.5 km");
456    }
457
458    #[test]
459    fn millimeters_display_rounds_to_whole() {
460        assert_eq!(Millimeters::from(406.0).to_string(), "406 mm");
461    }
462
463    #[test]
464    fn meters_per_second_display_rounds_to_whole() {
465        assert_eq!(MetersPerSecond::from(820.0).to_string(), "820 m/s");
466    }
467}