-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Add geometric primitives #1621
Add geometric primitives #1621
Conversation
I've opted to fully define these primitives in world space, instead of using transforms, intentionally.
I'd like to open this up for discussion. any disagreement on the above points? |
Co-authored-by: MinerSebas <66798382+MinerSebas@users.noreply.github.com>
These additions should probebly also be re-exported by the |
As an end user, it would be nice to have some alternate construction impls for each of these. E.g. Box by 3 points, plane by 3 points and so on. These structs should derive at least Clone and Debug, and probably Hash, PartialEq, and Eq. Also probably Reflect? |
The ability to construct Transform versions of these structs seems very useful in downstream users: it feels like this should be a shared trait method? |
As a general note, these are very much needed in Bevy itself to a) avoid ecosystem fragmentation b) use in critical internal tools. |
Great points. Yeah, I'd like to add some constructors and figure out what shared functionality should go into the primitive trait. I could see that trait being useful in the future for checking primitive intersections using the trait. |
This would be done by having a Transform component on the same entity, and any special logic would be handled in the user's systems. These primitives would be blind to this and could be used however you like. |
Should Line, Point, Arc and Triangle also be implemented as this same sort of primitive? They're not fully 3D, but very common. |
Yes, I think those "2D" primitives defined in 3d space would make sense to add, as well as capsule, cylinder, and anything else I've forgotten. I will be updating this PR sporadically, but (anyone!) please leave comments with ideas or feedback as I'm working. I want to keep the scope limited to primitive definition only. We can add helper functions in future PRs. |
For clarity, I would |
Yup, I just haven't pushed it yet. :) |
I've been going back and forth on this for a while, but I'm now feeling fairly confident this is the right choice. I implemented a Cache Efficiency
CPU Efficiency
Ergonomics
Use with Transforms
|
This was a fantastic analysis and I agree with your conclusions. A few thoughts:
I spent some time thinking about this, and I couldn't come up with a compelling example, especially a performance-critical one.
This is actually an interesting use case for trait queries (cc @Davier whose exploring these) : you could have a single system that queried for a |
let mut vertices = [Vec3::ZERO; 8]; | ||
let aab_vertices = self.aab.vertices(); | ||
for i in 0..vertices.len() { | ||
vertices[i] = self.transform.project_point3(aab_vertices[i]) |
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.
project_point3
performs a perspective divide which is only necessary if the transform is not a perspective projection. If the transform was a perspective projection the the vertices would no longer be a box shape, so perhaps it is OK to assume that it is not. For an affine transform transform_point3
is more efficient as it won't do an unnecessary divide.
/// \ | \ | | ||
/// (6)------(2) | ||
/// ``` | ||
pub fn vertices(&self) -> [Vec3; 8] { |
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.
It seems quite expensive calculating these every time this is called. It may be preferable to store the transformed vertices instead of storing it as an aabb and transform?
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'm really not sure. I've been deliberating how to make the structs as small as possible, while also retaining information needed to update the primitives. Some algorithms call for AABB box extents, while others need all vertices. Storing only box extents is the most memory efficient, while storing all vertices (might) be more CPU efficient. I don't want to store both in the struct, because that would be even worse for memory use.
The ECS idiomatic way to handle this would be to create a component for each style of struct, and only query the one I need. My hesitation with this approach is this could quickly lead to an explosion in number of types, and I'd like to keep primitives as simple as possible. Maybe namespacing them would be enough, e.g.: aabb::extents
and aabb::vertices
.
I'm really uncertain. I'll create an RFC once that process has been finalized, and that will hopefully allow a nuanced discussion on the topic.
Hey @bitshifter, I really appreciate this review. I'll be making an RFC where we take a closer look at the design of primitives, including looking into any alternative libraries. Would you mind if I ping you when I get that up? |
Yep, I'm happy to chime in @aevyrie . |
crates/bevy_geometry/src/lib.rs
Outdated
pub trait Primitive3d { | ||
/* | ||
/// Returns true if this primitive is on the outside (normal direction) of the supplied | ||
fn outside_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.
Is this the standard name for this function / property? Seems a bit confusing for non-planar shapes?
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 see how this might be confusing. It's checking if a 3d objects is fully on the outer side of the plane - i.e. fully contained by the halfspace in the direction of the plane's normal. I'll poke around and see if I can find a standard name.
crates/bevy_geometry/src/lib.rs
Outdated
pub trait Primitive3d {} | ||
pub trait Primitive3d { | ||
/* | ||
/// Returns true if this primitive is on the outside (normal direction) of the supplied |
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.
Can we even define this for lower dimensional shapes embedded in 3D?
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.
Yes, all 3d primitives can be bounded by a finite volume. Lower dimensional shapes may have a bounding volume of zero, but it still makes sense to check if they are fully in the outer halfspace of a 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.
Even a plane, with an infinite area, can be fully "outside" another plane if both planes are parallel.
crates/bevy_geometry/src/lib.rs
Outdated
projection_matrix: Mat4, | ||
) -> Frustum { | ||
let ndc_to_world: Mat4 = camera_position * projection_matrix.inverse(); | ||
// Near/Far, Top/Bottom, Left/Right |
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.
It might be nice to construct this whole thing in a for loop for clarity? The actual logic is hard to follow.
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 don't see a way to do this in a loop yet, without making the logic harder to follow. Everything is "unrolled" and named so it's easier to follow which points are being used to take cross products and compute normals. I could probably make the code shorter and less repetitive with a well-constructed loop, but I worry it will be harder to follow and debug.
I noticed I had some old pending comments stored up; sorry if anything is out of date! |
FYI: There are a lot of changes I want to make to this PR, but I'm holding off until we have the RFC process up (next week?) so I can flesh out the details of what I'm thinking. Edit: the RFC process PR: #1662 |
MinGreaterThanMax, | ||
NonPositiveExtents, | ||
} | ||
impl Error for PrimitiveError {} |
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.
https://github.com/bevyengine/bevy/search?q=thiserror
I see lots of use of thiserror
in other bevy crates FYI
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 for the heads up. For some reason I didn't think this was the case.
Co-authored-by: Grindv1k <torstein.grindvik@gmail.com>
Co-authored-by: Grindv1k <torstein.grindvik@gmail.com>
Hey @bitshifter, the RFC in question is up and ready for review. I'd appreciate your feedback! |
Closing, too stale to be useful. |
Note: See RFC bevyengine/rfcs#12
Adds geometric primitives to Bevy. These are lightweight mathematical representations of primitives commonly used for bounding volumes and physics.