From 6a67443ea4a4dd77df201747f3074b17ca18eaac Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 20 Apr 2021 17:41:23 -0700 Subject: [PATCH 01/50] Create geometric-primitives.md --- rfcs/geometric-primitives.md | 115 +++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 rfcs/geometric-primitives.md diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md new file mode 100644 index 00000000..56ea7ba9 --- /dev/null +++ b/rfcs/geometric-primitives.md @@ -0,0 +1,115 @@ +# Feature Name: `geometric-primitives` + +## Summary + +Lightweight geometric primitive types for use across bevy engine crates, and as interoperability types for external libraries. + +## Motivation + +This would provide a stardard way to model primitives across the bevy ecosystem to prevent ecosystem fragmentation amongst plugins. + +## Guide-level explanation + +Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as fully defined dimensions. These primitives are *not* discrete meshes, but the underlying precise geometric definition. For example, a circle is: + +```rust +pub struct Circle { + origin: Vec2 + radius: f32, +} +``` + +Geometric primitives have fully defined shape, size, position, and orientation. Position and orientation are handled by the existing `Transform` systems. + +Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: + +- Introducing new named concepts. +- Explaining the feature, ideally through simple examples of solutions to concrete problems. +- Explaining how Bevy users should *think* about the feature, and how it should impact the way they use Bevy. It should explain the impact as concretely as possible. +- If applicable, provide sample error messages, deprecation warnings, or migration guidance. +- If applicable, explain how this feature compares to similar existing features, and in what situations the user would use each one. + +## Reference-level explanation + +### `bevy_geom::2d` + +- `Line` +- `Arc` +- `Circle` +- `Triangle` +- `AABB` +- `OBB` + +### `bevy_geom::3d` + +- `Line` +- `Sphere` +- `Cylinder` +- `Capsule` +- `Torus` +- `Cone` +- `Pyramid` +- `Frustum` +- `AABB` +- `OBB` + +## Drawbacks + +An argument could be made to use an external crate for this, however these types are so fundamental, I think it's important that they are optimized for the engine's uses, and are not a generalized solution. + +This is also a technically simple addition that shouldn't present maintenance burden. The real challenge is upfront in ensuring the API is designed well, and the primitives are performant for their most common use cases. If anything, this furthers the argument for not using an external crate. + +## Rationale and alternatives + +- Why is this design the best in the space of possible designs? + +### Not Using Transforms + +Primitives are fully defined in space, and do not use `Transform` or `GlobalTransform`. This is an intentional decision. + +It's unsurprisingly much simpler to use these types when the primitives are fully defined internally, but maybe somewhat surprisingly, more efficient. + +#### Cache Efficiency + +- Some primitives such as AABB and Sphere don't need a rotation to be fully defined. +- By using a `GlobalTransform`, not only is this an unused Quat that fills the cache line, it would also cause redundant change detection on rotations. +- This is especially important for AABBs and Spheres, because they are fundamental to collision detection and BV(H), and as such need to be as efficient as possible. +- I still haven't found a case where you would use a `Primitive3d` without needing this positional information that fully defines the primitive in space. If this holds true, it means that storing the positional data inside the primitive is _not_ a waste of cache, which is normally why you would want to separate the transform into a separate component. + +#### CPU Efficiency + +- Storing the primitive's positional information internally serves as a form of memoization. +- Because you need the primitive to be fully defined in world space to run useful operations, this means that with a `GlobalTransform` you would need to apply the transform to the primitive every time you need to use it. +- By applying this transformation only once (e.g. during transform propagation), we only need to do this computation a single time. + +#### Ergonomics + +- As I've already mentioned a few times, primitives need to be fully defined in world space to do anything useful with them. +- By making the primitive components fully defined and standalone, computing operations is as simple as: `primitive1.some_function(primitive_2)`, instead of also having query and pass in 2 `GlobalTransform`s in the correct order. + +#### Use with Transforms + +- For use cases such as oriented bounding boxes, a primitive should be defined relative to its parent. +- In this case, the primitive would still be fully defined internally, but we would need to include primitive updates analogous to the transform propagation system. +- For example, a bounding sphere entity would be a child of a mesh, with a `Sphere` primitive and a `Transform`. On updates to the parent's `GlobalTransform`, the bounding sphere's `Transform` component would be used to update the `Sphere`'s position and radius by applying the scale and translation to a unit sphere. This could be applied to all primitives, with the update system being optimized for each primitive. + +- What other designs have been considered and what is the rationale for not choosing them? +- What is the impact of not doing this? +- Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? + +## \[Optional\] Prior art + +TODO + +## Unresolved questions + +- What parts of the design do you expect to resolve through the RFC process before this gets merged? +- What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? +- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +## Future possibilities + +- Bounding boxes +- Collisions +- Frustum Culling +- Debug Rendering From 3f4c7e05fab759c46e6d778a4799a06530a5d36d Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 20 Apr 2021 21:48:10 -0700 Subject: [PATCH 02/50] Correct typo --- rfcs/geometric-primitives.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 56ea7ba9..19b02e0f 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -19,8 +19,9 @@ pub struct Circle { } ``` -Geometric primitives have fully defined shape, size, position, and orientation. Position and orientation are handled by the existing `Transform` systems. +Geometric primitives have fully defined shape, size, position, and orientation. Position and orientation are **not** handled by `Transform` systems. +TODO: Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: - Introducing new named concepts. From fa1fc1c4575c4ed6a1a6b26397022bc2752ea199 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Wed, 21 Apr 2021 22:23:29 -0700 Subject: [PATCH 03/50] Adding details --- rfcs/geometric-primitives.md | 64 ++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 19b02e0f..66b9c364 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -6,7 +6,7 @@ Lightweight geometric primitive types for use across bevy engine crates, and as ## Motivation -This would provide a stardard way to model primitives across the bevy ecosystem to prevent ecosystem fragmentation amongst plugins. +This would provide a standard way to model primitives across the bevy ecosystem to prevent ecosystem fragmentation amongst plugins. ## Guide-level explanation @@ -19,7 +19,7 @@ pub struct Circle { } ``` -Geometric primitives have fully defined shape, size, position, and orientation. Position and orientation are **not** handled by `Transform` systems. +Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** handled by bevy's `Transform` systems. These are fundamental geometric primitives that must be usable and comparable as-is. TODO: Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: @@ -32,39 +32,62 @@ Explain the proposal as if it was already included in the engine and you were te ## Reference-level explanation -### `bevy_geom::2d` +### `bevy_geom::Shape2d` -- `Line` -- `Arc` +These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. + +- `Point`: type alias of Vec2 +- `Direction`: Vec2 that is guaranteed to be normalized through its getter/setter +- `Axis`: (point: Point, normal: Vec2) axis with infinite length +- `Line`: (start: Point, end: Point) line bounded by two points +- `Arc`: (start: Point, end: Point, radius_origin: Point) segment of a circle - `Circle` - `Triangle` -- `AABB` -- `OBB` +- `AABBCentered` origin and half-extents +- `AabbExtents` minimum and maximum extents +- `OBB` origin, orthonormal basis, and half-extents + +### `bevy_geom::Shape3d` -### `bevy_geom::3d` +These types are fully defined in 3d space. +- `Point`: type alias of Vec3 +- `Direction`: Vec3 that is guaranteed to be normalized through its getter/setter +- `Axis` - `Line` - `Sphere` - `Cylinder` - `Capsule` - `Torus` - `Cone` -- `Pyramid` -- `Frustum` -- `AABB` +- `Frustum`: defined with 6 planes +- `AabbCentered` +- `AabbExtents` - `OBB` +### Bounding Boxes + +This section is based off of prototyping work done in [bevy_mod_bounding](https://github.com/aevyrie/bevy_mod_bounding). + +Because bounding boxes are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. + +### Frustum Culling + +This section is based off of prototyping work done in [bevy_frustum_culling](https://github.com/aevyrie/bevy_frustum_culling). + +### Ray Casting + +This section is based off of prototyping work done in [bevy_mod_picking](https://github.com/aevyrie/bevy_mod_picking). + ## Drawbacks -An argument could be made to use an external crate for this, however these types are so fundamental, I think it's important that they are optimized for the engine's uses, and are not a generalized solution. +An argument could be made to use an external crate for this, however these types are so fundamental I think it's important that they are optimized for the engine's uses, and are not from a generalized solution. This is also a technically simple addition that shouldn't present maintenance burden. The real challenge is upfront in ensuring the API is designed well, and the primitives are performant for their most common use cases. If anything, this furthers the argument for not using an external crate. ## Rationale and alternatives -- Why is this design the best in the space of possible designs? - -### Not Using Transforms +### Lack of `Transform`s Primitives are fully defined in space, and do not use `Transform` or `GlobalTransform`. This is an intentional decision. @@ -100,13 +123,20 @@ It's unsurprisingly much simpler to use these types when the primitives are full ## \[Optional\] Prior art -TODO +Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html +Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh + +These examples intermingle primitive geometry with the meshes themselves. This RFC makes these distinct. + ## Unresolved questions - What parts of the design do you expect to resolve through the RFC process before this gets merged? - What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? -- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC? + +### Out of Scope + +- Value types, e.g. float vs. fixed is out of scope. This RFC is focused on the core geometry types and is intended to use Bevy's common rendering value types such as `f32`. ## Future possibilities From 7f5f21ff76690ea111a1a5e62a1929649065f455 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Sat, 24 Apr 2021 11:47:03 -0700 Subject: [PATCH 04/50] Incremental updates --- rfcs/geometric-primitives.md | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 66b9c364..16aa5946 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -14,7 +14,7 @@ Geometric primitives are lightweight representations of geometry that describe t ```rust pub struct Circle { - origin: Vec2 + origin: Point, radius: f32, } ``` @@ -38,10 +38,24 @@ These types only exist in 2d space: their dimensions and location are only defin - `Point`: type alias of Vec2 - `Direction`: Vec2 that is guaranteed to be normalized through its getter/setter -- `Axis`: (point: Point, normal: Vec2) axis with infinite length + +#### Axis + +Line with infinite length on the x/y plane. + + +```rust +struct Axis { point: Point, normal: Direction } +``` + +#### Line + - `Line`: (start: Point, end: Point) line bounded by two points - `Arc`: (start: Point, end: Point, radius_origin: Point) segment of a circle -- `Circle` + +```rust +struct Circle` { origin: Point, radius: f32 } +``` - `Triangle` - `AABBCentered` origin and half-extents - `AabbExtents` minimum and maximum extents @@ -53,8 +67,10 @@ These types are fully defined in 3d space. - `Point`: type alias of Vec3 - `Direction`: Vec3 that is guaranteed to be normalized through its getter/setter -- `Axis` +- `Axis` - `Line` +- `Plane` +- `Quad` - `Sphere` - `Cylinder` - `Capsule` From 9a890e76fec6c0ee03366b4f19f46c368593f474 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sat, 24 Apr 2021 13:17:24 -0700 Subject: [PATCH 05/50] Update rfcs/geometric-primitives.md Co-authored-by: bjorn3 --- rfcs/geometric-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 16aa5946..bb7f3ceb 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -67,7 +67,7 @@ These types are fully defined in 3d space. - `Point`: type alias of Vec3 - `Direction`: Vec3 that is guaranteed to be normalized through its getter/setter -- `Axis` +- `Axis` - `Line` - `Plane` - `Quad` From 175b8a08026115e2f1c19b12cf4965722c0fc1c2 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sat, 24 Apr 2021 14:52:43 -0700 Subject: [PATCH 06/50] Update rfcs/geometric-primitives.md Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index bb7f3ceb..a871c055 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -146,7 +146,7 @@ These examples intermingle primitive geometry with the meshes themselves. This R ## Unresolved questions - +TODO: discuss unresolved questions. - What parts of the design do you expect to resolve through the RFC process before this gets merged? - What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? From b43acde5da41e9d5831c8fd42f2261d5b3ed47e0 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sat, 24 Apr 2021 14:52:50 -0700 Subject: [PATCH 07/50] Update rfcs/geometric-primitives.md Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index a871c055..fe5da796 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -137,7 +137,7 @@ It's unsurprisingly much simpler to use these types when the primitives are full - What is the impact of not doing this? - Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? -## \[Optional\] Prior art +## Prior art Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh From 353399444d1fb35cbf0a509c8aa0a0bfdac6f45b Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sat, 24 Apr 2021 14:55:00 -0700 Subject: [PATCH 08/50] Apply suggestions from code review Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index fe5da796..8d3a77a0 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -109,25 +109,25 @@ Primitives are fully defined in space, and do not use `Transform` or `GlobalTran It's unsurprisingly much simpler to use these types when the primitives are fully defined internally, but maybe somewhat surprisingly, more efficient. -#### Cache Efficiency +### Cache Efficiency - Some primitives such as AABB and Sphere don't need a rotation to be fully defined. - By using a `GlobalTransform`, not only is this an unused Quat that fills the cache line, it would also cause redundant change detection on rotations. - This is especially important for AABBs and Spheres, because they are fundamental to collision detection and BV(H), and as such need to be as efficient as possible. - I still haven't found a case where you would use a `Primitive3d` without needing this positional information that fully defines the primitive in space. If this holds true, it means that storing the positional data inside the primitive is _not_ a waste of cache, which is normally why you would want to separate the transform into a separate component. -#### CPU Efficiency +### CPU Efficiency - Storing the primitive's positional information internally serves as a form of memoization. - Because you need the primitive to be fully defined in world space to run useful operations, this means that with a `GlobalTransform` you would need to apply the transform to the primitive every time you need to use it. - By applying this transformation only once (e.g. during transform propagation), we only need to do this computation a single time. -#### Ergonomics +### Ergonomics - As I've already mentioned a few times, primitives need to be fully defined in world space to do anything useful with them. - By making the primitive components fully defined and standalone, computing operations is as simple as: `primitive1.some_function(primitive_2)`, instead of also having query and pass in 2 `GlobalTransform`s in the correct order. -#### Use with Transforms +### Use with Transforms - For use cases such as oriented bounding boxes, a primitive should be defined relative to its parent. - In this case, the primitive would still be fully defined internally, but we would need to include primitive updates analogous to the transform propagation system. From 68f5485e87869fe54ec187c9a6dafa1b8965c937 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 29 Apr 2021 21:46:11 -0700 Subject: [PATCH 09/50] Update all the things --- rfcs/geometric-primitives.md | 212 ++++++++++++++++++++++++++--------- 1 file changed, 158 insertions(+), 54 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 16aa5946..68713065 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -8,9 +8,9 @@ Lightweight geometric primitive types for use across bevy engine crates, and as This would provide a standard way to model primitives across the bevy ecosystem to prevent ecosystem fragmentation amongst plugins. -## Guide-level explanation +## User-Facing Explanation -Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as fully defined dimensions. These primitives are *not* discrete meshes, but the underlying precise geometric definition. For example, a circle is: +Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as fully defined dimensions. These primitives are *not* meshes, but the underlying precise mathematical definition. For example, a circle is: ```rust pub struct Circle { @@ -19,82 +19,186 @@ pub struct Circle { } ``` -Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** handled by bevy's `Transform` systems. These are fundamental geometric primitives that must be usable and comparable as-is. +Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** defined using bevy's `Transform` components. This is because these are fundamental geometric primitives that must be usable and comparable as-is -TODO: -Explain the proposal as if it was already included in the engine and you were teaching it to another Bevy user. That generally means: +`bevy_geom` provides two main modules, `geom_2d` and `geom_3d`. Recall that the purpose of this crate is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `geom_2d::Line2d` and `geom_3d::Line`. Note that the 2d version of a line is lighter weight, and is only defined in 2d. 3d geometry (or 2d with depth which is 3d) is assumed to be the default for most cases. The names of the types were chosen with this in mind, to guide you toward using Line instead of Line2d for example, unless you know why you are making this choice. -- Introducing new named concepts. -- Explaining the feature, ideally through simple examples of solutions to concrete problems. -- Explaining how Bevy users should *think* about the feature, and how it should impact the way they use Bevy. It should explain the impact as concretely as possible. -- If applicable, provide sample error messages, deprecation warnings, or migration guidance. -- If applicable, explain how this feature compares to similar existing features, and in what situations the user would use each one. +## Implementation strategy -## Reference-level explanation +### 3D Geometry Types -### `bevy_geom::Shape2d` +These types are fully defined in 3d space. -These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. -- `Point`: type alias of Vec2 -- `Direction`: Vec2 that is guaranteed to be normalized through its getter/setter +```rust +/// Point in 3D space +struct Point(Vec3) -#### Axis +/// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. +struct Direction(Vec3) -Line with infinite length on the x/y plane. +/// Unbounded line in 3D space with direction +struct Axis { + point: Point, + normal: Direction, +} +/// Line in 3D space bounded by two points +struct Line { + start: Point, + end: Point, +} -```rust -struct Axis { point: Point, normal: Direction } -``` +/// Segment of a circle in 3D space +struct Arc { + start: Point, + end: Point, + radius_origin: Point, +} -#### Line +/// A circle in 3D space +struct Circle { + origin: Point, + radius: f32, +} + +/// A triangle in 2D space +struct Triangle([Point; 3]); +// impl deref so this can be iterated over + +/// A polygon represented by an ordered list of vertices +struct Polygon([Point; N]); + +// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. +struct Frustum{ + near: Plane, + far: Plane, + top: Plane, + bottom: Plane, + left: Plane, + right: Plane, +} -- `Line`: (start: Point, end: Point) line bounded by two points -- `Arc`: (start: Point, end: Point, radius_origin: Point) segment of a circle +// 3D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and frustum culling. +struct AabbExtents { + min: Vec3 + max: Vec3 +} +impl Aabb for AabbExtents {} //... -```rust -struct Circle` { origin: Point, radius: f32 } +// 3D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB +struct AabbCentered { + origin: Point, + half_extents: Vec3, +} +impl Aabb for AabbCentered {} //... + +// 3D Axis Aligned Bounding Box defined by its eight vertices, useful for culling or drawing +struct AabbVertices([Point; 8]); +impl Aabb for AabbVertices {} //... + +// 3D Oriented Bounding Box +struct ObbCentered { + origin: Point, + orthonormal_basis: Mat3, + half_extents: Vec3, +} +impl Obb for ObbCentered {} //... + +struct ObbVertices([Point; 4]); +impl Obb for ObbVertices {} //... ``` -- `Triangle` -- `AABBCentered` origin and half-extents -- `AabbExtents` minimum and maximum extents -- `OBB` origin, orthonormal basis, and half-extents -### `bevy_geom::Shape3d` +### 2D Geometry Types -These types are fully defined in 3d space. +These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. These types are suffixed with "2d" to disambiguate from the 3d types in user code, guide users to using 3d types by default, and remove the need for name-spacing the 2d and 3d types when used in the same scope. + +```rust +/// Point in 2D space +struct Point2d(Vec2) + +/// Vector direction in 2D space that is guaranteed to be normalized through its getter/setter. +struct Direction2d(Vec2) -- `Point`: type alias of Vec3 -- `Direction`: Vec3 that is guaranteed to be normalized through its getter/setter -- `Axis` -- `Line` -- `Plane` -- `Quad` -- `Sphere` -- `Cylinder` -- `Capsule` -- `Torus` -- `Cone` -- `Frustum`: defined with 6 planes -- `AabbCentered` -- `AabbExtents` -- `OBB` - -### Bounding Boxes +/// Unbounded line in 2D space with direction +struct Axis2d { + point: Point2d, + normal: Direction2d, +} + +/// Line in 2D space bounded by two points +struct Line2d { + start: Point2d, + end: Point2d, +} + +/// Segment of a circle in 2D space +struct Arc2d { + start: Point2d, + end: Point2d, + radius_origin: Point2d, +} + +/// A circle in 2D space +struct Circle2d { + origin: Point2d, + radius: f32, +} + +/// A triangle in 2D space +struct Triangle2d([Point2d; 3]); +// impl deref so this can be iterated over + +// 2D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and culling with an axis-aligned viewport +struct AabbExtents2d { + min: Vec2 + max: Vec2 +} +impl Aabb2d for AabbExtents2d {} //... + +// 2D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB +struct AabbCentered2d { + origin: Point2d, + half_extents: Vec2, +} +impl Aabb2d for AabbCentered2d {} //... + +// 2D Axis Aligned Bounding Box defined by its four vertices, useful for culling or drawing +struct AabbVertices2d([Point2d; 4]); +impl Aabb2d for AabbVertices2d {} //... + +// 2D Oriented Bounding Box +struct ObbCentered2d { + origin: Point2d, + orthonormal_basis: Mat2, + half_extents: Vec2, +} +impl Obb2d for ObbCentered2d {} //... + +struct ObbVertices2d([Point2d; 4]); +impl Obb2d for ObbVertices2d {} //... +``` + +### Bounding Boxes/Volumes This section is based off of prototyping work done in [bevy_mod_bounding](https://github.com/aevyrie/bevy_mod_bounding). -Because bounding boxes are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. +A number of bounding box types are provided for 2d and 3d use cases. Some representations of a bounding box are more efficient depending on the use case. Instead of storing all possible values in a component (wasted space) or relegating this to a helper function (wasted cpu), each representation is provided as a distinct component that can be updated independently. This gives users of Bevy the flexibility to optimize for their use case without needing to write their own incompatible types from scratch. Consider the functionality built on top of bounding such as physics or collisions - because they are all built on the same fundamental types, they can interoperate. + +Because bounding boxes are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. The plan would be to provide a system similar to transform propagation, that would apply an OBB's precomputed `Transform` to its parent's `GlobalTransform`. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose in the future. ### Frustum Culling This section is based off of prototyping work done in [bevy_frustum_culling](https://github.com/aevyrie/bevy_frustum_culling). +The provided `Frustum` type is useful for frustum culling, which is generally done on the CPU by comparing each frustum plane with each entity's bounding volume. + ### Ray Casting This section is based off of prototyping work done in [bevy_mod_picking](https://github.com/aevyrie/bevy_mod_picking). +The bounding volumes section covers how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Axis` component can be used to represent rays. + ## Drawbacks An argument could be made to use an external crate for this, however these types are so fundamental I think it's important that they are optimized for the engine's uses, and are not from a generalized solution. @@ -133,10 +237,6 @@ It's unsurprisingly much simpler to use these types when the primitives are full - In this case, the primitive would still be fully defined internally, but we would need to include primitive updates analogous to the transform propagation system. - For example, a bounding sphere entity would be a child of a mesh, with a `Sphere` primitive and a `Transform`. On updates to the parent's `GlobalTransform`, the bounding sphere's `Transform` component would be used to update the `Sphere`'s position and radius by applying the scale and translation to a unit sphere. This could be applied to all primitives, with the update system being optimized for each primitive. -- What other designs have been considered and what is the rationale for not choosing them? -- What is the impact of not doing this? -- Why is this important to implement as a feature of Bevy itself, rather than an ecosystem crate? - ## \[Optional\] Prior art Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html @@ -148,7 +248,8 @@ These examples intermingle primitive geometry with the meshes themselves. This R ## Unresolved questions - What parts of the design do you expect to resolve through the RFC process before this gets merged? -- What parts of the design do you expect to resolve through the implementation of this feature before the feature PR is merged? + +The best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`. ### Out of Scope @@ -159,4 +260,7 @@ These examples intermingle primitive geometry with the meshes themselves. This R - Bounding boxes - Collisions - Frustum Culling +- Ray casting +- Physics +- SDF rendering - Debug Rendering From 66d8c9ded75115b28e95d325c865f3ab8d91da79 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 29 Apr 2021 22:03:59 -0700 Subject: [PATCH 10/50] Add meshing section. --- rfcs/geometric-primitives.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 9eb395f9..a9f6c6e6 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -179,6 +179,10 @@ struct ObbVertices2d([Point2d; 4]); impl Obb2d for ObbVertices2d {} //... ``` +### Meshing + +While these primitives do not provide a meshing strategy, future work could build on these types so users can use something like `Sphere.mesh()` to generate meshes. + ### Bounding Boxes/Volumes This section is based off of prototyping work done in [bevy_mod_bounding](https://github.com/aevyrie/bevy_mod_bounding). @@ -239,8 +243,8 @@ It's unsurprisingly much simpler to use these types when the primitives are full ## Prior art -Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html -Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh +- Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html +- Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh These examples intermingle primitive geometry with the meshes themselves. This RFC makes these distinct. From ceac06d0cdb4f6ea6a90d3c97f7142ea2187cf41 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Thu, 29 Apr 2021 22:06:39 -0700 Subject: [PATCH 11/50] Added plane --- rfcs/geometric-primitives.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index a9f6c6e6..eee0ae92 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -49,6 +49,12 @@ struct Line { end: Point, } +// Plane in 3d space defined by a point on the plane, and the normal direction of the plane +struct Plane { + point: Point, + normal: Direction, +} + /// Segment of a circle in 3D space struct Arc { start: Point, From 88af32ec988ea9452454bcca0b19c9b3fdf0bd4e Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Sat, 1 May 2021 03:05:55 -0700 Subject: [PATCH 12/50] Add primitives and use demonstration --- rfcs/geometric-primitives.md | 149 ++++++++++++++++++++++++++--------- 1 file changed, 111 insertions(+), 38 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index eee0ae92..09bd8acc 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -13,8 +13,8 @@ This would provide a standard way to model primitives across the bevy ecosystem Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as fully defined dimensions. These primitives are *not* meshes, but the underlying precise mathematical definition. For example, a circle is: ```rust -pub struct Circle { - origin: Point, +pub struct Circle2d { + origin: Point2d, radius: f32, } ``` @@ -25,11 +25,17 @@ Geometric primitives have a defined shape, size, position, and orientation. Posi ## Implementation strategy +### Helper Types + +```rust +/// Stores an angle in radians, and supplies builder functions to prevent errors (from_radians, from_degrees) +struct Angle(f32); +``` + ### 3D Geometry Types These types are fully defined in 3d space. - ```rust /// Point in 3D space struct Point(Vec3) @@ -37,8 +43,14 @@ struct Point(Vec3) /// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. struct Direction(Vec3) +// Plane in 3d space defined by a point on the plane, and the normal direction of the plane +struct Plane { + point: Point, + normal: Direction, +} + /// Unbounded line in 3D space with direction -struct Axis { +struct Ray { point: Point, normal: Direction, } @@ -49,31 +61,43 @@ struct Line { end: Point, } -// Plane in 3d space defined by a point on the plane, and the normal direction of the plane -struct Plane { - point: Point, - normal: Direction, -} +struct Triangle([Point; 3]); -/// Segment of a circle in 3D space -struct Arc { - start: Point, - end: Point, - radius_origin: Point, +struct Quad([Point; 4]); + +struct Sphere{ + origin: Point, + radius: f32, } -/// A circle in 3D space -struct Circle { - origin: Point, +struct Cylinder { + /// The bottom center of the cylinder. + origin: Point, radius: f32, + /// The extent of the cylinder is the vector that extends from the origin to the opposite face of the cylinder. + extent: Vec3, } -/// A triangle in 2D space -struct Triangle([Point; 3]); -// impl deref so this can be iterated over +struct Cone{ + /// Origin of the cone located at the center of its base. + origin: Point, + /// The extent of the cone is the vector that extends from the origin to the tip of the cone. + extent: Vec3, + base_radius: f32, +} + +struct Torus{ + origin: Point, + normal: Direction, + major_radius: f32, + tube_radius: f32, +} + +struct Capsule { + origin: Point, + height: Vec3, -/// A polygon represented by an ordered list of vertices -struct Polygon([Point; N]); +} // A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. struct Frustum{ @@ -127,7 +151,7 @@ struct Point2d(Vec2) struct Direction2d(Vec2) /// Unbounded line in 2D space with direction -struct Axis2d { +struct Ray2d { point: Point2d, normal: Direction2d, } @@ -138,22 +162,39 @@ struct Line2d { end: Point2d, } -/// Segment of a circle in 2D space -struct Arc2d { - start: Point2d, - end: Point2d, - radius_origin: Point2d, +/// A line list represented by an ordered list of vertices in 2d space +struct LineList{ + points: [Point; N], + /// True if the LineList is a closed loop + closed: bool, +} + +struct Triangle2d([Point2d; 3]); + +struct Quad2d([Point2d; 4]); + +/// A regular polygon, such as a square or hexagon. +struct RegularPolygon2d{ + circumcircle: Circle2d, + /// Number of faces. + faces: u8, + /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. + orientation: Angle, } -/// A circle in 2D space struct Circle2d { origin: Point2d, radius: f32, } -/// A triangle in 2D space -struct Triangle2d([Point2d; 3]); -// impl deref so this can be iterated over +/// Segment of a circle +struct Arc2d { + circle: Circle, + /// Start of the arc, measured clockwise from the 12 o'clock position + start: Angle, + /// Angle in radians to sweep clockwise from the [start_angle] + sweep: Angle, +} // 2D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and culling with an axis-aligned viewport struct AabbExtents2d { @@ -162,7 +203,7 @@ struct AabbExtents2d { } impl Aabb2d for AabbExtents2d {} //... -// 2D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB +// 2D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB. struct AabbCentered2d { origin: Point2d, half_extents: Vec2, @@ -189,11 +230,21 @@ impl Obb2d for ObbVertices2d {} //... While these primitives do not provide a meshing strategy, future work could build on these types so users can use something like `Sphere.mesh()` to generate meshes. +```rust +let unit_sphere = Sphere::UNIT; +// Some ways we could build meshes from primitives +let sphere_mesh = Mesh::from(unit_sphere); +let sphere_mesh = unit_sphere.mesh(); +let sphere_mesh: Mesh = unit_sphere.into(); +``` + ### Bounding Boxes/Volumes This section is based off of prototyping work done in [bevy_mod_bounding](https://github.com/aevyrie/bevy_mod_bounding). -A number of bounding box types are provided for 2d and 3d use cases. Some representations of a bounding box are more efficient depending on the use case. Instead of storing all possible values in a component (wasted space) or relegating this to a helper function (wasted cpu), each representation is provided as a distinct component that can be updated independently. This gives users of Bevy the flexibility to optimize for their use case without needing to write their own incompatible types from scratch. Consider the functionality built on top of bounding such as physics or collisions - because they are all built on the same fundamental types, they can interoperate. +A number of bounding box types are provided for 2d and 3d use. Some representations of a bounding box are more efficient depending on the use case. Instead of storing all possible values in a component (wasted space) or computing them with a helper function every time (wasted cpu), each representation is provided as a distinct component that can be updated independently. This gives users of Bevy the flexibility to optimize for their use case without needing to write their own incompatible types from scratch. Consider the functionality built on top of bounding such as physics or collisions - because they are all built on the same fundamental types, they can interoperate. + +Note that the bounding types presented in this RFC implement their respective `Obb` or `Aabb` trait. Bounding boxes can be represented by a number of different underlying types; by making these traits instead of structs, systems can easily be made generic over these types depending on the situation. Because bounding boxes are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. The plan would be to provide a system similar to transform propagation, that would apply an OBB's precomputed `Transform` to its parent's `GlobalTransform`. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose in the future. @@ -203,11 +254,33 @@ This section is based off of prototyping work done in [bevy_frustum_culling](htt The provided `Frustum` type is useful for frustum culling, which is generally done on the CPU by comparing each frustum plane with each entity's bounding volume. +```rust +// What frustum culling might look like: +for bounding_volume in bound_vol_query.iter() { + for plane in camera_frustum.planes().iter() { + if bounding_volume.outside(plane) { + // Cull me! + return; + } + } +} +``` + +This data structure alone does not ensure the representation is valid; planes could be placed in nonsensical positions. To prevent this, the struct's fields should be made private, and constructors and setters should be provided to ensure `Frustum`s can only be initialized or mutated into valid arrangements. + +In addition, by defining the frustum as a set of planes, it is also trivial to support oblique frustums. + ### Ray Casting This section is based off of prototyping work done in [bevy_mod_picking](https://github.com/aevyrie/bevy_mod_picking). -The bounding volumes section covers how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Axis` component can be used to represent rays. +The bounding volumes section covers how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` component can be used to represent rays. Applicable 3d types could implement a `RayIntersection` trait to extend their functionality. + +```rust +let ray = Ray::X; +let sphere = Sphere::new(Point::x(5.0), 1.0); +let intersection = ray.cast(sphere); +``` ## Drawbacks @@ -256,10 +329,10 @@ These examples intermingle primitive geometry with the meshes themselves. This R ## Unresolved questions -TODO: discuss unresolved questions. -- What parts of the design do you expect to resolve through the RFC process before this gets merged? -The best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`. +What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`. + +How will fully defined primitives interact with `Transforms`? Will this confuse users? How can the API be shaped to prevent this? ### Out of Scope From e6a72750c09f24453eded5b6527e509530ce7b7a Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Mon, 3 May 2021 15:54:11 -0700 Subject: [PATCH 13/50] Update rfcs/geometric-primitives.md Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 09bd8acc..4b04a63c 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -189,7 +189,7 @@ struct Circle2d { /// Segment of a circle struct Arc2d { - circle: Circle, + circle: Circle2d, /// Start of the arc, measured clockwise from the 12 o'clock position start: Angle, /// Angle in radians to sweep clockwise from the [start_angle] From f8c8fbece2a60f784e4a76bca0bd732adc4489ba Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Mon, 3 May 2021 16:11:12 -0700 Subject: [PATCH 14/50] Update rfcs/geometric-primitives.md Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 4b04a63c..73893d30 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -19,7 +19,7 @@ pub struct Circle2d { } ``` -Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** defined using bevy's `Transform` components. This is because these are fundamental geometric primitives that must be usable and comparable as-is +Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** defined using bevy's `Transform` components. This is because these are fundamental geometric primitives that must be usable and comparable as-is. `bevy_geom` provides two main modules, `geom_2d` and `geom_3d`. Recall that the purpose of this crate is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `geom_2d::Line2d` and `geom_3d::Line`. Note that the 2d version of a line is lighter weight, and is only defined in 2d. 3d geometry (or 2d with depth which is 3d) is assumed to be the default for most cases. The names of the types were chosen with this in mind, to guide you toward using Line instead of Line2d for example, unless you know why you are making this choice. From 6c9cbfd8f5099e40044a908bc19a9c3f4f2965d6 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Fri, 7 May 2021 00:15:22 -0700 Subject: [PATCH 15/50] Update rfcs/geometric-primitives.md Co-authored-by: Alice Cecile --- rfcs/geometric-primitives.md | 1 - 1 file changed, 1 deletion(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index 73893d30..d96336f5 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -327,7 +327,6 @@ It's unsurprisingly much simpler to use these types when the primitives are full These examples intermingle primitive geometry with the meshes themselves. This RFC makes these distinct. - ## Unresolved questions What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`. From da6871e40dbc45747ab5d2ff02a8f8e76056c0ab Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Tue, 11 May 2021 22:21:04 -0700 Subject: [PATCH 16/50] wip updates --- rfcs/geometric-primitives.md | 157 +++++++++++++++++++++++++---------- 1 file changed, 114 insertions(+), 43 deletions(-) diff --git a/rfcs/geometric-primitives.md b/rfcs/geometric-primitives.md index d96336f5..e9296f7c 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/geometric-primitives.md @@ -10,15 +10,37 @@ This would provide a standard way to model primitives across the bevy ecosystem ## User-Facing Explanation -Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as fully defined dimensions. These primitives are *not* meshes, but the underlying precise mathematical definition. For example, a circle is: +Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as its dimensions. These primitives are *not* meshes, but the underlying precise mathematical definition. For example, a circle is: ```rust pub struct Circle2d { - origin: Point2d, radius: f32, } ``` +| Shape | Mesh | Bounding | Collision | +|--- |---|---|---| +| Sphere | ✔ | ✔ Sphere + Pos | ✔ Sphere + Pos | +| Cuboid | ✔ | ✔ Cuboid + Pos + Rot (OBB) | ✔ Cuboid + Pos + Rot | +| Capsule | ✔ | ❌ | ✔ Capsule + Pos + Rot | +| Cylinder | ✔ | ❌ | ✔ Cylinder + Pos + Rot | +| Cone | ✔ | ❌ | ✔ Cone + Pos + Rot | +| Ramp | ✔ | ❌ | ✔ Ramp + Pos + Rot | +| Plane | ✔ | ❌ | ✔ Plane | +| Torus | ✔ | ❌ | ❌ | + +### Meshing + +Both 2d and 3d primitives can implement the `Meshable` trait to provide the ability to generate a mesh from the primitive's definition. + +```rust +let circle_mesh: Mesh = Circle2d::new(2.0).mesh(); +``` +The base primitive types only define the shape and size of the geometry about the origin. From there, the mesh generated from the primitive can have a transform applied to it like any other mesh. + +## Bounding + +For use in physics, spatial queries, collisions, raycast, or other similar use cases, `BoundVol` and `BoundArea` traits are provided. These traits are implemented for primitive types that define position and orientation in addition to the shape and size defined in the base types. For example, a Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** defined using bevy's `Transform` components. This is because these are fundamental geometric primitives that must be usable and comparable as-is. `bevy_geom` provides two main modules, `geom_2d` and `geom_3d`. Recall that the purpose of this crate is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `geom_2d::Line2d` and `geom_3d::Line`. Note that the 2d version of a line is lighter weight, and is only defined in 2d. 3d geometry (or 2d with depth which is 3d) is assumed to be the default for most cases. The names of the types were chosen with this in mind, to guide you toward using Line instead of Line2d for example, unless you know why you are making this choice. @@ -31,10 +53,26 @@ Geometric primitives have a defined shape, size, position, and orientation. Posi /// Stores an angle in radians, and supplies builder functions to prevent errors (from_radians, from_degrees) struct Angle(f32); ``` +### Traits -### 3D Geometry Types +```rust +/// A trait for types that can be used to generate a [Mesh]. +trait Meshable{ + fn mesh(&self, subdivisions: u8) -> Mesh; +}; + +/// Convex geometric primitives that are fully defined in 3D space, and can be used to bound entities within their volume. [BoundingVolume]s are useful for raycasting, collision detection, physics, and building BVHs. +trait BoundingVolume { + fn raycast(&self, ray: Ray) -> Option(Point); +} + +/// Convex geometric primitives that are fully defined in 2D space, and can be used to bound entities within their area. Similar to [BoundingVolumes] but for 2D instead of 3D space. +trait BoundingArea { + fn raycast(&self, ray: Ray2d) -> Option(Point2d); +} +``` -These types are fully defined in 3d space. +### 3D Geometry Types ```rust /// Point in 3D space @@ -48,36 +86,50 @@ struct Plane { point: Point, normal: Direction, } +impl Meshable for Plane {} -/// Unbounded line in 3D space with direction -struct Ray { +/// Unbounded line in 3D space with directionality +struct Axis { point: Point, normal: Direction, } -/// Line in 3D space bounded by two points +/// A newtype that differentiates an axis from a ray, where an axis in infinite and a ray is directional half-line, although their underlying representation is the same. +struct Ray(Axis); + +/// A line segment bounded by two points struct Line { start: Point, end: Point, } +impl Meshable for Line {} + +/// A line drawn along a path of points +struct PolyLine { + points: Vec, +} +impl Meshable for PolyLine {} struct Triangle([Point; 3]); +impl Meshable for Triangle {} struct Quad([Point; 4]); +impl Meshable for Quad {} +/// A sphere centered on the origin struct Sphere{ - origin: Point, radius: f32, } +impl Meshable for Sphere {} +/// A cylinder with its origin at the center of the volume struct Cylinder { - /// The bottom center of the cylinder. - origin: Point, radius: f32, - /// The extent of the cylinder is the vector that extends from the origin to the opposite face of the cylinder. - extent: Vec3, + height: f32, } +impl Meshable for Cylinder {} +/// A cone with the origin located at the center of the circular base struct Cone{ /// Origin of the cone located at the center of its base. origin: Point, @@ -85,19 +137,19 @@ struct Cone{ extent: Vec3, base_radius: f32, } +impl Meshable for Cone {} struct Torus{ - origin: Point, - normal: Direction, major_radius: f32, tube_radius: f32, } +impl Meshable for Torus {} struct Capsule { - origin: Point, height: Vec3, - + radius: f32, } +impl Meshable for Capsule {} // A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. struct Frustum{ @@ -108,35 +160,48 @@ struct Frustum{ left: Plane, right: Plane, } +impl Meshable for Frustum {} -// 3D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and frustum culling. -struct AabbExtents { - min: Vec3 - max: Vec3 +``` + +### 3D Bounding Volumes + +```rust + +/// A spherical bounding volume fully defined in space +struct SphereBV { + sphere: Sphere, + origin: Point, } -impl Aabb for AabbExtents {} //... +impl BoundingVolume for SphereBV () -// 3D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB -struct AabbCentered { +struct CylinderBV { + cylinder: Cylinder, origin: Point, - half_extents: Vec3, + rotation: Quat, } -impl Aabb for AabbCentered {} //... +impl BoundingVolume for CylinderBV {} -// 3D Axis Aligned Bounding Box defined by its eight vertices, useful for culling or drawing -struct AabbVertices([Point; 8]); -impl Aabb for AabbVertices {} //... +struct CapsuleBV { + capsule: Capsule, + origin: Point, +} +impl BoundingVolume for CapsuleBV {} + +// 3D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and frustum culling. +struct Aabb { + min: Vec3 + max: Vec3 +} +impl Meshable for Aabb {} // 3D Oriented Bounding Box -struct ObbCentered { +struct Obb { origin: Point, orthonormal_basis: Mat3, half_extents: Vec3, } -impl Obb for ObbCentered {} //... - -struct ObbVertices([Point; 4]); -impl Obb for ObbVertices {} //... +impl Meshable for Obb {} ``` ### 2D Geometry Types @@ -150,20 +215,24 @@ struct Point2d(Vec2) /// Vector direction in 2D space that is guaranteed to be normalized through its getter/setter. struct Direction2d(Vec2) + /// Unbounded line in 2D space with direction -struct Ray2d { +struct Line2d { point: Point2d, normal: Direction2d, } +/// A newtype that differentiates a line from a ray, where a line is an infinite axis and a ray is directional half-line. +struct Ray2d(Line2d); + /// Line in 2D space bounded by two points -struct Line2d { +struct LineSegment2d { start: Point2d, end: Point2d, } /// A line list represented by an ordered list of vertices in 2d space -struct LineList{ +struct LineList2d{ points: [Point; N], /// True if the LineList is a closed loop closed: bool, @@ -173,6 +242,11 @@ struct Triangle2d([Point2d; 3]); struct Quad2d([Point2d; 4]); +struct Circle2d { + origin: Point2d, + radius: f32, +} + /// A regular polygon, such as a square or hexagon. struct RegularPolygon2d{ circumcircle: Circle2d, @@ -182,11 +256,6 @@ struct RegularPolygon2d{ orientation: Angle, } -struct Circle2d { - origin: Point2d, - radius: f32, -} - /// Segment of a circle struct Arc2d { circle: Circle2d, @@ -268,7 +337,7 @@ for bounding_volume in bound_vol_query.iter() { This data structure alone does not ensure the representation is valid; planes could be placed in nonsensical positions. To prevent this, the struct's fields should be made private, and constructors and setters should be provided to ensure `Frustum`s can only be initialized or mutated into valid arrangements. -In addition, by defining the frustum as a set of planes, it is also trivial to support oblique frustums. +In addition, by defining the frustum as a set of planes, it is also trivial to support oblique frustums. Oblique frustums are useful for 2d projections used in CAD, as well as in games to take advantage of projection distortion to do things like emphasizing the feeling of speed in a driving game. See the Unity docs: https://docs.unity3d.com/Manual/ObliqueFrustum.html ### Ray Casting @@ -325,7 +394,9 @@ It's unsurprisingly much simpler to use these types when the primitives are full - Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html - Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh -These examples intermingle primitive geometry with the meshes themselves. This RFC makes these distinct. +These examples intermingle primitive geometry with the meshes themselves. This RFC makes these +distinct. + ## Unresolved questions From 47ea3b61b4376b649a6d8b21076fedfc73265453 Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Mon, 17 May 2021 16:35:58 -0700 Subject: [PATCH 17/50] Update table and rename --- ...eometric-primitives.md => primitive-shapes.md} | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) rename rfcs/{geometric-primitives.md => primitive-shapes.md} (96%) diff --git a/rfcs/geometric-primitives.md b/rfcs/primitive-shapes.md similarity index 96% rename from rfcs/geometric-primitives.md rename to rfcs/primitive-shapes.md index e9296f7c..66b8fd96 100644 --- a/rfcs/geometric-primitives.md +++ b/rfcs/primitive-shapes.md @@ -1,4 +1,4 @@ -# Feature Name: `geometric-primitives` +# Feature Name: `primitive-shapes` ## Summary @@ -20,12 +20,13 @@ pub struct Circle2d { | Shape | Mesh | Bounding | Collision | |--- |---|---|---| -| Sphere | ✔ | ✔ Sphere + Pos | ✔ Sphere + Pos | -| Cuboid | ✔ | ✔ Cuboid + Pos + Rot (OBB) | ✔ Cuboid + Pos + Rot | -| Capsule | ✔ | ❌ | ✔ Capsule + Pos + Rot | -| Cylinder | ✔ | ❌ | ✔ Cylinder + Pos + Rot | -| Cone | ✔ | ❌ | ✔ Cone + Pos + Rot | -| Ramp | ✔ | ❌ | ✔ Ramp + Pos + Rot | +| Sphere | ✔ | ✔ Sphere + Translation | ✔ Sphere + Translation | +| Cuboid | ✔ | ✔ (OBB) Cuboid + Translation + Rotation | ✔ Cuboid + Translation + Rotation | +| Capsule | ✔ | ❌ | ✔ Capsule + Translation + Rotation | +| Cylinder | ✔ | ❌ | ✔ Cylinder + Translation + Rotation | +| Cone | ✔ | ❌ | ✔ Cone + Translation + Rotation | +| Pyramid | ✔ | ❌ | ✔ Pyramid + Translation + Rotation | +| Wedge | ✔ | ❌ | ✔ Ramp + Translation + Rotation | | Plane | ✔ | ❌ | ✔ Plane | | Torus | ✔ | ❌ | ❌ | From 5e846672751c5ae23be7eabc1451ea11b695deeb Mon Sep 17 00:00:00 2001 From: Aevyrie Roessler Date: Mon, 24 May 2021 22:24:39 -0700 Subject: [PATCH 18/50] Rewrite all the things. --- rfcs/primitive-shapes.md | 474 +++++++++++++++++++++++---------------- 1 file changed, 280 insertions(+), 194 deletions(-) diff --git a/rfcs/primitive-shapes.md b/rfcs/primitive-shapes.md index 66b8fd96..3956262d 100644 --- a/rfcs/primitive-shapes.md +++ b/rfcs/primitive-shapes.md @@ -2,11 +2,13 @@ ## Summary -Lightweight geometric primitive types for use across bevy engine crates, and as interoperability types for external libraries. +These geometric primitives, or "primitive shapes", are lightweight types for use across bevy engine crates and as interoperability types for plugins and libraries. The goal is to provide an API that considers ergonomics for the common use cases, such as meshing, bounding, and collision, without sacrificing performance. ## Motivation -This would provide a standard way to model primitives across the bevy ecosystem to prevent ecosystem fragmentation amongst plugins. +This provides a type-level foundation that subsequent engine (and plugin!) features can be built upon incrementally, preventing ecosystem fragmentation. A major goal is to make it possible for engine and game developers to prototype physics, bounding, collision, and raycasting functionality using the same base types. This will make engine integration and cross-plugin interoperability much more feasible, as the engine will have opinionated types that will be natural to implement `into` or `from`. I hope this opens the door to experimentation with multiple physics and bounding acceleration backends. + +There is significant complexity in the way seemingly equivalent shapes are defined depending on how they are used. By considering these use cases *first*, the goal is that geometric data structures can be composed depending on the needed functionality, with traits used to ensure the right data structure is used for the right task. ## User-Facing Explanation @@ -18,33 +20,59 @@ pub struct Circle2d { } ``` +Note that a `Circle2d` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the the information (Translation and Rotation) that are needed to fully define the shapes for these cases: + | Shape | Mesh | Bounding | Collision | |--- |---|---|---| -| Sphere | ✔ | ✔ Sphere + Translation | ✔ Sphere + Translation | -| Cuboid | ✔ | ✔ (OBB) Cuboid + Translation + Rotation | ✔ Cuboid + Translation + Rotation | -| Capsule | ✔ | ❌ | ✔ Capsule + Translation + Rotation | -| Cylinder | ✔ | ❌ | ✔ Cylinder + Translation + Rotation | -| Cone | ✔ | ❌ | ✔ Cone + Translation + Rotation | -| Pyramid | ✔ | ❌ | ✔ Pyramid + Translation + Rotation | -| Wedge | ✔ | ❌ | ✔ Ramp + Translation + Rotation | -| Plane | ✔ | ❌ | ✔ Plane | +| Sphere | ✔ | ✔ + Trans | ✔ + Trans | +| Box | ✔ | ✔ (AABB) | ✔ + Trans + Rot (OBB) | +| Capsule | ✔ | ❌ | ✔ + Trans + Rot | +| Cylinder | ✔ | ❌ | ✔ + Trans + Rot | +| Cone | ✔ | ❌ | ✔ + Trans + Rot | +| Wedge | ✔ | ❌ | ✔ + Trans + Rot | +| Plane | ✔ | ❌ | ✔ | | Torus | ✔ | ❌ | ❌ | +### Bounding vs. Collision + +The difference between the two is somewhat semantic. Both bounding and collision check for intersections between bounding volumes or areas. However, bounding volumes are generally used for broad phase checks due to their speed and small size, and are consequently often used in tree structures for spacial queries. Note that both bounding types are not oriented - this reduces the number of comparisons needed to check for intersections during broad phase tests. + +Although the traits for these may look similar, by making them distinct it will better conform to industry terminology and help guide users away from creating performance problems. For example, because a torus is a volume, it is conceivably possible to implement the `Bounding` trait, however it would be a terrible idea to be encouraging users to build BVTs/BVHs out of torii. This is also why the Oriented Bounding Box (OBB) is a collider, and not `Bounding`. Bounding spheres or AABBs are generally always preferred for broad phase bounding checks, despite the OBB's name. For advanced users who want to implement `Bounding` for OBBs, they always have the option of wrapping the OBB in a newtype and implementing the trait. + +### Where are the `Transform`s? + + Translation and rotation are **not** defined using bevy's `Transform` components. This is because types that implement `Bounding` and `Collider` must be fully self-contained. This: + + * makes the API simpler when using these components in functions and systems + * ensures bounding and collision types use an absolute minimum of memory + * prevents errors caused by nonuniform scale invalidating the shape of the primitive. + +Instead, changes to the parent's `GlobalTransform` should be used to derive a new primitive on scale changes - as well as a new translation and rotation if required. + ### Meshing -Both 2d and 3d primitives can implement the `Meshable` trait to provide the ability to generate a mesh from the primitive's definition. +Both 2d and 3d primitives can implement the `Meshable` trait to provide the ability to generate a tri mesh: ```rust -let circle_mesh: Mesh = Circle2d::new(2.0).mesh(); +let circle_mesh: Mesh = Circle2d{ radius: 2.0 }.mesh(); ``` -The base primitive types only define the shape and size of the geometry about the origin. From there, the mesh generated from the primitive can have a transform applied to it like any other mesh. +The base primitive types only define the shape (`Circle2d`) and size (`radius`) of the geometry about the origin. Once generated, the mesh can have a transform applied to it like any other mesh, and it is no longer tied to the primitive that generated it. The `Default::default()` implementation of primitives should be a "unit" variant, such as a circle with diameter 1.0, or a box with all edges of length 1.0. + +Meshing could be naturally extended with other libraries or parameters. For example, a sphere by default might use `Icosphere`, but could be extended to generate UV spheres or quad spheres. For 2d types, we can use a crate such as `lyon` to generate 2d meshes from parameterized primitive shapes within the `Meshable` interface. -## Bounding +### Bounding -For use in physics, spatial queries, collisions, raycast, or other similar use cases, `BoundVol` and `BoundArea` traits are provided. These traits are implemented for primitive types that define position and orientation in addition to the shape and size defined in the base types. For example, a -Geometric primitives have a defined shape, size, position, and orientation. Position and orientation are **not** defined using bevy's `Transform` components. This is because these are fundamental geometric primitives that must be usable and comparable as-is. +For use in spatial queries, broad phase collision detection, and raycasting, `Bounding` and `Bounding2d` traits are provided. These traits are implemented for types that define position in addition to the shape and size defined in the base primitive. The basic functionality of this trait is to check whether one bounding shape is contained within another bounding shape. -`bevy_geom` provides two main modules, `geom_2d` and `geom_3d`. Recall that the purpose of this crate is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `geom_2d::Line2d` and `geom_3d::Line`. Note that the 2d version of a line is lighter weight, and is only defined in 2d. 3d geometry (or 2d with depth which is 3d) is assumed to be the default for most cases. The names of the types were chosen with this in mind, to guide you toward using Line instead of Line2d for example, unless you know why you are making this choice. +### Colliders + +Colliders can provide intersection checks against other colliders, as well as check if they are within a bounding volume. + +### 3D and 2D + +This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line` and `Line2d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. 3d geometry (or 2d with depth) is assumed to be the default for most cases. The names of the types were chosen with this in mind. + +... ## Implementation strategy @@ -56,54 +84,68 @@ struct Angle(f32); ``` ### Traits +These traits are provided as reference to illustrate how these might be used. The details of implementation and interface should be determined in a separate RFC, PR, or independent prototypes. + ```rust -/// A trait for types that can be used to generate a [Mesh]. trait Meshable{ - fn mesh(&self, subdivisions: u8) -> Mesh; + fn mesh(&self) -> Mesh; }; -/// Convex geometric primitives that are fully defined in 3D space, and can be used to bound entities within their volume. [BoundingVolume]s are useful for raycasting, collision detection, physics, and building BVHs. -trait BoundingVolume { - fn raycast(&self, ray: Ray) -> Option(Point); +trait Bounding { + fn within(&self, other: &impl Bounding) -> bool; + fn contains(&self, collider: &impl Collider) -> bool; +} + +trait Bounding2d { + fn within(&self, other: &impl Bounding2d) -> bool; + fn contains(&self, collider: &impl Collider2d) -> bool; +} + +trait Collider { + fn collide(&self, other: &impl Collider) -> Option(Collision); + fn within(&self, bounds: &impl Bounding) -> bool; } -/// Convex geometric primitives that are fully defined in 2D space, and can be used to bound entities within their area. Similar to [BoundingVolumes] but for 2D instead of 3D space. -trait BoundingArea { - fn raycast(&self, ray: Ray2d) -> Option(Point2d); +trait Collider2d { + fn collide(&self, other: &impl Collider2d) -> Option(Collision2d); + fn within(&self, bounds: &impl Bounding2d) -> bool; } ``` ### 3D Geometry Types ```rust -/// Point in 3D space struct Point(Vec3) /// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. struct Direction(Vec3) -// Plane in 3d space defined by a point on the plane, and the normal direction of the plane struct Plane { point: Point, normal: Direction, } impl Meshable for Plane {} +/// Differentiates a line from a ray, where a line is infinite and a ray is directional half-line, although their underlying representation is the same. +struct Ray( + point: Point, + direction: Direction, +); + +// Line types + /// Unbounded line in 3D space with directionality -struct Axis { +struct Line { point: Point, - normal: Direction, + direction: Direction, } -/// A newtype that differentiates an axis from a ray, where an axis in infinite and a ray is directional half-line, although their underlying representation is the same. -struct Ray(Axis); - /// A line segment bounded by two points -struct Line { +struct LineSegment { start: Point, end: Point, } -impl Meshable for Line {} +impl Meshable for LineSegment {} /// A line drawn along a path of points struct PolyLine { @@ -113,16 +155,59 @@ impl Meshable for PolyLine {} struct Triangle([Point; 3]); impl Meshable for Triangle {} +impl Collider for Triangle {} struct Quad([Point; 4]); impl Meshable for Quad {} +impl Collider for Quad {} -/// A sphere centered on the origin -struct Sphere{ +/// Sphere types + +struct Sphere { radius: f32, } impl Meshable for Sphere {} +struct BoundingSphere { + sphere: Sphere, + translation: Vec3, +} +impl Meshable for BoundingSphere +impl Bounding for BoundingSphere + +struct SphereCollider { + sphere: Sphere, + translation: Vec3, +} +impl Meshable for BoundingSphere +impl Collider for BoundingSphere + +// Box Types + +struct Box { + half_extents: Vec3, +} +impl Meshable for Box + +struct BoundingBox { + box: Box, + translation: Vec3, +} +impl Meshable for BoundingBox +impl Bounding for BoundingBox +type Aabb = BoundingBox; + +struct BoxCollider { + box: Box, + translation: Vec3, + rotation: Quat, +} +impl Meshable for BoxCollider +impl Collider for BoxCollider +type Obb = BoxCollider; + +// Cylinder Types + /// A cylinder with its origin at the center of the volume struct Cylinder { radius: f32, @@ -130,79 +215,85 @@ struct Cylinder { } impl Meshable for Cylinder {} -/// A cone with the origin located at the center of the circular base -struct Cone{ - /// Origin of the cone located at the center of its base. - origin: Point, - /// The extent of the cone is the vector that extends from the origin to the tip of the cone. - extent: Vec3, - base_radius: f32, +struct CylinderCollider { + cylinder: Cylinder, + translation: Vec3, + rotation: Quat, } -impl Meshable for Cone {} +impl Meshable for CylinderCollider {} +impl Collider for CylinderCollider {} -struct Torus{ - major_radius: f32, - tube_radius: f32, -} -impl Meshable for Torus {} +// Capsule Types struct Capsule { - height: Vec3, + // Height of the cylindrical section + height: f32, radius: f32, } impl Meshable for Capsule {} -// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. -struct Frustum{ - near: Plane, - far: Plane, - top: Plane, - bottom: Plane, - left: Plane, - right: Plane, +struct CapsuleCollider { + capsule: Capsule, + translation: Vec3, + rotation: Quat, } -impl Meshable for Frustum {} +impl Meshable for CapsuleCollider {} +impl Collider for CapsuleCollider {} -``` +// Cone Types -### 3D Bounding Volumes +/// A cone with the origin located at the center of the circular base +struct Cone { + height: f32, + radius: f32, +} +impl Meshable for Cone {} -```rust +struct ConeCollider { + cone: Cone, + translation: Vec3, + rotation: Quat, +} +impl Meshable for ConeCollider {} +impl Collider for ConeCollider {} -/// A spherical bounding volume fully defined in space -struct SphereBV { - sphere: Sphere, - origin: Point, +// Wedge Types + +/// A ramp with the origin centered on the width, and coincident with the rear vertical wall. +struct Wedge { + height: f32, + width: f32, + depth: f32, } -impl BoundingVolume for SphereBV () +impl Meshable for Wedge {} -struct CylinderBV { - cylinder: Cylinder, - origin: Point, +struct WedgeCollider { + wedge: Wedge, + translation: Vec3, rotation: Quat, } -impl BoundingVolume for CylinderBV {} +impl Meshable for WedgeCollider {} +impl Collider for WedgeCollider {} -struct CapsuleBV { - capsule: Capsule, - origin: Point, -} -impl BoundingVolume for CapsuleBV {} +// Other Types -// 3D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and frustum culling. -struct Aabb { - min: Vec3 - max: Vec3 +struct Torus{ + major_radius: f32, + tube_radius: f32, } -impl Meshable for Aabb {} +impl Meshable for Torus {} -// 3D Oriented Bounding Box -struct Obb { - origin: Point, - orthonormal_basis: Mat3, - half_extents: Vec3, +// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. +struct Frustum{ + near: Plane, + far: Plane, + top: Plane, + bottom: Plane, + left: Plane, + right: Plane, } -impl Meshable for Obb {} +impl Meshable for Frustum {} + ``` ### 2D Geometry Types @@ -210,122 +301,150 @@ impl Meshable for Obb {} These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. These types are suffixed with "2d" to disambiguate from the 3d types in user code, guide users to using 3d types by default, and remove the need for name-spacing the 2d and 3d types when used in the same scope. ```rust -/// Point in 2D space struct Point2d(Vec2) -/// Vector direction in 2D space that is guaranteed to be normalized through its getter/setter. struct Direction2d(Vec2) +struct Ray2d( + point: Point2d, + direction: Direction2d,); -/// Unbounded line in 2D space with direction -struct Line2d { +struct Line2d { point: Point2d, - normal: Direction2d, + direction: Direction2d, } +impl Collider2d for Line2d {} -/// A newtype that differentiates a line from a ray, where a line is an infinite axis and a ray is directional half-line. -struct Ray2d(Line2d); - -/// Line in 2D space bounded by two points struct LineSegment2d { start: Point2d, end: Point2d, } +impl Meshable2d for LineSegment2d {} +impl Collider2d for LineSegment2d {} -/// A line list represented by an ordered list of vertices in 2d space -struct LineList2d{ - points: [Point; N], - /// True if the LineList is a closed loop - closed: bool, +struct PolyLine2d{ + points: [Point2d; N], } +impl Meshable2d for PolyLine2d {} +impl Collider2d for PolyLine2d {} struct Triangle2d([Point2d; 3]); +impl Meshable2d for Triangle2d {} +impl Collider2d for Triangle2d {} struct Quad2d([Point2d; 4]); - -struct Circle2d { - origin: Point2d, - radius: f32, -} +impl Meshable2d for Quad2d {} +impl Collider2d for Quad2d {} /// A regular polygon, such as a square or hexagon. -struct RegularPolygon2d{ +struct RegularPolygon2d { + /// The circumcircle that all points of the regular polygon lie on. circumcircle: Circle2d, /// Number of faces. faces: u8, /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. orientation: Angle, } +impl Meshable2d for RegularPolygon2d {} +impl Collider2d for RegularPolygon2d {} + +struct Polygon2d { + points: [Point; N], +} +impl Meshable2d for Polygon2d {} +impl Collider2d for Polygon2d {} -/// Segment of a circle -struct Arc2d { +/// Circle types + +struct Circle2d { + radius: f32, +} +impl Meshable2d for Circle2d {} + +struct BoundingCircle2d { circle: Circle2d, - /// Start of the arc, measured clockwise from the 12 o'clock position - start: Angle, - /// Angle in radians to sweep clockwise from the [start_angle] - sweep: Angle, + translation: Vec2, } +impl Meshable2d for BoundingCircle2d +impl Bounding2d for BoundingCircle2d -// 2D Axis Aligned Bounding Box defined by its extents, useful for fast intersection checks and culling with an axis-aligned viewport -struct AabbExtents2d { - min: Vec2 - max: Vec2 +struct CircleCollider2d { + sphere: Circle2d, + translation: Vec2, } -impl Aabb2d for AabbExtents2d {} //... +impl Meshable2d for CircleCollider2d +impl Collider2d for CircleCollider2d + +// Box Types -// 2D Axis Aligned Bounding Box defined by its center and half extents, easily converted into an OBB. -struct AabbCentered2d { - origin: Point2d, +struct Box2d { half_extents: Vec2, } -impl Aabb2d for AabbCentered2d {} //... +impl Meshable for Box2d -// 2D Axis Aligned Bounding Box defined by its four vertices, useful for culling or drawing -struct AabbVertices2d([Point2d; 4]); -impl Aabb2d for AabbVertices2d {} //... +struct BoundingBox2d { + box: Box2d, + translation: Vec2, +} +impl Meshable2d for BoundingBox2d +impl Bounding2d for BoundingBox2d +type Aabb2d = BoundingBox2d; + +struct BoxCollider2d { + box: Box2d, + translation: Vec2, + rotation: Mat3, +} +impl Meshable2d for BoxCollider2d +impl Collider2d for BoxCollider2d +type Obb2d = BoxCollider2d; -// 2D Oriented Bounding Box -struct ObbCentered2d { - origin: Point2d, - orthonormal_basis: Mat2, - half_extents: Vec2, +// Capsule Types + +struct Capsule2d { + // Height of the cylindrical section + height: f32, + radius: f32, +} +impl Meshable2d for Capsule2d {} + +struct CapsuleCollider2d { + capsule: Capsule2d, + translation: Vec2, + rotation: Mat3, } -impl Obb2d for ObbCentered2d {} //... +impl Meshable2d for CapsuleCollider2d {} +impl Collider2d for CapsuleCollider2d {} -struct ObbVertices2d([Point2d; 4]); -impl Obb2d for ObbVertices2d {} //... ``` +### Lack of `Transform`s -### Meshing +Primitives colliders and bounding volumes are fully defined in space, and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. -While these primitives do not provide a meshing strategy, future work could build on these types so users can use something like `Sphere.mesh()` to generate meshes. +#### Cache Efficiency -```rust -let unit_sphere = Sphere::UNIT; -// Some ways we could build meshes from primitives -let sphere_mesh = Mesh::from(unit_sphere); -let sphere_mesh = unit_sphere.mesh(); -let sphere_mesh: Mesh = unit_sphere.into(); -``` +- Some primitives such as AABB and Sphere don't need a rotation to be fully defined. +- By using a `GlobalTransform`, not only is this an unused Quat that fills the cache line, it would also cause redundant change detection on rotations. +- This is especially important for AABBs and Spheres, because they are fundamental to broad phase collision detection and BV(H), and as such need to be as efficient as possible. -### Bounding Boxes/Volumes +#### Ergonomics + +- Primitives need to be fully defined in world space to compute collision or bounding. +- By making the primitive components fully defined and standalone, computing operations is as simple as: `primitive1.some_function(primitive_2)`, instead of also having query and pass in 2 `GlobalTransform`s in the (hopefully) correct order. -This section is based off of prototyping work done in [bevy_mod_bounding](https://github.com/aevyrie/bevy_mod_bounding). +#### Use with Transforms -A number of bounding box types are provided for 2d and 3d use. Some representations of a bounding box are more efficient depending on the use case. Instead of storing all possible values in a component (wasted space) or computing them with a helper function every time (wasted cpu), each representation is provided as a distinct component that can be updated independently. This gives users of Bevy the flexibility to optimize for their use case without needing to write their own incompatible types from scratch. Consider the functionality built on top of bounding such as physics or collisions - because they are all built on the same fundamental types, they can interoperate. +- The meshes generated from these primitives can be transformed freely. The primitive types, however, do not interact with transforms for the reasons stated above. -Note that the bounding types presented in this RFC implement their respective `Obb` or `Aabb` trait. Bounding boxes can be represented by a number of different underlying types; by making these traits instead of structs, systems can easily be made generic over these types depending on the situation. +### Bounding Boxes/Volumes -Because bounding boxes are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. The plan would be to provide a system similar to transform propagation, that would apply an OBB's precomputed `Transform` to its parent's `GlobalTransform`. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose in the future. +Because bounding volumes and colliders are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. An implementation should provide a system similar to transform propagation, that would update the primitive as well as its translation and rotation if applicable. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose, whether for internal or external crates. ### Frustum Culling -This section is based off of prototyping work done in [bevy_frustum_culling](https://github.com/aevyrie/bevy_frustum_culling). - The provided `Frustum` type is useful for frustum culling, which is generally done on the CPU by comparing each frustum plane with each entity's bounding volume. ```rust -// What frustum culling might look like: for bounding_volume in bound_vol_query.iter() { for plane in camera_frustum.planes().iter() { if bounding_volume.outside(plane) { @@ -342,79 +461,46 @@ In addition, by defining the frustum as a set of planes, it is also trivial to s ### Ray Casting -This section is based off of prototyping work done in [bevy_mod_picking](https://github.com/aevyrie/bevy_mod_picking). - -The bounding volumes section covers how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` component can be used to represent rays. Applicable 3d types could implement a `RayIntersection` trait to extend their functionality. +The bounding volumes sections of this RFC cover how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` primitive component can be used to represent rays. Applicable 3d types could implement a `Raycast` trait to extend their functionality. ```rust let ray = Ray::X; -let sphere = Sphere::new(Point::x(5.0), 1.0); -let intersection = ray.cast(sphere); +let sphere = SphereCollider::new{Sphere{1.0}, Point::x(5.0)); +let intersection = sphere.raycast(ray); ``` ## Drawbacks -An argument could be made to use an external crate for this, however these types are so fundamental I think it's important that they are optimized for the engine's uses, and are not from a generalized solution. - -This is also a technically simple addition that shouldn't present maintenance burden. The real challenge is upfront in ensuring the API is designed well, and the primitives are performant for their most common use cases. If anything, this furthers the argument for not using an external crate. +Adding primitives invariably adds to the maintenance burden. However, this cost seems worth the benefits of the needed engine functionality that can be built on top of it. ## Rationale and alternatives -### Lack of `Transform`s - -Primitives are fully defined in space, and do not use `Transform` or `GlobalTransform`. This is an intentional decision. - -It's unsurprisingly much simpler to use these types when the primitives are fully defined internally, but maybe somewhat surprisingly, more efficient. - -### Cache Efficiency - -- Some primitives such as AABB and Sphere don't need a rotation to be fully defined. -- By using a `GlobalTransform`, not only is this an unused Quat that fills the cache line, it would also cause redundant change detection on rotations. -- This is especially important for AABBs and Spheres, because they are fundamental to collision detection and BV(H), and as such need to be as efficient as possible. -- I still haven't found a case where you would use a `Primitive3d` without needing this positional information that fully defines the primitive in space. If this holds true, it means that storing the positional data inside the primitive is _not_ a waste of cache, which is normally why you would want to separate the transform into a separate component. - -### CPU Efficiency - -- Storing the primitive's positional information internally serves as a form of memoization. -- Because you need the primitive to be fully defined in world space to run useful operations, this means that with a `GlobalTransform` you would need to apply the transform to the primitive every time you need to use it. -- By applying this transformation only once (e.g. during transform propagation), we only need to do this computation a single time. - -### Ergonomics - -- As I've already mentioned a few times, primitives need to be fully defined in world space to do anything useful with them. -- By making the primitive components fully defined and standalone, computing operations is as simple as: `primitive1.some_function(primitive_2)`, instead of also having query and pass in 2 `GlobalTransform`s in the correct order. - -### Use with Transforms - -- For use cases such as oriented bounding boxes, a primitive should be defined relative to its parent. -- In this case, the primitive would still be fully defined internally, but we would need to include primitive updates analogous to the transform propagation system. -- For example, a bounding sphere entity would be a child of a mesh, with a `Sphere` primitive and a `Transform`. On updates to the parent's `GlobalTransform`, the bounding sphere's `Transform` component would be used to update the `Sphere`'s position and radius by applying the scale and translation to a unit sphere. This could be applied to all primitives, with the update system being optimized for each primitive. +An argument could be made to use an external crate for shape primitives, however these types are so fundamental It's important that they are optimized for the engine's most common use cases, and are not from a generalized solution. In addition, the scope of this RFC does not include the implementation of features like bounding, collision, raycasting, or physics. These are acknowledged as areas that (for now) should be carried out in external crates. ## Prior art - Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html - Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh +- THe popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line -These examples intermingle primitive geometry with the meshes themselves. This RFC makes these -distinct. - +Many game engine docs appear to have many oddly-named and disconnected shape primitive types that are completely unrelated. This RFC aims to ensure Bevy doesn't go down this path, and instead derives functionality from common types to take advantage of the composability of components in the ECS. ## Unresolved questions -What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`. - -How will fully defined primitives interact with `Transforms`? Will this confuse users? How can the API be shaped to prevent this? +What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`? ### Out of Scope - Value types, e.g. float vs. fixed is out of scope. This RFC is focused on the core geometry types and is intended to use Bevy's common rendering value types such as `f32`. +- Implementation of bounding, collisions, physics, raycasting, meshing, etc. ## Future possibilities - Bounding boxes - Collisions - Frustum Culling +- Froxels for forward clustered rendering - Ray casting - Physics - SDF rendering -- Debug Rendering +- Immediate mode debug Rendering (just call `.mesh()`!) From f663bd16d56e0254a2a596f2269acade7f1b2cac Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Mon, 24 May 2021 22:37:29 -0700 Subject: [PATCH 19/50] Rename primitive-shapes.md to 12-primitive-shapes.md --- rfcs/{primitive-shapes.md => 12-primitive-shapes.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename rfcs/{primitive-shapes.md => 12-primitive-shapes.md} (100%) diff --git a/rfcs/primitive-shapes.md b/rfcs/12-primitive-shapes.md similarity index 100% rename from rfcs/primitive-shapes.md rename to rfcs/12-primitive-shapes.md From cef33e9cda08560b421aceebe7c3ee74428e2366 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 00:21:08 -0700 Subject: [PATCH 20/50] Update 12-primitive-shapes.md Corrected typos --- rfcs/12-primitive-shapes.md | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 3956262d..d1de4e07 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -2,7 +2,7 @@ ## Summary -These geometric primitives, or "primitive shapes", are lightweight types for use across bevy engine crates and as interoperability types for plugins and libraries. The goal is to provide an API that considers ergonomics for the common use cases, such as meshing, bounding, and collision, without sacrificing performance. +These geometric primitives, or "primitive shapes", are lightweight types for use across Bevy engine crates and as interoperability types for plugins and libraries. The goal is to provide an API that considers ergonomics for the common use cases, such as meshing, bounding, and collision, without sacrificing performance. ## Motivation @@ -72,8 +72,6 @@ Colliders can provide intersection checks against other colliders, as well as ch This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line` and `Line2d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. 3d geometry (or 2d with depth) is assumed to be the default for most cases. The names of the types were chosen with this in mind. -... - ## Implementation strategy ### Helper Types @@ -119,6 +117,7 @@ struct Point(Vec3) /// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. struct Direction(Vec3) +impl Meshable for Direction {} struct Plane { point: Point, @@ -131,6 +130,7 @@ struct Ray( point: Point, direction: Direction, ); +impl Meshable for Ray {} // Line types @@ -139,6 +139,7 @@ struct Line { point: Point, direction: Direction, } +impl Meshable for Line {} /// A line segment bounded by two points struct LineSegment { @@ -193,8 +194,8 @@ struct BoundingBox { box: Box, translation: Vec3, } -impl Meshable for BoundingBox -impl Bounding for BoundingBox +impl Meshable for BoundingBox {} +impl Bounding for BoundingBox {} type Aabb = BoundingBox; struct BoxCollider { @@ -202,8 +203,8 @@ struct BoxCollider { translation: Vec3, rotation: Quat, } -impl Meshable for BoxCollider -impl Collider for BoxCollider +impl Meshable for BoxCollider {} +impl Collider for BoxCollider {} type Obb = BoxCollider; // Cylinder Types @@ -365,15 +366,15 @@ struct BoundingCircle2d { circle: Circle2d, translation: Vec2, } -impl Meshable2d for BoundingCircle2d -impl Bounding2d for BoundingCircle2d +impl Meshable2d for BoundingCircle2d {} +impl Bounding2d for BoundingCircle2d {} struct CircleCollider2d { sphere: Circle2d, translation: Vec2, } -impl Meshable2d for CircleCollider2d -impl Collider2d for CircleCollider2d +impl Meshable2d for CircleCollider2d {} +impl Collider2d for CircleCollider2d {} // Box Types @@ -386,8 +387,8 @@ struct BoundingBox2d { box: Box2d, translation: Vec2, } -impl Meshable2d for BoundingBox2d -impl Bounding2d for BoundingBox2d +impl Meshable2d for BoundingBox2d {} +impl Bounding2d for BoundingBox2d {} type Aabb2d = BoundingBox2d; struct BoxCollider2d { @@ -395,8 +396,8 @@ struct BoxCollider2d { translation: Vec2, rotation: Mat3, } -impl Meshable2d for BoxCollider2d -impl Collider2d for BoxCollider2d +impl Meshable2d for BoxCollider2d {} +impl Collider2d for BoxCollider2d {} type Obb2d = BoxCollider2d; // Capsule Types From 9dd6b671747b9388a1471043e388bc4bc3b39538 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 00:22:29 -0700 Subject: [PATCH 21/50] Update 12-primitive-shapes.md More typos --- rfcs/12-primitive-shapes.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index d1de4e07..b1032e23 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -173,15 +173,15 @@ struct BoundingSphere { sphere: Sphere, translation: Vec3, } -impl Meshable for BoundingSphere -impl Bounding for BoundingSphere +impl Meshable for BoundingSphere {} +impl Bounding for BoundingSphere {} struct SphereCollider { sphere: Sphere, translation: Vec3, } -impl Meshable for BoundingSphere -impl Collider for BoundingSphere +impl Meshable for BoundingSphere {} +impl Collider for BoundingSphere {} // Box Types @@ -278,14 +278,14 @@ impl Collider for WedgeCollider {} // Other Types -struct Torus{ +struct Torus { major_radius: f32, tube_radius: f32, } impl Meshable for Torus {} // A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. -struct Frustum{ +struct Frustum { near: Plane, far: Plane, top: Plane, From b67f30e250eba65ddc439eb7f13acea8ed1d21fd Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 00:38:05 -0700 Subject: [PATCH 22/50] Update 12-primitive-shapes.md --- rfcs/12-primitive-shapes.md | 44 +++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index b1032e23..9ef1d269 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -180,8 +180,8 @@ struct SphereCollider { sphere: Sphere, translation: Vec3, } -impl Meshable for BoundingSphere {} -impl Collider for BoundingSphere {} +impl Meshable for SphereCollider {} +impl Collider for SphereCollider {} // Box Types @@ -305,36 +305,40 @@ These types only exist in 2d space: their dimensions and location are only defin struct Point2d(Vec2) struct Direction2d(Vec2) +impl Meshable for Direction2d {} -struct Ray2d( +struct Ray2d { point: Point2d, - direction: Direction2d,); + direction: Direction2d +} +impl Meshable for Ray2d {} struct Line2d { point: Point2d, direction: Direction2d, } +impl Meshable for Line2d {} impl Collider2d for Line2d {} struct LineSegment2d { start: Point2d, end: Point2d, } -impl Meshable2d for LineSegment2d {} +impl Meshable for LineSegment2d {} impl Collider2d for LineSegment2d {} struct PolyLine2d{ points: [Point2d; N], } -impl Meshable2d for PolyLine2d {} +impl Meshable for PolyLine2d {} impl Collider2d for PolyLine2d {} struct Triangle2d([Point2d; 3]); -impl Meshable2d for Triangle2d {} +impl Meshable for Triangle2d {} impl Collider2d for Triangle2d {} struct Quad2d([Point2d; 4]); -impl Meshable2d for Quad2d {} +impl Meshable for Quad2d {} impl Collider2d for Quad2d {} /// A regular polygon, such as a square or hexagon. @@ -346,13 +350,13 @@ struct RegularPolygon2d { /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. orientation: Angle, } -impl Meshable2d for RegularPolygon2d {} +impl Meshable for RegularPolygon2d {} impl Collider2d for RegularPolygon2d {} struct Polygon2d { points: [Point; N], } -impl Meshable2d for Polygon2d {} +impl Meshable for Polygon2d {} impl Collider2d for Polygon2d {} /// Circle types @@ -360,20 +364,20 @@ impl Collider2d for Polygon2d {} struct Circle2d { radius: f32, } -impl Meshable2d for Circle2d {} +impl Meshable for Circle2d {} struct BoundingCircle2d { circle: Circle2d, translation: Vec2, } -impl Meshable2d for BoundingCircle2d {} +impl Meshable for BoundingCircle2d {} impl Bounding2d for BoundingCircle2d {} struct CircleCollider2d { sphere: Circle2d, translation: Vec2, } -impl Meshable2d for CircleCollider2d {} +impl Meshable for CircleCollider2d {} impl Collider2d for CircleCollider2d {} // Box Types @@ -387,7 +391,7 @@ struct BoundingBox2d { box: Box2d, translation: Vec2, } -impl Meshable2d for BoundingBox2d {} +impl Meshable for BoundingBox2d {} impl Bounding2d for BoundingBox2d {} type Aabb2d = BoundingBox2d; @@ -396,7 +400,7 @@ struct BoxCollider2d { translation: Vec2, rotation: Mat3, } -impl Meshable2d for BoxCollider2d {} +impl Meshable for BoxCollider2d {} impl Collider2d for BoxCollider2d {} type Obb2d = BoxCollider2d; @@ -407,14 +411,14 @@ struct Capsule2d { height: f32, radius: f32, } -impl Meshable2d for Capsule2d {} +impl Meshable for Capsule2d {} struct CapsuleCollider2d { capsule: Capsule2d, translation: Vec2, rotation: Mat3, } -impl Meshable2d for CapsuleCollider2d {} +impl Meshable for CapsuleCollider2d {} impl Collider2d for CapsuleCollider2d {} ``` @@ -482,9 +486,11 @@ An argument could be made to use an external crate for shape primitives, however - Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html - Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh -- THe popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line +- The popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line + +Proir art was used to select the most common types of shape primitive, naming conventions, as well as sensible data structures for bounding, collision, and culling. -Many game engine docs appear to have many oddly-named and disconnected shape primitive types that are completely unrelated. This RFC aims to ensure Bevy doesn't go down this path, and instead derives functionality from common types to take advantage of the composability of components in the ECS. +Many game engine docs appear to have oddly-named and disconnected shape primitive types that are completely unrelated. This RFC aims to ensure Bevy doesn't go down this path, and instead derives functionality from common types to take advantage of the composability of components in the ECS. ## Unresolved questions From 47bcc26ae93563b84c847781a27554dceda98bb5 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 00:50:24 -0700 Subject: [PATCH 23/50] Update 12-primitive-shapes.md polish --- rfcs/12-primitive-shapes.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 9ef1d269..5e1a787c 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -428,9 +428,10 @@ Primitives colliders and bounding volumes are fully defined in space, and do not #### Cache Efficiency -- Some primitives such as AABB and Sphere don't need a rotation to be fully defined. -- By using a `GlobalTransform`, not only is this an unused Quat that fills the cache line, it would also cause redundant change detection on rotations. +- Some primitives such as AABB and Sphere don't need a rotation (or scale) to be fully defined. +- Using a `GlobalTransform` adds an unused Quat and Vec3 to the cache line. - This is especially important for AABBs and Spheres, because they are fundamental to broad phase collision detection and BV(H), and as such need to be as efficient as possible. +- This also applies to all other primitives, which don't use the `scale` field of the `GlobalTransform`. #### Ergonomics From 7d58f91cf1f8cee8d2bec6120059418b69c093a0 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 11:38:45 -0700 Subject: [PATCH 24/50] Correct Mat3 -> Mat2 --- rfcs/12-primitive-shapes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 5e1a787c..ffbf03f6 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -398,7 +398,7 @@ type Aabb2d = BoundingBox2d; struct BoxCollider2d { box: Box2d, translation: Vec2, - rotation: Mat3, + rotation: Mat2, } impl Meshable for BoxCollider2d {} impl Collider2d for BoxCollider2d {} @@ -416,7 +416,7 @@ impl Meshable for Capsule2d {} struct CapsuleCollider2d { capsule: Capsule2d, translation: Vec2, - rotation: Mat3, + rotation: Mat2, } impl Meshable for CapsuleCollider2d {} impl Collider2d for CapsuleCollider2d {} From a2aa8cfe4583b0eafa7cbdfa7b1bef391c4abbb4 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 15:04:19 -0700 Subject: [PATCH 25/50] Fix AABB error in table --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index ffbf03f6..46f0f9bd 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -25,7 +25,7 @@ Note that a `Circle2d` does not contain any information about the position of th | Shape | Mesh | Bounding | Collision | |--- |---|---|---| | Sphere | ✔ | ✔ + Trans | ✔ + Trans | -| Box | ✔ | ✔ (AABB) | ✔ + Trans + Rot (OBB) | +| Box | ✔ | ✔ + Trans (AABB) | ✔ + Trans + Rot (OBB) | | Capsule | ✔ | ❌ | ✔ + Trans + Rot | | Cylinder | ✔ | ❌ | ✔ + Trans + Rot | | Cone | ✔ | ❌ | ✔ + Trans + Rot | From bd9d5e25794322594679ae180a2fdf25e6b96f8f Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 25 May 2021 21:38:47 -0700 Subject: [PATCH 26/50] Update 12-primitive-shapes.md --- rfcs/12-primitive-shapes.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 46f0f9bd..ccae46e2 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -475,6 +475,30 @@ let sphere = SphereCollider::new{Sphere{1.0}, Point::x(5.0)); let intersection = sphere.raycast(ray); ``` +### Direction Normalization + +The notes for `Direction` mention it is gauranteed to be normalized through its getter and setter. There are a few ways to do this, but I'd like to propose a zero-cost implementation using the typestate pattern. To absolutely minimize the use of `normalize` on the contained `Vec3`, we can memoize the result _only when accessed_. We don't want to make `Direction` an enum, as that will add a discriminant, and we don't want to have normalized and unnormalized types for of all our primitives. So instead, we could use the typestate pattern to do something like: + +```rust +struct Plane { + point: Point, + normal: Direction, +} + +struct Direction { + direction: impl Normalizable, +} + +struct UncheckedDir(Vec3); +impl Normalizable for UncheckedDir {} + +struct NormalizedDir(Vec3); +impl Normalizable for NormalizedDir {} + +``` + +When a `Direction` is mutated or built, the direction will be an `UncheckedDir`. Once it is accessed, the `Directions`s getter method will normalize the direction, and swap it out with a `NormalizedDir`. Now the normalized direction is memoized in the `Direction` without increasing the size of the type. This complexity can be completely hidden to users. + ## Drawbacks Adding primitives invariably adds to the maintenance burden. However, this cost seems worth the benefits of the needed engine functionality that can be built on top of it. From 0f25cfe73c597b45d0197cf7a7d08df6c8ac8b3e Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Wed, 26 May 2021 09:29:04 -0700 Subject: [PATCH 27/50] Typo --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index ccae46e2..bac512b6 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -513,7 +513,7 @@ An argument could be made to use an external crate for shape primitives, however - Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh - The popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line -Proir art was used to select the most common types of shape primitive, naming conventions, as well as sensible data structures for bounding, collision, and culling. +Prior art was used to select the most common types of shape primitive, naming conventions, as well as sensible data structures for bounding, collision, and culling. Many game engine docs appear to have oddly-named and disconnected shape primitive types that are completely unrelated. This RFC aims to ensure Bevy doesn't go down this path, and instead derives functionality from common types to take advantage of the composability of components in the ECS. From b3fcd7df75d46f0ae3b80a793158aa392021efcb Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 1 Jun 2021 00:46:04 -0700 Subject: [PATCH 28/50] Add notes to bounding/collision --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index bac512b6..485c38cd 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -444,7 +444,7 @@ Primitives colliders and bounding volumes are fully defined in space, and do not ### Bounding Boxes/Volumes -Because bounding volumes and colliders are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. An implementation should provide a system similar to transform propagation, that would update the primitive as well as its translation and rotation if applicable. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose, whether for internal or external crates. +Because bounding volumes and colliders are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. An implementation should provide a system similar to transform propagation, that would update the primitive as well as its translation and rotation if applicable. This can be accomplished in the transform propogation system itself, or in system that runs directly after. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose, whether for internal or external crates. The purpose of considering bounding and collision is that they represent common use cases of these primitives, and their potential implementation strategy should be considered. ### Frustum Culling From cede3131a0e57051e79c8dd0129b4e1ce4c7b810 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 1 Jun 2021 00:59:11 -0700 Subject: [PATCH 29/50] Clarify traits as reference only --- rfcs/12-primitive-shapes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 485c38cd..7930f6ea 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -80,9 +80,9 @@ This RFC provides independent 2d and 3d primitives. Recall that the purpose of t /// Stores an angle in radians, and supplies builder functions to prevent errors (from_radians, from_degrees) struct Angle(f32); ``` -### Traits +### Traits (REFERENCE ONLY!) -These traits are provided as reference to illustrate how these might be used. The details of implementation and interface should be determined in a separate RFC, PR, or independent prototypes. +**These traits are provided as reference to illustrate how these primitive shape types might be used. The details of implementation and interface should be determined in a separate RFC, PR, or independent prototypes.** ```rust trait Meshable{ From 5e381f63018480ed04589a4fe5ff506ae24a45b4 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Tue, 1 Jun 2021 01:53:21 -0700 Subject: [PATCH 30/50] improve bounding/collision discussion Make it clear what is out of scope --- rfcs/12-primitive-shapes.md | 64 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 7930f6ea..02259a13 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -35,13 +35,13 @@ Note that a `Circle2d` does not contain any information about the position of th ### Bounding vs. Collision -The difference between the two is somewhat semantic. Both bounding and collision check for intersections between bounding volumes or areas. However, bounding volumes are generally used for broad phase checks due to their speed and small size, and are consequently often used in tree structures for spacial queries. Note that both bounding types are not oriented - this reduces the number of comparisons needed to check for intersections during broad phase tests. +The difference between the two is somewhat semantic. Both bounding and collision check for intersections between bounding volumes or areas. However, bounding volumes are generally used for broad phase checks due to their speed and small size, and are consequently often used in tree structures for spacial queries. Note that both bounding types are not oriented - this reduces the number of comparisons needed to check for intersections during broad phase tests. Bounding spheres or AABBs are generally always preferred for broad phase bounding checks, despite the OBB's name. -Although the traits for these may look similar, by making them distinct it will better conform to industry terminology and help guide users away from creating performance problems. For example, because a torus is a volume, it is conceivably possible to implement the `Bounding` trait, however it would be a terrible idea to be encouraging users to build BVTs/BVHs out of torii. This is also why the Oriented Bounding Box (OBB) is a collider, and not `Bounding`. Bounding spheres or AABBs are generally always preferred for broad phase bounding checks, despite the OBB's name. For advanced users who want to implement `Bounding` for OBBs, they always have the option of wrapping the OBB in a newtype and implementing the trait. +**Note that the Bounding/Collision interface is not part of this RFC**, the purpose of this discussion is to present common use cases of these primitive shape types to uncover deficiencies in their design. ### Where are the `Transform`s? - Translation and rotation are **not** defined using bevy's `Transform` components. This is because types that implement `Bounding` and `Collider` must be fully self-contained. This: +In this potential bounding and collision interface, translation and rotation are **not** defined using bevy's `Transform` components, nor are they stored in the base primitive shape types. This is because types that implement `Bounding` and `Collider` must be fully self-contained. This: * makes the API simpler when using these components in functions and systems * ensures bounding and collision types use an absolute minimum of memory @@ -49,20 +49,22 @@ Although the traits for these may look similar, by making them distinct it will Instead, changes to the parent's `GlobalTransform` should be used to derive a new primitive on scale changes - as well as a new translation and rotation if required. +Although bounding and collision are out of scope of this RFC, this helps to explain why primitive shape types contain no positional information. This hypothetical bounding/collision interface shows how the base primitive shapes can be composed to build common game engine functinality without adding memory overhead. + ### Meshing -Both 2d and 3d primitives can implement the `Meshable` trait to provide the ability to generate a tri mesh: +Both 2d and 3d primitives could implement the `Meshable` trait to provide the ability to generate a tri mesh: ```rust let circle_mesh: Mesh = Circle2d{ radius: 2.0 }.mesh(); ``` The base primitive types only define the shape (`Circle2d`) and size (`radius`) of the geometry about the origin. Once generated, the mesh can have a transform applied to it like any other mesh, and it is no longer tied to the primitive that generated it. The `Default::default()` implementation of primitives should be a "unit" variant, such as a circle with diameter 1.0, or a box with all edges of length 1.0. -Meshing could be naturally extended with other libraries or parameters. For example, a sphere by default might use `Icosphere`, but could be extended to generate UV spheres or quad spheres. For 2d types, we can use a crate such as `lyon` to generate 2d meshes from parameterized primitive shapes within the `Meshable` interface. +Meshing could be naturally extended with other libraries or parameters. For example, a sphere by default might use `Icosphere`, but could be extended to generate UV spheres or quad spheres. For 2d types, we could use a crate such as `lyon` to generate 2d meshes from parameterized primitive shapes within the `Meshable` interface. ### Bounding -For use in spatial queries, broad phase collision detection, and raycasting, `Bounding` and `Bounding2d` traits are provided. These traits are implemented for types that define position in addition to the shape and size defined in the base primitive. The basic functionality of this trait is to check whether one bounding shape is contained within another bounding shape. +For use in spatial queries, broad phase collision detection, and raycasting, hypothetical `Bounding` and `Bounding2d` traits are provided. These traits are implemented for types that define position in addition to the shape and size defined in the base primitive. The basic functionality of this trait is to check whether one bounding shape is contained within another bounding shape. ### Colliders @@ -169,6 +171,7 @@ struct Sphere { } impl Meshable for Sphere {} +/* REFERENCE ONLY struct BoundingSphere { sphere: Sphere, translation: Vec3, @@ -182,6 +185,7 @@ struct SphereCollider { } impl Meshable for SphereCollider {} impl Collider for SphereCollider {} +*/ // Box Types @@ -190,6 +194,7 @@ struct Box { } impl Meshable for Box +/* REFERENCE ONLY struct BoundingBox { box: Box, translation: Vec3, @@ -206,6 +211,7 @@ struct BoxCollider { impl Meshable for BoxCollider {} impl Collider for BoxCollider {} type Obb = BoxCollider; +*/ // Cylinder Types @@ -216,6 +222,7 @@ struct Cylinder { } impl Meshable for Cylinder {} +/* REFERENCE ONLY struct CylinderCollider { cylinder: Cylinder, translation: Vec3, @@ -223,6 +230,7 @@ struct CylinderCollider { } impl Meshable for CylinderCollider {} impl Collider for CylinderCollider {} +*/ // Capsule Types @@ -233,6 +241,7 @@ struct Capsule { } impl Meshable for Capsule {} +/* REFERENCE ONLY struct CapsuleCollider { capsule: Capsule, translation: Vec3, @@ -240,6 +249,7 @@ struct CapsuleCollider { } impl Meshable for CapsuleCollider {} impl Collider for CapsuleCollider {} +*/ // Cone Types @@ -250,6 +260,7 @@ struct Cone { } impl Meshable for Cone {} +/* REFERENCE ONLY struct ConeCollider { cone: Cone, translation: Vec3, @@ -257,6 +268,7 @@ struct ConeCollider { } impl Meshable for ConeCollider {} impl Collider for ConeCollider {} +*/ // Wedge Types @@ -268,6 +280,7 @@ struct Wedge { } impl Meshable for Wedge {} +/* REFERENCE ONLY struct WedgeCollider { wedge: Wedge, translation: Vec3, @@ -275,6 +288,7 @@ struct WedgeCollider { } impl Meshable for WedgeCollider {} impl Collider for WedgeCollider {} +*/ // Other Types @@ -366,6 +380,7 @@ struct Circle2d { } impl Meshable for Circle2d {} +/* REFERENCE ONLY struct BoundingCircle2d { circle: Circle2d, translation: Vec2, @@ -379,6 +394,7 @@ struct CircleCollider2d { } impl Meshable for CircleCollider2d {} impl Collider2d for CircleCollider2d {} +*/ // Box Types @@ -387,6 +403,7 @@ struct Box2d { } impl Meshable for Box2d +/* REFERENCE ONLY struct BoundingBox2d { box: Box2d, translation: Vec2, @@ -403,16 +420,17 @@ struct BoxCollider2d { impl Meshable for BoxCollider2d {} impl Collider2d for BoxCollider2d {} type Obb2d = BoxCollider2d; +*/ // Capsule Types struct Capsule2d { - // Height of the cylindrical section - height: f32, - radius: f32, + height: f32, // Height of the rectangular section + radius: f32, // End cap radius } impl Meshable for Capsule2d {} +/* REFERENCE ONLY struct CapsuleCollider2d { capsule: Capsule2d, translation: Vec2, @@ -420,31 +438,14 @@ struct CapsuleCollider2d { } impl Meshable for CapsuleCollider2d {} impl Collider2d for CapsuleCollider2d {} +*/ ``` -### Lack of `Transform`s - -Primitives colliders and bounding volumes are fully defined in space, and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. - -#### Cache Efficiency - -- Some primitives such as AABB and Sphere don't need a rotation (or scale) to be fully defined. -- Using a `GlobalTransform` adds an unused Quat and Vec3 to the cache line. -- This is especially important for AABBs and Spheres, because they are fundamental to broad phase collision detection and BV(H), and as such need to be as efficient as possible. -- This also applies to all other primitives, which don't use the `scale` field of the `GlobalTransform`. - -#### Ergonomics - -- Primitives need to be fully defined in world space to compute collision or bounding. -- By making the primitive components fully defined and standalone, computing operations is as simple as: `primitive1.some_function(primitive_2)`, instead of also having query and pass in 2 `GlobalTransform`s in the (hopefully) correct order. - -#### Use with Transforms - -- The meshes generated from these primitives can be transformed freely. The primitive types, however, do not interact with transforms for the reasons stated above. +### Bounding and Collision -### Bounding Boxes/Volumes +Primitives colliders and bounding volumes are fully defined in space (in their hypothetical implementation here), and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. -Because bounding volumes and colliders are fully defined in world space, this leads to the natural question of how they are kept in sync with their parent. An implementation should provide a system similar to transform propagation, that would update the primitive as well as its translation and rotation if applicable. This can be accomplished in the transform propogation system itself, or in system that runs directly after. Further details are more appropriate for a subsequent bounding RFC/PR. The important point to consider is how this proposal provides common types that can be used for this purpose, whether for internal or external crates. The purpose of considering bounding and collision is that they represent common use cases of these primitives, and their potential implementation strategy should be considered. +The provided reference implementaiton of bounding and collision types demonstrate how primitive shape types can be composed with other types, triats, or components to build added functionality from shared parts. While the collder and bounding primitives provided as reference are out of scope, they also highlight the incompatibility of shape primitives with the `Transform` component. ### Frustum Culling @@ -467,7 +468,7 @@ In addition, by defining the frustum as a set of planes, it is also trivial to s ### Ray Casting -The bounding volumes sections of this RFC cover how these types would be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` primitive component can be used to represent rays. Applicable 3d types could implement a `Raycast` trait to extend their functionality. +The bounding volumes sections of this RFC cover how these types could be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` primitive component can be used to naturally represent raycasting rays. Applicable 3d types could implement a `Raycast` trait to extend their functionality. ```rust let ray = Ray::X; @@ -512,6 +513,7 @@ An argument could be made to use an external crate for shape primitives, however - Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html - Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh - The popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line +- Rapier/Parry Prior art was used to select the most common types of shape primitive, naming conventions, as well as sensible data structures for bounding, collision, and culling. From 019047ed50c78438bcb6e0d5628c7b9ee70f998e Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Wed, 2 Jun 2021 19:32:58 -0700 Subject: [PATCH 31/50] Add notes about Parry types --- rfcs/12-primitive-shapes.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 02259a13..5e63fc03 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -508,6 +508,18 @@ Adding primitives invariably adds to the maintenance burden. However, this cost An argument could be made to use an external crate for shape primitives, however these types are so fundamental It's important that they are optimized for the engine's most common use cases, and are not from a generalized solution. In addition, the scope of this RFC does not include the implementation of features like bounding, collision, raycasting, or physics. These are acknowledged as areas that (for now) should be carried out in external crates. +### Using Parry/Rapier Types + +"Parry is the defacto standard for physics, why not use those types? If we make our own types, doesn't this add overhead if everyone is using Parry?" + +The choice of what physics engine to integrate into Bevy, if at all, will require much more discussion than can be covered in this RFC. However, it is true that Parry appears to be the most common choice for physics in Bevy, and its types should be considered for this RFC. + +Parry uses nalgebra for linear algebra; we would need to convert to/from glam types to interoperate with the rest of the engine whether or not we use Parry's geometric primitives. In that sense, making our own types doesn't add overhead to conversion, but we _would_ own the process. Parry plugins already exist for bevy. Adding our own primitive types to the engine wouldn't break those plugins, but it does add the ability to interoperate. + +Using Parry types would also mean every bevy crate that wants to implement some geometry focused feature, now needs to use the nalgebra and glam math types. This runs counter to Bevy's goal of simplicity. + +Perhaps most critically, Parry types are opinionated for physics/raycasting use. The goals of those types simply don't align with the goals outlined in this RFC. Bevy's shape primitives should be able to be used, for example, for meshing in 2d (UI/CAD) and 3d, frustum culling, clustered rendering, or anything else that that lives in the engine <-> application stack. If we want to add a 2d arc type for UI, we would need to to add primitives to Parry that are completely orthogonal to its goals. + ## Prior art - Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html From 24dbd0b3af3bf9a8176c1594f6c295211a40a49c Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Mon, 26 Jul 2021 10:03:46 -0700 Subject: [PATCH 32/50] Update 12-primitive-shapes.md Remove erroneous reference `Collider2d` impls --- rfcs/12-primitive-shapes.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 5e63fc03..6d5dc039 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -332,28 +332,23 @@ struct Line2d { direction: Direction2d, } impl Meshable for Line2d {} -impl Collider2d for Line2d {} struct LineSegment2d { start: Point2d, end: Point2d, } impl Meshable for LineSegment2d {} -impl Collider2d for LineSegment2d {} struct PolyLine2d{ points: [Point2d; N], } impl Meshable for PolyLine2d {} -impl Collider2d for PolyLine2d {} struct Triangle2d([Point2d; 3]); impl Meshable for Triangle2d {} -impl Collider2d for Triangle2d {} struct Quad2d([Point2d; 4]); impl Meshable for Quad2d {} -impl Collider2d for Quad2d {} /// A regular polygon, such as a square or hexagon. struct RegularPolygon2d { @@ -365,13 +360,11 @@ struct RegularPolygon2d { orientation: Angle, } impl Meshable for RegularPolygon2d {} -impl Collider2d for RegularPolygon2d {} struct Polygon2d { points: [Point; N], } impl Meshable for Polygon2d {} -impl Collider2d for Polygon2d {} /// Circle types From f37352ee5907d0f0e7c2a8b8d11d03a6caf3853d Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Tue, 26 Oct 2021 19:09:47 +0200 Subject: [PATCH 33/50] Fix markdown lint violations --- rfcs/12-primitive-shapes.md | 47 ++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 6d5dc039..f6b1eead 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -6,7 +6,7 @@ These geometric primitives, or "primitive shapes", are lightweight types for use ## Motivation -This provides a type-level foundation that subsequent engine (and plugin!) features can be built upon incrementally, preventing ecosystem fragmentation. A major goal is to make it possible for engine and game developers to prototype physics, bounding, collision, and raycasting functionality using the same base types. This will make engine integration and cross-plugin interoperability much more feasible, as the engine will have opinionated types that will be natural to implement `into` or `from`. I hope this opens the door to experimentation with multiple physics and bounding acceleration backends. +This provides a type-level foundation that subsequent engine (and plugin!) features can be built upon incrementally, preventing ecosystem fragmentation. A major goal is to make it possible for engine and game developers to prototype physics, bounding, collision, and raycasting functionality using the same base types. This will make engine integration and cross-plugin interoperability much more feasible, as the engine will have opinionated types that will be natural to implement `into` or `from`. I hope this opens the door to experimentation with multiple physics and bounding acceleration backends. There is significant complexity in the way seemingly equivalent shapes are defined depending on how they are used. By considering these use cases *first*, the goal is that geometric data structures can be composed depending on the needed functionality, with traits used to ensure the right data structure is used for the right task. @@ -25,7 +25,7 @@ Note that a `Circle2d` does not contain any information about the position of th | Shape | Mesh | Bounding | Collision | |--- |---|---|---| | Sphere | ✔ | ✔ + Trans | ✔ + Trans | -| Box | ✔ | ✔ + Trans (AABB) | ✔ + Trans + Rot (OBB) | +| Box | ✔ | ✔ + Trans (AABB) | ✔ + Trans + Rot (OBB) | | Capsule | ✔ | ❌ | ✔ + Trans + Rot | | Cylinder | ✔ | ❌ | ✔ + Trans + Rot | | Cone | ✔ | ❌ | ✔ + Trans + Rot | @@ -42,10 +42,10 @@ The difference between the two is somewhat semantic. Both bounding and collision ### Where are the `Transform`s? In this potential bounding and collision interface, translation and rotation are **not** defined using bevy's `Transform` components, nor are they stored in the base primitive shape types. This is because types that implement `Bounding` and `Collider` must be fully self-contained. This: - - * makes the API simpler when using these components in functions and systems - * ensures bounding and collision types use an absolute minimum of memory - * prevents errors caused by nonuniform scale invalidating the shape of the primitive. + +* makes the API simpler when using these components in functions and systems +* ensures bounding and collision types use an absolute minimum of memory +* prevents errors caused by nonuniform scale invalidating the shape of the primitive. Instead, changes to the parent's `GlobalTransform` should be used to derive a new primitive on scale changes - as well as a new translation and rotation if required. @@ -58,6 +58,7 @@ Both 2d and 3d primitives could implement the `Meshable` trait to provide the ab ```rust let circle_mesh: Mesh = Circle2d{ radius: 2.0 }.mesh(); ``` + The base primitive types only define the shape (`Circle2d`) and size (`radius`) of the geometry about the origin. Once generated, the mesh can have a transform applied to it like any other mesh, and it is no longer tied to the primitive that generated it. The `Default::default()` implementation of primitives should be a "unit" variant, such as a circle with diameter 1.0, or a box with all edges of length 1.0. Meshing could be naturally extended with other libraries or parameters. For example, a sphere by default might use `Icosphere`, but could be extended to generate UV spheres or quad spheres. For 2d types, we could use a crate such as `lyon` to generate 2d meshes from parameterized primitive shapes within the `Meshable` interface. @@ -82,6 +83,7 @@ This RFC provides independent 2d and 3d primitives. Recall that the purpose of t /// Stores an angle in radians, and supplies builder functions to prevent errors (from_radians, from_degrees) struct Angle(f32); ``` + ### Traits (REFERENCE ONLY!) **These traits are provided as reference to illustrate how these primitive shape types might be used. The details of implementation and interface should be determined in a separate RFC, PR, or independent prototypes.** @@ -434,9 +436,10 @@ impl Collider2d for CapsuleCollider2d {} */ ``` + ### Bounding and Collision -Primitives colliders and bounding volumes are fully defined in space (in their hypothetical implementation here), and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. +Primitives colliders and bounding volumes are fully defined in space (in their hypothetical implementation here), and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. The provided reference implementaiton of bounding and collision types demonstrate how primitive shape types can be composed with other types, triats, or components to build added functionality from shared parts. While the collder and bounding primitives provided as reference are out of scope, they also highlight the incompatibility of shape primitives with the `Transform` component. @@ -457,7 +460,7 @@ for bounding_volume in bound_vol_query.iter() { This data structure alone does not ensure the representation is valid; planes could be placed in nonsensical positions. To prevent this, the struct's fields should be made private, and constructors and setters should be provided to ensure `Frustum`s can only be initialized or mutated into valid arrangements. -In addition, by defining the frustum as a set of planes, it is also trivial to support oblique frustums. Oblique frustums are useful for 2d projections used in CAD, as well as in games to take advantage of projection distortion to do things like emphasizing the feeling of speed in a driving game. See the Unity docs: https://docs.unity3d.com/Manual/ObliqueFrustum.html +In addition, by defining the frustum as a set of planes, it is also trivial to support oblique frustums. Oblique frustums are useful for 2d projections used in CAD, as well as in games to take advantage of projection distortion to do things like emphasizing the feeling of speed in a driving game. See the Unity docs: ### Ray Casting @@ -515,10 +518,10 @@ Perhaps most critically, Parry types are opinionated for physics/raycasting use. ## Prior art -- Unity `PrimitiveObjects`: https://docs.unity3d.com/Manual/PrimitiveObjects.html -- Godot `PrimitiveMesh`: https://docs.godotengine.org/en/stable/classes/class_primitivemesh.html#class-primitivemesh -- The popular *Shapes* plugin for Unity https://acegikmo.com/shapes/docs/#line -- Rapier/Parry +* Unity `PrimitiveObjects`: +* Godot `PrimitiveMesh`: +* The popular *Shapes* plugin for Unity +* Rapier/Parry Prior art was used to select the most common types of shape primitive, naming conventions, as well as sensible data structures for bounding, collision, and culling. @@ -530,16 +533,16 @@ What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d ### Out of Scope -- Value types, e.g. float vs. fixed is out of scope. This RFC is focused on the core geometry types and is intended to use Bevy's common rendering value types such as `f32`. -- Implementation of bounding, collisions, physics, raycasting, meshing, etc. +* Value types, e.g. float vs. fixed is out of scope. This RFC is focused on the core geometry types and is intended to use Bevy's common rendering value types such as `f32`. +* Implementation of bounding, collisions, physics, raycasting, meshing, etc. ## Future possibilities -- Bounding boxes -- Collisions -- Frustum Culling -- Froxels for forward clustered rendering -- Ray casting -- Physics -- SDF rendering -- Immediate mode debug Rendering (just call `.mesh()`!) +* Bounding boxes +* Collisions +* Frustum Culling +* Froxels for forward clustered rendering +* Ray casting +* Physics +* SDF rendering +* Immediate mode debug Rendering (just call `.mesh()`!) From c50b330bac68fa0c651017177e279b396cd7e91a Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Wed, 27 Oct 2021 08:55:05 +0200 Subject: [PATCH 34/50] Fixing typo --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 6d5dc039..33524930 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -35,7 +35,7 @@ Note that a `Circle2d` does not contain any information about the position of th ### Bounding vs. Collision -The difference between the two is somewhat semantic. Both bounding and collision check for intersections between bounding volumes or areas. However, bounding volumes are generally used for broad phase checks due to their speed and small size, and are consequently often used in tree structures for spacial queries. Note that both bounding types are not oriented - this reduces the number of comparisons needed to check for intersections during broad phase tests. Bounding spheres or AABBs are generally always preferred for broad phase bounding checks, despite the OBB's name. +The difference between the two is somewhat semantic. Both bounding and collision check for intersections between bounding volumes or areas. However, bounding volumes are generally used for broad phase checks due to their speed and small size, and are consequently often used in tree structures for spatial queries. Note that both bounding types are not oriented - this reduces the number of comparisons needed to check for intersections during broad phase tests. Bounding spheres or AABBs are generally always preferred for broad phase bounding checks, despite the OBB's name. **Note that the Bounding/Collision interface is not part of this RFC**, the purpose of this discussion is to present common use cases of these primitive shape types to uncover deficiencies in their design. From cc192cbe20f7db310d3d674a91ffd732874e97f0 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:17:07 +0200 Subject: [PATCH 35/50] Explicitly naming x2d or x3d where it makes sense --- rfcs/12-primitive-shapes.md | 146 +++++++++++++++++++++--------------- 1 file changed, 85 insertions(+), 61 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 3d4551c3..22bfd19e 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -15,12 +15,12 @@ There is significant complexity in the way seemingly equivalent shapes are defin Geometric primitives are lightweight representations of geometry that describe the type of geometry as well as its dimensions. These primitives are *not* meshes, but the underlying precise mathematical definition. For example, a circle is: ```rust -pub struct Circle2d { +pub struct Circle { radius: f32, } ``` -Note that a `Circle2d` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the the information (Translation and Rotation) that are needed to fully define the shapes for these cases: +Note that a `Circle` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the the information (Translation and Rotation) that are needed to fully define the shapes for these cases: | Shape | Mesh | Bounding | Collision | |--- |---|---|---| @@ -56,10 +56,9 @@ Although bounding and collision are out of scope of this RFC, this helps to expl Both 2d and 3d primitives could implement the `Meshable` trait to provide the ability to generate a tri mesh: ```rust -let circle_mesh: Mesh = Circle2d{ radius: 2.0 }.mesh(); +let circle_mesh: Mesh = Circle{ radius: 2.0 }.mesh(); ``` - -The base primitive types only define the shape (`Circle2d`) and size (`radius`) of the geometry about the origin. Once generated, the mesh can have a transform applied to it like any other mesh, and it is no longer tied to the primitive that generated it. The `Default::default()` implementation of primitives should be a "unit" variant, such as a circle with diameter 1.0, or a box with all edges of length 1.0. +The base primitive types only define the shape (`Circle`) and size (`radius`) of the geometry about the origin. Once generated, the mesh can have a transform applied to it like any other mesh, and it is no longer tied to the primitive that generated it. The `Default::default()` implementation of primitives should be a "unit" variant, such as a circle with diameter 1.0, or a box with all edges of length 1.0. Meshing could be naturally extended with other libraries or parameters. For example, a sphere by default might use `Icosphere`, but could be extended to generate UV spheres or quad spheres. For 2d types, we could use a crate such as `lyon` to generate 2d meshes from parameterized primitive shapes within the `Meshable` interface. @@ -73,7 +72,32 @@ Colliders can provide intersection checks against other colliders, as well as ch ### 3D and 2D -This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line` and `Line2d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. 3d geometry (or 2d with depth) is assumed to be the default for most cases. The names of the types were chosen with this in mind. +This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line2d` and `Line3d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. + +| Shape | 2D | 3D | +|-----------------|-----------------|---------------| +| Point | Point2d | Point3d | +| Plane | Plane2d | Plane3d | +| Direction | Direction2d | Direction3d | +| Ray | Ray2d | Ray3d | +| Line | Line2d | Line3d | +| LineSegment | LineSegment2d | LineSegment3d | +| Polyline | Polyline2d | Polyline3d | +| Triangle | Triangle2d | Triangle3d | +| Quad | Quad2d | Quad3d | +| Sphere | - | Sphere | +| Box | - | Box | +| Cylinder | - | Cylinder | +| Capsule | - | Capsule | +| Cone | - | Cone | +| Wedge | - | Wedge | +| Torus | - | Torus | +| Frustrum | - | Frustrum | +| RegularPolygon | RegularPolygon | - | +| Polygon | Polygon | - | +| Rectangle | Rectangle | - | +| Circle | Circle | - | + ## Implementation strategy @@ -93,9 +117,9 @@ trait Meshable{ fn mesh(&self) -> Mesh; }; -trait Bounding { - fn within(&self, other: &impl Bounding) -> bool; - fn contains(&self, collider: &impl Collider) -> bool; +trait Bounding3d { + fn within(&self, other: &impl Bounding3d) -> bool; + fn contains(&self, collider: &impl Collider3d) -> bool; } trait Bounding2d { @@ -103,9 +127,9 @@ trait Bounding2d { fn contains(&self, collider: &impl Collider2d) -> bool; } -trait Collider { - fn collide(&self, other: &impl Collider) -> Option(Collision); - fn within(&self, bounds: &impl Bounding) -> bool; +trait Collider3d { + fn collide(&self, other: &impl Collider3d) -> Option(Collision3d); + fn within(&self, bounds: &impl Bounding3d) -> bool; } trait Collider2d { @@ -117,54 +141,54 @@ trait Collider2d { ### 3D Geometry Types ```rust -struct Point(Vec3) +struct Point3d(Vec3) /// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. -struct Direction(Vec3) -impl Meshable for Direction {} +struct Direction3d(Vec3) +impl Meshable for Direction3d {} -struct Plane { - point: Point, - normal: Direction, +struct Plane3d { + point: Point3d, + normal: Direction3d, } -impl Meshable for Plane {} +impl Meshable for Plane3d {} /// Differentiates a line from a ray, where a line is infinite and a ray is directional half-line, although their underlying representation is the same. -struct Ray( - point: Point, - direction: Direction, +struct Ray3d( + point: Point3d, + direction: Direction3d, ); -impl Meshable for Ray {} +impl Meshable for Ray3d {} // Line types /// Unbounded line in 3D space with directionality -struct Line { - point: Point, - direction: Direction, +struct Line3d { + point: Point3d, + direction: Direction3d, } -impl Meshable for Line {} +impl Meshable for Line3d {} /// A line segment bounded by two points -struct LineSegment { - start: Point, - end: Point, +struct LineSegment3d { + start: Point3d, + end: Point3d, } -impl Meshable for LineSegment {} +impl Meshable for LineSegment3d {} /// A line drawn along a path of points -struct PolyLine { - points: Vec, +struct PolyLine3d { + points: Vec, } -impl Meshable for PolyLine {} +impl Meshable for PolyLine3d {} -struct Triangle([Point; 3]); -impl Meshable for Triangle {} -impl Collider for Triangle {} +struct Triangle3d([Point3d; 3]); +impl Meshable for Triangle3d {} +impl Collider for Triangle3d {} -struct Quad([Point; 4]); -impl Meshable for Quad {} -impl Collider for Quad {} +struct Quad3d([Point3d; 4]); +impl Meshable for Quad3d {} +impl Collider for Quad3d {} /// Sphere types @@ -302,12 +326,12 @@ impl Meshable for Torus {} // A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. struct Frustum { - near: Plane, - far: Plane, - top: Plane, - bottom: Plane, - left: Plane, - right: Plane, + near: Plane3d, + far: Plane3d, + top: Plane3d, + bottom: Plane3d, + left: Plane3d, + right: Plane3d, } impl Meshable for Frustum {} @@ -364,31 +388,31 @@ struct RegularPolygon2d { impl Meshable for RegularPolygon2d {} struct Polygon2d { - points: [Point; N], + points: [Point2d; N], } impl Meshable for Polygon2d {} /// Circle types -struct Circle2d { +struct Circle { radius: f32, } -impl Meshable for Circle2d {} +impl Meshable for Circle {} /* REFERENCE ONLY struct BoundingCircle2d { - circle: Circle2d, + circle: Circle, translation: Vec2, } impl Meshable for BoundingCircle2d {} impl Bounding2d for BoundingCircle2d {} -struct CircleCollider2d { - sphere: Circle2d, +struct CircleCollider { + sphere: Circle, translation: Vec2, } -impl Meshable for CircleCollider2d {} -impl Collider2d for CircleCollider2d {} +impl Meshable for CircleCollider {} +impl Collider2d for CircleCollider {} */ // Box Types @@ -467,8 +491,8 @@ In addition, by defining the frustum as a set of planes, it is also trivial to s The bounding volumes sections of this RFC cover how these types could be used for the bounding volumes which are used for accelerating ray casting. In addition, the `Ray` primitive component can be used to naturally represent raycasting rays. Applicable 3d types could implement a `Raycast` trait to extend their functionality. ```rust -let ray = Ray::X; -let sphere = SphereCollider::new{Sphere{1.0}, Point::x(5.0)); +let ray = Ray3d::X; +let sphere = SphereCollider::new{Sphere{1.0}, Point3d::x(5.0)); let intersection = sphere.raycast(ray); ``` @@ -477,12 +501,12 @@ let intersection = sphere.raycast(ray); The notes for `Direction` mention it is gauranteed to be normalized through its getter and setter. There are a few ways to do this, but I'd like to propose a zero-cost implementation using the typestate pattern. To absolutely minimize the use of `normalize` on the contained `Vec3`, we can memoize the result _only when accessed_. We don't want to make `Direction` an enum, as that will add a discriminant, and we don't want to have normalized and unnormalized types for of all our primitives. So instead, we could use the typestate pattern to do something like: ```rust -struct Plane { - point: Point, - normal: Direction, +struct Plane3d { + point: Point3d, + normal: Direction3d, } -struct Direction { +struct Direction3d { direction: impl Normalizable, } @@ -494,7 +518,7 @@ impl Normalizable for NormalizedDir {} ``` -When a `Direction` is mutated or built, the direction will be an `UncheckedDir`. Once it is accessed, the `Directions`s getter method will normalize the direction, and swap it out with a `NormalizedDir`. Now the normalized direction is memoized in the `Direction` without increasing the size of the type. This complexity can be completely hidden to users. +When a `Direction` is mutated or built, the direction will be an `UncheckedDir`. Once it is accessed, the `Directions`s getter method will normalize the direction, and swap it out with a `NormalizedDir`. Now the normalized direction is memoized in the `Direction3d` without increasing the size of the type. This complexity can be completely hidden to users. ## Drawbacks From 2ff59789d7e6b1bb2b1b9137b6b85dbab7fa3482 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:17:31 +0200 Subject: [PATCH 36/50] Rename Box2d to Rectangle --- rfcs/12-primitive-shapes.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 22bfd19e..70f549b0 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -417,28 +417,28 @@ impl Collider2d for CircleCollider {} // Box Types -struct Box2d { +struct Rectangle { half_extents: Vec2, } -impl Meshable for Box2d +impl Meshable for Rectangle /* REFERENCE ONLY struct BoundingBox2d { - box: Box2d, + box: Rectangle, translation: Vec2, } impl Meshable for BoundingBox2d {} impl Bounding2d for BoundingBox2d {} type Aabb2d = BoundingBox2d; -struct BoxCollider2d { - box: Box2d, +struct RectangleCollider { + box: Rectangle, translation: Vec2, rotation: Mat2, } -impl Meshable for BoxCollider2d {} -impl Collider2d for BoxCollider2d {} -type Obb2d = BoxCollider2d; +impl Meshable for RectangleCollider {} +impl Collider2d for RectangleCollider {} +type Obb2d = RectangleCollider; */ // Capsule Types From e88a42a43c94ce7daa289d69a045019ba29b12ed Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Tue, 26 Oct 2021 20:17:47 +0200 Subject: [PATCH 37/50] Removing unresolved question --- rfcs/12-primitive-shapes.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 70f549b0..bbc75fbd 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -551,10 +551,6 @@ Prior art was used to select the most common types of shape primitive, naming co Many game engine docs appear to have oddly-named and disconnected shape primitive types that are completely unrelated. This RFC aims to ensure Bevy doesn't go down this path, and instead derives functionality from common types to take advantage of the composability of components in the ECS. -## Unresolved questions - -What is the best naming scheme, e.g., `2d::Line`/`3d::Line` vs. `Line2d`/`Line3d` vs. `Line2d`/`Line`? - ### Out of Scope * Value types, e.g. float vs. fixed is out of scope. This RFC is focused on the core geometry types and is intended to use Bevy's common rendering value types such as `f32`. From 2a3375608ce2ff80f14ab83b88e3231aa58f0cdc Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 09:33:37 +0100 Subject: [PATCH 38/50] Fix typo --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index bbc75fbd..dfdb839e 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -92,7 +92,7 @@ This RFC provides independent 2d and 3d primitives. Recall that the purpose of t | Cone | - | Cone | | Wedge | - | Wedge | | Torus | - | Torus | -| Frustrum | - | Frustrum | +| Frustum | - | Frustum | | RegularPolygon | RegularPolygon | - | | Polygon | Polygon | - | | Rectangle | Rectangle | - | From 5222ee869855b2bb32e6beec5216718173bc2e86 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 09:35:01 +0100 Subject: [PATCH 39/50] Moving 2d shapes ontop --- rfcs/12-primitive-shapes.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index dfdb839e..91021fc7 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -76,6 +76,10 @@ This RFC provides independent 2d and 3d primitives. Recall that the purpose of t | Shape | 2D | 3D | |-----------------|-----------------|---------------| +| Rectangle | Rectangle | - | +| Circle | Circle | - | +| Polygon | Polygon | - | +| RegularPolygon | RegularPolygon | - | | Point | Point2d | Point3d | | Plane | Plane2d | Plane3d | | Direction | Direction2d | Direction3d | @@ -93,11 +97,6 @@ This RFC provides independent 2d and 3d primitives. Recall that the purpose of t | Wedge | - | Wedge | | Torus | - | Torus | | Frustum | - | Frustum | -| RegularPolygon | RegularPolygon | - | -| Polygon | Polygon | - | -| Rectangle | Rectangle | - | -| Circle | Circle | - | - ## Implementation strategy From fbcc8da152e2fd795fe985972090f8aa33f378a3 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 09:37:07 +0100 Subject: [PATCH 40/50] Moving 2d shapes before 3d shapes --- rfcs/12-primitive-shapes.md | 246 ++++++++++++++++++------------------ 1 file changed, 123 insertions(+), 123 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 91021fc7..185bb9bd 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -135,6 +135,129 @@ trait Collider2d { fn collide(&self, other: &impl Collider2d) -> Option(Collision2d); fn within(&self, bounds: &impl Bounding2d) -> bool; } +``` +### 2D Geometry Types + +These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. These types are suffixed with "2d" to disambiguate from the 3d types in user code, guide users to using 3d types by default, and remove the need for name-spacing the 2d and 3d types when used in the same scope. + +```rust +struct Point2d(Vec2) + +struct Direction2d(Vec2) +impl Meshable for Direction2d {} + +struct Ray2d { + point: Point2d, + direction: Direction2d +} +impl Meshable for Ray2d {} + +struct Line2d { + point: Point2d, + direction: Direction2d, +} +impl Meshable for Line2d {} + +struct LineSegment2d { + start: Point2d, + end: Point2d, +} +impl Meshable for LineSegment2d {} + +struct PolyLine2d{ + points: [Point2d; N], +} +impl Meshable for PolyLine2d {} + +struct Triangle2d([Point2d; 3]); +impl Meshable for Triangle2d {} + +struct Quad2d([Point2d; 4]); +impl Meshable for Quad2d {} + +/// A regular polygon, such as a square or hexagon. +struct RegularPolygon2d { + /// The circumcircle that all points of the regular polygon lie on. + circumcircle: Circle2d, + /// Number of faces. + faces: u8, + /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. + orientation: Angle, +} +impl Meshable for RegularPolygon2d {} + +struct Polygon2d { + points: [Point2d; N], +} +impl Meshable for Polygon2d {} + +/// Circle types + +struct Circle { + radius: f32, +} +impl Meshable for Circle {} + +/* REFERENCE ONLY +struct BoundingCircle2d { + circle: Circle, + translation: Vec2, +} +impl Meshable for BoundingCircle2d {} +impl Bounding2d for BoundingCircle2d {} + +struct CircleCollider { + sphere: Circle, + translation: Vec2, +} +impl Meshable for CircleCollider {} +impl Collider2d for CircleCollider {} +*/ + +// Box Types + +struct Rectangle { + half_extents: Vec2, +} +impl Meshable for Rectangle + +/* REFERENCE ONLY +struct BoundingBox2d { + box: Rectangle, + translation: Vec2, +} +impl Meshable for BoundingBox2d {} +impl Bounding2d for BoundingBox2d {} +type Aabb2d = BoundingBox2d; + +struct RectangleCollider { + box: Rectangle, + translation: Vec2, + rotation: Mat2, +} +impl Meshable for RectangleCollider {} +impl Collider2d for RectangleCollider {} +type Obb2d = RectangleCollider; +*/ + +// Capsule Types + +struct Capsule2d { + height: f32, // Height of the rectangular section + radius: f32, // End cap radius +} +impl Meshable for Capsule2d {} + +/* REFERENCE ONLY +struct CapsuleCollider2d { + capsule: Capsule2d, + translation: Vec2, + rotation: Mat2, +} +impl Meshable for CapsuleCollider2d {} +impl Collider2d for CapsuleCollider2d {} +*/ + ``` ### 3D Geometry Types @@ -336,129 +459,6 @@ impl Meshable for Frustum {} ``` -### 2D Geometry Types - -These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. These types are suffixed with "2d" to disambiguate from the 3d types in user code, guide users to using 3d types by default, and remove the need for name-spacing the 2d and 3d types when used in the same scope. - -```rust -struct Point2d(Vec2) - -struct Direction2d(Vec2) -impl Meshable for Direction2d {} - -struct Ray2d { - point: Point2d, - direction: Direction2d -} -impl Meshable for Ray2d {} - -struct Line2d { - point: Point2d, - direction: Direction2d, -} -impl Meshable for Line2d {} - -struct LineSegment2d { - start: Point2d, - end: Point2d, -} -impl Meshable for LineSegment2d {} - -struct PolyLine2d{ - points: [Point2d; N], -} -impl Meshable for PolyLine2d {} - -struct Triangle2d([Point2d; 3]); -impl Meshable for Triangle2d {} - -struct Quad2d([Point2d; 4]); -impl Meshable for Quad2d {} - -/// A regular polygon, such as a square or hexagon. -struct RegularPolygon2d { - /// The circumcircle that all points of the regular polygon lie on. - circumcircle: Circle2d, - /// Number of faces. - faces: u8, - /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. - orientation: Angle, -} -impl Meshable for RegularPolygon2d {} - -struct Polygon2d { - points: [Point2d; N], -} -impl Meshable for Polygon2d {} - -/// Circle types - -struct Circle { - radius: f32, -} -impl Meshable for Circle {} - -/* REFERENCE ONLY -struct BoundingCircle2d { - circle: Circle, - translation: Vec2, -} -impl Meshable for BoundingCircle2d {} -impl Bounding2d for BoundingCircle2d {} - -struct CircleCollider { - sphere: Circle, - translation: Vec2, -} -impl Meshable for CircleCollider {} -impl Collider2d for CircleCollider {} -*/ - -// Box Types - -struct Rectangle { - half_extents: Vec2, -} -impl Meshable for Rectangle - -/* REFERENCE ONLY -struct BoundingBox2d { - box: Rectangle, - translation: Vec2, -} -impl Meshable for BoundingBox2d {} -impl Bounding2d for BoundingBox2d {} -type Aabb2d = BoundingBox2d; - -struct RectangleCollider { - box: Rectangle, - translation: Vec2, - rotation: Mat2, -} -impl Meshable for RectangleCollider {} -impl Collider2d for RectangleCollider {} -type Obb2d = RectangleCollider; -*/ - -// Capsule Types - -struct Capsule2d { - height: f32, // Height of the rectangular section - radius: f32, // End cap radius -} -impl Meshable for Capsule2d {} - -/* REFERENCE ONLY -struct CapsuleCollider2d { - capsule: Capsule2d, - translation: Vec2, - rotation: Mat2, -} -impl Meshable for CapsuleCollider2d {} -impl Collider2d for CapsuleCollider2d {} -*/ - -``` ### Bounding and Collision From bafe5a5240b03033c3a4f6187b0360170c21a627 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 11:03:27 +0100 Subject: [PATCH 41/50] Adding descriptions to table --- rfcs/12-primitive-shapes.md | 94 ++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 43 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 185bb9bd..4041029a 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -23,15 +23,15 @@ pub struct Circle { Note that a `Circle` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the the information (Translation and Rotation) that are needed to fully define the shapes for these cases: | Shape | Mesh | Bounding | Collision | -|--- |---|---|---| -| Sphere | ✔ | ✔ + Trans | ✔ + Trans | +|-----------|----|------------------|---| +| Sphere | ✔ | ✔ + Trans | ✔ + Trans | | Box | ✔ | ✔ + Trans (AABB) | ✔ + Trans + Rot (OBB) | -| Capsule | ✔ | ❌ | ✔ + Trans + Rot | -| Cylinder | ✔ | ❌ | ✔ + Trans + Rot | -| Cone | ✔ | ❌ | ✔ + Trans + Rot | -| Wedge | ✔ | ❌ | ✔ + Trans + Rot | -| Plane | ✔ | ❌ | ✔ | -| Torus | ✔ | ❌ | ❌ | +| Capsule | ✔ | ❌ | ✔ + Trans + Rot | +| Cylinder | ✔ | ❌ | ✔ + Trans + Rot | +| Cone | ✔ | ❌ | ✔ + Trans + Rot | +| Wedge | ✔ | ❌ | ✔ + Trans + Rot | +| Plane | ✔ | ❌ | ✔ | +| Torus | ✔ | ❌ | ❌ | ### Bounding vs. Collision @@ -70,33 +70,33 @@ For use in spatial queries, broad phase collision detection, and raycasting, hyp Colliders can provide intersection checks against other colliders, as well as check if they are within a bounding volume. -### 3D and 2D +### 2D and 3D This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line2d` and `Line3d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. -| Shape | 2D | 3D | -|-----------------|-----------------|---------------| -| Rectangle | Rectangle | - | -| Circle | Circle | - | -| Polygon | Polygon | - | -| RegularPolygon | RegularPolygon | - | -| Point | Point2d | Point3d | -| Plane | Plane2d | Plane3d | -| Direction | Direction2d | Direction3d | -| Ray | Ray2d | Ray3d | -| Line | Line2d | Line3d | -| LineSegment | LineSegment2d | LineSegment3d | -| Polyline | Polyline2d | Polyline3d | -| Triangle | Triangle2d | Triangle3d | -| Quad | Quad2d | Quad3d | -| Sphere | - | Sphere | -| Box | - | Box | -| Cylinder | - | Cylinder | -| Capsule | - | Capsule | -| Cone | - | Cone | -| Wedge | - | Wedge | -| Torus | - | Torus | -| Frustum | - | Frustum | +| Shape | 2D | 3D | Description | +|-----------------|-----------------|---------------|------------------------------------------------------------------------------------------| +| Rectangle | Rectangle | - | A rectangle defined by its width and height | +| Circle | Circle | - | A circle defined by its radius | +| Polygon | Polygon | - | A closed shape in a plane defined by a finite number of line-segments between vertices | +| RegularPolygon | RegularPolygon | - | A polygon where all vertices lies on the circumscribed circle, equally far apart | +| Point | Point2d | Point3d | A single point in space | +| Plane | Plane2d | Plane3d | An unbounded plane defined by a position and normal | +| Direction | Direction2d | Direction3d | A normalized vector pointing in a direction | +| Ray | Ray2d | Ray3d | An infinite half-line pointing in a direction | +| Line | Line2d | Line3d | An infinite line | +| LineSegment | LineSegment2d | LineSegment3d | A finite line between two vertices | +| Polyline | Polyline2d | Polyline3d | A line drawn along a path of vertices | +| Triangle | Triangle2d | Triangle3d | A polygon with 3 vertices | +| Quad | Quad2d | Quad3d | A polygon with 4 vertices | +| Sphere | - | Sphere | A sphere defined by its radius | +| Box | - | Box | A cuboid with six quadrilateral faces, defined by height, width, depth | +| Cylinder | - | Cylinder | A cylinder with its origin at the center of the volume | +| Capsule | - | Capsule | A capsule with its origin at the center of the volume | +| Cone | - | Cone | A cone with the origin located at the center of the circular base | +| Wedge | - | Wedge | A ramp with the origin centered on the width, and coincident with the rear vertical wall | +| Torus | - | Torus | A torous | +| Frustum | - | Frustum | The portion of a pyramid that lies between two parallel planes | ## Implementation strategy @@ -138,7 +138,7 @@ trait Collider2d { ``` ### 2D Geometry Types -These types only exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. These types are suffixed with "2d" to disambiguate from the 3d types in user code, guide users to using 3d types by default, and remove the need for name-spacing the 2d and 3d types when used in the same scope. +These types exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. Types that are present in both 2d and 3d space are suffixed with "2d" to disambiguate from the 3d types in user code. Types that only exist in 2d space have no "2d" suffix. ```rust struct Point2d(Vec2) @@ -158,12 +158,14 @@ struct Line2d { } impl Meshable for Line2d {} +/// A finite line between two vertices struct LineSegment2d { start: Point2d, end: Point2d, } impl Meshable for LineSegment2d {} +/// struct PolyLine2d{ points: [Point2d; N], } @@ -175,7 +177,14 @@ impl Meshable for Triangle2d {} struct Quad2d([Point2d; 4]); impl Meshable for Quad2d {} -/// A regular polygon, such as a square or hexagon. +/// A closed shape in a plane defined by a finite number of line-segments between vertices +struct Polygon2d { + points: [Point2d; N], +} +impl Meshable for Polygon2d {} + +/// A polygon where all vertices lies on the circumscribed circle, equally far apart +/// Example: Square, Triangle, Hexagon struct RegularPolygon2d { /// The circumcircle that all points of the regular polygon lie on. circumcircle: Circle2d, @@ -185,14 +194,11 @@ struct RegularPolygon2d { orientation: Angle, } impl Meshable for RegularPolygon2d {} - -struct Polygon2d { - points: [Point2d; N], -} -impl Meshable for Polygon2d {} -/// Circle types +// Circle types + +/// A circle defined by its radius struct Circle { radius: f32, } @@ -216,6 +222,7 @@ impl Collider2d for CircleCollider {} // Box Types +/// A rectangle defined by its width and height struct Rectangle { half_extents: Vec2, } @@ -262,6 +269,8 @@ impl Collider2d for CapsuleCollider2d {} ### 3D Geometry Types +These types exist in 3d space: their dimensions and location are defined in `x`, `y` and `z`. Types that are present in both 2d and 3d space are suffixed with "3d" to disambiguate from the 2d types in user code. Types that only exist in 3d space have no "3d" suffix. + ```rust struct Point3d(Vec3) @@ -312,7 +321,7 @@ struct Quad3d([Point3d; 4]); impl Meshable for Quad3d {} impl Collider for Quad3d {} -/// Sphere types +// Sphere types struct Sphere { radius: f32, @@ -446,7 +455,7 @@ struct Torus { } impl Meshable for Torus {} -// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. +/// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. struct Frustum { near: Plane3d, far: Plane3d, @@ -459,7 +468,6 @@ impl Meshable for Frustum {} ``` - ### Bounding and Collision Primitives colliders and bounding volumes are fully defined in space (in their hypothetical implementation here), and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. From 3494f3a978f4bf2941d0ced8f927412e101afa2e Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 11:12:29 +0100 Subject: [PATCH 42/50] Collecting and rewording 2d / 3d section --- rfcs/12-primitive-shapes.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 4041029a..63d6211d 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -72,7 +72,12 @@ Colliders can provide intersection checks against other colliders, as well as ch ### 2D and 3D -This RFC provides independent 2d and 3d primitives. Recall that the purpose of this is to provide lightweight types, so there are what appear to be duplicates in 2d and 3d, such as `Line2d` and `Line3d`. Note that the 2d version of a line is smaller than its 3d counterpart because it is only defined in 2d. +This RFC provides independent 2d and 3d primitives. Types that are present in both 2d and 3d space are suffixed with "2d" or "3d" to disambiguate them such as `Line2d` and `Line3d`. Types that only exist in either space have no suffix. + +* 2D shapes have their dimensions defined in `x` and `y`. +* 3D shapes have their dimensions defined in `x`, `y` and `z` + +The complete overview of shapes, their dimensions and their names can be seen in this table: | Shape | 2D | 3D | Description | |-----------------|-----------------|---------------|------------------------------------------------------------------------------------------| @@ -138,8 +143,6 @@ trait Collider2d { ``` ### 2D Geometry Types -These types exist in 2d space: their dimensions and location are only defined in `x` and `y` unlike their 3d counterparts. Types that are present in both 2d and 3d space are suffixed with "2d" to disambiguate from the 3d types in user code. Types that only exist in 2d space have no "2d" suffix. - ```rust struct Point2d(Vec2) @@ -269,8 +272,6 @@ impl Collider2d for CapsuleCollider2d {} ### 3D Geometry Types -These types exist in 3d space: their dimensions and location are defined in `x`, `y` and `z`. Types that are present in both 2d and 3d space are suffixed with "3d" to disambiguate from the 2d types in user code. Types that only exist in 3d space have no "3d" suffix. - ```rust struct Point3d(Vec3) From 2ac63588241c842e875b692bf28f98e3c1640687 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sat, 6 Nov 2021 20:02:49 +0100 Subject: [PATCH 43/50] Update rfcs/12-primitive-shapes.md Co-authored-by: Alice Cecile --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 63d6211d..4e5788e2 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -100,7 +100,7 @@ The complete overview of shapes, their dimensions and their names can be seen in | Capsule | - | Capsule | A capsule with its origin at the center of the volume | | Cone | - | Cone | A cone with the origin located at the center of the circular base | | Wedge | - | Wedge | A ramp with the origin centered on the width, and coincident with the rear vertical wall | -| Torus | - | Torus | A torous | +| Torus | - | Torus | A torus, shaped like a donut | | Frustum | - | Frustum | The portion of a pyramid that lies between two parallel planes | ## Implementation strategy From 5ec59ee7ff080d9fed59b85d920c2ee6dcbda15b Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:22:47 +0100 Subject: [PATCH 44/50] 2d before 3d consistency --- rfcs/12-primitive-shapes.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 4e5788e2..afba3f65 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -100,7 +100,7 @@ The complete overview of shapes, their dimensions and their names can be seen in | Capsule | - | Capsule | A capsule with its origin at the center of the volume | | Cone | - | Cone | A cone with the origin located at the center of the circular base | | Wedge | - | Wedge | A ramp with the origin centered on the width, and coincident with the rear vertical wall | -| Torus | - | Torus | A torus, shaped like a donut | +| Torus | - | Torus | A torus, shaped like a donut | | Frustum | - | Frustum | The portion of a pyramid that lies between two parallel planes | ## Implementation strategy @@ -121,25 +121,25 @@ trait Meshable{ fn mesh(&self) -> Mesh; }; -trait Bounding3d { - fn within(&self, other: &impl Bounding3d) -> bool; - fn contains(&self, collider: &impl Collider3d) -> bool; -} - trait Bounding2d { fn within(&self, other: &impl Bounding2d) -> bool; fn contains(&self, collider: &impl Collider2d) -> bool; } -trait Collider3d { - fn collide(&self, other: &impl Collider3d) -> Option(Collision3d); - fn within(&self, bounds: &impl Bounding3d) -> bool; +trait Bounding3d { + fn within(&self, other: &impl Bounding3d) -> bool; + fn contains(&self, collider: &impl Collider3d) -> bool; } trait Collider2d { fn collide(&self, other: &impl Collider2d) -> Option(Collision2d); fn within(&self, bounds: &impl Bounding2d) -> bool; } + +trait Collider3d { + fn collide(&self, other: &impl Collider3d) -> Option(Collision3d); + fn within(&self, bounds: &impl Bounding3d) -> bool; +} ``` ### 2D Geometry Types @@ -168,7 +168,7 @@ struct LineSegment2d { } impl Meshable for LineSegment2d {} -/// +/// A line drawn along a path of vertices struct PolyLine2d{ points: [Point2d; N], } From 46ae46cf606744cea0cdae7677718e4540a2da67 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Mon, 13 Dec 2021 19:31:55 +0100 Subject: [PATCH 45/50] Removing point as a primitive type --- rfcs/12-primitive-shapes.md | 41 ++++++++++++++++--------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index afba3f65..c4d078df 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -85,7 +85,6 @@ The complete overview of shapes, their dimensions and their names can be seen in | Circle | Circle | - | A circle defined by its radius | | Polygon | Polygon | - | A closed shape in a plane defined by a finite number of line-segments between vertices | | RegularPolygon | RegularPolygon | - | A polygon where all vertices lies on the circumscribed circle, equally far apart | -| Point | Point2d | Point3d | A single point in space | | Plane | Plane2d | Plane3d | An unbounded plane defined by a position and normal | | Direction | Direction2d | Direction3d | A normalized vector pointing in a direction | | Ray | Ray2d | Ray3d | An infinite half-line pointing in a direction | @@ -144,45 +143,43 @@ trait Collider3d { ### 2D Geometry Types ```rust -struct Point2d(Vec2) - struct Direction2d(Vec2) impl Meshable for Direction2d {} struct Ray2d { - point: Point2d, + point: Vec2, direction: Direction2d } impl Meshable for Ray2d {} struct Line2d { - point: Point2d, + point: Vec2, direction: Direction2d, } impl Meshable for Line2d {} /// A finite line between two vertices struct LineSegment2d { - start: Point2d, - end: Point2d, + start: Vec2, + end: Vec2, } impl Meshable for LineSegment2d {} /// A line drawn along a path of vertices struct PolyLine2d{ - points: [Point2d; N], + points: [Vec2; N], } impl Meshable for PolyLine2d {} -struct Triangle2d([Point2d; 3]); +struct Triangle2d([Vec2; 3]); impl Meshable for Triangle2d {} -struct Quad2d([Point2d; 4]); +struct Quad2d([Vec2; 4]); impl Meshable for Quad2d {} /// A closed shape in a plane defined by a finite number of line-segments between vertices struct Polygon2d { - points: [Point2d; N], + points: [Vec2; N], } impl Meshable for Polygon2d {} @@ -273,21 +270,19 @@ impl Collider2d for CapsuleCollider2d {} ### 3D Geometry Types ```rust -struct Point3d(Vec3) - /// Vector direction in 3D space that is guaranteed to be normalized through its getter/setter. struct Direction3d(Vec3) impl Meshable for Direction3d {} struct Plane3d { - point: Point3d, + point: Vec3, normal: Direction3d, } impl Meshable for Plane3d {} /// Differentiates a line from a ray, where a line is infinite and a ray is directional half-line, although their underlying representation is the same. struct Ray3d( - point: Point3d, + point: Vec3, direction: Direction3d, ); impl Meshable for Ray3d {} @@ -296,29 +291,29 @@ impl Meshable for Ray3d {} /// Unbounded line in 3D space with directionality struct Line3d { - point: Point3d, + point: Vec3, direction: Direction3d, } impl Meshable for Line3d {} /// A line segment bounded by two points struct LineSegment3d { - start: Point3d, - end: Point3d, + start: Vec3, + end: Vec3, } impl Meshable for LineSegment3d {} /// A line drawn along a path of points struct PolyLine3d { - points: Vec, + points: Vec, } impl Meshable for PolyLine3d {} -struct Triangle3d([Point3d; 3]); +struct Triangle3d([Vec3; 3]); impl Meshable for Triangle3d {} impl Collider for Triangle3d {} -struct Quad3d([Point3d; 4]); +struct Quad3d([Vec3; 4]); impl Meshable for Quad3d {} impl Collider for Quad3d {} @@ -500,7 +495,7 @@ The bounding volumes sections of this RFC cover how these types could be used fo ```rust let ray = Ray3d::X; -let sphere = SphereCollider::new{Sphere{1.0}, Point3d::x(5.0)); +let sphere = SphereCollider::new{Sphere{1.0}, Vec3::x(5.0)); let intersection = sphere.raycast(ray); ``` @@ -510,7 +505,7 @@ The notes for `Direction` mention it is gauranteed to be normalized through its ```rust struct Plane3d { - point: Point3d, + point: Vec3, normal: Direction3d, } From 7a5e2c14439b9d6ecaed7c163ef51d255043e690 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sun, 23 Jan 2022 11:00:55 +0100 Subject: [PATCH 46/50] Remove angle as a component --- rfcs/12-primitive-shapes.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index afba3f65..93c107b3 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -105,13 +105,6 @@ The complete overview of shapes, their dimensions and their names can be seen in ## Implementation strategy -### Helper Types - -```rust -/// Stores an angle in radians, and supplies builder functions to prevent errors (from_radians, from_degrees) -struct Angle(f32); -``` - ### Traits (REFERENCE ONLY!) **These traits are provided as reference to illustrate how these primitive shape types might be used. The details of implementation and interface should be determined in a separate RFC, PR, or independent prototypes.** @@ -194,7 +187,7 @@ struct RegularPolygon2d { /// Number of faces. faces: u8, /// Clockwise rotation of the polygon about the origin. At zero rotation, a point will always be located at the 12 o'clock position. - orientation: Angle, + orientation: f32, } impl Meshable for RegularPolygon2d {} From ca533b1157955b87663cfe81778b4e057119abd5 Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sun, 23 Jan 2022 13:03:25 +0100 Subject: [PATCH 47/50] merging 'bounding and collision' with 'where are the transforms' --- rfcs/12-primitive-shapes.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index afba3f65..bcb163ef 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -43,13 +43,16 @@ The difference between the two is somewhat semantic. Both bounding and collision In this potential bounding and collision interface, translation and rotation are **not** defined using bevy's `Transform` components, nor are they stored in the base primitive shape types. This is because types that implement `Bounding` and `Collider` must be fully self-contained. This: -* makes the API simpler when using these components in functions and systems -* ensures bounding and collision types use an absolute minimum of memory -* prevents errors caused by nonuniform scale invalidating the shape of the primitive. +* Makes the API simpler when using these components in functions and systems +* Ensures bounding and collision types use an absolute minimum of memory +* Prevents errors caused by nonuniform scale invalidating the shape of the primitive Instead, changes to the parent's `GlobalTransform` should be used to derive a new primitive on scale changes - as well as a new translation and rotation if required. -Although bounding and collision are out of scope of this RFC, this helps to explain why primitive shape types contain no positional information. This hypothetical bounding/collision interface shows how the base primitive shapes can be composed to build common game engine functinality without adding memory overhead. +Note: We could use transforms in the future _if_ the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` where applicable. + +This hypothetical bounding/collision interface shows how the base primitive shapes can be composed to build common game engine functinality without adding memory overhead and highlight the incompatibility of shape primitives with the `Transform` component. + ### Meshing @@ -469,12 +472,6 @@ impl Meshable for Frustum {} ``` -### Bounding and Collision - -Primitives colliders and bounding volumes are fully defined in space (in their hypothetical implementation here), and do not use `Transform` or `GlobalTransform`. This is an intentional decision. Because transforms can include nonuniform scale, they are fundamentally incompatible with shape primitives. We could use transforms in the future if the `translation`, `rotation`, and `scale` fields were distinct components, and shape primitives could be bundled with a `translation` or `rotation` if applicable. - -The provided reference implementaiton of bounding and collision types demonstrate how primitive shape types can be composed with other types, triats, or components to build added functionality from shared parts. While the collder and bounding primitives provided as reference are out of scope, they also highlight the incompatibility of shape primitives with the `Transform` component. - ### Frustum Culling The provided `Frustum` type is useful for frustum culling, which is generally done on the CPU by comparing each frustum plane with each entity's bounding volume. From 7981ae8c03820a66ec77a0a421cede509f59ea9d Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Sun, 23 Jan 2022 12:49:23 -0800 Subject: [PATCH 48/50] Update rfcs/12-primitive-shapes.md Co-authored-by: Alice Cecile --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index 5efd18ec..cfff1cfd 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -491,7 +491,7 @@ let intersection = sphere.raycast(ray); ### Direction Normalization -The notes for `Direction` mention it is gauranteed to be normalized through its getter and setter. There are a few ways to do this, but I'd like to propose a zero-cost implementation using the typestate pattern. To absolutely minimize the use of `normalize` on the contained `Vec3`, we can memoize the result _only when accessed_. We don't want to make `Direction` an enum, as that will add a discriminant, and we don't want to have normalized and unnormalized types for of all our primitives. So instead, we could use the typestate pattern to do something like: +The notes for `Direction` mention it is guaranteed to be normalized through its getter and setter. There are a few ways to do this, but I'd like to propose a zero-cost implementation using the typestate pattern. To absolutely minimize the use of `normalize` on the contained `Vec3`, we can memoize the result _only when accessed_. We don't want to make `Direction` an enum, as that will add a discriminant, and we don't want to have normalized and unnormalized types for of all our primitives. So instead, we could use the typestate pattern to do something like: ```rust struct Plane3d { From f632b2ba247bf972eb97d7a0abfe824381cbb31a Mon Sep 17 00:00:00 2001 From: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Date: Sun, 24 Apr 2022 23:05:58 +0200 Subject: [PATCH 49/50] Removing torus and placing non-convex in future work --- rfcs/12-primitive-shapes.md | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index cfff1cfd..a4aa4bd6 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -31,7 +31,6 @@ Note that a `Circle` does not contain any information about the position of the | Cone | ✔ | ❌ | ✔ + Trans + Rot | | Wedge | ✔ | ❌ | ✔ + Trans + Rot | | Plane | ✔ | ❌ | ✔ | -| Torus | ✔ | ❌ | ❌ | ### Bounding vs. Collision @@ -102,7 +101,6 @@ The complete overview of shapes, their dimensions and their names can be seen in | Capsule | - | Capsule | A capsule with its origin at the center of the volume | | Cone | - | Cone | A cone with the origin located at the center of the circular base | | Wedge | - | Wedge | A ramp with the origin centered on the width, and coincident with the rear vertical wall | -| Torus | - | Torus | A torus, shaped like a donut | | Frustum | - | Frustum | The portion of a pyramid that lies between two parallel planes | ## Implementation strategy @@ -441,12 +439,6 @@ impl Collider for WedgeCollider {} // Other Types -struct Torus { - major_radius: f32, - tube_radius: f32, -} -impl Meshable for Torus {} - /// A 3d frustum used to represent the volume rendered by a camera, defined by the 6 planes that set the frustum limits. struct Frustum { near: Plane3d, @@ -559,3 +551,4 @@ Many game engine docs appear to have oddly-named and disconnected shape primitiv * Physics * SDF rendering * Immediate mode debug Rendering (just call `.mesh()`!) +* Non-convex primitive types From 03eaf5fe0ed2eae056cd27f8a7f598754ddda4c9 Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Fri, 26 Aug 2022 16:28:28 -0700 Subject: [PATCH 50/50] Update rfcs/12-primitive-shapes.md Co-authored-by: TimJentzsch --- rfcs/12-primitive-shapes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/12-primitive-shapes.md b/rfcs/12-primitive-shapes.md index a4aa4bd6..4dfd2abe 100644 --- a/rfcs/12-primitive-shapes.md +++ b/rfcs/12-primitive-shapes.md @@ -20,7 +20,7 @@ pub struct Circle { } ``` -Note that a `Circle` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the the information (Translation and Rotation) that are needed to fully define the shapes for these cases: +Note that a `Circle` does not contain any information about the position of the circle. This is due to how shapes are composed to add more complex functionality. Consider the following common use cases of primitives shapes, as well as the information (Translation and Rotation) that are needed to fully define the shapes for these cases: | Shape | Mesh | Bounding | Collision | |-----------|----|------------------|---|