alloy/geometry/
line.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
//! The line module defines a line that runs infinitely in both directions.

use crate::geometry::Point3;
use crate::math::vec::{Vec3, Vector3};
use crate::ops::{Cross, Dot};

use super::Intersects;

/// Representation of a line in 3D space that continues infinitely in both
/// directions.
pub struct Line {
  /// The origin of the line.
  origin: Point3,
  /// The direction of the line.
  direction: Vector3,
}

// Construction
impl Line {
  /// Create a new line from an origin and direction.
  ///
  /// The direction is assumed to be normalized
  ///
  /// # Safety
  ///
  /// The direction must be normalized, otherwise the behavior is undefined.
  ///
  /// # Parameters
  ///
  /// * `origin` - The origin of the line
  /// * `direction` - The direction of the line
  #[inline(always)]
  pub const unsafe fn new_unchecked(origin: Point3, direction: Vector3) -> Self {
    Self { origin, direction }
  }

  /// Create a new line from an origin and direction.
  ///
  /// The direction is normalized if it is not already.
  ///
  /// # Parameters
  ///
  /// * `origin` - The origin of the line
  /// * `direction` - The direction of the line
  #[inline(always)]
  pub fn new(origin: Point3, direction: &Vec3) -> Self {
    Self {
      origin,
      direction: direction.normalized(),
    }
  }
}

// Properties
impl Line {
  /// Get the origin of the line.
  #[inline(always)]
  pub const fn origin(&self) -> &Point3 {
    &self.origin
  }

  /// Get the direction of the line.
  #[inline(always)]
  pub const fn direction(&self) -> &Vec3 {
    self.direction.as_vec3()
  }

  /// Gets the point at the specified distance on the line
  ///
  /// A value of `0` for the distance will return the origin of the line.
  ///
  /// # Parameters
  ///
  /// * `distance` - The distance along the line to get the point at
  pub fn point_at_distance(&self, distance: f32) -> Point3 {
    Point3::from_vec3(&(self.origin.as_vec3() + self.direction.as_vec3() * distance))
  }

  /// Gets the distance between the line and a point
  ///
  /// # Parameters
  ///
  /// * `point` - The point to get the distance to
  pub fn distance_to(&self, point: &Point3) -> f32 {
    let origin_to_point = &self.origin - point;
    let projection = origin_to_point.dot(self.direction.as_vec3());

    origin_to_point.magnitude() - projection
  }

  /// Checks if the line contains a point
  ///
  /// # Parameters
  ///
  /// * `point` - The point to check
  pub fn contains_point(&self, point: &Point3) -> bool {
    let origin_to_point = point - &self.origin;

    // Compare ratios to check collinearity
    let t_x = if self.direction.x() != 0.0 {
      Some(origin_to_point.x() / self.direction.x)
    } else {
      None
    };
    let t_y = if self.direction.y() != 0.0 {
      Some(origin_to_point.y() / self.direction.y)
    } else {
      None
    };
    let t_z = if self.direction.z() != 0.0 {
      Some(origin_to_point.z() / self.direction.z)
    } else {
      None
    };

    t_x == t_y && t_y == t_z
  }
}

impl Intersects for Line {
  fn intersects(&self, other: &Self) -> bool {
    let cross = self.direction.as_vec3().cross(other.direction.as_vec3());

    // If the cross product is zero, the lines are parallel and can't intersect.
    if cross.square_magnitude() < 0.0001 {
      return false;
    }

    let delta = other.origin.as_vec3() - self.origin.as_vec3();
    let scalar_triple = delta.dot(cross.as_vec3());

    // If the scalar triple product is zero, or very near to zero, the lines are
    // coplanar and don't intersect.
    scalar_triple.abs() < 0.0001
  }
}

#[cfg(test)]
#[path = "line.test.rs"]
mod test;