Skip to content

Commit 949ac64

Browse files
committed
godot-core: builtin: reimplement Plane functions/methods
1 parent c0935ac commit 949ac64

File tree

1 file changed

+154
-3
lines changed

1 file changed

+154
-3
lines changed

godot-core/src/builtin/plane.rs

Lines changed: 154 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,26 @@ use std::ops::Neg;
99
use godot_ffi as sys;
1010
use sys::{ffi_methods, GodotFfi};
1111

12-
use super::{is_equal_approx, real, Vector3};
12+
use super::{is_zero_approx, is_equal_approx, real, Vector3};
13+
14+
pub const CMP_EPSILON: real = 0.00001;
1315

1416
/// 3D plane in [Hessian normal form](https://mathworld.wolfram.com/HessianNormalForm.html).
1517
///
1618
/// The Hessian form defines all points `point` which satisfy the equation
1719
/// `dot(normal, point) + d == 0`, where `normal` is the normal vector and `d`
1820
/// the distance from the origin.
1921
///
20-
/// Currently most methods are only available through [`InnerPlane`](super::inner::InnerPlane).
21-
///
2222
/// Note: almost all methods on `Plane` require that the `normal` vector have
2323
/// unit length and will panic if this invariant is violated. This is not separately
2424
/// annotated for each method.
2525
#[derive(Copy, Clone, PartialEq, Debug)]
2626
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
2727
#[repr(C)]
2828
pub struct Plane {
29+
/// The plane's normal component.
2930
pub normal: Vector3,
31+
/// The plane's d component.
3032
pub d: real,
3133
}
3234

@@ -105,6 +107,90 @@ impl Plane {
105107
}
106108
}
107109

110+
/// Returns the shortest distance from the `self` `Plane` to the provided `Vector3`. The distance
111+
/// will be positive if the `Vector3` is above the `Plane`, and will be negative if the `Vector3`
112+
/// is below the `Plane`.
113+
///
114+
/// _Godot equivalent: `Plane.distance_to(Vector3 point)`_
115+
#[inline]
116+
pub fn distance_to(&self, point: Vector3) -> real {
117+
self.normal.dot(point) - self.d
118+
}
119+
120+
/// Returns the center of the `self` `Plane` in `Vector3` form.
121+
///
122+
/// _Godot equivalent: `Plane.get_center()`_
123+
#[inline]
124+
pub fn get_center(&self) -> Vector3 {
125+
self.normal * self.d
126+
}
127+
128+
/// Returns `true` if the specified `Vector3` is inside the `self` `Plane`, will return otherwise
129+
/// if not. Tolerance of the function unless specified will be using the default value of 1e-05.
130+
///
131+
/// _Godot equivalent: `Plane.has_point(Vector3 point, float tolerance=1e-05)`_
132+
#[inline]
133+
pub fn has_point(&self, point: Vector3, tolerance: Option<real>) -> bool {
134+
let dist: real = (self.normal.dot(point) - self.d).abs();
135+
dist <= tolerance.unwrap_or(CMP_EPSILON)
136+
}
137+
138+
/// Returns the intersection point of the `Plane`s of the current `Plane`, `b`, and `c` enclosed
139+
/// in `Some`. If no intersection point is found, `None` is returned.
140+
///
141+
/// _Godot equivalent: `Plane.intersect_3(Place b, Plane c)`_
142+
#[inline]
143+
pub fn intersect_3(&self, b: &Self, c: &Self) -> Option<Vector3> {
144+
let normal0: Vector3 = self.normal;
145+
let normal1: Vector3 = b.normal;
146+
let normal2: Vector3 = c.normal;
147+
let denom: real = normal0.cross(normal1).dot(normal2);
148+
if is_zero_approx(denom) {
149+
return None;
150+
}
151+
let result = ((normal1.cross(normal2) * self.d) +
152+
(normal2.cross(normal0) * b.d) +
153+
(normal0.cross(normal1) * c.d)) /
154+
denom;
155+
Some(result)
156+
}
157+
158+
/// Returns the intersection point of a ray consisting of the position `from` and the direction
159+
/// normal `dir` with the `self` `Plane` enclosed in `Some`. If no intersection is found, `None`
160+
/// is returned.
161+
///
162+
/// _Godot equivalent: `Plane.intersects_ray(Vector3 from, Vector3 dir)`_
163+
#[inline]
164+
pub fn intersects_ray(&self, from: Vector3, dir: Vector3) -> Option<Vector3> {
165+
let den: real = self.normal.dot(dir);
166+
if is_zero_approx(den) {
167+
return None;
168+
}
169+
let dist: real = (self.normal.dot(from) - self.d) / den;
170+
if dist > CMP_EPSILON {
171+
return None;
172+
}
173+
Some(from + dir * -dist)
174+
}
175+
176+
/// Returns the intersection point of a segment from position `from` to position `to` with the
177+
/// `self` `Plane` enclosed in `Some`. If no intersection is found, `None` will be returned.
178+
///
179+
/// _Godot equivalent: `Plane.intersects_segment(Vector3 from, Vector3 to)`_
180+
#[inline]
181+
pub fn intersects_segment(&self, from: Vector3, to: Vector3) -> Option<Vector3> {
182+
let segment: Vector3 = from - to;
183+
let den: real = self.normal.dot(segment);
184+
if is_zero_approx(den) {
185+
return None;
186+
}
187+
let dist: real = (self.normal.dot(from) - self.d) / den;
188+
if dist < -CMP_EPSILON || dist > (1.0 + CMP_EPSILON) {
189+
return None;
190+
}
191+
Some(from + segment * -dist)
192+
}
193+
108194
/// Returns `true` if the two `Plane`s are approximately equal, by calling `is_equal_approx` on
109195
/// `normal` and `d` or on `-normal` and `-d`.
110196
///
@@ -115,6 +201,39 @@ impl Plane {
115201
|| (self.normal.is_equal_approx(-other.normal) && is_equal_approx(self.d, -other.d))
116202
}
117203

204+
/// Returns `true` if the `self` `Plane` is finite by calling `is_finite` on `normal` and `d`.
205+
///
206+
/// _Godot equivalent: `Plane.is_finite()`_
207+
#[inline]
208+
pub fn is_finite(&self) -> bool {
209+
self.normal.is_finite() && self.d.is_finite()
210+
}
211+
212+
/// Returns `true` if `point` is located above the `self` `Plane`.
213+
///
214+
/// _Godot equivalent: `Plane.is_point_over(Vector3 point)`_
215+
#[inline]
216+
pub fn is_point_over(&self, point: Vector3) -> bool {
217+
self.normal.dot(point) > self.d
218+
}
219+
220+
/// Returns a normalized copy of the `self` `Plane`.
221+
///
222+
/// _Godot equivalent: `Plane.normalized()`_
223+
#[inline]
224+
pub fn normalized(&self) -> Self {
225+
self.normal.normalized();
226+
self.clone()
227+
}
228+
229+
/// Returns a orthogonal of the variable `point` into a point in the `self` `Plane`.
230+
///
231+
/// _Godot equivalent: `Plane.project(Vector3 point)`_
232+
#[inline]
233+
pub fn project(&self, point: Vector3) -> Vector3 {
234+
point - self.normal * self.distance_to(point)
235+
}
236+
118237
#[inline]
119238
fn assert_normalized(self) {
120239
assert!(
@@ -202,4 +321,36 @@ mod test {
202321

203322
crate::builtin::test_utils::roundtrip(&plane, expected_json);
204323
}
324+
325+
/// Tests plane methods.
326+
#[test]
327+
fn tests_methods() {
328+
// Random planes including (0.0, 0.0, 0.0).
329+
let a: Plane = Plane::new(Vector3::new(1.0, 2.0, 3.0).normalized(), 0.0);
330+
let b: Plane = Plane::new(Vector3::new(0.5, 2.1, 7.1).normalized(), 0.0);
331+
let c: Plane = Plane::new(Vector3::new(4.3, 9.2, 3.14).normalized(), 0.0);
332+
let d: Vector3 = Vector3::new(0.0, 0.0, 0.0);
333+
let e: Vector3 = Vector3::new(0.0, 0.0, 1.0);
334+
assert_eq!(a.distance_to(d), 0.0);
335+
assert_eq!(a.get_center(), d);
336+
assert_eq!(a.has_point(d, None), true);
337+
assert_eq!(match a.intersect_3(&b, &c) {
338+
Some(x) => x,
339+
None => Vector3::new(1.0, 1.0, 1.0),
340+
}, d);
341+
assert_eq!(a.is_finite(), true);
342+
assert_eq!(a.is_point_over(d), false);
343+
assert_eq!(a.is_point_over(e), true);
344+
assert_eq!(a.is_point_over(-e), false);
345+
assert_eq!(match a.intersects_ray(d, e) {
346+
Some(x) => x,
347+
None => Vector3::new(1.0, 1.0, 1.0),
348+
}, d);
349+
assert_eq!(match a.intersects_segment(d, e) {
350+
Some(x) => x,
351+
None => Vector3::new(1.0, 1.0, 1.0),
352+
}, d);
353+
assert_eq!(a.is_equal_approx(&a.normalized()), true);
354+
assert_eq!(a.project(Plane::new(a.normalized().normal, 3.0).get_center()), a.get_center());
355+
}
205356
}

0 commit comments

Comments
 (0)