1use std::ops::{Add, Div, Mul, Neg, Sub};
7use super::point::PgaPoint;
8use super::plane::PgaPlane;
9
10#[repr(C, align(32))]
14#[derive(Debug, Clone, Copy, PartialEq)]
15#[cfg_attr(feature = "vulkan", derive(bytemuck::Pod, bytemuck::Zeroable))]
16pub struct PgaLine {
17 pub vx: f32, pub vy: f32, pub vz: f32, pub mx: f32, pub my: f32, pub mz: f32, pub _pad0: f32,
24 pub _pad1: f32,
25}
26
27impl Default for PgaLine {
28 #[inline(always)]
29 fn default() -> Self {
30 Self::z_axis()
31 }
32}
33
34impl PgaLine {
35 #[inline(always)]
37 pub const fn new(vx: f32, vy: f32, vz: f32, mx: f32, my: f32, mz: f32) -> Self {
38 Self {
39 vx,
40 vy,
41 vz,
42 mx,
43 my,
44 mz,
45 _pad0: 0.0,
46 _pad1: 0.0,
47 }
48 }
49
50 #[inline(always)]
52 pub const fn from_direction_and_moment(dir: [f32; 3], mom: [f32; 3]) -> Self {
53 Self::new(dir[0], dir[1], dir[2], mom[0], mom[1], mom[2])
54 }
55
56 #[inline(always)]
58 pub const fn x_axis() -> Self {
59 Self::new(1.0, 0.0, 0.0, 0.0, 0.0, 0.0)
60 }
61
62 #[inline(always)]
64 pub const fn y_axis() -> Self {
65 Self::new(0.0, 1.0, 0.0, 0.0, 0.0, 0.0)
66 }
67
68 #[inline(always)]
70 pub const fn z_axis() -> Self {
71 Self::new(0.0, 0.0, 1.0, 0.0, 0.0, 0.0)
72 }
73
74 #[inline]
76 pub fn from_point_and_direction(point: &[f32; 3], dir: &[f32; 3]) -> Self {
77 let mx = point[1] * dir[2] - point[2] * dir[1];
78 let my = point[2] * dir[0] - point[0] * dir[2];
79 let mz = point[0] * dir[1] - point[1] * dir[0];
80 Self::new(dir[0], dir[1], dir[2], mx, my, mz)
81 }
82
83 #[inline(always)]
85 pub const fn direction(&self) -> [f32; 3] {
86 [self.vx, self.vy, self.vz]
87 }
88
89 #[inline(always)]
91 pub const fn moment(&self) -> [f32; 3] {
92 [self.mx, self.my, self.mz]
93 }
94
95 #[inline(always)]
97 pub fn norm(&self) -> f32 {
98 (self.vx * self.vx + self.vy * self.vy + self.vz * self.vz).sqrt()
99 }
100
101 #[inline]
103 pub fn normalized(&self) -> Self {
104 let n = self.norm();
105 if n > 1e-12 {
106 let inv = 1.0 / n;
107 Self::new(
108 self.vx * inv,
109 self.vy * inv,
110 self.vz * inv,
111 self.mx * inv,
112 self.my * inv,
113 self.mz * inv,
114 )
115 } else {
116 *self
117 }
118 }
119
120 #[inline]
122 pub fn closest_point_to_origin(&self) -> PgaPoint {
123 let v2 = self.vx * self.vx + self.vy * self.vy + self.vz * self.vz;
124 if v2 > 1e-12 {
125 let inv_v2 = 1.0 / v2;
126 let px = (self.vy * self.mz - self.vz * self.my) * inv_v2;
127 let py = (self.vz * self.mx - self.vx * self.mz) * inv_v2;
128 let pz = (self.vx * self.my - self.vy * self.mx) * inv_v2;
129 PgaPoint::new(px, py, pz)
130 } else {
131 PgaPoint::origin()
132 }
133 }
134
135 #[inline]
137 pub fn project_point(&self, p: &PgaPoint) -> PgaPoint {
138 let norm_line = self.normalized();
139 let pt = p.to_euclidean();
140 let p0 = norm_line.closest_point_to_origin().to_euclidean();
141
142 let dx = pt[0] - p0[0];
143 let dy = pt[1] - p0[1];
144 let dz = pt[2] - p0[2];
145
146 let dot = dx * norm_line.vx + dy * norm_line.vy + dz * norm_line.vz;
147 PgaPoint::new(
148 p0[0] + dot * norm_line.vx,
149 p0[1] + dot * norm_line.vy,
150 p0[2] + dot * norm_line.vz,
151 )
152 }
153
154 #[inline]
156 pub fn distance_to_point(&self, p: &PgaPoint) -> f32 {
157 let proj = self.project_point(p);
158 p.distance(&proj)
159 }
160
161 #[inline]
163 pub fn meet_plane(&self, plane: &PgaPlane) -> PgaPoint {
164 let norm_p = plane.normalized();
165 let [nx, ny, nz] = norm_p.normal();
166 let d = norm_p.d;
167
168 let denom = self.vx * nx + self.vy * ny + self.vz * nz;
169 if denom.abs() < 1e-12 {
170 PgaPoint::ideal(self.vx, self.vy, self.vz)
172 } else {
173 let p0 = self.closest_point_to_origin().to_euclidean();
174 let t = -(nx * p0[0] + ny * p0[1] + nz * p0[2] + d) / denom;
175 PgaPoint::new(
176 p0[0] + t * self.vx,
177 p0[1] + t * self.vy,
178 p0[2] + t * self.vz,
179 )
180 }
181 }
182}
183
184impl Neg for PgaLine {
185 type Output = Self;
186 #[inline(always)]
187 fn neg(self) -> Self {
188 Self::new(
189 -self.vx,
190 -self.vy,
191 -self.vz,
192 -self.mx,
193 -self.my,
194 -self.mz,
195 )
196 }
197}
198
199impl Add for PgaLine {
200 type Output = Self;
201 #[inline(always)]
202 fn add(self, rhs: Self) -> Self {
203 Self::new(
204 self.vx + rhs.vx,
205 self.vy + rhs.vy,
206 self.vz + rhs.vz,
207 self.mx + rhs.mx,
208 self.my + rhs.my,
209 self.mz + rhs.mz,
210 )
211 }
212}
213
214impl Sub for PgaLine {
215 type Output = Self;
216 #[inline(always)]
217 fn sub(self, rhs: Self) -> Self {
218 Self::new(
219 self.vx - rhs.vx,
220 self.vy - rhs.vy,
221 self.vz - rhs.vz,
222 self.mx - rhs.mx,
223 self.my - rhs.my,
224 self.mz - rhs.mz,
225 )
226 }
227}
228
229impl Mul<f32> for PgaLine {
230 type Output = Self;
231 #[inline(always)]
232 fn mul(self, rhs: f32) -> Self {
233 Self::new(
234 self.vx * rhs,
235 self.vy * rhs,
236 self.vz * rhs,
237 self.mx * rhs,
238 self.my * rhs,
239 self.mz * rhs,
240 )
241 }
242}
243
244impl Div<f32> for PgaLine {
245 type Output = Self;
246 #[inline(always)]
247 fn div(self, rhs: f32) -> Self {
248 let inv = 1.0 / rhs;
249 Self::new(
250 self.vx * inv,
251 self.vy * inv,
252 self.vz * inv,
253 self.mx * inv,
254 self.my * inv,
255 self.mz * inv,
256 )
257 }
258}