fenris_geometry/primitives/
line.rs1use crate::{ConvexPolygon, Disk, HalfPlane, Plane};
2use fenris_traits::Real;
3use nalgebra::allocator::Allocator;
4use nalgebra::{clamp, DefaultAllocator, DimName, Matrix2, OPoint, OVector, Vector2, U2, U3};
5use nalgebra::{Point2, Point3, Scalar};
6use numeric_literals::replace_float_literals;
7use std::fmt::Debug;
8
9pub type LineSegment3d<T> = LineSegment<T, U3>;
10
11impl<T: Real> LineSegment3d<T> {
12 #[allow(non_snake_case)]
13 pub fn closest_point_to_plane_parametric(&self, plane: &Plane<T>) -> T {
14 let n = plane.normal();
15 let x0 = plane.point();
16 let [a, b] = [self.start(), self.end()];
17 let d = &b.coords - &a.coords;
18 let y = &x0.coords - &a.coords;
19
20 let nTd = n.dot(&d);
21 let nTy = n.dot(&y);
22
23 let t = if nTd.signum() == nTy.signum() {
28 if nTy.abs() >= nTd.abs() {
30 T::one()
31 } else {
32 nTy / nTd
33 }
34 } else {
35 T::zero()
37 };
38
39 t
40 }
41
42 pub fn closest_point_to_plane(&self, plane: &Plane<T>) -> Point3<T> {
43 let t = self.closest_point_to_plane_parametric(plane);
44 self.point_from_parameter(t)
45 }
46}
47
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct LineSegment<T, D>
50where
51 T: Scalar,
52 D: DimName,
53 DefaultAllocator: Allocator<T, D>,
54{
55 start: OPoint<T, D>,
56 end: OPoint<T, D>,
57}
58
59pub type LineSegment2d<T> = LineSegment<T, U2>;
60
61impl<T, D> LineSegment<T, D>
62where
63 T: Scalar,
64 D: DimName,
65 DefaultAllocator: Allocator<T, D>,
66{
67 pub fn from_end_points(start: OPoint<T, D>, end: OPoint<T, D>) -> Self {
68 Self { start, end }
69 }
70
71 pub fn start(&self) -> &OPoint<T, D> {
72 &self.start
73 }
74
75 pub fn end(&self) -> &OPoint<T, D> {
76 &self.end
77 }
78
79 pub fn reverse(&self) -> Self {
80 Self {
81 start: self.end.clone(),
82 end: self.start.clone(),
83 }
84 }
85}
86
87impl<T, D> LineSegment<T, D>
88where
89 T: Real,
90 D: DimName,
91 DefaultAllocator: Allocator<T, D>,
92{
93 pub fn to_line(&self) -> Line<T, D> {
94 let dir = &self.end - &self.start;
95 Line::from_point_and_dir(self.start.clone(), dir)
96 }
97
98 pub fn tangent_dir(&self) -> OVector<T, D> {
102 &self.end().coords - &self.start().coords
103 }
104
105 pub fn length(&self) -> T {
106 self.tangent_dir().norm()
107 }
108
109 pub fn midpoint(&self) -> OPoint<T, D> {
110 OPoint::from((&self.start().coords + &self.end().coords) / (T::one() + T::one()))
111 }
112
113 pub fn closest_point_parametric(&self, point: &OPoint<T, D>) -> T {
116 let t = self.to_line().project_point_parametric(point);
117 clamp(t, T::zero(), T::one())
118 }
119
120 pub fn closest_point(&self, point: &OPoint<T, D>) -> OPoint<T, D> {
122 let t = self.closest_point_parametric(point);
123 self.point_from_parameter(t)
124 }
125
126 pub fn point_from_parameter(&self, t: T) -> OPoint<T, D> {
127 OPoint::from(&self.start().coords + self.tangent_dir() * t)
128 }
129}
130
131impl<T> LineSegment2d<T>
132where
133 T: Real,
134{
135 pub fn normal_dir(&self) -> Vector2<T> {
143 let tangent = self.tangent_dir();
144 Vector2::new(tangent.y, -tangent.x)
145 }
146
147 pub fn intersect_line_parametric(&self, line: &Line2d<T>) -> Option<T> {
148 self.to_line()
149 .intersect_line_parametric(line)
150 .map(|(t1, _)| t1)
151 }
152
153 #[replace_float_literals(T::from_f64(literal).unwrap())]
154 pub fn intersect_disk_parametric(&self, disk: &Disk<T>) -> Option<[T; 2]> {
155 let [t1, t2] = self.to_line().intersect_disk_parametric(disk)?;
156 let t1 = clamp(t1, 0.0, 1.0);
157 let t2 = clamp(t2, 0.0, 1.0);
158 Some([t1, t2])
159 }
160
161 #[replace_float_literals(T::from_f64(literal).unwrap())]
162 pub fn intersect_disk(&self, disk: &Disk<T>) -> Option<Self> {
163 self.intersect_disk_parametric(disk)
164 .map(|[t1, t2]| self.segment_from_parameters(&t1, &t2))
165 }
166
167 pub fn segment_from_parameters(&self, t_begin: &T, t_end: &T) -> Self {
168 let begin = self.point_from_parameter(t_begin.clone());
169 let end = self.point_from_parameter(t_end.clone());
170 Self::from_end_points(begin, end)
171 }
172
173 pub fn intersect_segment_parametric(&self, other: &LineSegment2d<T>) -> Option<T> {
179 let d1 = &self.end - &self.start;
187 let d2 = &other.end - &other.start;
188
189 let line1 = Line2d::from_point_and_dir(self.start.clone(), d1);
190 let line2 = Line2d::from_point_and_dir(other.start.clone(), d2);
191
192 line1
193 .intersect_line_parametric(&line2)
194 .and_then(|(t1, t2)| {
195 if t2 < T::zero() || t2 > T::one() {
202 None
203 } else if t1 < T::zero() || t1 > T::one() {
204 None
205 } else {
206 Some(t1)
207 }
208 })
209 }
210
211 #[replace_float_literals(T::from_f64(literal).unwrap())]
217 pub fn intersect_half_plane_parametric(&self, half_plane: &HalfPlane<T>) -> Option<[T; 2]> {
218 let contains_start = half_plane.contains_point(self.start());
219 let contains_end = half_plane.contains_point(self.end());
220
221 match (contains_start, contains_end) {
222 (true, true) => Some([0.0, 1.0]),
223 (false, false) => None,
224 (true, false) | (false, true) => {
225 let t_intersect = self
226 .intersect_line_parametric(&half_plane.surface())
227 .map(|t| clamp(t, 0.0, 1.0));
231
232 let (t_start, t_end);
233 if contains_start {
234 t_start = 0.0;
240 t_end = t_intersect.unwrap_or(1.0);
241 } else {
242 t_start = t_intersect.unwrap_or(0.0);
243 t_end = 1.0;
244 }
245
246 debug_assert!(t_start <= t_end);
247 Some([t_start, t_end])
248 }
249 }
250 }
251
252 #[replace_float_literals(T::from_f64(literal).unwrap())]
253 pub fn intersect_half_plane(&self, half_plane: &HalfPlane<T>) -> Option<Self> {
254 self.intersect_half_plane_parametric(half_plane)
255 .map(|[t1, t2]| self.segment_from_parameters(&t1, &t2))
256 }
257
258 pub fn intersect_polygon(&self, other: &ConvexPolygon<T>) -> Option<LineSegment2d<T>> {
259 let mut result = self.clone();
260 for half_plane in other.half_planes() {
261 result = result.intersect_half_plane(&half_plane)?;
262 }
263 Some(result)
264 }
265}
266
267impl<T: Real> LineSegment3d<T> {
268 pub fn intersect_plane_parametric(&self, plane: &Plane<T>) -> Option<T> {
269 self.to_line()
270 .intersect_plane_parametric(plane)
271 .filter(|t| t >= &T::zero() && t <= &T::one())
272 }
273}
274
275#[derive(Debug, Clone)]
276pub struct Line<T, D>
277where
278 T: Scalar,
279 D: DimName,
280 DefaultAllocator: Allocator<T, D>,
281{
282 point: OPoint<T, D>,
283 dir: OVector<T, D>,
284}
285
286pub type Line2d<T> = Line<T, U2>;
287pub type Line3d<T> = Line<T, U3>;
288
289impl<T, D> Line<T, D>
290where
291 T: Scalar,
292 D: DimName,
293 DefaultAllocator: Allocator<T, D>,
294{
295 pub fn from_point_and_dir(point: OPoint<T, D>, dir: OVector<T, D>) -> Self {
296 Self { point, dir }
298 }
299
300 pub fn point(&self) -> &OPoint<T, D> {
301 &self.point
302 }
303
304 pub fn dir(&self) -> &OVector<T, D> {
305 &self.dir
306 }
307}
308
309impl<T, D> Line<T, D>
310where
311 T: Real,
312 D: DimName,
313 DefaultAllocator: Allocator<T, D>,
314{
315 pub fn tangent(&self) -> OVector<T, D> {
317 self.dir.normalize()
318 }
319
320 pub fn from_point_through_point(point: OPoint<T, D>, through: &OPoint<T, D>) -> Self {
321 let dir = through - &point;
322 Self::from_point_and_dir(point, dir)
323 }
324
325 pub fn project_point_parametric(&self, point: &OPoint<T, D>) -> T {
328 let d2 = self.dir.magnitude_squared();
329 if d2 == T::zero() {
330 T::zero()
334 } else {
335 (point - &self.point).dot(&self.dir) / d2
336 }
337 }
338
339 pub fn project_point(&self, point: &OPoint<T, D>) -> OPoint<T, D> {
341 let t = self.project_point_parametric(point);
342 self.point_from_parameter(t)
343 }
344
345 pub fn point_from_parameter(&self, t: T) -> OPoint<T, D> {
346 &self.point + &self.dir * t
347 }
348}
349
350impl<T> Line2d<T>
351where
352 T: Real,
353{
354 pub fn intersect(&self, other: &Line2d<T>) -> Option<Point2<T>> {
355 self.intersect_line_parametric(other)
356 .map(|(t1, _)| self.point_from_parameter(t1))
357 }
358
359 pub fn intersect_line_parametric(&self, other: &Line2d<T>) -> Option<(T, T)> {
365 let rhs = &other.point - &self.point;
373 let matrix = Matrix2::from_columns(&[self.dir, -other.dir]);
374
375 matrix
377 .try_inverse()
378 .map(|inv| inv * rhs)
379 .map(|t| (t.x, t.y))
381 }
382
383 pub fn intersect_disk(&self, disk: &Disk<T>) -> Option<LineSegment2d<T>> {
384 let [t1, t2] = self.intersect_disk_parametric(disk)?;
385 let p1 = self.point_from_parameter(t1);
386 let p2 = self.point_from_parameter(t2);
387 Some(LineSegment2d::from_end_points(p1, p2))
388 }
389
390 #[replace_float_literals(T::from_f64(literal).unwrap())]
391 pub fn intersect_disk_parametric(&self, disk: &Disk<T>) -> Option<[T; 2]> {
392 let a = self.point();
393 let d = self.dir();
394 let r = disk.radius();
395 let a0 = a - disk.center();
396
397 let alpha = d.dot(&d);
400 let beta = 2.0 * d.dot(&a0);
401 let gamma = a0.dot(&a0) - r * r;
402
403 let discriminant = beta * beta - 4.0 * alpha * gamma;
404 if discriminant >= 0.0 {
405 let disc_sqrt = discriminant.sqrt();
408 let t1 = (-beta - disc_sqrt) / (2.0 * alpha);
409 let t2 = (-beta + disc_sqrt) / (2.0 * alpha);
410 debug_assert!(t1 <= t2);
411 Some([t1, t2])
412 } else {
413 None
415 }
416 }
417}
418
419impl<T> Line3d<T>
420where
421 T: Real,
422{
423 pub fn intersect_plane_parametric(&self, plane: &Plane<T>) -> Option<T> {
424 let n = plane.normal();
425 let d = self.dir();
426 let b = self.point() - plane.point();
427 let d_dot_n = d.dot(&n);
428 (d_dot_n != T::zero()).then(|| -b.dot(&n) / d_dot_n)
433 }
434}