Skip to main content

cad_cs/libs/cs/math/
d3.rs

1// 📃 ./src/libs/cs/math/d3.rs
2
3use crate::libs::{
4	angle::AbstractAngle,
5	cs::{
6		abstracts::{AbstractMathCs3, AbstractSignStrExt},
7		model::Cs,
8	},
9	tolerance,
10};
11
12impl AbstractMathCs3 for Cs<3> {
13	/// 📚 【 POL】: Długość rzutu (promień) na płaszczyznę XY.
14	/// 📚 【 ENG】: Projection length (radius) on the XY plane.
15	#[rustfmt::skip] #[inline]	fn rxy(&self)  -> f64 { self.0[0].hypot(self.0[1]) }
16
17	/// 📚 【 POL】: Długość rzutu (promień) na płaszczyznę XZ.
18	/// 📚 【 ENG】: Projection length (radius) on the XZ plane.
19	#[rustfmt::skip] #[inline]	fn rxz(&self)  -> f64 { self.0[0].hypot(self.0[2]) }
20
21	/// 📚 【 POL】: Długość rzutu (promień) na płaszczyznę YZ.
22	/// 📚 【 ENG】: Projection length (radius) on the YZ plane.
23	#[rustfmt::skip] #[inline]	fn ryz(&self)  -> f64 { self.0[1].hypot(self.0[2]) }
24
25	/// 📚 【 POL】: Pełna długość wektora w przestrzeni XYZ.
26	/// 📚 【 ENG】: Full vector length in XYZ space.
27	#[rustfmt::skip] #[inline]	fn rxyz(&self) -> f64 { self.0[0].hypot(self.0[1]).hypot(self.0[2]) }
28
29	// --- KĄTY PŁASKIE (AZYMUTY MATEMATYCZNE CCW) ---
30
31	/// 📚 【 POL】: Azymut na płaszczyźnie XY (od X do Y).
32	/// 📚 【 ENG】: Azimuth on the XY plane (from X to Y).
33	#[rustfmt::skip] #[inline]	fn arctan_y_x(&self) -> f64 { self.0[1].atan2(self.0[0]) }
34
35	/// 📚 【 POL】: Azymut na płaszczyźnie XZ (od X do Z).
36	/// 📚 【 ENG】: Azimuth on the XZ plane (from X to Z).
37	#[rustfmt::skip] #[inline]	fn arctan_z_x(&self) -> f64 { self.0[2].atan2(self.0[0]) }
38
39	/// 📚 【 POL】: Azymut na płaszczyźnie YZ (od Y do Z).
40	/// 📚 【 ENG】: Azimuth on the YZ plane (from Y to Z).
41	#[rustfmt::skip] #[inline]	fn arctan_z_y(&self) -> f64 { self.0[2].atan2(self.0[1]) }
42
43	// --- KĄTY KOMPASOWE / MAPOWE (CW, 0° NA OSI PIONOWEJ) ---
44
45	/// 📚 【 POL】: Azymut kompasowy na XY (0° na osi Y).
46	/// 📚 【 ENG】: Compass azimuth on XY (0° on Y-axis).
47	#[rustfmt::skip] #[inline]	fn arctan_x_y(&self) -> f64 { self.0[0].atan2(self.0[1]) }
48
49	/// 📚 【 POL】: Azymut kompasowy na XZ (0° na osi Z).
50	/// 📚 【 ENG】: Compass azimuth on XZ (0° on Z-axis).
51	#[rustfmt::skip] #[inline]	fn arctan_x_z(&self) -> f64 { self.0[0].atan2(self.0[2]) }
52
53	/// 📚 【 POL】: Azymut kompasowy na YZ (0° na osi Z).
54	/// 📚 【 ENG】: Compass azimuth on YZ (0° on Z-axis).
55	#[rustfmt::skip] #[inline]	fn arctan_y_z(&self) -> f64 { self.0[1].atan2(self.0[2]) }
56
57	// --- KĄTY PRZESTRZENNE (INKLINACJA) ---
58
59	/// 📚 【 POL】: Kąt między wektorem a osią X.
60	/// 📚 【 ENG】: Angle between the vector and the X-axis.
61	#[rustfmt::skip] #[inline]	fn arccos_x_rxyz(&self) -> f64 { let r = self.rxyz(); if tolerance::is_zero(r) { 0.0 } else { (self.0[0] / r).clamp(-1.0, 1.0).acos() } }
62
63	/// 📚 【 POL】: Kąt między wektorem a osią Y.
64	/// 📚 【 ENG】: Angle between the vector and the Y-axis.
65	#[rustfmt::skip] #[inline]	fn arccos_y_rxyz(&self) -> f64 { let r = self.rxyz(); if tolerance::is_zero(r) { 0.0 } else { (self.0[1] / r).clamp(-1.0, 1.0).acos() } }
66
67	/// 📚 【 POL】: Kąt między wektorem a osią Z (Inklinacja sferyczna).
68	/// 📚 【 ENG】: Angle between the vector and the Z-axis (Spherical inclination).
69	#[rustfmt::skip] #[inline]	fn arccos_z_rxyz(&self) -> f64 { let r = self.rxyz(); if tolerance::is_zero(r) { 0.0 } else { (self.0[2] / r).clamp(-1.0, 1.0).acos() } }
70
71	// --- KONWERSJE 3D -> 2D (RZUTY BIEGUNOWE) ---
72
73	/// 📚 【 POL】: Rzutuje wektor 3D na płaszczyznę XY w formacie biegunowym [R, Φ].
74	/// 📚 【 ENG】: Projects a 3D vector onto the XY plane in polar format [R, Φ].
75	#[rustfmt::skip] #[inline]	fn to_rf_from_xy(&self) -> Cs<2> { Cs([self.rxy(), self.arctan_y_x()]) }
76
77	/// 📚 【 POL】: Rzutuje wektor 3D na płaszczyznę XZ w formacie biegunowym [R, Φ].
78	/// 📚 【 ENG】: Projects a 3D vector onto the XZ plane in polar format [R, Φ].
79	#[rustfmt::skip] #[inline]	fn to_rf_from_xz(&self) -> Cs<2> { Cs([self.rxz(), self.arctan_z_x()]) }
80
81	/// 📚 【 POL】: Rzutuje wektor 3D na płaszczyznę YZ w formacie biegunowym [R, Φ].
82	/// 📚 【 ENG】: Projects a 3D vector onto the YZ plane in polar format [R, Φ].
83	#[rustfmt::skip] #[inline]	fn to_rf_from_yz(&self) -> Cs<2> { Cs([self.ryz(), self.arctan_z_y()]) }
84
85	// --- KONWERSJE 3D -> 3D (UKŁADY CYLINDRYCZNE I SFERYCZNE) ---
86
87	/// 📚 【 POL】: Konwertuje XYZ na układ cylindryczny względem osi X [R_yz, Φ_zy, X].
88	/// 📚 【 ENG】: Converts XYZ to a cylindrical system relative to the X-axis [R_yz, Φ_zy, X].
89	#[rustfmt::skip] #[inline]	fn to_rfx_from_xyz(&self) -> Cs<3> { Cs([self.ryz(),  self.arctan_z_y(), self.0[0]]) }
90
91	/// 📚 【 POL】: Konwertuje XYZ na układ cylindryczny względem osi Y [R_xz, Φ_zx, Y].
92	/// 📚 【 ENG】: Converts XYZ to a cylindrical system relative to the Y-axis [R_xz, Φ_zx, Y].
93	#[rustfmt::skip] #[inline]	fn to_rfy_from_xyz(&self) -> Cs<3> { Cs([self.rxz(),  self.arctan_z_x(), self.0[1]]) }
94
95	/// 📚 【 POL】: Konwertuje XYZ na układ cylindryczny względem osi Z [R_xy, Φ_yx, Z].
96	/// 📚 【 ENG】: Converts XYZ to a cylindrical system relative to the Z-axis [R_xy, Φ_yx, Z].
97	#[rustfmt::skip] #[inline]	fn to_rfz_from_xyz(&self) -> Cs<3> { Cs([self.rxy(),  self.arctan_y_x(), self.0[2]]) }
98
99	/// 📚 【 POL】: Konwertuje XYZ na pełny układ sferyczny [R_xyz, Φ_yx, Θ_zr].
100	/// 📚 【 ENG】: Converts XYZ to a full spherical system [R_xyz, Φ_yx, Θ_zr].
101	#[rustfmt::skip] #[inline]	fn to_rft_from_xyz(&self) -> Cs<3> { Cs([self.rxyz(), self.arctan_y_x(), self.arccos_z_rxyz()]) }
102
103	// --- GEODEZJA ---
104
105	/// 📚 【 POL】: Tworzy wektor ECEF (XYZ) bezpośrednio z danych DMS i promienia.
106	/// 📚 【 ENG】: Creates an ECEF vector (XYZ) directly from DMS data and radius.
107	#[rustfmt::skip]
108	fn to_ecef_from_dms_sn_we(
109		sn_d: i16, sn_m: u8, sn_s: f32,
110		we_d: i16, we_m: u8, we_s: f32,
111		r: f64
112	) -> Self {
113		use crate::libs::angle::Angle;
114		let lat_rad = Angle::from_dms(sn_d, sn_m, sn_s).rad();
115		let lon_rad = Angle::from_dms(we_d, we_m, we_s).rad();
116
117		let (sin_lat, cos_lat) = lat_rad.sin_cos();
118		let (sin_lon, cos_lon) = lon_rad.sin_cos();
119
120		Cs([
121			r * cos_lat * cos_lon,
122			r * cos_lat * sin_lon,
123			r * sin_lat
124		])
125	}
126
127	/// 📚 【 POL】: Konwertuje wektor XYZ na geodezyjny format DMS (Szerokość, Długość).
128	/// 📚 【 ENG】: Converts a XYZ vector to geodetic DMS format (Latitude, Longitude).
129	#[rustfmt::skip]
130	fn to_dms_sn_we_from_xyz(&self) -> crate::libs::cs::model::CoordsSphericalEcefSnWeDms {
131		use crate::libs::angle::Angle;
132		use crate::libs::tolerance;
133
134		let r = self.rxyz();
135
136		// Zabezpieczenie przed osobliwością w samym jądrze Ziemi (r = 0)
137		let (lat_rad, lon_rad) = if tolerance::is_zero(r) {
138			(0.0, 0.0)
139		} else {
140			(
141				(self.0[2] / r).clamp(-1.0, 1.0).asin(), // Szerokość z osi Z
142				self.0[1].atan2(self.0[0])               // Długość z płaszczyzny XY
143			)
144		};
145
146		// Rzutujemy radiany na nasz izolator Angle
147		let lat_angle = Angle::from_rad(lat_rad);
148		let lon_angle = Angle::from_rad(lon_rad);
149
150		// Dekodujemy do składowych (Stopnie, Minuty, Sekundy)
151		let (lat_d, lat_m, lat_s) = lat_angle.to_dms();
152		let (lon_d, lon_m, lon_s) = lon_angle.to_dms();
153
154		// Pakujemy w DTO
155		crate::libs::cs::model::CoordsSphericalEcefSnWeDms {
156			sn_lat_d: lat_d as i8,
157			sn_lat_m: lat_m,
158			sn_lat_s: lat_s,
159			we_lon_d: lon_d,
160			we_lon_m: lon_m,
161			we_lon_s: lon_s,
162		}
163	}
164
165	// --- ANALIZA PRZESTRZENNA ---
166
167	/// 📚 【 POL】: Zwraca numer oktantu (1-8) w którym znajduje się wektor.
168	/// 📚 【 ENG】: Returns the octant number (1-8) where the vector is located.
169	#[rustfmt::skip] #[inline]
170	fn q(&self) -> u8 {
171		match (self.0[0] >= 0.0, self.0[1] >= 0.0, self.0[2] >= 0.0) {
172			(true, true, true)   => 1, (false, true, true)  => 2, (false, false, true) => 3, (true, false, true)  => 4,
173			(true, true, false)  => 5, (false, true, false) => 6, (false, false, false)=> 7, (true, false, false) => 8,
174		}
175	}
176
177	/// 📚 【 POL】: Zwraca znaki kierunkowe osi XYZ (np. ["+", "-", "+"]).
178	/// 📚 【 ENG】: Returns the directional signs of the XYZ axes (e.g., ["+", "-", "+"]).
179	#[rustfmt::skip] #[inline]
180	fn q_sign(&self) -> [&'static str; 3] {
181		[self.0[0].sign_str(), self.0[1].sign_str(), self.0[2].sign_str()]
182	}
183
184	/// 📚 【 POL】: Kwadrat pełnej długości wektora 3D.
185	/// 📚 【 ENG】: Squared full length of the 3D vector.
186	#[rustfmt::skip] #[inline]	fn rxyz_sq(&self) -> f64 { self.0[0] * self.0[0] + self.0[1] * self.0[1] + self.0[2] * self.0[2] }
187
188	/// 📚 【 POL】: Kwadrat długości rzutu na płaszczyznę XY.
189	/// 📚 【 ENG】: Squared projection length on the XY plane.
190	#[rustfmt::skip] #[inline]	fn rxy_sq(&self) -> f64 { self.0[0] * self.0[0] + self.0[1] * self.0[1] }
191
192	/// 📚 【 POL】: Kwadrat długości rzutu na płaszczyznę XZ.
193	/// 📚 【 ENG】: Squared projection length on the XZ plane.
194	#[rustfmt::skip] #[inline]	fn rxz_sq(&self) -> f64 { self.0[0] * self.0[0] + self.0[2] * self.0[2] }
195
196	/// 📚 【 POL】: Kwadrat długości rzutu na płaszczyznę YZ.
197	/// 📚 【 ENG】: Squared projection length on the YZ plane.
198	#[rustfmt::skip] #[inline]	fn ryz_sq(&self) -> f64 { self.0[1] * self.0[1] + self.0[2] * self.0[2] }
199
200	// --- NORMALIZACJE RZUTOWE ---
201
202	/// 📚 【 POL】: Normalizuje rzut XY do długości 1.0, zachowując składową Z.
203	/// 📚 【 ENG】: Normalizes the XY projection to length 1.0, preserving the Z component.
204	#[rustfmt::skip] #[inline]
205	fn normalize_rxy_projection(&self) -> Cs<3> {
206		let r = self.rxy();
207		if tolerance::is_zero(r) { Cs([0.0, 0.0, self.0[2]]) } else { Cs([self.0[0] / r, self.0[1] / r, self.0[2]]) }
208	}
209
210	/// 📚 【 POL】: Normalizuje rzut XZ do długości 1.0, zachowując składową Y.
211	/// 📚 【 ENG】: Normalizes the XZ projection to length 1.0, preserving the Y component.
212	#[rustfmt::skip] #[inline]
213	fn normalize_rxz_projection(&self) -> Cs<3> {
214		let r = self.rxz();
215		if tolerance::is_zero(r) { Cs([0.0, self.0[1], 0.0]) } else { Cs([self.0[0] / r, self.0[1], self.0[2] / r]) }
216	}
217
218	/// 📚 【 POL】: Normalizuje rzut YZ do długości 1.0, zachowując składową X.
219	/// 📚 【 ENG】: Normalizes the YZ projection to length 1.0, preserving the X component.
220	#[rustfmt::skip] #[inline]
221	fn normalize_ryz_projection(&self) -> Cs<3> {
222		let r = self.ryz();
223		if tolerance::is_zero(r) { Cs([self.0[0], 0.0, 0.0]) } else { Cs([self.0[0], self.0[1] / r, self.0[2] / r]) }
224	}
225
226	/// 📚 【 POL】: Iloczyn wektorowy (Cross product). Zwraca wektor ortogonalny.
227	/// 📚 【 ENG】: Cross product. Returns an orthogonal vector.
228	#[rustfmt::skip] #[inline]
229	fn cross(&self, other: &Cs<3>) -> Cs<3> {
230		Cs([
231			self.0[1] * other.0[2] - self.0[2] * other.0[1],
232			self.0[2] * other.0[0] - self.0[0] * other.0[2],
233			self.0[0] * other.0[1] - self.0[1] * other.0[0]
234		])
235	}
236}