Skip to content

Commit 5fde423

Browse files
Merge #620
620: Implement Plane methods r=toasteater a=Robert7301201 Implement all of `Plane` methods supported by Godot. Includes documentation and tests to make sure it matches Godot's outputs. Resolves #612. Co-authored-by: Robert McDermott <[email protected]>
2 parents 529a2fc + f4f6d2b commit 5fde423

File tree

1 file changed

+276
-0
lines changed
  • gdnative-core/src/core_types/geom

1 file changed

+276
-0
lines changed

gdnative-core/src/core_types/geom/plane.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use crate::core_types::Vector3;
2+
use euclid::approxeq::ApproxEq;
23

34
/// Plane in hessian form.
45
#[repr(C)]
@@ -20,4 +21,279 @@ impl Plane {
2021
pub fn from_sys(c: sys::godot_plane) -> Self {
2122
unsafe { std::mem::transmute::<sys::godot_plane, Self>(c) }
2223
}
24+
25+
/// Creates a new `Plane` from the ['Vector3'](./type.Vector3.html) normal and the distance from the origin.
26+
#[inline]
27+
pub fn new(normal: Vector3, d: f32) -> Plane {
28+
Plane { normal, d }
29+
}
30+
31+
/// Creates a new `Plane` from four floats.
32+
/// a, b, c are used for the normal ['Vector3'](./type.Vector3.html).
33+
/// d is the distance from the origin.
34+
#[inline]
35+
pub fn from_coordinates(a: f32, b: f32, c: f32, d: f32) -> Plane {
36+
Plane {
37+
normal: Vector3::new(a, b, c),
38+
d,
39+
}
40+
}
41+
42+
/// Creates a new `Plane` from three [`Vector3`](./type.Vector3.html), given in clockwise order.
43+
/// If all three points are collinear, returns `None`.
44+
#[inline]
45+
pub fn from_points(a: Vector3, b: Vector3, c: Vector3) -> Option<Plane> {
46+
let normal = (a - c).cross(a - b).normalize();
47+
48+
if normal.x.is_nan() || normal.y.is_nan() || normal.z.is_nan() {
49+
None
50+
} else {
51+
Some(Plane {
52+
normal,
53+
d: normal.dot(a),
54+
})
55+
}
56+
}
57+
58+
/// Returns the center of the `Plane`.
59+
#[inline]
60+
pub fn center(&self) -> Vector3 {
61+
self.normal * self.d
62+
}
63+
64+
/// Returns the shortest distance from the `Plane` to `point`.
65+
#[inline]
66+
pub fn distance_to(&self, point: Vector3) -> f32 {
67+
(self.normal.dot(point)) - self.d
68+
}
69+
70+
/// Returns `true` if `point` is inside the `Plane`.
71+
/// `epislon` specifies the minimum threshold to be considered inside the `Plane`.
72+
#[inline]
73+
pub fn has_point(&self, point: Vector3, epsilon: f32) -> bool {
74+
let dist = self.distance_to(point).abs();
75+
76+
dist <= epsilon
77+
}
78+
79+
/// Returns the intersection point of the three planes `b`, `c` and this `Plane`.
80+
/// Returns `None` if the 'Plane's don't intersect.
81+
#[inline]
82+
pub fn intersect_3(&self, b: Plane, c: Plane) -> Option<Vector3> {
83+
let a = &self;
84+
85+
let denom = Vector3::cross(a.normal, b.normal).dot(c.normal);
86+
87+
if denom.approx_eq(&0.0) {
88+
None
89+
} else {
90+
Some(
91+
((Vector3::cross(b.normal, c.normal) * a.d)
92+
+ (Vector3::cross(c.normal, a.normal) * b.d)
93+
+ (Vector3::cross(a.normal, b.normal) * c.d))
94+
/ denom,
95+
)
96+
}
97+
}
98+
99+
/// Returns the intersection point of a ray consisting of the position `from` and the direction normal `dir` with this plane/
100+
/// Returns `None` if the ray doesn't intersect.
101+
#[inline]
102+
pub fn intersects_ray(&self, from: Vector3, dir: Vector3) -> Option<Vector3> {
103+
let den = self.normal.dot(dir);
104+
105+
if den.approx_eq(&0.0) {
106+
return None;
107+
}
108+
109+
let dist = (self.normal.dot(from) - self.d) / den;
110+
111+
if dist > std::f32::EPSILON {
112+
return None;
113+
}
114+
115+
Some(from + dir * -dist)
116+
}
117+
118+
/// Returns the intersection point of a segment from `begin` to `end` with this `Plane`.
119+
/// Returns `None` if the the segment doesn't intersect.
120+
#[inline]
121+
pub fn intersects_segment(&self, begin: Vector3, end: Vector3) -> Option<Vector3> {
122+
let segment = begin - end;
123+
let den = self.normal.dot(segment);
124+
125+
if den.approx_eq(&0.0) {
126+
return None;
127+
}
128+
129+
let dist = (self.normal.dot(begin) - self.d) / den;
130+
131+
if dist < -std::f32::EPSILON || dist > (1.0 + std::f32::EPSILON) {
132+
return None;
133+
}
134+
135+
Some(begin + segment * -dist)
136+
}
137+
138+
/// Returns `true` if this `Plane` and `other` are approximately equal.
139+
/// Determined by running `approx_eq` on both `normal` and `d`.
140+
#[inline]
141+
pub fn approx_eq(&self, other: Plane) -> bool {
142+
self.normal.approx_eq(&other.normal) && self.d.approx_eq(&other.d)
143+
}
144+
145+
/// Returns `true` if `point` is above the `Plane`.
146+
#[inline]
147+
pub fn is_point_over(&self, point: Vector3) -> bool {
148+
self.normal.dot(point) > self.d
149+
}
150+
151+
/// Returns the `Plane` normalized.
152+
#[inline]
153+
pub fn normalize(mut self) -> Plane {
154+
let l = self.normal.length();
155+
156+
if l == 0.0 {
157+
self.normal = Vector3::new(0.0, 0.0, 0.0);
158+
self.d = 0.0;
159+
} else {
160+
self.normal /= l;
161+
self.d /= l;
162+
}
163+
164+
self
165+
}
166+
167+
/// Returns the orthogonal projection of `point` into a point in the `Plane`.
168+
#[inline]
169+
pub fn project(&self, point: Vector3) -> Vector3 {
170+
point - self.normal * self.distance_to(point)
171+
}
172+
}
173+
174+
#[cfg(test)]
175+
mod test {
176+
use super::*;
177+
178+
fn test_inputs() -> (Plane, Vector3) {
179+
(
180+
Plane::from_coordinates(0.01, 0.02, 0.04, 0.08),
181+
Vector3::new(0.16, 0.32, 0.64),
182+
)
183+
}
184+
185+
#[test]
186+
fn from_points() {
187+
let a = Vector3::new(-1.0, 1.0, 0.0);
188+
let b = Vector3::new(-1.0, 0.0, 0.0);
189+
let c = Vector3::new(1.0, 1.0, 1.0);
190+
let d = Vector3::new(-1.0, -1.0, 0.0);
191+
192+
let expected_valid = Plane::from_coordinates(0.447214, 0.0, -0.894427, -0.447214);
193+
194+
assert!(Plane::from_points(a, b, c)
195+
.unwrap()
196+
.approx_eq(expected_valid));
197+
assert_eq!(Plane::from_points(a, b, d), None);
198+
}
199+
200+
#[test]
201+
fn center() {
202+
let (p, _v) = test_inputs();
203+
204+
let expected = Vector3::new(0.0008, 0.0016, 0.0032);
205+
206+
assert!(p.center().approx_eq(&expected));
207+
}
208+
209+
#[test]
210+
fn distance_to() {
211+
let (p, v) = test_inputs();
212+
213+
let expected = -0.0464;
214+
215+
assert!(p.distance_to(v).approx_eq(&expected));
216+
}
217+
218+
#[test]
219+
fn has_point() {
220+
let p = Plane::new(Vector3::new(1.0, 1.0, 1.0), 1.0);
221+
222+
let outside = Vector3::new(0.0, 0.0, 0.0);
223+
let inside = Vector3::new(1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0);
224+
225+
assert!(!p.has_point(outside, 0.00001));
226+
assert!(p.has_point(inside, 0.00001));
227+
}
228+
229+
#[test]
230+
fn intersect_3() {
231+
let (p, _v) = test_inputs();
232+
233+
let b = Plane::from_coordinates(0.08, 0.04, 0.03, 0.01);
234+
let c = Plane::from_coordinates(0.05, 0.2, 0.1, 0.6);
235+
236+
let expected = Vector3::new(-1.707317, 2.95122, 0.95122);
237+
238+
let d = Plane::from_coordinates(0.01, 0.02, 0.4, 0.16);
239+
let e = Plane::from_coordinates(0.01, 0.02, 0.4, 0.32);
240+
241+
assert!(p.intersect_3(b, c).unwrap().approx_eq(&expected));
242+
assert_eq!(p.intersect_3(d, e), None);
243+
}
244+
245+
#[test]
246+
fn intersects_ray() {
247+
let (p, v) = test_inputs();
248+
249+
let expected = Vector3::new(0.16, 2.64, 0.64);
250+
251+
assert!(p
252+
.intersects_ray(v, Vector3::new(0.0, 1.0, 0.0))
253+
.unwrap()
254+
.approx_eq(&expected));
255+
assert_eq!(p.intersects_ray(v, Vector3::new(0.0, -1.0, 0.0)), None);
256+
}
257+
258+
#[test]
259+
fn intersects_segment() {
260+
let (p, v) = test_inputs();
261+
262+
let expected = Vector3::new(0.16, 2.64, 0.64);
263+
264+
assert!(p
265+
.intersects_segment(v, Vector3::new(0.16, 10.0, 0.64))
266+
.unwrap()
267+
.approx_eq(&expected));
268+
assert_eq!(
269+
p.intersects_segment(v, Vector3::new(0.16, -10.0, 0.64)),
270+
None
271+
);
272+
}
273+
274+
#[test]
275+
fn is_point_over() {
276+
let (p, v) = test_inputs();
277+
278+
assert!(!p.is_point_over(v));
279+
assert!(p.is_point_over(Vector3::new(1.0, 10.0, 2.0)));
280+
}
281+
282+
#[test]
283+
fn normalize() {
284+
let (p, _v) = test_inputs();
285+
286+
assert!(p.normalize().approx_eq(Plane::from_coordinates(
287+
0.218218, 0.436436, 0.872872, 1.745743
288+
)));
289+
}
290+
291+
#[test]
292+
fn project() {
293+
let (p, v) = test_inputs();
294+
295+
let expected = Vector3::new(0.160464, 0.320928, 0.641856);
296+
297+
assert!(p.project(v).approx_eq(&expected))
298+
}
23299
}

0 commit comments

Comments
 (0)