-
-
Notifications
You must be signed in to change notification settings - Fork 217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
godot-core: builtin: reimplement Plane functions/methods #268
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-268 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for this pull request. This is a great feature addition to the library! 😀
I know there's a need to implement test cases for it by looking at the Rect2i commits, but I would like to know if this part is correct yet since I am an still an absolute beginner at this stuff. Please let me know of anything that is an issue.
That's a great approach, being iterative 👍 you can definitely add some tests once we've sorted out other issues 🙂
Also, would implementing test cases for Plane be similar in implementation as the Rect2i ones?
Yes, can be similar; a good way is also to look at Godot tests or even adopt those.
Many documentations start with a longer sentence, sometimes 3 and more lines. For API docs, it would be great to have an additional brief description, which is to the point and very concise. For example:
/// Returns the intersection point of a segment from position `from` to position `to` with the
/// `self` `Plane` enclosed in `Some`. If no intersection is found, `None` will be returned.
///
/// _Godot equivalent: `Plane.intersects_segment(Vector3 from, Vector3 to)`_
#[inline]
pub fn intersects_segment(&self, from: Vector3, to: Vector3) -> Option<Vector3> {
could become:
/// Finds intersections of the plane with a line segment.
///
/// Returns the intersection point of a segment from position `from` to position `to` with the
/// `self` `Plane` enclosed in `Some`. If no intersection is found, `None` will be returned.
///
/// _Godot equivalent: `Plane.intersects_segment(Vector3 from, Vector3 to)`_
#[inline]
pub fn intersects_segment(&self, from: Vector3, to: Vector3) -> Option<Vector3> {
godot-core/src/builtin/plane.rs
Outdated
/// The plane's normal component. | ||
pub normal: Vector3, | ||
/// The plane's d component. | ||
pub d: real, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current documentation has not more information than the field names -- could you instead write what they mean?
Doesn't need to be long, something like "normal vector pointing away from the plane" is enough.
godot-core/src/builtin/plane.rs
Outdated
/// Returns a normalized copy of the `self` `Plane`. | ||
/// | ||
/// _Godot equivalent: `Plane.normalized()`_ | ||
#[inline] | ||
pub fn normalized(&self) -> Self { | ||
self.normal.normalized(); | ||
self.clone() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This implementation is just returning self.clone()
.
The past-tense also indicates that a copy is generated (rather than mutating the object directly), so self.normal.normalized()
has no effect.
You can use the #[must_use]
attribute here, to make sure the caller gets a warning if they don't use the result.
Regarding CI failure, the To see the API docs of your PR, check the comment from our bot. The docs will be updated every time you push (with a few minutes latency). |
Alright, thank you very much for the inputs! I will try to resolve them one by one. |
Alright, this update (hopefully) addresses many of the issues pointed out with the previous version of the commit. Again, let me know of any more issues in this one! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems pretty good in general so far to me! Just got some nitpicks here and there.
Also it seems like you're using mixed tabs and spaces in some places? I feel like rustfmt should be able to fix that? So run that if you haven't. And if rustfmt doesn't catch it then please manually fix it (turn the tabs into spaces).
godot-core/src/builtin/plane.rs
Outdated
use super::{is_equal_approx, real, Vector3}; | ||
use super::{is_zero_approx, is_equal_approx, real, Vector3}; | ||
|
||
pub const CMP_EPSILON: real = 0.00001; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This already exists in builtin/math.rs
godot-core/src/builtin/plane.rs
Outdated
|
||
/// Projects a point orthogonally to the plane. | ||
/// | ||
/// Returns a orthogonal of the variable `point` into a point in the `self` `Plane`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not very clearly worded. Returns (an) orthogonal what? a point? points cant really be orthogonal to other points.
godot-core/src/builtin/plane.rs
Outdated
assert_eq!(plane_a.is_equal_approx(&plane_e), false); | ||
// Both attributes are approximately equal. | ||
assert!(plane_a.is_equal_approx(&plane_f)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you should be able to use assert_eq_approx
/assert_ne_approx
here something like
assert_eq!(plane_a.is_equal_approx(&plane_e), false); | |
// Both attributes are approximately equal. | |
assert!(plane_a.is_equal_approx(&plane_f)); | |
assert_ne_approx!(&plane_a, &plane_e, Plane::is_equal_approx); | |
// Both attributes are approximately equal. | |
assert_eq_approx!(&plane_a, &plane_f, Plane::is_equal_approx); |
godot-core/src/builtin/plane.rs
Outdated
/// Finds whether the plane is finite or not. | ||
/// | ||
/// Returns `true` if the `self` `Plane` is finite by calling `is_finite` on `normal` and `d`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first sentence here seems a bit redundant to me. is_finite
already communicates that this function finds whether the plane is finite or not. I think you can just cut out the first sentence. Same for is_point_over
.
godot-core/src/builtin/plane.rs
Outdated
/// Creates a normalized copy of the plane. | ||
/// | ||
/// Returns a normalized copy of the `self` `Plane`. | ||
#[inline] | ||
pub fn normalized(&self) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the docs here you're just repeating yourself. Also this function can take self
instead of &self
.
Gotcha, thanks for reviewing! I'll get these issues ironed out. |
1a1c86f
to
3ca4fe6
Compare
So, this latest commit should addresses all the previous issues and action checks (besides from the integration tests I assume) here. If there's no more issues here, I'll move on to the integration tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for the update! 🙂
I have added a few more comments, some related to concise documentation and some to testing.
godot-core/src/builtin/plane.rs
Outdated
/// Returns `true` if the specified `Vector3` is inside the `self` `Plane`, will return otherwise | ||
/// if not. Tolerance of the function unless specified will be using the default value of 1e-05. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Documentation, if provided, should contain more information than the method name itself. Otherwise it should be omitted.
In this case, you already mentioned twice that an "inside check" is happening: through the name contains_point
, and the through the brief description "Finds whether a point is inside the plane or not."
So, instead of repeating the same thing a 3rd time, you should directly focus on the important part, i.e. the tolerance.
Also, don't mention the value of the tolerance here; the value may change over time and the docs become obsolete. Instead, since CMP_EPSILON
is a public symbol, you can refer to it via intra-doc link.
/// Returns `true` if the specified `Vector3` is inside the `self` `Plane`, will return otherwise | |
/// if not. Tolerance of the function unless specified will be using the default value of 1e-05. | |
/// A point is considered part of the plane if its distance to it is less or equal than | |
/// [`CMP_EPSILON`][crate::builtin::CMP_EPSILON]. |
godot-core/src/builtin/plane.rs
Outdated
/// Finds the intersection point of three planes. | ||
/// | ||
/// Returns the intersection point of the `Plane`s of the current `Plane`, `b`, and `c` enclosed | ||
/// in `Some`. If no intersection point is found, `None` will be returned. | ||
#[inline] | ||
pub fn intersect_3(&self, b: &Self, c: &Self) -> Option<Vector3> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here, no need to repeat or mention what is obvious from the signature.
/// Finds the intersection point of three planes. | |
/// | |
/// Returns the intersection point of the `Plane`s of the current `Plane`, `b`, and `c` enclosed | |
/// in `Some`. If no intersection point is found, `None` will be returned. | |
#[inline] | |
pub fn intersect_3(&self, b: &Self, c: &Self) -> Option<Vector3> { | |
/// Finds the intersection point of three planes. | |
/// | |
/// If no intersection point is found, `None` will be returned. | |
#[inline] | |
pub fn intersect_3(&self, b: &Self, c: &Self) -> Option<Vector3> { |
godot-core/src/builtin/plane.rs
Outdated
/// Finds the intersection point of the plane with a ray. | ||
/// | ||
/// Returns the intersection point of a ray consisting of the position `from` and the direction | ||
/// normal `dir` with the `self` `Plane` enclosed in `Some`. If no intersection is found, `None` | ||
/// will be returned. | ||
#[inline] | ||
pub fn intersects_ray(&self, from: Vector3, dir: Vector3) -> Option<Vector3> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"direction normal" -> "direction normal"
Name should also be intersect_ray
without "s" 🙂
Also a bit shorter sentences for easier understanding. The "enclosed in Some
" is implied. Also added a bit of geometrical context.
/// Finds the intersection point of the plane with a ray. | |
/// | |
/// Returns the intersection point of a ray consisting of the position `from` and the direction | |
/// normal `dir` with the `self` `Plane` enclosed in `Some`. If no intersection is found, `None` | |
/// will be returned. | |
#[inline] | |
pub fn intersects_ray(&self, from: Vector3, dir: Vector3) -> Option<Vector3> { | |
/// Finds the intersection point of the plane with a ray. | |
/// | |
/// The ray starts at position `from` and has direction vector `dir`, i.e. it is unbounded in one direction. | |
/// | |
/// If no intersection is found (the ray is parallel to the plane or points away from it), `None` will be returned. | |
#[inline] | |
pub fn intersect_ray(&self, from: Vector3, dir: Vector3) -> Option<Vector3> { |
godot-core/src/builtin/plane.rs
Outdated
/// Finds the intersection point of the plane with a line segment. | ||
/// | ||
/// Returns the intersection point of a segment from position `from` to position `to` with the | ||
/// `self` `Plane` enclosed in `Some`. If no intersection is found, `None` will be returned. | ||
#[inline] | ||
pub fn intersects_segment(&self, from: Vector3, to: Vector3) -> Option<Vector3> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe similar structure as suggested above. Could be worth mentioning that a line segment is bounded in both directions.
Also: intersect_segment
.
godot-core/src/builtin/plane.rs
Outdated
#[inline] | ||
pub fn is_equal_approx(&self, other: &Self) -> bool { | ||
(self.normal.is_equal_approx(other.normal) && is_equal_approx(self.d, other.d)) | ||
|| (self.normal.is_equal_approx(-other.normal) && is_equal_approx(self.d, -other.d)) | ||
} | ||
|
||
/// Returns `true` if the `self` `Plane` is finite by calling `is_finite` on `normal` and `d`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it helps readability if you use "plane" instead of "Plane
". Instead, the monospace style should be used for other symbols, like involved parameters.
This applies to many of the documentations, a search&replace might do a lot.
godot-core/src/builtin/plane.rs
Outdated
// From a point straight up the origin point, the distance from the origin to the intersection point | ||
// formed by a slanted ray should follow the Pythagorean Theorem. | ||
assert_eq!( | ||
plane.intersects_ray(vec_c, vec_e - vec_c).unwrap().length(), | ||
(vec_c.distance_squared_to(vec_e) - vec_c.length_squared()).sqrt() | ||
); | ||
assert_eq!( | ||
plane.intersects_ray(vec_b, vec_e - vec_b).unwrap().length(), | ||
(vec_b.distance_squared_to(vec_e) - vec_b.length_squared()).sqrt() | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the rationale for testing derived properties?
We're testing our implementation, not geometry... So I wonder if it weren't more straightforward to directly check the outcome of intersects_ray
, like you did above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, I thought it would be useful to check for mathematically accurate results for the implementations too since it's a mathematical object 😅, but I can see why not comparing straight from the results of intersect_ray
(already removed the s) would be problematic for just testing for implementation.
godot-core/src/builtin/plane.rs
Outdated
// From a point straight up the origin point, the distance from the origin to the intersection point | ||
// formed by a slanted segment should follow the Pythagorean Theorem only if the segment ended on or | ||
// beyond the `plane`. | ||
assert_eq!( | ||
plane.intersects_segment(vec_c, vec_f).unwrap().length(), | ||
(vec_c.distance_squared_to(vec_f) - vec_c.length_squared()).sqrt() | ||
); | ||
assert_eq!( | ||
plane.intersects_segment(vec_b, vec_f).unwrap().length(), | ||
(vec_b.distance_squared_to(vec_f) - vec_b.length_squared()).sqrt() | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See above; these might not be necessary
godot-core/src/builtin/plane.rs
Outdated
// Same planes should be equals. | ||
assert_eq_approx!(&plane_a, &plane_a, Plane::is_equal_approx); | ||
assert_eq_approx!(&plane_b, &plane_b, Plane::is_equal_approx); | ||
// Although similar, planes below shouldn't be approximately equals. | ||
assert_ne_approx!(&plane_a, &plane_b, Plane::is_equal_approx); | ||
assert_ne_approx!(&plane_a, &plane_c, Plane::is_equal_approx); | ||
// Planes below should be approximately equal because it's lower than the set tolerance constant. | ||
assert_eq_approx!(&plane_a, &plane_d, Plane::is_equal_approx); | ||
// Although approximately equal in the `normal` part, it is not approximately equal in the `d` | ||
// part. | ||
assert_ne_approx!(&plane_a, &plane_e, Plane::is_equal_approx); | ||
// Both attributes are approximately equal. | ||
assert_eq_approx!(&plane_a, &plane_f, Plane::is_equal_approx); | ||
// Although considered approximately equal with `plane_a`, `plane_b` is not considered approximately | ||
// equal with `plane_e` because the baseline comparison is tighter. | ||
assert_ne_approx!(&plane_b, &plane_e, Plane::is_equal_approx); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Really appreciate the comments explaining why or why not approximate equality applies! 👍
Could you group them to have first all the equality checks, then the non-equality ones? Also please add an empty line before a new //
comment block, to make it immediately clear that a comment refers to the code below it.
godot-core/src/builtin/plane.rs
Outdated
let mut plane_a: Plane = Plane::new(Vector3::new(0.7, 2.0, 6.0).normalized(), 0.0); | ||
let mut plane_b: Plane = Plane::new(Vector3::new(1.5, 7.2, 9.1).normalized(), 2.0); | ||
let mut plane_c: Plane = Plane::new(Vector3::new(1.4, 9.1, 1.2).normalized(), 5.3); | ||
let mut plane_d: Plane = Plane::new(Vector3::new(4.2, 2.9, 1.5).normalized(), 2.4); | ||
let plane_e: Plane = Plane::new(Vector3::new(5.1, 3.0, 2.1).normalized(), 0.2); | ||
|
||
// Denormalize planes. | ||
plane_a.normal = Vector3::new(0.7, 2.0, 6.0); | ||
plane_b.normal = Vector3::new(1.5, 7.2, 9.1); | ||
plane_c.normal = Vector3::new(1.4, 9.1, 1.2); | ||
plane_d.normal = Vector3::new(4.2, 2.9, 1.5); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The normals above are overwritten without ever being used.
Why not just create the planes with the struct syntax Plane { normal: ..., d: ... }
?
Needs half the code and avoids mut
as well.
godot-core/src/builtin/plane.rs
Outdated
// Initial ordinary planes. | ||
let mut plane_a: Plane = Plane::new(Vector3::new(0.7, 2.0, -6.0).normalized(), 10.2); | ||
let mut plane_b: Plane = Plane::new(Vector3::new(1.5, 7.2, 9.1).normalized(), 20.0); | ||
let mut plane_c: Plane = Plane::new(Vector3::new(1.4, 9.1, 1.2).normalized(), 50.3); | ||
let mut plane_d: Plane = Plane::new(Vector3::new(-4.2, 2.9, 1.5).normalized(), 20.4); | ||
let plane_e: Plane = Plane::new(Vector3::new(7.2, -2.9, 2.2).normalized(), 3.3); | ||
|
||
// Test with `normal`s. | ||
plane_a.normal = Vector3::new(0.7, real::INFINITY, -6.0); | ||
plane_b.normal = Vector3::new(0.7, 2.0, real::NEG_INFINITY); | ||
plane_d.normal = Vector3::new(real::NAN, real::INFINITY, real::NEG_INFINITY); | ||
|
||
// Test with `d`s. | ||
plane_c.d = real::INFINITY; | ||
plane_d.d = real::NAN; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As above, directly initialize the planes with their final values.
It would even make sense to have initialization and test statements together, for clarity.
Just reuse the same variable name:
let plane = Plane { normal: Vector3::new(0.7, real::INFINITY, -6.0), d: 10.2 };
assert!(!plane.is_finite());
let plane = Plane { normal: Vector3::new(0.7, 2.0, real::NEG_INFINITY), d: 10.2 };
assert!(!plane.is_finite());
...
godot-core/src/builtin/plane.rs
Outdated
@@ -9,24 +9,26 @@ use std::ops::Neg; | |||
use godot_ffi as sys; | |||
use sys::{ffi_methods, GodotFfi}; | |||
|
|||
use super::{is_equal_approx, real, Vector3}; | |||
use crate::builtin::math::*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid glob imports.
Thanks again! I'll try resolve these issues. |
Alright, another round of review requested. With the names, some of them are a bit hard to write expressively, since some of them are quite hard to do while being short. Also, what's up with the workflows thing? |
GDExtension upstream breakage. I'm on it, and while doing so, improving 4.0 <-> 4.1.dev compatibility 🙂 |
bors try |
bors p=6 |
tryBuild succeeded! The publicly hosted instance of bors-ng is deprecated and will go away soon. If you want to self-host your own instance, instructions are here. If you want to switch to GitHub's built-in merge queue, visit their help page. |
bors r+ |
Build succeeded! The publicly hosted instance of bors-ng is deprecated and will go away soon. If you want to self-host your own instance, instructions are here. If you want to switch to GitHub's built-in merge queue, visit their help page. |
Thanks a lot! 🚀 |
Thanks for merging! If necessary, I'll put the integration tests in a separate pr. |
If you have the time, that would be nice, no rush though 🙂 |
295: Implementation of Plane integration tests r=Bromeon a=T4rmin Adds integration tests to the re-implemented Plane functions (#268, and fixing a function in it) and should also address the Plane section of issue #209. This PR is also a recreation of #286 since it has the great error of force-pushing after rebasing. Co-authored-by: Aaron Lawrence <[email protected]>
295: Implementation of Plane integration tests r=Bromeon a=T4rmin Adds integration tests to the re-implemented Plane functions (#268, and fixing a function in it) and should also address the Plane section of issue #209. This PR is also a recreation of #286 since it has the great error of force-pushing after rebasing. Co-authored-by: Aaron Lawrence <[email protected]>
Partly addresses issue #209 by re-implementing
Plane
's functions/methods.I know there's a need to implement test cases for it by looking at the
Rect2i
commits, but I would like to know if this part is correct yet since I am an still an absolute beginner at this stuff. Please let me know of anything that is an issue.Also, would implementing test cases for
Plane
be similar in implementation as theRect2i
ones?