Skip to content

Commit f2740e8

Browse files
committed
Implemented plane methods, docs, and tests
1 parent 529a2fc commit f2740e8

File tree

1 file changed

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

1 file changed

+259
-0
lines changed

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

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

0 commit comments

Comments
 (0)