jcornaz / heron Goto Github PK
View Code? Open in Web Editor NEW[DISCONTINUED] An ergonomic physics API for bevy games
License: MIT License
[DISCONTINUED] An ergonomic physics API for bevy games
License: MIT License
By easy to set or well documented exposure of i.e. mass_properties or external forces (at point) the user can do full fledged flight/boat simulation from the get go.
As a User, I would like to use cuboid collision shape, so that I can define a good collision shape of terrain, platforms, and other rectangular objects.
As a User, I would like to define a "Capsule" collision shape, so that I can define an appropriate collision shape for my sprite (character, bullet, etc.)
There should be a BodyType
enum component, that allow to create different type of bodies: dynamic, static, kinematic and senor.
It should be an enum to make impossible to have an conflicting definition in the same entity.
If missing, it should default to dynamic if there is a velocity, or static if there is no velocity.
Right now, the only way to control kinematic bodies is by manually changing their transform. However, collisions are not taken into account by default, delegating that logic to the final user. This would be really useful to have even in basic games, such as platformers, where the player needs to collide with the floor.
For instance, Godot includes some high-level functions to handle this behavior, as explained in its documentation: Movement and collision and Detecting collisions.
This was discussed a while ago in the Bevy's discord physics channel, where Rapier's developer said:
Almost no physics engines compute contacts between two non-dynamic bodies (however kinematic bodies do affect dynamic bodies). So you are supposed to use shape-casting to detect collision with the static environment.
The godot "move_and_collide" and "move_and_slide" are just higher-level operations which are based on shape-casting.
Knowing the mass is necessary to calculate an acceleration from a force or a delta-v from an impulse.
As a User, I would like to define a Sensor that is not affected by forces nor affect any other bodies, but trigger collision events, so that I can setup detection area to trigger logic (ex. The player reached the end of the level)
A sensor is NOT an actual physics body. But that doesn't mean we don't want to move or rotate it. There are plenty of use-case where we may want to move some sensor without having to make it a "dynamic" body. It would be much easier if I could simply use the velocity component for them.
Thinking about it, the Velocity
could work for any entity. And the behavior would be:
next_position
at each steap and let rapier continue the simulation (not mutating the velocity)Body
component: Update the Transform
each frame at the desired velocity.Since #88 the step
system copy the IntegrationParameters
each time. Which sounds wasteful to me. Even though, I admit, haven't measured the perfs...
Instead there could be a private resource struct PhysicsStepPerSecond(f32)
that would remember how many steps per second are run. Then a system can update IntegrationParameters
based on the data in PhysicsStepPerSecond
and PhysicsTime
.
Hi! First of all, excellent work in this crate :)
I'm trying to compile with the provided readme version dependencies over a empty project:
bevy = "^0.5.0"
heron = { version = "0.8.0", features = ["3d"] }
And I get the following error coming from the heron_debug
crate:
error[E0433]: failed to resolve: could not find `rapier` in `heron_rapier`
--> /home/luis/.cargo/registry/src/github.com-1ecc6299db9ec823/heron_debug-0.8.0/src/dim2.rs:10:19
|
10 | use heron_rapier::rapier::geometry::{ColliderHandle, ColliderSet, Shape};
| ^^^^^^ could not find `rapier` in `heron_rapier`
I have this error only when I compile it from another project. Using cargo build --features 2d
compiles without problems.
Thanks in advance
It'd be nice to be able to easily raycast from a system.
fn ray_cast_system(world: Res<PhysicsWorld>) {
match world.ray_cast(Vec3::default(), Vec3::unit_x()) {
Some(entity) => { println!("raycast found: {:?}", entity) }
None => { println!("raycast found nothing") }
}
}
Add an Body::TriMesh enum variant that will allow creation of collider meshes based on bevy verteex position and indices data.
Also consider providing an easy interface of constructing those collider meshes from bevy's mesh handles
As a User, I want an easy way to query for components of the entities involved in a collision, so that it is easier to read/update components as part of the reaction to the event.
Here is how usage could look like:
fn damage_system(
mut event_reader: Local<EventReader<CollisionEvent>>,
events: Res<Events<CollisionEvent>>,
mut damageables: Query<&mut Hp>,
bullets: Query<Bullet>,
) {
event_reader.iter(events)
.flat_map(|event| event.query(&mut damageables, &bullets))
.for_each(|(mut hp, bullet)| {
hp.take_dmg(bullet.dmg());
});
}
Pretty self explanatory. I think it would be nice if you could also update to the latest version of rapier while you're at it, if its possible.
Here are some proposed convenience shapes for 2d that mostly don't exist directly in Rapier but are easy to implement. The argument to have them is because when dealing with 2d, people think in 2d terms. It's weird to refer to a circle as a "sphere", for instance. We can also use Vec2 instead of Vec3 to define them where vecs are involved.
I think we can make them in a special Body2
enum that gets translated into Body
underneath.
// alias for Sphere
Body2::Circle {
radius: f32
}
// like Cuboid, but with Vec2
Body2::Rectangle {
half_extends: Vec2
}
// Convenience over Body2::Rectangle
Body2::Square {
size: f32 // size of side. Or should this be half_side?
}
// Parry has a specific triangle shape
Body2::Triangle {
a: Vec2,
b: Vec2,
c: Vec2
}
// Stadium is the geometric name for a 2d capsule. Since this is such an obscure name, the name Capsule still works too
Body2::Stadium {
half_segment: f32,
radius: f32 // radius of the half circles
}
// A convex polygon defined exactly by its points (convex_polyline in Rapier)
Body2::ConvexPolygon {
points: Vec<Vec2>
}
// This requires some code but it's convenient to have
Body2::RegularPolygon {
sides: int; // amount of sides
radius: f32; // distance from center for each point
}
In a game the time between each frame may vary significantly from frame to frame. The physics simulation should not suffer from that. Its progression should always be at the same "speed" of the real world time (eventually scaled by a user-defined factor).
It is also important to garantee a maximum "delta-time" between each physics step to prevent objects passing through each others at lower frame rate.
For these reasons, the physics step runs at a fixed time-step which makes it out-of-sync of the main frame update. That means the physics step may be executed: 0, 1 or n times during a single bevy update.
But this adds complexity for both usage and design/maintenance of the library:
add_physics_system
And it also has a performance cost because it requires a dedicated bevy stage which introduces thread synchronization points and prevent other (non-physics related) systems to run in parallel of the physics.
The most problematic part is that there can be multiple physics step during a single bevy update.
But instead of using the bevy built-in FixedTimeStep
there could be a different run criteria to make sure that at each bevy update the physics step runs either exactly once OR doesn't run. But it would never run the physics step multiple times in a single bevy update.
In other words:
Then the physics system could run in the CoreStage::PostUpdate
stage and provide labels to allow running user-systems before or after the physics step.
add_physics_system
. The user can read and mutate the physics component normally
I think the first point is fine. We cannot please everyone. Some will like that physics simulation slow downs in case of low FPS. And some will not like it. Heron doesn't aim to be a drop-in replacement for rapier and bevy_rapier. It aims to makes things easier for people who do not need advanced physics control. If someone needs a more advanced/different handling of the physics in case of low FPS, they can use rapier/bevy_rapier.
As a user, I would like to have an offset for the collision shape (relative to its entity GlobalTransform
), so that I don't need to use parent-child hierarchy (with shape being parent) when the origin and orientation of the colision shape doesn't match the origin/orientation of my sprite/mesh.
There could be a BodyOffset { translate: Vec3, rotate: Quat }
component.
@yaymalaga convinced me in #31 (comment) that it can be more ergonomic to mark the CollisionShape
as "sensor" rather than marking the rigid-body.
We have a use case where we want to access shape data in the renderer code. For instance, ConvexHull creates a set of points that defines the convex hull depending on the points you give it. It should be possible to access this data through a Heron API. This issue is to remind us to think about this.
In Rapier, once you have the shape, you can call this:
shape.as_convex_polygon().unwrap().points()
Where as_convex_polygon
is a typecast and then points is the method that returns the points to draw.
Do we want to expose some kind of typecast API that can fail like this or is there another pattern that would work better? I know absolutely nothing about the reflection system of Bevy, is that relevant?
We need a Heron version of a convex polygon, that exposes Bevy vectors instead of Rapier points.
See also #43 as a fallback on how to get to the Rapier world. This could be a quick way to implement it without any new Heron APIs for the debug renderer, but in the longer term it would be better to expose this information in some nice Heron API.
Following the issue #81, that feature would allow us to run the defined physics_systems only on specific States. However, the physics engine itself will continue to be called.
A common feature for games is the ability to pause it at any time. In order to achieve that, we would need to be able to manually choose in which States the physics step will run.
Another option is exposing a resource for allowing the user to start/stop the physics from any system. For this last option, it could be cool to also allow the user to modify the step used and how often it's called (i.e running the game in slow-motion while in pause to hurry up the player!).
As a User, I want to subscribe to collision events, so that I can execute logic when two bodies collides (or stop to collide).
If I understand correctly from what I've seen in bevy_rapier2d
, it looks like it actually stores all of the simulation data such as rigid body positions, etc. in the bevy ECS, by implementing the ComponentSet
trait. With the way we have heron
right now we are storing the physics world data in Bevy resources that use arena based storage.
I'm curious if that has any advantages performance or otherwise and if that would be a useful strategy in heron
or not. I'm mostly just musing over the possibility, but do you have any ideas why we would want to or not want to do that?
As a User, I would like define the restitution of a body, so that I can make some object to "bounce" when they hit something.
As a User, I wan't to get clear and quick feedback when I make a mistake, so that I avoid spending time guessing/debuging in order to make my game work.
For example the Body
component needs the entity to also have a GlobalTransoform
. And a Velocity
works only on dynamic bodies.
When a required components is missing it can be considered a programming mistake of the user of this library, and Heron should at least report the situation, so the user is aware of it.
As a User, I would like to define a static body that affect other bodies, but is not affected by physic, so that I can create platforms, or other static world objects.
Probably leveraging bevy hierarchy
Even if it is too small and far from complete, "some" documentation is better than "none".
Let's publish the guide.
rapier
and bevy_rapier
just published some awesome improvements that will make the user-experience of using rapier in a bevy game much nicer.
If theses changes are awesome, they also makes the distinction between bevy_rapier
and heron
a bit thinner and blurry than before.
I think there is still room for both projects as they still have slightly different approaches, and it is likely that they are both useful to a different type of user.
Neverthelless, I have to make sure that efforts in the developement of heron are well spent by making sure that:
heron
and bevy_rapier
is big enough and clearily communicated (in the readme).heron
being a relevant alternative to bevy_rapier
.It seems that no documentation was generated by docs.rs for heron 0.9.0, although the build did not fail (here's the log, although it doesn't seem useful)
Currently Heron supports these bodies, based on Rapier shapes:
Sphere (circle)
Capsule
Cuboid (rectangle)
Here's my analysis of what useful other things exist in Rapier. The Heron body names would typically be the names in Rapier but converted to CamelCase. Ideally all shapes supported by at least 2d would have an implementation in the debug renderer as well.
I think the following have relatively straightforward implementations:
convex_hull
creates a hull around the given points. See #59
round_convex_hull
does a convex hull with rounded corners.
round_cuboid
: a cuboid with rounded corners.
segment
: a line segment shape from one point to another
triangle
. I imagine this is a flat triangle in 3d.
These I'm not entirely sure about what they do.
polyline
: polyline shape defined by vertex and optional index buffers. Without index buffers it's assumed to describe a line strip.
trimesh
: triangle mesh shape defined by vertex and index buffers. See #41
convex_decomposition
: creates a compound shape obtained from the decomposition of the given trimesh (in 3d) and polyline (in 2d) into convex parts
round_convex_decomposition
: the same, but with rounded corners
convex_decomposition_with_params
: control the decomposition using a set of parameters for the VHACD algorithm.
round_convex_decomposition_with_params
: control the decomposition using a set of parameters for the VHACD algorithm.
heightfield
: a heightfield shape
compound
: a compound shape defined by subshapes
Then there are shapes that only exist in either 2d or 3d. The question is what Heron should do with these as it aims to offer a unified API. The simplest would be to have particular Body
types to be only constructable in 2d or 3d.
Only exist in 2d:
convex_polyline
assumes the given points already form a convex polygon, no convex hull is computed automatically.
round_convex_polyline
does a convex polyline with rounded corners.
Only exists in 3d:
cylinder
round_cylinder
cone
round_cone
Not directly implemented in Rapier but would be nice to implement in Heron is a regular polygon shape defined by the amount of sides.
In a typical game we don't want to have everything to collide with everything. This is especially true when using sensors.
Using physics layer to simplify/optimize game logic is a standard, useful and powerful technique in game-development.
As the 0.5 release notes show, we can now add systems to run on the lifecycles of custom states:
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
enum AppState {
Menu,
InGame,
}
fn main() {
App::build()
.add_state(AppState::Menu)
.add_system_set(SystemSet::on_enter(AppState::Menu).with_system(setup_menu.system()))
.add_system_set(SystemSet::on_update(AppState::Menu).with_system(menu_logic.system()))
.add_system_set(
SystemSet::on_update(AppState::InGame)
.with_system(game_logic.system())
)
.run();
}
Ideally, we should be able to run the physics systems just on AppState::InGame
, instead of using .add_physics_system()
:
fn main() {
App::build()
.add_system_set(
SystemSet::on_update(AppState::InGame)
.with_system(game_logic.system())
.with_physics_system(game_physics_logic.system())
)
.run();
}
When debbugging I want to see every sensor and physics collision shapes.
But I also want to easily differentiate the physics collision shapes from the sensors.
By default the colors could be:
Ideally, the two colors should be configurable.
I'm looking into extending Heron so it supports ConvexHull as a Body. I'm running into the boundaries of my understanding of Rust as well as some design questions concerning Heron.
As far as I understand it you want to support the same API for 2d and 3d with Heron, meaning that you use Vec3 everywhere, but ignore the Z coordinate if in 2d mode.
So here's my definition of ConvexHull
for Body
:
/// A convex polygon/polyhedron shape
ConvexHull {
/// A vector of points describing the convex hull
points: Vec<Vec3>,
},
This also means I need to extend build_collider
:
Body::ConvexHull { points } => convex_hull_builder(points.as_slice()),
Here I run into my limitations on my knowledge of Rust. I tried to introduce "IntoRapier" traits in convert.rs
to gain the ability to convert an array of Vec3 into an array of Point3 (3d) as well as an array of Vec3 into an array of Point2 (2d). But Rust wouldn't accept both traits existing at the same time for some reason. Here's some broken code:
impl IntoRapier<Vec<Point3<f32>>> for &Vec<Vec3> {
fn into_rapier(self) -> Vec<Point3<f32>> {
let mut result: Vec<Point3<f32>> = Vec::new();
for p in self.iter() {
result.push(p.into_rapier())
}
result
}
}
impl IntoRapier<Vec<Point2<f32>>> for &Vec<Vec3> {
fn into_rapier(self) -> Vec<Point2<f32>> {
let mut result: Vec<Point2<f32>> = Vec::new();
for p in self.iter() {
result.push(p.into_rapier())
}
result
}
}
In the end I gave up and wrote more direct conversion code instead:
#[inline]
#[cfg(feature = "2d")]
fn convex_hull_builder(points: &[Vec3]) -> ColliderBuilder {
let mut converted_points: Vec<Point2<f32>> = vec![];
for point in points.iter() {
converted_points.push(Point2::new(point.x, point.y));
}
ColliderBuilder::convex_hull(converted_points.as_slice()).unwrap()
}
#[inline]
#[cfg(feature = "3d")]
fn convex_hull_builder(points: &[Vec3]) -> ColliderBuilder {
let mut converted_points: Vec<Point3<f32>> = vec![];
for point in points.iter() {
converted_points.push(Point3::new(point.x, point.y, point.z));
}
ColliderBuilder::convex_hull(converted_points.as_slice()).unwrap()
}
(the unwrap
hints at an interesting design wrinkle; construction of the collider may fail in this case. How to deal with it in Heron?)
So the first question is: how do we successfully convert arrays of vec3 into either point2 and point3 using the traits in convert.rs
? Because with this direct approach, body.rs
depends directly on rapier types.
Now we move on to the major design issue that arose when I tried to adjust the debug renderer. It should be so simple:
Body::ConvexHull { points } => {
builder.add(&shapes::Polygon {
points: points,
closed: true,
});
}
but this won't work, because Polygon needs an array of Vec2 and points is an array of Vec3.
So I need to do this instead:
Body::ConvexHull { points } => {
builder.add(&shapes::Polygon {
points: points.iter().map(|p| (*p).into()).collect(),
closed: true,
});
}
which I guess is acceptable but hardly pretty. Maybe we need to make it easier to turn an array of Vec3 into an array of Vec2? Perhaps there's one I don't know about.
It is already in the resources
As a User, I would like to define kinematic bodies (not affected by forces, but can move, have velocity and affect other bodies), so that I can create a player character or movable object in the world.
I create this issue issue to track the biggest missing pieces of documentation that are required to publish an "initial version" of the guide.
(Thanks @faassen for the list)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.