1use std::fmt;
18use std::ops::Add;
19use std::ops::Mul;
20use std::ops::Sub;
21
22const BW_TO_METERS: f32 = 30.0;
24
25const SHIP_TO_METERS: f32 = 15.0;
29
30const WORLD_TO_METERS: f32 = SHIP_TO_METERS;
51
52#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
54#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
55#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
56pub struct Meters(f32);
57
58#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
88pub struct BigWorldDistance(f32);
89
90#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
121#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
122#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
123pub struct WorldDistance(f32);
124
125#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
147#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
148#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
149pub struct ShipModelDistance(f32);
150
151#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
153#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
154#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
155pub struct Km(f32);
156
157#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
159#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
160#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
161pub struct Millimeters(f32);
162
163#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
165#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
166#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
167pub struct MetersPerSecond(f32);
168
169#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
171#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
172#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
173pub struct Degrees(f32);
174
175#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
178#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
179#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
180pub struct Radians(f32);
181
182#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
184#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
185#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
186pub struct Kilograms(f32);
187
188#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
190#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
191#[cfg_attr(feature = "rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))]
192pub struct Seconds(f32);
193
194impl From<f32> for Meters {
195 fn from(v: f32) -> Self {
196 Self(v)
197 }
198}
199impl From<i32> for Meters {
200 fn from(v: i32) -> Self {
201 Self(v as f32)
202 }
203}
204
205impl From<f32> for BigWorldDistance {
206 fn from(v: f32) -> Self {
207 Self(v)
208 }
209}
210impl From<i32> for BigWorldDistance {
211 fn from(v: i32) -> Self {
212 Self(v as f32)
213 }
214}
215
216impl From<f32> for ShipModelDistance {
217 fn from(v: f32) -> Self {
218 Self(v)
219 }
220}
221
222impl From<f32> for WorldDistance {
223 fn from(v: f32) -> Self {
224 Self(v)
225 }
226}
227
228impl From<f32> for Km {
229 fn from(v: f32) -> Self {
230 Self(v)
231 }
232}
233impl From<i32> for Km {
234 fn from(v: i32) -> Self {
235 Self(v as f32)
236 }
237}
238
239impl From<f32> for Millimeters {
240 fn from(v: f32) -> Self {
241 Self(v)
242 }
243}
244impl From<i32> for Millimeters {
245 fn from(v: i32) -> Self {
246 Self(v as f32)
247 }
248}
249
250impl From<f32> for MetersPerSecond {
251 fn from(v: f32) -> Self {
252 Self(v)
253 }
254}
255impl From<i32> for MetersPerSecond {
256 fn from(v: i32) -> Self {
257 Self(v as f32)
258 }
259}
260
261impl From<f32> for Degrees {
262 fn from(v: f32) -> Self {
263 Self(v)
264 }
265}
266
267impl From<f32> for Radians {
268 fn from(v: f32) -> Self {
269 Self(v)
270 }
271}
272
273impl From<f32> for Kilograms {
274 fn from(v: f32) -> Self {
275 Self(v)
276 }
277}
278
279impl From<f32> for Seconds {
280 fn from(v: f32) -> Self {
281 Self(v)
282 }
283}
284
285impl Meters {
286 pub const fn new(v: f32) -> Self {
288 Self(v)
289 }
290
291 pub fn value(self) -> f32 {
292 self.0
293 }
294 pub fn to_bigworld(self) -> BigWorldDistance {
295 BigWorldDistance(self.0 / BW_TO_METERS)
296 }
297 pub fn to_ship_model(self) -> ShipModelDistance {
301 ShipModelDistance(self.0 / SHIP_TO_METERS)
302 }
303 pub fn to_km(self) -> Km {
304 Km(self.0 / 1000.0)
305 }
306 pub fn to_mm(self) -> Millimeters {
307 Millimeters(self.0 * 1000.0)
308 }
309 pub fn to_world(self) -> WorldDistance {
312 WorldDistance(self.0 / WORLD_TO_METERS)
313 }
314}
315
316impl BigWorldDistance {
317 pub fn value(self) -> f32 {
318 self.0
319 }
320 pub fn to_meters(self) -> Meters {
321 Meters(self.0 * BW_TO_METERS)
322 }
323 pub fn to_km(self) -> Km {
324 self.to_meters().to_km()
325 }
326}
327
328impl ShipModelDistance {
329 pub const ZERO: ShipModelDistance = ShipModelDistance(0.0);
330
331 pub const fn new(v: f32) -> Self {
333 Self(v)
334 }
335
336 pub fn value(self) -> f32 {
337 self.0
338 }
339 pub fn max_zero(self) -> Self {
341 Self(self.0.max(0.0))
342 }
343 pub fn to_meters(self) -> Meters {
344 Meters(self.0 * SHIP_TO_METERS)
345 }
346 pub fn to_bigworld(self) -> BigWorldDistance {
347 self.to_meters().to_bigworld()
348 }
349}
350
351impl WorldDistance {
352 pub const fn new(v: f32) -> Self {
354 Self(v)
355 }
356
357 pub fn value(self) -> f32 {
358 self.0
359 }
360 pub fn to_meters(self) -> Meters {
361 Meters(self.0 * WORLD_TO_METERS)
362 }
363 pub fn to_km(self) -> Km {
364 self.to_meters().to_km()
365 }
366}
367
368impl Km {
369 pub const fn new(v: f32) -> Self {
371 Self(v)
372 }
373
374 pub fn value(self) -> f32 {
375 self.0
376 }
377 pub fn to_meters(self) -> Meters {
378 Meters(self.0 * 1000.0)
379 }
380 pub fn to_bigworld(self) -> BigWorldDistance {
381 self.to_meters().to_bigworld()
382 }
383}
384
385impl Millimeters {
386 pub const fn new(v: f32) -> Self {
388 Self(v)
389 }
390
391 pub fn value(self) -> f32 {
392 self.0
393 }
394 pub fn to_meters(self) -> Meters {
395 Meters(self.0 / 1000.0)
396 }
397 pub fn to_bigworld(self) -> BigWorldDistance {
398 self.to_meters().to_bigworld()
399 }
400}
401
402impl MetersPerSecond {
403 pub const fn new(v: f32) -> Self {
405 Self(v)
406 }
407
408 pub fn value(self) -> f32 {
409 self.0
410 }
411
412 pub fn travel_over(self, duration: Seconds) -> Meters {
414 Meters(self.0 * duration.0)
415 }
416}
417
418impl Degrees {
419 pub const fn new(v: f32) -> Self {
421 Self(v)
422 }
423
424 pub fn value(self) -> f32 {
425 self.0
426 }
427 pub fn to_radians(self) -> Radians {
428 Radians(self.0.to_radians())
429 }
430 pub fn abs(self) -> Self {
431 Self(self.0.abs())
432 }
433}
434
435impl Radians {
436 pub const fn new(v: f32) -> Self {
438 Self(v)
439 }
440
441 pub fn value(self) -> f32 {
442 self.0
443 }
444 pub fn to_degrees(self) -> Degrees {
445 Degrees(self.0.to_degrees())
446 }
447 pub fn abs(self) -> Self {
448 Self(self.0.abs())
449 }
450 pub fn cos(self) -> f32 {
451 self.0.cos()
452 }
453 pub fn sin(self) -> f32 {
454 self.0.sin()
455 }
456 pub fn max_zero(self) -> Self {
460 Self(self.0.max(0.0))
461 }
462}
463
464impl Kilograms {
465 pub const fn new(v: f32) -> Self {
467 Self(v)
468 }
469
470 pub fn value(self) -> f32 {
471 self.0
472 }
473}
474
475impl Seconds {
476 pub const fn new(v: f32) -> Self {
478 Self(v)
479 }
480
481 pub fn value(self) -> f32 {
482 self.0
483 }
484}
485
486impl fmt::Display for Meters {
488 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
489 write!(f, "{:.0} m", self.0)
490 }
491}
492
493impl fmt::Display for Km {
495 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
496 write!(f, "{:.1} km", self.0)
497 }
498}
499
500impl fmt::Display for Millimeters {
502 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
503 write!(f, "{:.0} mm", self.0)
504 }
505}
506
507impl fmt::Display for MetersPerSecond {
509 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510 write!(f, "{:.0} m/s", self.0)
511 }
512}
513
514impl fmt::Display for Degrees {
515 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
516 write!(f, "{:.1} deg", self.0)
517 }
518}
519
520impl fmt::Display for Kilograms {
521 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522 write!(f, "{:.0} kg", self.0)
523 }
524}
525
526impl fmt::Display for Seconds {
527 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528 write!(f, "{:.1} s", self.0)
529 }
530}
531
532impl Add for Degrees {
533 type Output = Degrees;
534 fn add(self, rhs: Degrees) -> Degrees {
535 Degrees(self.0 + rhs.0)
536 }
537}
538impl Sub for Degrees {
539 type Output = Degrees;
540 fn sub(self, rhs: Degrees) -> Degrees {
541 Degrees(self.0 - rhs.0)
542 }
543}
544
545impl Add for Radians {
546 type Output = Radians;
547 fn add(self, rhs: Radians) -> Radians {
548 Radians(self.0 + rhs.0)
549 }
550}
551impl Sub for Radians {
552 type Output = Radians;
553 fn sub(self, rhs: Radians) -> Radians {
554 Radians(self.0 - rhs.0)
555 }
556}
557
558impl Add for Seconds {
559 type Output = Seconds;
560 fn add(self, rhs: Seconds) -> Seconds {
561 Seconds(self.0 + rhs.0)
562 }
563}
564impl Sub for Seconds {
565 type Output = Seconds;
566 fn sub(self, rhs: Seconds) -> Seconds {
567 Seconds(self.0 - rhs.0)
568 }
569}
570
571impl Mul<f32> for Seconds {
572 type Output = Seconds;
573 fn mul(self, rhs: f32) -> Seconds {
574 Seconds(self.0 * rhs)
575 }
576}
577
578impl Mul<f32> for Meters {
579 type Output = Meters;
580 fn mul(self, rhs: f32) -> Meters {
581 Meters(self.0 * rhs)
582 }
583}
584
585impl Mul<f32> for BigWorldDistance {
586 type Output = BigWorldDistance;
587 fn mul(self, rhs: f32) -> BigWorldDistance {
588 BigWorldDistance(self.0 * rhs)
589 }
590}
591
592impl Mul<f32> for Km {
593 type Output = Km;
594 fn mul(self, rhs: f32) -> Km {
595 Km(self.0 * rhs)
596 }
597}
598
599impl Mul<f32> for Millimeters {
600 type Output = Millimeters;
601 fn mul(self, rhs: f32) -> Millimeters {
602 Millimeters(self.0 * rhs)
603 }
604}
605
606impl Add for Meters {
607 type Output = Meters;
608 fn add(self, rhs: Meters) -> Meters {
609 Meters(self.0 + rhs.0)
610 }
611}
612impl Sub for Meters {
613 type Output = Meters;
614 fn sub(self, rhs: Meters) -> Meters {
615 Meters(self.0 - rhs.0)
616 }
617}
618
619impl Add for BigWorldDistance {
620 type Output = BigWorldDistance;
621 fn add(self, rhs: BigWorldDistance) -> BigWorldDistance {
622 BigWorldDistance(self.0 + rhs.0)
623 }
624}
625impl Sub for BigWorldDistance {
626 type Output = BigWorldDistance;
627 fn sub(self, rhs: BigWorldDistance) -> BigWorldDistance {
628 BigWorldDistance(self.0 - rhs.0)
629 }
630}
631
632impl Add for ShipModelDistance {
633 type Output = ShipModelDistance;
634 fn add(self, rhs: ShipModelDistance) -> ShipModelDistance {
635 ShipModelDistance(self.0 + rhs.0)
636 }
637}
638impl Sub for ShipModelDistance {
639 type Output = ShipModelDistance;
640 fn sub(self, rhs: ShipModelDistance) -> ShipModelDistance {
641 ShipModelDistance(self.0 - rhs.0)
642 }
643}
644
645impl Add for Km {
646 type Output = Km;
647 fn add(self, rhs: Km) -> Km {
648 Km(self.0 + rhs.0)
649 }
650}
651impl Sub for Km {
652 type Output = Km;
653 fn sub(self, rhs: Km) -> Km {
654 Km(self.0 - rhs.0)
655 }
656}
657
658impl Add for Millimeters {
659 type Output = Millimeters;
660 fn add(self, rhs: Millimeters) -> Millimeters {
661 Millimeters(self.0 + rhs.0)
662 }
663}
664impl Sub for Millimeters {
665 type Output = Millimeters;
666 fn sub(self, rhs: Millimeters) -> Millimeters {
667 Millimeters(self.0 - rhs.0)
668 }
669}
670
671impl Add<BigWorldDistance> for Meters {
672 type Output = Meters;
673 fn add(self, rhs: BigWorldDistance) -> Meters {
674 Meters(self.0 + rhs.to_meters().0)
675 }
676}
677impl Sub<BigWorldDistance> for Meters {
678 type Output = Meters;
679 fn sub(self, rhs: BigWorldDistance) -> Meters {
680 Meters(self.0 - rhs.to_meters().0)
681 }
682}
683
684impl Add<Meters> for BigWorldDistance {
685 type Output = BigWorldDistance;
686 fn add(self, rhs: Meters) -> BigWorldDistance {
687 BigWorldDistance(self.0 + rhs.to_bigworld().0)
688 }
689}
690impl Sub<Meters> for BigWorldDistance {
691 type Output = BigWorldDistance;
692 fn sub(self, rhs: Meters) -> BigWorldDistance {
693 BigWorldDistance(self.0 - rhs.to_bigworld().0)
694 }
695}
696
697impl Add<Km> for Meters {
698 type Output = Meters;
699 fn add(self, rhs: Km) -> Meters {
700 Meters(self.0 + rhs.to_meters().0)
701 }
702}
703impl Sub<Km> for Meters {
704 type Output = Meters;
705 fn sub(self, rhs: Km) -> Meters {
706 Meters(self.0 - rhs.to_meters().0)
707 }
708}
709
710impl Add<Meters> for Km {
711 type Output = Km;
712 fn add(self, rhs: Meters) -> Km {
713 Km(self.0 + rhs.to_km().0)
714 }
715}
716impl Sub<Meters> for Km {
717 type Output = Km;
718 fn sub(self, rhs: Meters) -> Km {
719 Km(self.0 - rhs.to_km().0)
720 }
721}
722
723impl std::ops::Div<f32> for Meters {
724 type Output = Meters;
725 fn div(self, rhs: f32) -> Meters {
726 Meters(self.0 / rhs)
727 }
728}
729
730impl std::ops::Div<f32> for BigWorldDistance {
731 type Output = BigWorldDistance;
732 fn div(self, rhs: f32) -> BigWorldDistance {
733 BigWorldDistance(self.0 / rhs)
734 }
735}
736
737impl std::ops::Div<f32> for Km {
738 type Output = Km;
739 fn div(self, rhs: f32) -> Km {
740 Km(self.0 / rhs)
741 }
742}
743
744impl std::ops::Div<f32> for Millimeters {
745 type Output = Millimeters;
746 fn div(self, rhs: f32) -> Millimeters {
747 Millimeters(self.0 / rhs)
748 }
749}
750
751impl std::iter::Sum for Meters {
752 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
753 Meters(iter.map(|m| m.0).sum())
754 }
755}
756
757impl std::iter::Sum for BigWorldDistance {
758 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
759 BigWorldDistance(iter.map(|d| d.0).sum())
760 }
761}
762
763impl std::iter::Sum for Km {
764 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
765 Km(iter.map(|k| k.0).sum())
766 }
767}
768
769impl std::iter::Sum for Millimeters {
770 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
771 Millimeters(iter.map(|m| m.0).sum())
772 }
773}
774
775impl PartialEq<BigWorldDistance> for Meters {
776 fn eq(&self, other: &BigWorldDistance) -> bool {
777 self.0 == other.to_meters().0
778 }
779}
780impl PartialOrd<BigWorldDistance> for Meters {
781 fn partial_cmp(&self, other: &BigWorldDistance) -> Option<std::cmp::Ordering> {
782 self.0.partial_cmp(&other.to_meters().0)
783 }
784}
785
786impl PartialEq<Meters> for BigWorldDistance {
787 fn eq(&self, other: &Meters) -> bool {
788 self.0 == other.to_bigworld().0
789 }
790}
791impl PartialOrd<Meters> for BigWorldDistance {
792 fn partial_cmp(&self, other: &Meters) -> Option<std::cmp::Ordering> {
793 self.0.partial_cmp(&other.to_bigworld().0)
794 }
795}
796
797#[cfg(test)]
798mod tests {
799 use super::*;
800
801 #[test]
802 fn meters_display_rounds_to_whole() {
803 assert_eq!(Meters::from(740.6).to_string(), "741 m");
804 }
805
806 #[test]
807 fn km_display_one_decimal() {
808 assert_eq!(Km::from(10.5).to_string(), "10.5 km");
809 }
810
811 #[test]
812 fn millimeters_display_rounds_to_whole() {
813 assert_eq!(Millimeters::from(406.0).to_string(), "406 mm");
814 }
815
816 #[test]
817 fn meters_per_second_display_rounds_to_whole() {
818 assert_eq!(MetersPerSecond::from(820.0).to_string(), "820 m/s");
819 }
820
821 #[test]
824 fn ship_model_units_are_fifteen_meters() {
825 assert_eq!(ShipModelDistance::from(6.489).to_meters().value(), 97.335);
826 assert_eq!(Meters::from(97.335).to_ship_model().value(), 6.489);
827 }
828
829 #[test]
833 fn replay_world_units_are_fifteen_meters() {
834 assert_eq!(WorldDistance::from(8.0).to_meters().value(), 120.0);
835 assert_eq!(Meters::from(120.0).to_world().value(), 8.0);
836 }
837
838 #[test]
842 fn game_params_and_replay_spaces_are_not_the_same() {
843 let one = 1.0f32;
844 assert_eq!(BigWorldDistance::from(one).to_meters().value(), 30.0);
845 assert_eq!(WorldDistance::from(one).to_meters().value(), 15.0);
846 }
847
848 #[test]
851 fn angles_round_trip_between_units() {
852 assert!((Degrees::from(60.0).to_radians().value() - std::f32::consts::FRAC_PI_3).abs() < 1e-6);
853 assert!((Radians::from(std::f32::consts::FRAC_PI_3).to_degrees().value() - 60.0).abs() < 1e-4);
854 }
855
856 #[test]
859 fn travel_over_multiplies_speed_by_time() {
860 assert!((MetersPerSecond::from(224.0).travel_over(Seconds::from(0.033)).value() - 7.392).abs() < 1e-3);
861 }
862
863 #[test]
867 fn game_params_distances_match_the_published_consumable_ranges() {
868 assert!((BigWorldDistance::from(133.3333).to_km().value() - 4.0).abs() < 0.001);
869 assert_eq!(BigWorldDistance::from(250.0).to_km().value(), 7.5);
870 }
871}