Skip to main content

piper_client/types/
units.rs

1//! 强类型单位系统
2//!
3//! 使用 NewType 模式防止单位混淆,在编译期保证类型安全。
4//!
5//! # 设计目标
6//!
7//! - **编译期类型安全**: 防止 `Rad` 与 `Deg` 混用
8//! - **零开销抽象**: NewType 编译后与原始类型性能相同
9//! - **符合人体工程学**: 支持运算符重载和链式调用
10//!
11//! # 示例
12//!
13//! ```rust
14//! use piper_client::types::{Rad, Deg};
15//!
16//! let angle_rad = Rad(std::f64::consts::PI);
17//! let angle_deg = angle_rad.to_deg();
18//! assert!((angle_deg.0 - 180.0).abs() < 1e-6);
19//!
20//! // 类型安全:以下代码无法编译
21//! // let _ = Rad(1.0) + Deg(1.0);  // ❌ 类型不匹配
22//! ```
23
24use std::fmt;
25use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
26
27/// 弧度(NewType)
28///
29/// 表示角度的弧度值。使用 NewType 模式防止与角度值混淆。
30#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
32pub struct Rad(pub f64);
33
34impl Rad {
35    /// 零弧度常量
36    pub const ZERO: Self = Rad(0.0);
37
38    /// π 弧度(180度)
39    pub const PI: Self = Rad(std::f64::consts::PI);
40
41    /// 2π 弧度(360度)
42    pub const TAU: Self = Rad(std::f64::consts::TAU);
43
44    /// π/2 弧度(90度)
45    pub const FRAC_PI_2: Self = Rad(std::f64::consts::FRAC_PI_2);
46
47    /// π/4 弧度(45度)
48    pub const FRAC_PI_4: Self = Rad(std::f64::consts::FRAC_PI_4);
49
50    /// 创建新的弧度值
51    #[inline]
52    pub const fn new(value: f64) -> Self {
53        Rad(value)
54    }
55
56    /// 转换为角度
57    #[inline]
58    pub fn to_deg(self) -> Deg {
59        Deg(self.0.to_degrees())
60    }
61
62    /// 获取原始值
63    #[inline]
64    pub fn value(self) -> f64 {
65        self.0
66    }
67
68    /// 计算正弦值
69    #[inline]
70    pub fn sin(self) -> f64 {
71        self.0.sin()
72    }
73
74    /// 计算余弦值
75    #[inline]
76    pub fn cos(self) -> f64 {
77        self.0.cos()
78    }
79
80    /// 计算正切值
81    #[inline]
82    pub fn tan(self) -> f64 {
83        self.0.tan()
84    }
85
86    /// 取绝对值
87    #[inline]
88    pub fn abs(self) -> Self {
89        Rad(self.0.abs())
90    }
91
92    /// 归一化到 [-π, π] 范围
93    pub fn normalize(self) -> Self {
94        let mut angle = self.0 % std::f64::consts::TAU;
95        if angle > std::f64::consts::PI {
96            angle -= std::f64::consts::TAU;
97        } else if angle < -std::f64::consts::PI {
98            angle += std::f64::consts::TAU;
99        }
100        Rad(angle)
101    }
102
103    /// 限制范围
104    #[inline]
105    pub fn clamp(self, min: Self, max: Self) -> Self {
106        Rad(self.0.clamp(min.0, max.0))
107    }
108}
109
110impl fmt::Display for Rad {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        write!(f, "{:.4} rad", self.0)
113    }
114}
115
116// 运算符重载
117impl Add for Rad {
118    type Output = Self;
119    #[inline]
120    fn add(self, rhs: Self) -> Self {
121        Rad(self.0 + rhs.0)
122    }
123}
124
125impl Sub for Rad {
126    type Output = Self;
127    #[inline]
128    fn sub(self, rhs: Self) -> Self {
129        Rad(self.0 - rhs.0)
130    }
131}
132
133impl Mul<f64> for Rad {
134    type Output = Self;
135    #[inline]
136    fn mul(self, rhs: f64) -> Self {
137        Rad(self.0 * rhs)
138    }
139}
140
141impl Mul<Rad> for f64 {
142    type Output = Rad;
143    #[inline]
144    fn mul(self, rhs: Rad) -> Rad {
145        Rad(self * rhs.0)
146    }
147}
148
149impl Div<f64> for Rad {
150    type Output = Self;
151    #[inline]
152    fn div(self, rhs: f64) -> Self {
153        Rad(self.0 / rhs)
154    }
155}
156
157impl Div<Rad> for Rad {
158    type Output = f64;
159    #[inline]
160    fn div(self, rhs: Rad) -> f64 {
161        self.0 / rhs.0
162    }
163}
164
165impl Neg for Rad {
166    type Output = Self;
167    #[inline]
168    fn neg(self) -> Self {
169        Rad(-self.0)
170    }
171}
172
173impl AddAssign for Rad {
174    #[inline]
175    fn add_assign(&mut self, rhs: Self) {
176        self.0 += rhs.0;
177    }
178}
179
180impl SubAssign for Rad {
181    #[inline]
182    fn sub_assign(&mut self, rhs: Self) {
183        self.0 -= rhs.0;
184    }
185}
186
187impl MulAssign<f64> for Rad {
188    #[inline]
189    fn mul_assign(&mut self, rhs: f64) {
190        self.0 *= rhs;
191    }
192}
193
194impl DivAssign<f64> for Rad {
195    #[inline]
196    fn div_assign(&mut self, rhs: f64) {
197        self.0 /= rhs;
198    }
199}
200
201/// 角度(NewType)
202///
203/// 表示角度值。使用 NewType 模式防止与弧度值混淆。
204#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
205#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
206pub struct Deg(pub f64);
207
208impl Deg {
209    /// 零角度常量
210    pub const ZERO: Self = Deg(0.0);
211
212    /// 180 度
213    pub const DEG_180: Self = Deg(180.0);
214
215    /// 360 度
216    pub const DEG_360: Self = Deg(360.0);
217
218    /// 90 度
219    pub const DEG_90: Self = Deg(90.0);
220
221    /// 45 度
222    pub const DEG_45: Self = Deg(45.0);
223
224    /// 创建新的角度值
225    #[inline]
226    pub const fn new(value: f64) -> Self {
227        Deg(value)
228    }
229
230    /// 转换为弧度
231    #[inline]
232    pub fn to_rad(self) -> Rad {
233        Rad(self.0.to_radians())
234    }
235
236    /// 获取原始值
237    #[inline]
238    pub fn value(self) -> f64 {
239        self.0
240    }
241
242    /// 取绝对值
243    #[inline]
244    pub fn abs(self) -> Self {
245        Deg(self.0.abs())
246    }
247
248    /// 归一化到 [-180, 180] 范围
249    pub fn normalize(self) -> Self {
250        let mut angle = self.0 % 360.0;
251        if angle > 180.0 {
252            angle -= 360.0;
253        } else if angle < -180.0 {
254            angle += 360.0;
255        }
256        Deg(angle)
257    }
258
259    /// 限制范围
260    #[inline]
261    pub fn clamp(self, min: Self, max: Self) -> Self {
262        Deg(self.0.clamp(min.0, max.0))
263    }
264}
265
266impl fmt::Display for Deg {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        write!(f, "{:.2}°", self.0)
269    }
270}
271
272// 运算符重载
273impl Add for Deg {
274    type Output = Self;
275    #[inline]
276    fn add(self, rhs: Self) -> Self {
277        Deg(self.0 + rhs.0)
278    }
279}
280
281impl Sub for Deg {
282    type Output = Self;
283    #[inline]
284    fn sub(self, rhs: Self) -> Self {
285        Deg(self.0 - rhs.0)
286    }
287}
288
289impl Mul<f64> for Deg {
290    type Output = Self;
291    #[inline]
292    fn mul(self, rhs: f64) -> Self {
293        Deg(self.0 * rhs)
294    }
295}
296
297impl Mul<Deg> for f64 {
298    type Output = Deg;
299    #[inline]
300    fn mul(self, rhs: Deg) -> Deg {
301        Deg(self * rhs.0)
302    }
303}
304
305impl Div<f64> for Deg {
306    type Output = Self;
307    #[inline]
308    fn div(self, rhs: f64) -> Self {
309        Deg(self.0 / rhs)
310    }
311}
312
313impl Div<Deg> for Deg {
314    type Output = f64;
315    #[inline]
316    fn div(self, rhs: Deg) -> f64 {
317        self.0 / rhs.0
318    }
319}
320
321impl Neg for Deg {
322    type Output = Self;
323    #[inline]
324    fn neg(self) -> Self {
325        Deg(-self.0)
326    }
327}
328
329impl AddAssign for Deg {
330    #[inline]
331    fn add_assign(&mut self, rhs: Self) {
332        self.0 += rhs.0;
333    }
334}
335
336impl SubAssign for Deg {
337    #[inline]
338    fn sub_assign(&mut self, rhs: Self) {
339        self.0 -= rhs.0;
340    }
341}
342
343impl MulAssign<f64> for Deg {
344    #[inline]
345    fn mul_assign(&mut self, rhs: f64) {
346        self.0 *= rhs;
347    }
348}
349
350impl DivAssign<f64> for Deg {
351    #[inline]
352    fn div_assign(&mut self, rhs: f64) {
353        self.0 /= rhs;
354    }
355}
356
357/// 牛顿·米(力矩单位)
358///
359/// 表示力矩值。使用 NewType 模式提供类型安全。
360#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
361#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
362pub struct NewtonMeter(pub f64);
363
364impl NewtonMeter {
365    /// 零力矩常量
366    pub const ZERO: Self = NewtonMeter(0.0);
367
368    /// 创建新的力矩值
369    #[inline]
370    pub const fn new(value: f64) -> Self {
371        NewtonMeter(value)
372    }
373
374    /// 获取原始值
375    #[inline]
376    pub fn value(self) -> f64 {
377        self.0
378    }
379
380    /// 取绝对值
381    #[inline]
382    pub fn abs(self) -> Self {
383        NewtonMeter(self.0.abs())
384    }
385
386    /// 限制范围
387    #[inline]
388    pub fn clamp(self, min: Self, max: Self) -> Self {
389        NewtonMeter(self.0.clamp(min.0, max.0))
390    }
391}
392
393impl fmt::Display for NewtonMeter {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        write!(f, "{:.3} N·m", self.0)
396    }
397}
398
399// 运算符重载
400impl Add for NewtonMeter {
401    type Output = Self;
402    #[inline]
403    fn add(self, rhs: Self) -> Self {
404        NewtonMeter(self.0 + rhs.0)
405    }
406}
407
408impl Sub for NewtonMeter {
409    type Output = Self;
410    #[inline]
411    fn sub(self, rhs: Self) -> Self {
412        NewtonMeter(self.0 - rhs.0)
413    }
414}
415
416impl Mul<f64> for NewtonMeter {
417    type Output = Self;
418    #[inline]
419    fn mul(self, rhs: f64) -> Self {
420        NewtonMeter(self.0 * rhs)
421    }
422}
423
424impl Mul<NewtonMeter> for f64 {
425    type Output = NewtonMeter;
426    #[inline]
427    fn mul(self, rhs: NewtonMeter) -> NewtonMeter {
428        NewtonMeter(self * rhs.0)
429    }
430}
431
432impl Div<f64> for NewtonMeter {
433    type Output = Self;
434    #[inline]
435    fn div(self, rhs: f64) -> Self {
436        NewtonMeter(self.0 / rhs)
437    }
438}
439
440impl Div<NewtonMeter> for NewtonMeter {
441    type Output = f64;
442    #[inline]
443    fn div(self, rhs: NewtonMeter) -> f64 {
444        self.0 / rhs.0
445    }
446}
447
448impl Neg for NewtonMeter {
449    type Output = Self;
450    #[inline]
451    fn neg(self) -> Self {
452        NewtonMeter(-self.0)
453    }
454}
455
456impl AddAssign for NewtonMeter {
457    #[inline]
458    fn add_assign(&mut self, rhs: Self) {
459        self.0 += rhs.0;
460    }
461}
462
463impl SubAssign for NewtonMeter {
464    #[inline]
465    fn sub_assign(&mut self, rhs: Self) {
466        self.0 -= rhs.0;
467    }
468}
469
470impl MulAssign<f64> for NewtonMeter {
471    #[inline]
472    fn mul_assign(&mut self, rhs: f64) {
473        self.0 *= rhs;
474    }
475}
476
477impl DivAssign<f64> for NewtonMeter {
478    #[inline]
479    fn div_assign(&mut self, rhs: f64) {
480        self.0 /= rhs;
481    }
482}
483
484/// 角速度单位(弧度/秒)
485///
486/// 表示角速度值。使用 NewType 模式提供类型安全。
487#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
488#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
489pub struct RadPerSecond(pub f64);
490
491impl RadPerSecond {
492    /// 零角速度常量
493    pub const ZERO: Self = RadPerSecond(0.0);
494
495    /// 创建新的角速度值
496    #[inline]
497    pub const fn new(value: f64) -> Self {
498        RadPerSecond(value)
499    }
500
501    /// 获取内部值(弧度/秒)
502    #[inline]
503    pub fn value(&self) -> f64 {
504        self.0
505    }
506
507    /// 从弧度/秒创建
508    #[inline]
509    pub fn from_rad_per_sec(value: f64) -> Self {
510        RadPerSecond(value)
511    }
512
513    /// 取绝对值
514    #[inline]
515    pub fn abs(self) -> Self {
516        RadPerSecond(self.0.abs())
517    }
518
519    /// 限制范围
520    #[inline]
521    pub fn clamp(self, min: Self, max: Self) -> Self {
522        RadPerSecond(self.0.clamp(min.0, max.0))
523    }
524}
525
526impl fmt::Display for RadPerSecond {
527    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
528        write!(f, "{:.4} rad/s", self.0)
529    }
530}
531
532// 运算符重载
533impl Add for RadPerSecond {
534    type Output = Self;
535    #[inline]
536    fn add(self, rhs: RadPerSecond) -> Self {
537        RadPerSecond(self.0 + rhs.0)
538    }
539}
540
541impl Sub for RadPerSecond {
542    type Output = Self;
543    #[inline]
544    fn sub(self, rhs: RadPerSecond) -> Self {
545        RadPerSecond(self.0 - rhs.0)
546    }
547}
548
549impl Mul<f64> for RadPerSecond {
550    type Output = Self;
551    #[inline]
552    fn mul(self, rhs: f64) -> Self {
553        RadPerSecond(self.0 * rhs)
554    }
555}
556
557impl Mul<RadPerSecond> for f64 {
558    type Output = RadPerSecond;
559    #[inline]
560    fn mul(self, rhs: RadPerSecond) -> RadPerSecond {
561        RadPerSecond(self * rhs.0)
562    }
563}
564
565impl Div<f64> for RadPerSecond {
566    type Output = Self;
567    #[inline]
568    fn div(self, rhs: f64) -> Self {
569        RadPerSecond(self.0 / rhs)
570    }
571}
572
573impl Div<RadPerSecond> for RadPerSecond {
574    type Output = f64;
575    #[inline]
576    fn div(self, rhs: RadPerSecond) -> f64 {
577        self.0 / rhs.0
578    }
579}
580
581impl Neg for RadPerSecond {
582    type Output = Self;
583    #[inline]
584    fn neg(self) -> Self {
585        RadPerSecond(-self.0)
586    }
587}
588
589impl AddAssign for RadPerSecond {
590    #[inline]
591    fn add_assign(&mut self, rhs: Self) {
592        self.0 += rhs.0;
593    }
594}
595
596impl SubAssign for RadPerSecond {
597    #[inline]
598    fn sub_assign(&mut self, rhs: Self) {
599        self.0 -= rhs.0;
600    }
601}
602
603impl MulAssign<f64> for RadPerSecond {
604    #[inline]
605    fn mul_assign(&mut self, rhs: f64) {
606        self.0 *= rhs;
607    }
608}
609
610impl DivAssign<f64> for RadPerSecond {
611    #[inline]
612    fn div_assign(&mut self, rhs: f64) {
613        self.0 /= rhs;
614    }
615}
616
617// 实现与 Duration 的除法,用于计算加速度
618impl Div<std::time::Duration> for RadPerSecond {
619    type Output = f64; // 结果单位:弧度/秒²
620    fn div(self, rhs: std::time::Duration) -> Self::Output {
621        self.0 / rhs.as_secs_f64()
622    }
623}
624
625#[cfg(test)]
626mod tests {
627    use super::*;
628
629    // Rad 测试
630    #[test]
631    fn test_rad_to_deg() {
632        let rad = Rad(std::f64::consts::PI);
633        let deg = rad.to_deg();
634        assert!((deg.0 - 180.0).abs() < 1e-6);
635    }
636
637    #[test]
638    fn test_deg_to_rad() {
639        let deg = Deg(180.0);
640        let rad = deg.to_rad();
641        assert!((rad.0 - std::f64::consts::PI).abs() < 1e-6);
642    }
643
644    #[test]
645    fn test_rad_operations() {
646        let r1 = Rad(1.0);
647        let r2 = Rad(2.0);
648
649        assert_eq!(r1 + r2, Rad(3.0));
650        assert_eq!(r2 - r1, Rad(1.0));
651        assert_eq!(r1 * 2.0, Rad(2.0));
652        assert_eq!(r2 / 2.0, Rad(1.0));
653        assert_eq!(-r1, Rad(-1.0));
654    }
655
656    #[test]
657    fn test_deg_operations() {
658        let d1 = Deg(90.0);
659        let d2 = Deg(180.0);
660
661        assert_eq!(d1 + d2, Deg(270.0));
662        assert_eq!(d2 - d1, Deg(90.0));
663        assert_eq!(d1 * 2.0, Deg(180.0));
664        assert_eq!(d2 / 2.0, Deg(90.0));
665        assert_eq!(-d1, Deg(-90.0));
666    }
667
668    #[test]
669    fn test_newton_meter_operations() {
670        let nm1 = NewtonMeter(10.0);
671        let nm2 = NewtonMeter(5.0);
672
673        assert_eq!(nm1 + nm2, NewtonMeter(15.0));
674        assert_eq!(nm1 - nm2, NewtonMeter(5.0));
675        assert_eq!(nm1 * 2.0, NewtonMeter(20.0));
676        assert_eq!(nm1 / 2.0, NewtonMeter(5.0));
677        assert_eq!(-nm1, NewtonMeter(-10.0));
678    }
679
680    #[test]
681    fn test_rad_normalize() {
682        use std::f64::consts::PI;
683
684        assert_eq!(Rad(0.0).normalize(), Rad(0.0));
685        assert_eq!(Rad(PI).normalize(), Rad(PI));
686        assert_eq!(Rad(-PI).normalize(), Rad(-PI));
687
688        // 测试归一化:3π 应该归一化到 -π(因为 3π % 2π = π,然后 π > π 不成立,π < -π 不成立,所以保持 π)
689        // 实际上 3π = π(mod 2π),所以结果应该是 π 附近
690        let normalized = Rad(3.0 * PI).normalize();
691        // 3π % 2π = π,所以归一化后应该是 π
692        assert!((normalized.0 - PI).abs() < 1e-10);
693    }
694
695    #[test]
696    fn test_deg_normalize() {
697        assert_eq!(Deg(0.0).normalize(), Deg(0.0));
698        assert_eq!(Deg(180.0).normalize(), Deg(180.0));
699        assert_eq!(Deg(-180.0).normalize(), Deg(-180.0));
700
701        // 测试归一化
702        let normalized = Deg(540.0).normalize();
703        assert!((normalized.0 - 180.0).abs() < 1e-10);
704    }
705
706    #[test]
707    fn test_rad_trig_functions() {
708        let rad = Rad(std::f64::consts::FRAC_PI_2);
709        assert!((rad.sin() - 1.0).abs() < 1e-10);
710        assert!(rad.cos().abs() < 1e-10);
711    }
712
713    #[test]
714    fn test_display() {
715        let rad = Rad(std::f64::consts::FRAC_PI_2);
716        let deg = Deg(90.0);
717        let nm = NewtonMeter(10.5);
718
719        assert_eq!(format!("{}", rad), "1.5708 rad");
720        assert_eq!(format!("{}", deg), "90.00°");
721        assert_eq!(format!("{}", nm), "10.500 N·m");
722    }
723
724    #[test]
725    fn test_clamp() {
726        let rad = Rad(5.0);
727        assert_eq!(rad.clamp(Rad(-1.0), Rad(1.0)), Rad(1.0));
728
729        let deg = Deg(200.0);
730        assert_eq!(deg.clamp(Deg(-90.0), Deg(90.0)), Deg(90.0));
731
732        let nm = NewtonMeter(-100.0);
733        assert_eq!(
734            nm.clamp(NewtonMeter(-50.0), NewtonMeter(50.0)),
735            NewtonMeter(-50.0)
736        );
737    }
738
739    #[test]
740    fn test_assign_operators() {
741        let mut rad = Rad(1.0);
742        rad += Rad(2.0);
743        assert_eq!(rad, Rad(3.0));
744
745        rad -= Rad(1.0);
746        assert_eq!(rad, Rad(2.0));
747
748        rad *= 2.0;
749        assert_eq!(rad, Rad(4.0));
750
751        rad /= 2.0;
752        assert_eq!(rad, Rad(2.0));
753    }
754
755    // RadPerSecond 测试
756    #[test]
757    fn test_rad_per_second_operations() {
758        let v1 = RadPerSecond(10.0);
759        let v2 = RadPerSecond(5.0);
760
761        assert_eq!(v1 + v2, RadPerSecond(15.0));
762        assert_eq!(v1 - v2, RadPerSecond(5.0));
763        assert_eq!(v1 * 2.0, RadPerSecond(20.0));
764        assert_eq!(v1 / 2.0, RadPerSecond(5.0));
765        assert_eq!(-v1, RadPerSecond(-10.0));
766    }
767
768    #[test]
769    fn test_rad_per_second_assign_operators() {
770        let mut vel = RadPerSecond(1.0);
771        vel += RadPerSecond(2.0);
772        assert_eq!(vel, RadPerSecond(3.0));
773
774        vel -= RadPerSecond(1.0);
775        assert_eq!(vel, RadPerSecond(2.0));
776
777        vel *= 2.0;
778        assert_eq!(vel, RadPerSecond(4.0));
779
780        vel /= 2.0;
781        assert_eq!(vel, RadPerSecond(2.0));
782    }
783
784    #[test]
785    fn test_rad_per_second_div_duration() {
786        let vel = RadPerSecond(10.0);
787        let duration = std::time::Duration::from_secs(2);
788        let accel = vel / duration;
789        assert!((accel - 5.0).abs() < 1e-10); // 10 rad/s / 2s = 5 rad/s²
790    }
791
792    #[test]
793    fn test_rad_per_second_display() {
794        let vel = RadPerSecond(std::f64::consts::PI);
795        assert_eq!(format!("{}", vel), "3.1416 rad/s");
796    }
797
798    #[test]
799    fn test_rad_per_second_clamp() {
800        let vel = RadPerSecond(10.0);
801        assert_eq!(
802            vel.clamp(RadPerSecond(-5.0), RadPerSecond(5.0)),
803            RadPerSecond(5.0)
804        );
805    }
806}