#Avian Physics
1 messages ยท Page 21 of 1
they only have interpolation between previous and current frame, but I also haven't properly looked at how it's implemented
following up to myself: hard to say, attempting to apply the correction along the free axis of the revolute joint results in explosive behavior (do I need to rotate the free axis to the current rotation first? will try that next)
(lightyear mentions it here)
You don't need to call that, it's intended to be internal
OK!
If by FPS you mean tickrate, then that's kind of similar to this issue: https://github.com/bevyengine/bevy/issues/12465
huh, ok, in that case, it's hard to tell if the anglelimit is doing anything ๐ฆ
What have you set it to?
+/- 0.01 of a target value (default 0.0)
this is like the steering joint of a bicycle, the neck
the repo's revolute joint examples work at least
yeah
I could swear one of them did interpolation and extrapolation hm, but maybe not
They do also have two kinds of interpolation themselves already, visual and snapshot ๐
naw i was just riffing off of just dont have a fixed timestep at all
or just have the tickrate so high no one even knows until it crashes lmao
OK, it is working, but the maximum angle_limit_torque isn't high enough to constrain it
I had forgotten about this issue, it'd be cool to have that as an option
I definitely wouldn't make it the default strategy tho
I tried to make a tiled floor where each tile was a separate entity with a static rigid body and a collider, where they perfectly touch but don't overlap
But I got an explosive overlap warning and when I try to walk over them, it's bumpy as if there was a gap between them
Is there a better way to do this?
basically trying to do simple procedural 2d platform generation
with a grid of rectangles
I have responses for this scattered around, should probably add it to docs... Here's one in the context of Rapier, but also applies to Avian
#1241403964591968267 message
Other things that might help if you're using a dynamic character, specify SpeculativeMargin::ZERO and maybe add a small CollisionMargin
(CollisionMargin is kinda similar to that character offset)
Thanks - I guess I could also try to join the colliders together to prevent anything static from touching
Might get complicated though since it's procedural
I guess only flat surfaces need to be joined, not perpendicular ones
if I want to detect whether or not a ray has hit Layer1 or Layer2, should i just do two raycasts?
btw I still think there's a bug with CollidingEntities but I switched to ShapeCaster for ground detection and it works like a charm
combined with collision layers to prevent jumping on coins
This issue?
yeah
you can specify both layers for the raycaster
in a list or bitwise union/or
Yeah I can see that being an issue currently ๐ค I think we could fix it in an unoptimal O(n) way already, but it'd probably be more efficient once we have a contact graph so we can look up the edges of the despawned entity
@vestal minnow Had a question about the intended mechanism for adding custom collider types in Avian.
Parry supports this typically through chaining their query dispatchers, which allows you to create totally custom collision handling, but by default Bevy colliders use a DefaultQueryDispatcher without allowing chaining of the dispatcher at all.
I assumed the intended mechanism was to instead implement our own AnyColliders, but that means we have to have this enum as a layer to interact with the avian plugins, as the default avian plugins only support avian Colliders:
#[derive(Component)]
pub enum ColliderWrapper {
/// A default bevy collider
AVIAN(Collider),
/// A custom "foo" collider
FOO(FooCollider)
}
// ...
let builder = builder
.add(ColliderBackendPlugin::<ColliderWrapper>::new(self.schedule))
.add(NarrowPhasePlugin::<ColliderWrapper>::default());
// ...
impl AnyCollider for ColliderWrapper {
fn aabb(&self, position: Vector, rotation: impl Into<Rotation>) -> ColliderAabb {
match self {
ColliderWrapper::AVIAN(collider) => collider.aabb(position, rotation),
ColliderWrapper::FOO(collider) => collider.aabb(position, rotation),
}
}
// ...
}
it says that I ned to implement BitOr, which i don't know how to do ๐
Is this the intended way? I feel like I must be missing something here, as the enum delegation feels very wrong.
it's probably not too high priority - can always hack around it with a custom system or filter out nonexistent entities when iterating through it right?
yep
you can just use [Layers::A, Layers::B] for example - it's written in the docs under collision layers
I opted for bitmasks though
oh thanks i didn't see that, i was looking under LayerMask
it's pretty neat
one thing I feel like could be a footgun though is if you forget to add collision layers to an entity it presumes it belongs to all layers and interacts with all layers
don't know of a better solution, but just something to keep in mind
I'm not sure how easy it is to make fully custom collision logic, but there's a few custom shapes in the Avian repo which you could use for inspiration, like Ellipse and RegularPolygon (I'm using wrappers to overcome the orphan rule, but you could use your own shape directly if you own it). Basically you need to implement Parry's Shape trait and some other traits depending on the type of shape, and then you can create a SharedShape and Collider from it.
That might not be enough if you need a more complicated custom shape, and in that case you would indeed need to derive AnyCollider and use a fully custom collider type
true, fortunately i don't think i'll have that many "types" of entities so shouldnt be too hard to debug if it comes up
yeah, making your own bundles helps too
do you still plan on moving away from Parry eventually?
yes, but reaching feature parity will take time
In this context I mean custom manifold detection with a voxel world, so I found that the case of just having a shared shape, shape, and avian Collider didn't suffice because I needed to determine my own manifolds. I essentially wanted to have a fn collide(world: &VoxelWorld, avian_collider: &Collider) -> Vec<ContactManifold> that Avian would use
I've been trying to get our project to run on WASM, but keep getting the time not implemented on this platform error. Our deps are bevy, bevy_ecs, avian2d, leafwing-input-manager, and bevy_asset_loader. I think avian2d is causing it, but I'm not 100% sure since I'm still not good with more complex rust project structures. Does the code in avian/src go into the 2d crate? I assume the tests/benchmarks/examples shouldn't be causing the error, but there are 2 instances of std::time::Duration in src that I'm not sure about. They are at src/schedule/mod.rs and src/schedule/time.rs.
I have a project running on WASM with Avian so I kind of doubt that's the cause
It's actually using avian3d, LWIM, bevy_asset_loader, and ofc bevy
so the same crates pretty much
But yes src essentially goes into crates/avian2d
same
std::time::Instant doesn't work on Wasm but iirc Duration does
is there just no monotonic clock in browsers?
Hm, so it's just instant? I guess I have to keep digging then, thanks.
rip
Yeah, I've already replaced all the spots in our code with web-time.
I think you could technically add your own system that adds collisions to Collisions in the PostProcessCollisions schedule
But if you used a custom component for the custom voxel collider (without using AnyCollider stuff), you'd need to handle everything you need for it manually, which might be kinda painful
this is the approach I'm going with rn, kinda inbetween
Not an ideal layer to have but IG I'm using anycollider so better than from scratch
(re: replacing Parry) For reference, I've been writing a bunch of issues for Peck in preparation for making the repo public soon even though it's very WIP. Currently have these issues, many of these are very complicated or otherwise large tasks
I think I could get all (or most) geometric queries for Bevy's primitive shapes implemented relatively soon though
One other thing that you might want to know about, but also is probably just us being dumb, we ran into a race condition? with Collisions. Our setup was Crouching is started if player.grounded, player.grounded is if any of body.collider_ref's collisions have a negative manifold. When Crouching starts, it replaces the reference in body.collider_ref to a collider half the height and stores the old one for replacement on uncrouching. Very rarely, we would get cycles of crouch -> uncrouch -> nothing -> crouch -> .... As far as I can tell this was caused by body.collider_ref being "stale", so checking Collisions returns nothing since it doesn't exist/is set to CollisionLayers::NONE. It was fixed by checking both the current body.collider_ref and stored crouch ref, but was almost impossible to solve. Thoughts?
I'm not sure if I understand exactly what's happening so it's hard to say
Is the crouched collider positioned at the "bottom" of the uncrouched collider so it's not in the air for a moment when starting to crouch?
Generally I'd also do ground detection with something like shape casts instead of actual contact data
It is a lot going on and probably not avian specific, but here the links:
broken ground check method
crouching plugin
fixed ground check method
As far as I could tell from all my testing with printing positions and shifting the colliders, it was always touching the ground, there wasn't any floating point nonsense happening, it was just something making the Collisions check in our method use the wrong entity reference.
I did try implementing the crouch by altering the scale of the player collider instead of replacing it, but I just could not get it working, probably because of how the systems were getting ordered.
I see, so using either https://docs.rs/avian2d/latest/avian2d/spatial_query/index.html#shapecasting or https://docs.rs/avian2d/latest/avian2d/spatial_query/struct.ShapeCaster.html would be a less janky approach than using Collisions, thanks. Hopefully it doesn't run into the same stale ref thing.
is there any way to do something along the lines of this?
#[derive(PhysicsLayer, Clone, Copy)]
pub enum Layer {
Camera,
Ant,
Pheromone,
Obstacle,
Colony,
Ground,
}
impl Layer {
fn interactions(&self) -> impl Into<LayerMask> {
match self {
Layer::Camera => [],
Layer::Ant => [Layer::Pheromone, Layer::Obstacle, Layer::Colony],
Layer::Pheromone => [Layer::Ant],
Layer::Obstacle => [Layer::Ant],
Layer::Colony => [],
Layer::Ground => [],
}
}
}
this currently gives an error that the match arms each have different lengths, which makes sense, but i'm unsure how to work around it. in the end I basically want to do something like
commands.spawn(CollisionLayers::new(layer, layer.interactions()))
would I have to do something like mapping each variant to a specific CollisionLayers?
impl Trait has to be just one, or the match has to be just one return type, so either use a dyn or manually call .into() and make the function -> LayerMask. Or something else I missed.
thanks, ill give those a go
seems like dyn requires the trait to be an "object" and Into<> isn't one, so it doesn't work :(. i'll probably just go witht he manual .into() approach for now
thanks for the help ๐
I wonder if anyone has any suggestions for what Iโm doing wrong here.
Iโve got 4 spheres each with RigidBodies attached to another RigidBody (all Dynamic) via RevoluteJoints.
Although itโs all stable, once the wheels reach an Angular Velocity of around 20 the whole thing lifts off the ground and flips over.
I'm using MassPropertiesBundle, with the body set to 10,000 density and the wheels are each set to 10. (No particular reason for any of these values; they were just guesses. With the body at a probably more realistic 1,000 density the problem persists).
Mass is showing as really really small value (~5) for the vehicleโs parent, and its child collider is around ~50,000.
Each wheelโs Mass is around 1.2.
The wheels are not children of the vehicle; they are separate entities entirely. The RevouteJoints are also separate entities.
Side note: In case it's relevant, the wheel colliders are ignoring the vehicle's body collider via PostProcessCollisions.
Oh, and the back wheels are being spun via ExternalTorque
Are there any implementations for modifying the local scale of a collider?
...
FYI the Bevy game jam is currently nearing its end, so you might get fewer answers than normally as people are busy finishing their entries ๐
You can just use Transform scale, it'll also scale the collider. If you want to only scale the collider and not the entity it is attached to, you can work around it by putting the collider on a child entity and only scaling that
If you need to scale a collider and get the actual underlying shape, you could also call set_scale on it and get shape_scaled
(GlobalTransform scale will generally override this if the collider is on an entity with a transform)
re collisions, you could also use CollisionLayers and put the wheels and body in different layers. This is a dumb question, but how are you applying the external torque? If you're not transforming it to the world space you might see something like that as it moves and the applied torque gets out of whack with expectation; I do like torque.apply_torque(*wheel_xform.left() * magnitude)
Thanks!
Thanks for the reply. CollisionLayers won't work because I want the wheels to collide with other vehicles' main colliders. I just don't want inter-collision. Once #364 gets merged I'll be able to use that instead.
I was indeed applying the torque in world space, thank you for that suggestion.
However, even with it corrected it still takes off ๐ฆ
I did a bunch of playing around with it yesterday like adding suspension and giving it more realistic masses and I think I've managed to get it to a point where it's at least close to being stable. The wheels have to spin at 100rads/sec for it to take off, which is something like 80km/h with the current wheel radius. So I have a feeling it was just weight ratios.
It seems like a bug that a very light thing spinning quickly can move a very very heavy thing, though?
@vestal minnow https://joonaa.dev/blog/06/avian-0-1#runtime-collider-constructors-378
There's a typo in the AsyncSceneCollider example code,
AsyncSceneCollider::new(Some(CoutedCollide:TriMesh)),
vs
AsyncSceneCollider::new(Some(ComputedCollider::TriMesh)),
Is there a way to attach a sensor collider as a child of a rigidbody without affecting the parent's centerpoint?
I think this is already the behavior on main
Hey, how do I prevent/reduce things going with a relatively higher speed falling through the ground?
@vestal minnow I managed to make quickhull work with robust for arbitrary 3d shapes, tested on 10000 different random point clouds.
More info here https://github.com/Jondolf/quickhull/issues/1
Was the tolerance value only needed for the robustness issues? If so, I can remove it and clean up my fork into a mergeable state.
There is also some low-hanging fruit in the form of the HashSets and HashMaps hogging ~15% of the performance which should be easy enough to fix with a faster hashing algorithm. I can take care of that too.
thanks! that appears to have done the trick.
It also got me to upgrade from 0.1.0 -> 0.1.1, and I noticed what appeared to be a regression ๐ค unless I'm doing something really funky. first video is 0.1.0, 2nd is 0.1.1.
Possibly relevant system? Perhaps because I am directly modifying the velocity?
/// Apply input to the controllers
fn apply_controller_movement(
mut characters: Query<
(
&Transform,
&Craft,
&mut Controller,
&mut LinearVelocity,
&mut AngularVelocity,
&mut LinearDamping,
),
Without<Destroyed>,
>,
time: Res<Time>,
) {
let dt = time.delta_seconds();
characters.iter_mut().for_each(
|(transform, craft, controller, mut velocity, mut angular, mut damping)| {
**velocity += controller.thrust * transform.forward() * dt * craft.acceleration;
**velocity = velocity.clamp_length_max(craft.speed);
**angular = controller.angular_thrust * -Vec3::Z * dt * craft.rotation;
**damping = controller.brake * dt * craft.brake;
},
);
}
What exactly is the regression here? It appears the rotation is not as snappy?
It feels like the set angular velocity is fighting with something else, and sometimes it just won't take at all (despite it appearing in the inspector that the velocity is set?) It's very possible I'm doing something weird though. I'm not sure if there are any guarentees with minor versions being compatible.
Ah, I think it might have something to do with LockedAxes restricting movement. Not sure what changed but I'm guessing I might just have the wrong axis or something, and something with transforms was changed/fixed in .1 and maybe i just have things set up wrong?
edit - yeah, when I remove .lock_rotation_y(), the issue resolves. (Note my project is top down aligned with the Z axis as up, though). It looks to work as expected in 1.0
Yeah, IIRC the tolerance was just for robustness. I think I added it based on the implementation here, which in turn took it from somewhere else
With robust it might not be needed
(or specifically, chull had a tolerance already, but I added automatic computation for it like this implementation)
Does avian collision detection skip Visibility::Hidden entities?
Pretty sure it does not 
@vestal minnow is this intentional? It seems to me if an entity is set to Hidden it would be nice if the collision process skip that?
I'd bet it is intentional. You often want invisible walls / barriers, no?
Works the same in Rapier
You are right. It is definitely useful for hidden objects to collide with other objects. A good use case I can think of is having a low poly mesh collider to represent a high ploy object in the collision process, and the collider should always be hidden
you don't need to have a mesh displayable to add a collider to an entity, your invisible wall could be just the collider
I have a floating hand that can pass through objects connected to a rock with a joint the rock should be able to stop at walls while it's being held and stop everything in it's tracks but it just passes through them and applies a little spin on the thing that's being held as if it can feel it but can't stop it from going through not quite sure on how to solve this
I have a working example for this bug now I guess if you go into my game and push something against the wall you'll notice that it can partially phase through the wall I'm trying to prevent this and I'm not sure how.
https://poopnugget142.itch.io/manos
is there any collider type that resembles an arc or should i just use a circle collider and check the angle?
There isn't one yet, as Parry doesn't support it. We could probably add it, but it'd most likely have to be a polyline approximation since an arc is not convex or watertight
Unless you mean a circular sector or circular segment, those we could probably implement decently
don't know the exact term, but yeah something that looks like this
Yeah that's a circular sector. I think it should be relatively straightforward to implement as a collider shape, but it's not built-in yet
yup, im just checking the angle right now to see if its within a range, but it would also to have stuff like a debug gizmo ๐
hey everyone! i'm getting an error when using SpatialQuery alongside another Query<[...]> that includes Collider:
spatial_query: SpatialQuery,
pub fn fps_controller_move(
spatial_query: SpatialQuery, // ๐
mut query: Query<(
Entity,
&FpsControllerInput,
&mut FpsController,
&mut Collider, // ๐
&mut Transform,
&mut LinearVelocity
)>,
)
error[B0001]: Query<[...]> in system shooter::controller::fps_controller_move accesses component(s) avian3d::collision::collider::parry::Collider in a way that conflicts with a previous system parameter. Consider using `Without<T>` to create disjoint Queries or merging conflicting Queries into a `ParamSet`. See: https://bevyengine.org/learn/errors/#b0001
i hadn't much success using ParamSet (i get a cannot borrow set as mutable more than once at a time error when trying to acess the spatial_query and query from the set) and don't know how i could use Without<> here to fix this... any ideas?
I think you can use Res<SpatialQueryPipeline> directly instead of SpatialQuery for now. I'll try to remember to make an issue or PR tomorrow for fixing this
nice, tysm!!
Interesting, I've also run into this while trying to get CollisionLayers, Transform, and Collider (though not all at once). ParamSet has been working for my cases, basically doing ```rust
fn system(
mut params: ParamSet<(SpatialQuery, Query<&mut Collider>)>,
) {
let spatial_query = params.p0();
if spatial_query ...
// As long as spatial_query is not used past this point Rust should be smart enough to figure out the borrows.
let mut q_collider = params.p1();
// Do whatever mut query stuff.
}
Hey folks, I run into
thread '<unnamed>' panicked at /Users/yatekii/.cargo/git/checkouts/avian-b0a258754c9e72b0/5c2188c/crates/avian2d/../../src/position.rs:252:9:
the given sine and cosine produce an invalid rotation
Just when I add a RigidBody::Static or a Collider::rectangle
Whole bundle is:
commands.spawn((
SpriteBundle {
transform: Transform {
translation: Vec3::new(x, y + CIRCLE_OFFSET_Y, 0.0),
scale: TILE_SIZE * 0.99,
rotation: Quat::from_axis_angle(Vec3::new(0.0, 0.0, 1.0), phi),
},
sprite: Sprite {
color: TILE_COLOR,
..default()
},
..default()
},
// RigidBody::Static,
// Collider::rectangle(TILE_SIZE.x, TILE_SIZE.y),
Tile,
));
with phi being (0, 1)
What is TILE_SIZE? I'm usually working on 3D, so take this with a grain of salt, but I think even in 2D Z is not allowed to be 0
Your rotation can be Quat::from_rotation_z(phi). Talking about it, what do you mean with phi being (0, 1)? I expect it to be an angle in radians.
It looks about right to me, I assume they mean 0..=1 since (0, 1) shouldn't compile, and while we haven't used SpriteBundles yet we do routinely have translations with 0 z. I couldn't find why it's panicking since it looks like a valid rotation to me, though isn't there a way to specify rotation from an angle so you can skip the quat?
Ah sorry, (0, 1) is an exclusive range from 0 to 1 so indeed radians, but very low numbers, which I stated because there was some GH issues about big radian numbers
TILE_SIZE is 30
If I make Z=1 that does not change anything. Quat::from_rotation_z(phi) is very nice, thanks! I am using it now ๐ But still crashes ๐ฆ
Translations of 0 are fine of course, I was cautioning about scales of 0 ๐
Vec3::splat(30.0)? Just making sure
ahhh that works!
are overlapping at spawn, which can result in explosive behavior. ihihihi
thanks a lot!
now i need some debug grid to find where my characer goes, lets see
hey, im wondering whether there's a reason that the PhysicsDebugPlugin doesn't use AnyCollider
and whether ShapeCasters work with AnyCollider
Oh, I forgot to post it here, in case anyone is out of the loop.
The Bevy Game Jam has just concluded and @vestal minnow and me submitted a lovely little game about being yeeted by physics while trying to deliver stuff on your bike: https://janhohenheim.itch.io/bike-game 
I modified the kinematic character controller to meet the needs of my hedgehog game. The hedgehog should rotate according to the ground normal on uneven terrain to appear natural. Currently, Iโm struggling with the rotation because the hedgehog needs to face the direction it is walking and combine the directional rotation with the surface rotation (from the ground normal). However, as soon as it stops walking, the hedgehog slowly rotates away from the direction it was walking (I assume due to the normal rotation). Any ideas on how to fix this?
Here is my code, I think the relevant parts are in update_surface_normal and update_rotation functions: https://pastebin.com/xW5yxbgV
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
Hmm the colliders seem to scale with the transform. should I make the colliders all nominal size?
err normalized size
Depends on how you add them imo. If you add everything through code and have transforms of 1, making the colliders the "right size" directly is fine. If you add them through external tools such as Blenvy, it's easier to leave them at nominal size because then you can just scale them using that tool, e.g. pressing "S" in Blender.
everything is completely manual atm. no textures, no blender, nothing
problem is that the colliders dont rotate with the tiles
or is there a way to make more accurate colliders?
Hmm? They should, if they are just children
If your tiles have a Mesh, you can generate the collider automatically.
They do, the orange outline is the collider. The white rectangle is the AABB which is axis-aligned by definition
Ah you're right, that just tripped me up too ๐
ah this is just the AABB
Thanks a lot folks! Now to map controls to the character and play a bit with the gravity, jumping etc ๐
Seems to work nicer than back in February when I tried the crate ๐
Cool, that's nice to hear ๐
Agreed, the new solver has been lovely in my experience โค๏ธ
I had tunneling issues back then. Let's see what happens if I add lots of force ๐
Or acceleration
The PhysicsDebugPlugin needs to know what shape each collider represents and how they should be drawn, and AnyCollider doesn't really provide that information since the shape could be literally anything. We could have a separate DebugRenderCollider trait or similar though, so that people could implement that logic for their own collider types
They don't, currently. Like with debug rendering, we could maybe make the spatial query pipeline generic over collider types with another trait
But I'm not sure how doable that is with Parry
The collider would likely have to implement some of Parry's traits, and at that point the value of a custom collider is smaller
so essentially thereโs no way to do any raycast/shapecasts with custom colliders
Ok, so the is_grounded bug seemingly still exists :/
You need custom logic for that, yes
ah okay, do you have any pointers for that?
because i am lost
and kind of spent the last 3 hours trying to get my shapecast to work with custom colliders
Depends a lot on what the shape is
essentially just a cube
Hi!
I want to make a wheel go spin by dragging with the mouse.
I already thought about making a plane which does 3D raycast onto so I only have 2D world coordinates directly correlating to the wheel and the relative position change.
Now I wondern how I would "connect" a force at the start position to the wheel and "move" it to the newly dragged position such as that the wheel rotates there.
Inigo Quilez has some ray casting implementations here, like a cube
https://iquilezles.org/articles/intersectors/
I also have code for a Rust adaptation using Glam (Bevy's math library) if that's all you need.
Then spatial queries like ray casts typically have a Bounding Volume Hierarchy (BVH) as an acceleration structure if you have a lot of colliders in your world. Parry has a Qbvh for this, but it uses Nalgebra.
If there aren't tons of colliders, then just iterating through them can be fine.
Is there a particular reason you need custom colliders?
it's a voxel mesh collider
Why don't you use a regular cuboid collider for that?
this is considered not grounded ๐ฆ
(I stole the character controller from the examples)
I highly recommend using bevy_tnua instead of rolling your own character controller unless you have very good reasons to do so.
well, for now I just want to make it work
but so far that's not even possible with the examples
which is a bid :sadge:
mostly because efficiency
Hmm? Why would a custom collider be more efficient in this case?
you could try increasing the max time of impact for the ground shape caster, and maybe the MaxSlopeAngle
scale of the world i'm guessing
i'm not really the physics guy in this project
so i'm not 100% sure on the decisions
Alright. I assume the physics person has done benchmarks on that? Just trying to save you time here ๐
I would probably start with built-in colliders and explore the custom collider route if performance is actually identified as an issue
Using Tnua will just make it work
MaxSlopeAngle is 30 radian (same as in example code), that's already huuge ๐ It also is in contact with the ground forever ^^
Keep in mind avian is not a character controller library and any example doing a character controller is just an example. Do not be surprised if it does not work for your use-case.
I don't have specific pointers on implementing optimized voxel colliders, haven't done it before myself. I would imagine that you would need to implement a lot of things custom if you actually want to hyper-optimize it, like a custom broad phase.
well, I litterally took the example for avian that showcases colliders and added a character with the component and some tiles with static colliders ๐ as simple as it gets. moving left & right works
You could add logs to update_grounded to see what the normals etc. are
is there some joint I can use for that? or force component?
Yeah I just found out that there is 0 ShapeHits :/
(even though the collision seems to be working)
The example character controller has been repeatedly reported to break down quite quickly, so expect to modify its guts manually if you want to make it work. If you care about implementing character controllers and learning more about how Avian works, that's cool. If you just want to have something that works, I would not advise that.
So I am not sure what's fudged
Might not be exactly what you're looking for, but I have a 3D grabber plugin here
https://gist.github.com/Jondolf/adf45fe70867b0575e48471d6992e8cd
You might need to change a couple things since it was for an older version
Not trying to roast @vestal minnow's implementation or anything, just want to save you some time ๐
Yeah ๐ fair ๐ I just have a feeling that sth with my physics is fudged, that's why I am fiddling ๐
it was already a bit weird because I have a bit of a weird world setup ๐
Hmm how are ShapeHits produced though ๐ค
The example also has this warning :P yeah it's not a very good KCC and has some issues. Should be better when we eventually implement built-in collide-and-slide
Ah, I am getting the hang of it I think
I was about to write that maybe we should be more explicit about this and then I remembered we already are ๐
Well, I would expect it to have odd behavior with tunelling or weird explosive stuff on edges after this warning, but not for jumps to not work outright ๐
Thing is, implementing a kinematic character controller is notoriously hard
Any implementation you will find as part of an example will have glaring issues quite quickly
i think the biggest problem is the voxel world is mutable
and fitting cuboid colliders on the world can become kind of complex
What do you mean by that?
you cannot simple fit a single cuboid collider on the world
and giving each separate voxel it's own collider also isn't really possible
Why not?
Can't the physics-enabled voxels be entities? Surely you are not simulating physics for the entire world at once
But of those, most would not be doing physics, would they?
correct, but we want physics objects to be able to interact with them
and the easiest/best way to do that is to have a custom collider that uses our spatial acceleration structure to test for collisions i'm assuming
Can't you give a cuboid collider only to surface voxels?
That could very well be the case, but I'm skeptical without doing any benchmarks on the naive solution first
i still think that would end up with an unholy amount of colliders
If you have custom collisions, spatial queries, and acceleration structures, and heavily specialized performance needs, you might potentially be better off with custom physics tbh
oh boy don't say that too loud 
perhaps yea, i've really been enjoying using avian so far
also wouldn't mind PR'ing support for AnyCollider or some other trait that adds support
The majority of SpatialQueryPlugin is Parry-specific logic, so it probably wouldn't be that helpful. You could implement your own version with your own acceleration structures though
Ok, I fixed it, jumps are working with more travel time
What I do not understand is how MaxSlopeAngle goes from degrees to radians
there is no conversion bit oO
omg I am blind like a goldfish
Here's where SpatialQueryPipeline is btw, for reference
https://github.com/Jondolf/avian/blob/main/src/spatial_query/pipeline.rs
mostly forwarding to Parry APIs
and then the plugin just calls update_pipeline
yea that's what I was afraid of, ideally we'd use avian for all of our physics objects. the issue is the world and collision with it. if we handroll our own spatial query logic with support for our world it'd be kind of ugly I think if we want to keep support for parry colliders as well
Other question, is there a way for an idiot like me to help in avian?
Don't know that much stuff about physics yet but learning isn't impossible ๐
There's a lot of open issues, maybe you could find something suitable there ๐ I haven't labelled them by difficulty yet though
Yeah, so the max_time_of_impact is the problem. With 50 inead of 10 it works, but I really don't see how :/
Like the two colliders touch. I would expect the travel distance to be infinitesimal
It could be because of this
and if you're using pixels for dimensions, 10 can be small
I am just using the bevy units basically. But I got it
A good tracking issue to look at is Are We Upstream Yet?, which links to more tracking issues. Some of these are quite complex, but one thing that is sorely needed and should be easy to implement for anyone is adding examples for various features in avian.
Removing the 2D/3D split should also not require any physics knowledge, because the code is already written, but would take a long time. If anyone did that, I'd be supremely happy.
Adding a cool test scene would also be fairly easy an fun. Just throw in a bunch of cool gadgets and colliders ๐
that seems like stuff I could try
so that everything's named Joint3d etc.?
Well, the end goal would be to just have a library called avian instead of two libraries
Yeah, the max_travel_distance needs to be bigger than the player diameter ๐ what I do not get is why. I mean for a raycast, yes. But apparently it's a shape cast?
That one is architecturally challenging/controversial. It'd likely need a lot of generics to avoid too much duplication, and Parry might also be a bit annoying since it still has that split crate structure.
Having Joint3 and Joint2 or somesuch would be a correct API for that, since that would be consistent with glam
Yeah @molten mason definitely submit a plan for that before actually working on it
Also, the player can go a biiiit into the terrain if I press hard enough. While I understand that the CC might not be optimal, wouldn't this be controlled by the underlying physics?
We generally keep the d in Bevy APIs (Camera2d, Isometry2d, etc.) and only omit it for types that are very close to Glam types and could be upstreamed, like Dir2/Dir3 and Rot2/Rot3
One ugly bandaid until we have a replacement for parry could be to just import both parry2d and parry3d. It will increase compile times, but since we want to yeet parry at some point anyways, I wouldn't worry too much
Oh right, good point
Note that my idea here was to add a set of physics test cases like Solver2D or a lot of Rapier's examples, and maybe an all_examples example that could run all of those test cases, similarly to Rapier's testbed or Solver2D. Not just one example.
Agreed. I mean that adding a single case should be fairly easy as a start ๐
(Added this comment to the GitHub issue)
Something like a showcase where every single joint is explained and demonstrated might be nice, but I'm not sure if you would want every joint as another example instead of stashed together
Yup the joint examples should be combined imo
https://github.com/Jondolf/avian/issues/452
Was about to link that ๐
Forgot to add it to the tracking issue for examples, fixing that
When does the ShapeCaster do the intersection tests? I see where it's configured but not when it actually casts. is it a System as well?
that is run near the end of the PhysicsSchedule
Yes, the actual shape cast is performed using the SpatialQueryPipeline though. The components are mainly just an abstraction to make some patterns nicer
already fixed my problem 
hmm, the arrow in the middle is the linear velocity. and it jumps around hard ... is that because of the colliders always giving some impulse?
somehow it just falls in this one ๐ all the other ones don't seem to be a problem ๐
And I cannot use a ready made character controller I think because I need a polar coordinate system
is it possible to project a shape on a plane and get its outline?
Lumen has an implementation of it somewhere for Bevy's primitive shapes
#math-dev message
But I don't think Parry supports it ๐ค
I'm using intersection_with_plane on TriMesh from parry... I guess I could do it every few units along an axis then union all the polygons ๐
that seems efficient
My cat seems to be falling into the ground a bit ๐ค
I have Gravity(Vec2::new(0.0, -9.81 * 16.0)) since gravity is so slow in 2d (multiplying by 16 since I want it to be 16 pixels per meter)
It is all good unless the cat falls through the ground ๐. You could maybe try enabling SweptCcd on it, not sure if it will do anything.
Does using PhysicsPlugins::default().with_length_unit(16.0) instead of custom gravity help?
same thing
the gravity is super slow with just that
and it does still fall in a little bit even with default gravity
I can open source my code if it helps
Sadly Avian is still pretty new and stuff, I used it in the recent jam and encountered a ton of issues with it, if you want to have perfect physics it is not the right tool yet.
yeah bevy_xpbd was a lot more stable, I guess just some kinks to iron out with the refactor
I could use rapier in the meantime I guess
or try and make an Avian branch to fix this
have you set the length unit?
since your game is in pixels
oh mb bestRanar suggested it already
You need both that and the scaled gravity though. Gravity isn't scaled by the length unit, only internal tolerances are
Also xpbd was not more stable for the vast majority of cases, it had horrible stability issues and way more bugs (although ofc the refactor did cause a few new issues, but they should mostly be fixed). Most likely your situation just isn't configured correctly or there's some other issue
The examples in the repo do not have this sinking issue
If I move the Collider into the parent (I have it in a child to offset it vertically), it doesn't sink into the ground
But it does re-introduce issues with colliding against the corners of overlapping squares (still haven't dug too deep into fixing that)
I tried removing the Restitution and Friction and neither seem to be causing it
then my ground is just a rectangle collider on a static body
That's exactly the opposite of my experience ๐
I guess it used to be pretty explosive
I remember cubes flying apart at mach 10 when they collided sometimes lol
for whatever reason I never had collision issues like this in the 3d game I made with xpbd though
Thanks, that's valuable info, I think I can reproduce this. My suspicion would be that either (1) child collider transforms have a one-frame-delay, or (2) speculative collision doesn't work correctly for child colliders
In bevy_xpbd it wasn't as noticeable since collision detection was substepped (which was horrible for performance)
I can make my project public if you need a repo to test against too
I have a test scene that reproduces the issue already, so that's probably not necessary :)
I'll look into it more tomorrow and try to fix it
Is it currently considered to add center and orientation parameters to the colliders (meaning you can have the collider on the main entity, but offset it by a cartain value and rotate it without needing to create a child entity and attach the collider there)?
Not currently, that would add extra transformation operations everywhere where colliders are used since the engine now needs to take multiple different transforms into account
You can already use compound shapes to offset things though, they just have some more overhead
Maybe with relations it also become more convenient to use.
Currently working with unity ecs physics, and having those parameters is certainly very convenient.
And need to look into compound colliders for that indeed.
I think the idea of a generic Translated<T> and Rotated<T> or similar wrapper for Bevy's shapes has been brought up in #math-dev at some point, it could maybe be possible to have opt-in offsets with something like that
APIs could use helpers so you could do e.g. Sphere::new(0.5).translated(foo) to get Translated<Sphere>
(might be tricky to support nicely with Parry, but I think I could support it decently in my own lib Peck if we plan on switching to it eventually)
How far along is Peck?
It has ray casting, some point queries, support maps, GJK, EPA, and single contact computation, and then in bevy_heavy I have mass property computation, and in Quickhull a WIP convex hull computation algorithm. Peck doesn't currently have shape casting, contact manifolds, triangle meshes, heightfields, compound shapes, convex polyhedra, convex decomposition, and so on (although I do have issues with rough implementation outlines for these)
So pretty early stages, and reaching parity with Parry will take a lot of work and time
With GJK I can implement a bunch of geometric queries (shape casting, intersection tests, closest points...) really quickly and easily, but ideally a lot of shapes should have more optimized and robust analytic solutions
Follow up to this. After applying some TLC to the mass values and writing my own Joint for steering (a Revolute Joint with an offset) I now have a somewhat convincing car! @prime turret Thanks for your suggestion about the Torque ๐
if i want to temporarily "pause" an object so that it isn't affected by physics should I set its rigidbody to Rigidbody::Static?
Depends. Should it still affect other objects?
i guess it doesnt really depend becaues i want all objects to be paused
actually probably makes more sense to just set Time<Virtual> ๐ค
use Time<Physics> and time.pause()
The clock representing physics time, following Time. Can be configured to use a fixed or variable timestep.
for some reason it's giving me the error that there's no method named pause
fn set_time_scale(
mut virtual_time: ResMut<Time<Virtual>>,
mut physics_time: ResMut<Time<Physics>>,
slider: Query<&Slider, (With<TimeScaleSlider>, Changed<Slider>)>,
) {
if let Ok(slider) = slider.get_single() {
virtual_time.set_relative_speed(slider.current);
if slider.current == 0.0 {
physics_time.pause();
}
}
}
Are you not importing prelude::*? You might need to import PhysicsTime since 3rd party crates can only add methods to Time<Whatever> with extension traits
ah i was importing prelude::Physics, changing it to * fixed it ๐
Is there any prior art on combining avian with big_space? Doesn't have to be robust or anything (I suspect that would require at least simulation islands?). Just wondering if anyone's cobbled that together before
I guess one easy way could be f64 and a custom sync plugin, that might work well enough for now
or stick with f32 and copy paste the big_space GlobalTransform logic into a sync plugin if, avian doesn't mind the teleporting. I'm not simulating anything away from the camera anyway.
pretty old but there's a fork of big_space that adds a custom SyncPlugin for bevy_xpbd
relevant parts are recenter_position, the changes to position_to_transform, and probably something else
doesn't handle multiple floating origins though, and might have some other issues, but there was at least one project using this succesfully iirc
i fire a SelfDestruct event when an asteroid (2d poly) takes critical damage, and an observer calls commands.entity(asteroid_entity).despawn(). same observer triggers a bunch of SpawnAsteroid events to create smaller parts of the larger asteroid that was destroyed. (an observer for SpawnAsteroid calls commands.spawn(Asteroid...etc)).
I am getting warnings that the newly spawned smaller asteroids overlap at spawn, and i get explosive behaviour. I added a spatial query to the SpawnAsteroid observer, and it seems to be overlapping with the parent asteroid i despawned. i guess it's all happening in the same frame. those commands are sequential in the command queue. despawn happens first, but nothing is updated to that effect before the smaller asteroids get spawned. is this expected, and can someone suggest a better approach? thanks!
do i need to wait a tick before spawning the smaller parts, or is there a way to ensure the despawn is fully processed first
you can use apply_deferred to execute commands in between systems - if I'm understanding correctly this could help
for example (system_1, apply_deferred, system2).chain()
all the commands are in the queue though, since they are observers i'm not sure how i could use apply deferreed
that's done automatically by default
with auto sync points
that must be new ๐ค
since 0.13 at least
ah I haven't used it since 0.11 I'm pretty sure
is there a way to opt out of it if you want the extra parallelization?
or does it only affect chain
it doesn't affect parallelization, it's applied between systems that have commands and are ordered relative to each other, which implies opting out of parallelization between those systems anyway
Which schedule are you triggering SelfDestruct in?
let me check
Fixed, since it's part of my client-prediction collision logic
in a FixedUpdate set that runs just before the physics sets
hmmm
if you're getting an overlap warning then I'm pretty sure the entity must still exist at that point in time since the system that logs the warnings queries for the entities
yeah; i do a spatial query and it detects the original as an intersecting entity with where i want to spawn the new smaller one
(right before i call commands.spawn to create the new smaller asteroid)
that one makes sense because the spatial query pipeline is updated after physics, so the despawn only registers the next frame
but for collisions there shouldn't be a delay
the flow is like this, all in fixedupdate:
system the processes collisions, and triggers TakeDamage events
observer on TakeDamage that issues SelfDestruct (on critical damage)
observer on SelfDestruct that calls commands.entity(e).despawn(), and then triggers a bunch of SpawnAsteroid events to create smaller asteroid parts
observer on SpawnAsteroid does a spatial query, sees it will overlap with original entity, if it calls commands.spawn anyway i see explosive behaviour and warning
i had this working pre 0.14 and pre observers, but it probably had a tick separation between despawn/spawn due to how it used systems and whatnot
Just making sure, do the asteroids have child entities? If so, you'd probably want despawn_recursive (this probably isn't the issue though if it was working before)
no child entities
i could perhaps do the despawn in the same place as now, but move the "spawn a bunch of smaller ones" to after the physics sets. feels a bit inelegant though
that would mean 1 frame without anything at all existing though
visually they would exist, but for physics, yeah
maybe i can use a collision filter of some sort so the small parts can't collide with the parent that's despawning, does that sound sane?
hacky, but it could work
you could also remove the RigidBody, but that would probably have the same delay issue as despawning the entity
if it doesn't have the same issue, I'd be thoroughly confused lol
feels like there should be a way to remove a collider and place a different one at the same location all in one tick somehow, without any funny business. i'll experiement with some workarounds and see if i can provide any more useful feedback. i rather liked swapping over to observers and triggered events for this, but this has taken the wind out of my sails a bit
thanks ๐
no problem ๐ lmk if you figure out the issue, I can't think of why the despawn would be delayed either on the Bevy or the Avian side
Is there any way to include sensors in SpatialQueryFilter?
or do I just need to maintain a Sensors(HashSet<Entity>)
or is there a sensor layermask?
you could have a collision layer for sensors and filter based on that, but there isn't a built-in sensor filter atm
gotcha, just important for a kinematic controller
I'll just throw together a collision layer for it
pub const SENSOR: LayerMask = LayerMask(1 << 31);
pub fn add_sensor_mask(trigger: Trigger<OnAdd, (Sensor, CollisionLayers)>, mut layers: Query<&mut CollisionLayers>) {
let Ok(mut layer) = layers.get_mut(trigger.entity()) else {return;};
layer.memberships |= SENSOR;
}
pub fn remove_sensor_mask(trigger: Trigger<OnRemove, Sensor>, mut layers: Query<&mut CollisionLayers>) {
let Ok(mut layer) = layers.get_mut(trigger.entity()) else {return;};
layer.memberships &= !SENSOR;
}
observers ๐
@vestal minnow have you moved to using observers to add all of the other components? or is it slower
I've been havin a ball messing around with them
Not yet, but I will
https://github.com/Jondolf/avian/issues/475
just a note: entity mapping is messed up with it, but I don't think any of the physics components use that except joints?
Yeah I don't think other components use that currently
Good to know though, wasn't aware of that ๐ค
yeah, they are working on it I believe, but it's just tricky
oh sensors dont get CollisionLayers added automatically
nothing gets it automatically, the engine interprets entities without it as CollisionLayers::ALL
oh never knew that
hmm
complicates this a little bit
I think I'd have to do this in reverse then
const NOT_SENSOR = LayerMask(1 << 31);
lol
or I just add/modify every body to have a collision layer
hmm, do other physics engines default to all layers for memberships?
just sounds weirdly backwards to me
the filter including every group makes sense but the membership is weird to me
"The default values are 0x0001 for categoryBits and 0xFFFF for maskBits"
for box2d
I think that would make sense, yeah ๐ค
I feel like it'd be more intuitive too
I'm trying to think of why we wouldn't do that but can't come up with a good reason lol
since otherwise you have to make sure every object has their layers set up correctly
since you'd get interactions you didn't expect between things
trying to find the defaults for unity right now and asked in the rapier discord about it
Godot seems to default to just one layer (membership) and one mask (filter)
yeah the filter defaulting to only 1 makes some sense too, but I think it's less important
not sure though
I think the filter one is just a tradeoff of default interactions vs special interactions
I am confused by unity's "collision matrix"
but seems like they default to all membership?
though idk if unity is the best source of good practices
I'm gonna throw together a PR for avian and rapier and just try to get some thoughts
I do feel pretty strongly about doing it the box2d/godot way though, I feel like that makes much more intuitive sense for the usecases of collision layers (plus it simplifies logic since otherwise it makes more sense to make memberships the opposite way like with the "NOT_SENSOR" thing I was talking about before)
just the filters part is iffy to me
this does mean we have to reserve 1 bit for a "DEFAULT" though
what's the point of all_bits? couldn't it just be a LayerMask(u32::MAX) instead of on a PhysicsLayer? I do see that it's derived to be all the bits in that specific physics layer though
also ngl, filter seems backwards as a name as well
interacts or interactions makes more sense
@cinder summit @sleek thicket Does this stuff make more sense or am I going insane? https://github.com/Jondolf/avian/pull/476
actually yeah no I can't even do the Sensor membership stuff currently I don't think
I'd have to instead of adding/removing the membership for sensor entities, modify every default object to not be a member of Sensor
otherwise I'd be excluding everything
i kinda mentioned similar thing not that long ago
all my stuff has custom layers but gltf stuff was weird so it had to be default, everything broke because of it
@royal helm "31 layers" message should probably be something like 31 custom/additional layers
Having a problem syncing state correctly - I have two chained FixedUpdate systems, control_player and control_camera. The control_player system sets the player (RigidBody::Dynamic) and the control_camera sets the camera's position to a fixed offset relative to the player. I explicitly set
app.add_systems(FixedUpdate, (control_player.after(avian3d::prelude::PhysicsSet::Sync), control_camera).chain()))
The control_camera script basically sets camera_transform.translation = player_transform.translation + CONST so the player should always be in the exact center of the screen. However, what happens is that most of the time, the player is "one frame ahead" of the center of the screen- so while moving right, they are slightly to the right of the center, etc. This would be just a minor hiccup, except that every few seconds, for a single frame, the player jumps to actually being in the right spot in the center of the screen, so there's a sudden and very visible jerk.
I'm not actually sure if this is avian-specific, but wanted to post here in case there were any insights/examples on properly syncing a camera to a rigidbody that don't have this problem.
Ah, I see where I went wrong I think - avian3d seems to continue to run physics every frame, but my systems only run on Time::<Fixed>, so they usually lag behind
Hm, that doesn't actually help either, so I'm stumped
I guess a real thing to think about with the filters including everything is why should they
but idk I'm still on the fence about it
I'll have to properly think through the potential implications of this, but intuitively it makes sense imo. If I have a GameLayer::Player, it doesn't make sense that everything belongs to that by default
I realized it also just makes certain things impossible with our current system without modifying everything else to not be the default anymore
I posted this in the rapier discord but:
since the default is ALL/ALL, the interaction will happen as long as any bit is set in my other membership/filter
e.g. I want to shapecast to find that CAR group right? how do I do that without also hitting every default object as well
since that collision group i pass to the cast_shape is membership: 0b0001, filter: 0b0001, when I shapecast and hit an object that is defaulted to ALL it does this: (0b0001 & 0b1111 = 0b0001) && (0b1111 & 0b0001 = 0b0001) and passes
Also, while Unity does define which layers interact with each other with the collision matrix thing, it's not like objects belong to every layer by default. Objects can only be on one layer, and that is "Default", which essentially matches the "one membership, all filters" default, unless I'm misunderstanding what it does
actually true I hadn't thought about that
going to go to sleep but I'll fix up the PRs more tomorrow
I think they also allow configuring the default for your project
idk I'm leaning towards maybe one member/one filter
Hi! I'm currently playing around with the kinematic_character3d example, potentially gonna try to implement the collide and slide algorithm. At the moment I have an issue with it that I'm not really sure how to approach.
My floor is made of tiles that each have a static body collider. My character has a capsule collider. When I walk from one tile to another, I sometimes get a large upwards impulse and fly into the air, even though the floor as a whole is a completely flat surface (just made of multiple colliders).
You can also see this in the official example - when you hug the wall facing the camera and walk towards the ramp, once you cross the edge between the wall's collider and the ramp's collider the player gets pushed away from the wall a bit.
I can sorta see why this may happen, but I don't know how other game engines / character controllers solve this.
At the moment of walking across the edge the collision vector does not point completely upwards. I don't know if this is a problem at the level of collision detection or at the level of collision response.
My approach currently is to never work with CollisionLayers directly, but with a custom enum I call CollisionLayerPreset. Every collider gets one of these, and when added, it adds the correct collision layers.
See code here
ok thinking about it more, 1 membership/all filter makes sense if using && for combining collision groups, 1 membership/1 filter makes sense if using ||, since the main thing we'd want from a user standpoint is custom groups that want to interact with the default should interact with it. If using && then the default needs to include all otherwise you miss those and don't have a way to interact with default objects
&& vs || however is another question
does it make more sense intuitively for a consensus between the two for the interaction to happen or allow either to decide whether the interaction should happen
ya that makes sense for a layer ontop, but I do think we should still make more sane defaults
that essentially does the "overwrite defaults for everything" approach
The consensus approach allows me to currently say "The player is both a character and the special player object" and now the rest of my code can just work with "character", except for a few special cases that need to work only with "player"
Not saying this outweighs the alternative, but this is a nice result of the current system that I want to highlight
Yeah, I just mentioned it because this setup makes your problem easy to solve for the moment, as the default preset can just exclude sensors
Agreed, this is too much boilerplate for something most games of a certain complexity will want
One thing that is not working nice with the current bitmask approach is that editor support is basically nonexistent
To work with Blenvy, I still have to create this Preset struct, even if I was able to easily change the default layers
I think you could still do this with the || approach
you'd just have 2 layers, character, player right?
I guess hmm, yeah nvm I see what you mean
like a "player only" door that will collide with characters, but not players
or maybe im a bit confused
im a bit delirious right now, need to sleep
Good night ๐
It depends a lot on the usual way things are done in the engine, in bevy cameras default to just the "default layer" so I guess it makes sense for physics to do that too. Probably would need some clear docs so people either keep the "default layer" empty or preferably a sensible default in their collision layer type ๐ค
Defaulting to ALL/ALL isn't particularly useful behavior at the very least
One thing I noticed when I worked on a car: RevoluteJoint has a default angular velocity damping of 1.0, so my car kept flipping forward above a certain speed, exactly like in your earlier video (it's as if brakes were applied all the time). Setting it to 0.0 solved my issue.
Although it doesn't really fix the problems you're seeing, you can use a floating character controller to mitigate most of these issues: https://youtube.com/watch?v=qdskE8PJy6Q
This is the approach Bevy-tnua takes
A detailed look at how we built our physics-based character controller in Unity for our game Very Very Valet - available for Nintendo Switch, PS5, and Steam
BUY NOW!! https://toyful.games/vvv-buy
~ More from Toyful Games ~
- Animation Deep Dive mentioned in the video - https://toyful.games/blog/character-animations
- Custom Car Physics in Unity...
@vestal minnow hey qq, using avian right now for a basic character controller with 2d sprites
is there a way to have a pixel perfect collider (my game is pixel art, and the pixel art is small)
and have it change dynamically based on the current sprite that is being used (animation)?
like if a character is jumping and they do a flip, smaller collider for the flip and larger for idle etc
No built-in functionality for that, but you can either just re-insert the Collider component or call collider.set_shape() to change the shape. For a character controller you'd typically want a capsule collider or maybe a (rounded) rectangle.
I think you could get the sprite's dimensions at run-time roughly like this
fn my_system(
mut sprite_query: Query<(&Transform, &Handle<Image>), With<Sprite>>,
assets: Res<Assets<Image>>,
) {
for (transform, image_handle) in sprite_query.iter_mut() {
let Some(image) = assets.get(image_handle) else {
continue;
};
let scaled_image_dimension = image.size() * transform.scale.truncate();
// Do something with sprite dimensions
}
}
and update the collider based on that
that is super useful thank you
is there a way to get the sprite shape for a pixel perfect collider?
like a polygon 2d in godot that automatically sets the shape?
There's this 3rd party crate for generating colliders from images with transparency, and it has support for bevy_xpbd (Avian's predecessor), but it hasn't been updated for Bevy 0.14 or Avian
https://github.com/shnewto/bevy_collider_gen
Do you really need pixel-perfect colliders though? Generally they're not as great for performance or stability and the accuracy is rarely necessary
especially for something like a character controller, unless it's some really weird shape or something
capsule or rectangle ftw
yeah it's not super needed i thought it would be interesting to test it out though
thank you @vestal minnow for your help!
I think one nice bit about this how well it mirrors ecs components and queries
I'll keep that option in mind, but I'd like to learn how the underlying issues are usually handled by regular kinematic controllers if possible, to have something to compare the floating version to. Also I wanted to make sure if the problem I encountered points toward a bug in avian or parry, or if it's expected behavior that needs to be handled by the collision response
Typically that problem is solved via skin/floating off a surface a bit
I'm assuming the problem is just the KCC in the examples uses a naive/very simple collision response compared to the actual physics and tiles like that are a notorious problem with them
I'm making a simple KCC with collide & slide for my own game but I just don't really need a lot of features like auto-stepping
I have a collide & slide function you can use if you'd like, but just know you'd have to re-implement anything you'd want in a KCC still
Oh yeah that would be super helpful to take a look at, thanks!
also it isn't necessarily a bug in avian/parry, just a bug in the example for kcc
I think that collision response stuff (flush colliders right next to eachother) is usually solved more with contact manifolds? I have no idea, jondolf would have a much better idea of how that stuff works
the issue with bumping into tile edges is mostly a ghost collision issue, explained here
https://box2d.org/posts/2020/06/ghost-collisions/
Dealing with ghost collisions is a challenging problem in game physics. The basic idea comes from motion across a flat surface. You may have several shapes joined together to make a flat surface, like a triangle mesh in 3D or a chain of edges in 2D. Convex shapes can hit the internal connections and have their motion blocked. This is undesirable...
oooo box2d article ๐ฎ
it can be fixed for chain shapes or polyline colliders, and trimeshes (Parry can fix internal edges), but separate colliders don't have the knowledge of representing continuous surfaces like that, so the issue can't really be solved for them entirely
speculative collision can also make ghost collisions a bit more prevalent
the fixes are generally to use rounded colliders for e.g. the character controller (a capsule is good) and to maintain a slight margin/skin/offset around the character so it's slightly "floating"
disabling speculative collision for the character might help slightly as well, at the cost of potential tunneling issues (could use SweptCcd instead though)
In theory we could maybe make some tilemap collider that fixes the issue for them and has other optimizations, although I don't remember seeing prior art there
Thank you for the explanations! I'm not super familiar with game physics yet and have been having a hard time finding resources. Knowing the term ghost collisions will already help a lot
Does disabling speculative collisions mean setting the SpeculativeMargin to zero?
Yup
box2d's articles are a great resource for physics, also if you're interested in networking gaffer on games is great for that
I promised myself not to look into networking until I've released at least one solid single player game haha. But will definitely check out more of box2d articles!
thierryberger makes a good point of maybe the first layer should be the default, but also should still include that in the derive/let the user rename it normally, just with documentation about it
@vestal minnow ty for the add function in avian, I've already built an entire virtual computer with it
np, I'm glad you found it useful ๐
are shapecast results in avian accurate when penetrating?
In case any of you wanted to give Blenvy a chance, I just finished a PR that adds a general quickstart for how to setup Blenvy and a tutorial for how to best integrate Avian colliders ๐
For those who don't know it, Blenvy is a plugin and a Blender addon that allows you to use Blender as 3D Bevy editor. It's the sole reason I did the whole deferred collider rework that resulted in Avian's ColliderConstructor looking the way it does today!
I wonder if the crate is already at a state where it can support most things a top-down 2d game needs, with respect to colliders with projectiles/swings which should affect physics (push back, pull, etc), and be kind of ergonomic with it compared to rapier/rolling my own thing
It looks promising but the v0.1 tag is scary haha
Those features are supported, and ergonomics are generally wayyyyy better with Avian than with Rapier
Good to know
Iโll look further into it, thanks ๐
Thanks for the tip! Dampening is what Iโve been using for breaks so Iโll see what changing the โno breaksโ value to 0 instead of 1 does some time today
I was actually very interested
I need to start building a map and placing stuff by hand in files is rough
Hi, I have another question about the kinematic character example. Specifically this part:
for contact in manifold.contacts.iter() {
if contact.penetration > 0.0 {
position.0 += normal * contact.penetration;
}
deepest_penetration = deepest_penetration.max(contact.penetration);
}
If I understand correctly this is the code responsible for resolving overlap between colliders. But wouldn't this overshoot the goal if there are multiple contact points in the same manifold? E.g. when my capsule shaped character walks against a wall there would be multiple contacts each with the same penetration, but moving the body for each contact pushes it to a distance of three times the penetration depth. So would this perhaps be better?
for contact in manifold.contacts.iter() {
deepest_penetration = deepest_penetration.max(contact.penetration);
}
if deepest_penetration > 0.0 {
position.0 += normal * deepest_penetration;
}
With this code I can walk against walls without issues, whereas before I'd get bounced back in a strange way.
thanks again for the code! I've been playing around with it and have some questions if you don't mind. So if I understand correctly, with this approach you do not run a system using Res<Collisions> like in the kinematic controller example, and instead you manually cast the shape of the player in the direction of the velocity and compute the next position based on that. Is that right? With that approach I can now smoothly walk across the floor and and avoid ghost collisions. Second question is, how do you deal with multiple colliding objects? Not sure if I'm doing something wrong but when I walk against a wall at an angle I don't move at all. Also, since position is calculated manually, do you set LinearVelocity to zero to avoid the rigidbody being moved automatically by the physics engine? (Sorry if I'm asking too much ^^')
You're all good! I have some bugs I need to fix with it and one I needed to do was use the velocity instead of setting position directly
but yeah, if the distance is too low it ends up not moving at all as well
I'm going to be working on that tomorrow though
and yes, collide and slide is just a bunch of shapecasts in the direction you wanna go
Got it! I think that was the largest conceptual hurdle I had to adapting the example to collide and slide. I'll play around with it some more later, this seems like a solid base upon.
Btw if this works out the way I imagine then Bevy would enable much more precise character controls than are possible in Godot, since apparently it doesn't support sweep tests
it definitely does
https://docs.godotengine.org/en/stable/classes/class_shapecast2d.html
Inherits: Node2D< CanvasItem< Node< Object A 2D shape that sweeps a region of space to detect CollisionObject2D s. Description: Shape casting allows to detect collision objects by sweeping its shap...
afaik you can also use the physics server, not sure tho
lmaoo code from Godot
I can't say I fully understand the problem (that's part of the reason I'm playing around with this in Bevy, to learn what goes into contact resolution) but a friend of mine has been fighting to get a decent character controller in Godot for years but has ultimately given up because Godot just is incapable of giving accurate collision information (in 3D at least). The project leaders have also admitted the physics is buggy and they can't find anyone to fix it
He wrote a long essay about the problem here, I hope to learn enough game physics to be able to understand it
https://github.com/godotengine/godot-proposals/discussions/9646
@vestal minnow Did you look at the compile errors without parry yet? If not I could make a PR if you let me know what solution is desired there ๐ค
Not yet, but I can start working on it right now
I'll read this later, based on a quick look at Godot's KCC implementation the collision logic did look a bit weird
only if it interests you of course :) it's a bit of a long read
Quick assets processing question -- I'm trying to have a dynamic rigid body with colliders built from a gltf asset, and prevent physics from running on it until the asset is loaded & colliders built. I'm a bit confused which steps have to happen in which order:
- I have a system that handles AssetEvent::Added for my gltf Scene, by inserting ColliderConstructorHierarchy on the scene asset's root entity
- I spawn an entity with all the components I need, including Handle<Scene> (to load the scene from the previous step), but without RigidBody::Dynamic
- I have another system that waits until the scene has been spawned as a child of the entity, then adds RigidBody::Dynamic to it
But this results in the newly spawned object not colliding with anything at all? and I suspect I'm overcomplicating things.
@edgy nacelle you might be interested in this as an alternative
This should fix it ๐ค
https://github.com/Jondolf/avian/pull/477
*once CI is happy
Ah I didn't look into Blenvy until now -- I think it's only for Blender-based workflows? I'm trying to spawn some gltfs that were made in a different tool (onshape), dynamically based on things that happen in-game, so I'm not sure that workflow works for me
Are you creating the gltfs at runtime?
Ah no, it's always the same set of gltf files
Then you should be able to import them in Blender and place them however you want ๐
Ah okay gotcha. I'll have a look at how blenvy does things ๐ mostly I'm trying to understand what I was doing wrong with assets and colliders & how everything was meant to work together
I don't have blender in my workflow yet and it seems like overkill to start using it just so I can use gltf assets (which I already have) in bevy+avian
It compiles and starts at least, need to update a bunch of scenes before I can test it (who decided a scene system using full paths to components was a good idea?)
Everything seems to work except I get rollback issues ... Are there new components in avian to roll back compared to bevy_xpbd 0.5, or is there a determinism issue like @dreamy viper mentioned? ๐ค
I only get rollback issues on collisions
Yea, that's what I get too. If I bump into a ball, the moment it touches another ball things will jump around
Occasionally it also happens on that initial collisions, so I guess it can happen for all collisions. So either a new component needs to be rolled back or collision responses aren't deterministic ๐ค
@vestal minnow what happened to not getting a lot of performance? There used to be multiple systems running as long as the green block at the end (generate_bvh) and even that got sped up by the 0.14 update (my last profile was on 0.13). You basically removed all the bottlenecks. The narrow phase especially. It runs 4x faster on top of not running in each of the 4 substeps
It is a bit funny how the longest running systems in the substeps are two mysterious closures inside the SolverPlugin tho ๐
Oh lmao those are the actual contact solve systems, this probably isn't how you're meant to use systems ๐
Is there a nice way to pass an argument to a system instead of doing whatever this is?
I know there's In<T> but idk how to use it outside of one-shot system stuff
I mean realistically I should at least make these named systems like solve_contacts_with_bias and solve_contacts_without_bias or smth
Const generics? ๐ค
true, I guess that'd work
In<T> outside of one-shot systems normally involves system piping iirc
I guess looking at what these do, this is just more stuff that will just entirely not need to run once we get better sleeping? ๐ค
I found this the other day, it was a nice read and pointed me to some nice resources. I'm working on a collide-and-slide character controller at the moment if you are interested in collaborating.
Not sure how exactly we'll structure things, but with an island architecture the solver would iterate over active (non-sleeping) islands, and each island would solve its own constraints
@vestal minnow Any ideas on this ^?
I setup a system to record inputs to a file, play it back, and manually step frames to debug edgecases
I can't really think of new components that would need to be rolled back ๐ค or why contacts would be less deterministic than before
Hmm I don't suppose warm starting would be problematic? I.e. collision data from the previous frame is used to warm start the solver
The previous physics step that got ran was one in the future in this context at least. So that sounds somewhat problematic ๐ค
That can at least be disabled by setting SolverConfig.warm_start_coefficient to 0
to test if that's the issue
it'll just hurt stacking and overall solver convergence
pretty much all engines with an impulse-based solver do warm starting from what I know
Seems to remove the rollback jitters, but now my spheres slowly sink into the ground 
Definitely not how the laws of physics work 
Yeah, just pipe it in. If you donโt want to do that, I have some manual system invocation voodoo in YarnSpinner that manually passes In args around
Itโs a swamp level ๐
A swamp with explosive spheres 
Ah wait, also try setting NarrowPhaseConfig.match_contacts to false ๐ค
I think there might be a bug where setting the warm start coefficient thing to zero still warm starts the impulses, but just doesn't apply them (but it affects the solver)
Ah, that behaves a lot better
Still seeing some minor jitters when walking into objects that aren't moving ... Do we have some form of sleeping that works now? ๐ค
Objects can sleep when not in contact with other dynamic bodies
with islands we could make colliding bodies sleep too, as long as the energy for all bodies in that island is low enough
Yea that's the only situation where it goes wrong, I don't roll back any of the sleeping components so I guess that makes sense
They also only jitter for exactly 1 frame
I think you'd need to roll back TimeSleeping and/or Sleeping
(or just disable sleeping)
TimeSleeping is a bad name ๐ค
Yea probably both ... I'll just disable it for now cause my rollback doesn't support adding/removing components
This is actually the time it's been inactive enough to count towards the sleep threshold right?
Like this right? .disable::<SleepingPlugin>();
Yea that, or set a negative SleepThreshold
disabling the plugin makes more sense though
For the warm starting issue, is it viable to like... roll back Collisions? Or would that be too expensive / otherwise bad
I don't know networking stuff lol
A resource? Yea I guess that would be doable ... Yet another thing to add to my rollback 
Rollback isn't even really networking anymore, you just save the state every frame and load it when you rewind ... Similar to a time rewind system, but without being a really cool gameplay mechanic 
No more mispredictions ๐ฅณ
The physics are a bit explosive tho, I guess 4 substeps is a bit low without warm starting
yeah warm starting is generally really important for proper stability with impulse-based methods
cc @dreamy viper: It's not really a determinism issue, the new solver just has "warm starting" based on the previous frame's contacts, which seems to be problematic for rollback. It should be fixed by either setting NarrowPhaseConfig.match_contacts to false (disables warm starting, can make physics less stable), or by rolling back the Collisions resource (haven't tried but should work?)
also roll back some sleeping components or disable sleeping
What does warm starting mean?
Huh ... This bevy_xpbd -> avian port was surprisingly simple considering this change even includes updating my sdf collisions ๐ค
22 files changed, 117 insertions(+), 108 deletions(-)
Warm starting uses the impulses from the previous frame as the initial solution for the current frame. This helps the solver reach the desired state much faster, meaning that convergence is improved.
Configuration parameters for the constraint solver that handles things like contacts and joints.
or from Solver2D article
https://box2d.org/posts/2024/02/solver2d/
but does it work 
After disabling warm starting and sleeping, yes
And doubling the substeps ... Which is still a net performance improvement somehow ๐
magic โจ
and there was no warm start in xpbd?
Yes, and sleeping was also broken in the last two bevy_xpbd releases iirc
what is sleeping lol? i'll need to read more on the physics solver
Sleeping is a way to skip calculating for colliders that should not have been affected by physics and thus don't need to be updated
You see, when a rigid body gets very very tired after a long day...
how come if you disable warm start, the object goes through walls? I assume it's becaues of sleeping?
yeah warm starting doesn't really work with XPBD since you can't warm start the Lagrange multipliers properly (essentially like constraint forces)
I disabled sleeping and warm_starting; there's no rollback but the physics seem way worse. My ball still manages to go through walls
What would I need to do to rollback warm starting? Is that a component?
Probably the Collisions resource
And for sleeping it's Sleeping and TimeSleeping (which does not do what the name suggests)
yeah roll back Collisions since that's where the contact impulses for warm starting are stored
Is there a list of stateful resources/components? that could be useful to know for rollbacks
But also that would mean that my physics is less good and less efficient, because there's no sleeping and we don't use pre-cached results? And somehow my ball now goes through walls so I assume I need to increase the substep count
The list should probably also include conditions for when things should be rolled back or not. Like how you only need to roll back ExternalImpulse/ExternalForce if they're persistent
Yea substep count is the way to counter the slower convergance from disabling warm starting
Rolling it back is definitely the more efficient option, especially since the settings need to match between client and server to be deterministic
And not having sleeping on the server sounds like a nightmare ๐
even with substep count 12 my objects cross walls; is the new algorithm better than xpbd?
I'm not having issues with objects going through walls, even with warm starting disabled
Yea I don't have anything passing trough walls either, that's odd ๐ค
And I only have 6 substeps
Tho every collider in my game does behave solid unlike mesh colliders
Stab in the dark, check all the physics components are not nan. I've had AngularVelocity be nan, it would still render correctly and even move in translation, but would phase through everything
For reference, with 6 substeps and warm starting disabled
(one ball does tunnel through because it's pushed by another ball and there's contact softness)
My example is here if it's helpful: https://github.com/cBournhonesque/lightyear/tree/cb/remove-rollback-avian/examples/avian_physics
cargo run -- server and cargo run -- client
thanks! they don't seem to be nan
one thing you probably want is to set the length unit / physics scale if you're using pixels for object dimensions, similarly to what Rapier, Box2D, etc. have
PhysicsPlugins::default().with_length_unit(50.0) or something where 50.0 would essentially be 1 m = 50 px for internal tolerances and stuff
Does avian use the bevy_math's bounding anywhere? If so you'd also want to be on bevy 0.14.1, there's a few nasty NaNs that can pop up from bounding that got fixed in 0.14.1
@cinder summit Do you notice anything important missing here?
https://github.com/Jondolf/avian/issues/478
what is the default?
1.0
"1 unit = 1 meter" if we're assuming SI units
Ideally 2D games would scale the camera projection instead of using pixels for dimensions, at least for physics... but pixels can be useful for many games ig
hm even with 50.0 or 100.0 i get weird physics
Maybe document some stuff with transform -> position/rotation and hierarchy stuff too. They can be indirectly impacted by things like rollback and replication after all ๐ค
yeah, true
hmm I'll clone it and try to see what's happening
I'm not sure how much I can contribute but sure! I'll try to upload what I have so far tomorrow. If you have a repo with your controller I'll gladly check it out
oh @dreamy viper I meant that you should set NarrowPhaseConfig.match_contacts to false, not set SolverConfig.warm_start_coefficient ^
you're having the same issue Nise was initially having ^
but then we're still using warm starting?
No, it disables it since contact matching is the thing that transfers the old impulses to the current/next frame. But yes setting the warm start coefficient should also work for this, there's just a bug there atm
I just tried it, unfortunately i still have rollbacks
I'm actual doubtful that these are the issue. This is just a 1 player game where both client/server have exactly the same inputs
So there should be no rollbacks at all, so warm_start, Sleeping, etc. should have no impact
(disabling these might be important in case a rollback happens)
but there should be no rollbacks in the first place since client/server should basically be running the same simulation
so it has to be a determinism issue
let me remove all the networking stuff and turn it in to a determinism example for you
They don't always both start from the same default conditions right? Like the server usually starts first, then sends something to the client, and if that data isn't from frame 0 warm starting and sleeping could already have some impact
That said I can't actually test that specific thing, since I don't do conditional rollback. My game always rolls back when data comes in
i can't reproduce the non-determinism in my test aha
yep, I also haven't noticed non-determinism when testing in FixedUpdate before
let me add more logs in the networked example, i definitely don't get the same values. Any ideas what I should log? Maybe Collisions?
yes collision data in Collisions would probably be good
Talking about that, I finally took a look at https://github.com/Jondolf/avian/pull/457
It's suprisingly straightforward. I like the scheduling simplifications ๐ Approved
Also, https://github.com/Jondolf/avian/pull/402 got a merge conflict in the meantime :/ bummer
fixed
that was fast ๐
just used GitHub's UI :P
Thanks, I was hoping to get that one in for some optimization in a project
I've been holding off on merging so I could release a 0.1.2 patch without meaningful breaking changes without having to cherry-pick things
I see, that makes sense!
I'm supremely giddy for finally having one of the Are We Upstream Yet issues resolved ๐
actually I have one
Server:
Collision detected event=Collision(Contacts { entity1: Entity { index: 16, generation: 1 }, entity2: Entity { index: 18, generation: 1 }, body_entity1: Some(Entity { index: 16, generation: 1 }), body_entity2: Some(Entity { index: 18, generation: 1 }), manifolds: [ContactManifold { contacts: [ContactData { point1: Vec2(0.34488943, -14.996035), point2: Vec2(13.712224, 20.0), normal1: Vec2(0.02299263, -0.99973565), normal2: Vec2(0.0, 1.0), penetration: -0.920166, normal_impulse: 1715.9131, tangent_impulse: 0.0,
Client:
Collision detected event=Collision(Contacts { entity1: Entity { index: 41, generation: 1 }, entity2: Entity { index: 43, generation: 1 }, body_entity1: Some(Entity { index: 41, generation: 1 }), body_entity2: Some(Entity { index: 43, generation: 1 }), manifolds: [ContactManifold { contacts: [ContactData { point1: Vec2(13.712223, 20.0), point2: Vec2(0.34488943, -14.996035), normal1: Vec2(0.0, 1.0), normal2: Vec2(0.02299263, -0.99973565), penetration: -0.920166, normal_impulse: 1715.9131, tangent_impulse: 0.0,
The contact point is 13.712224 on the server, 13.712223 on the client

okay I wonder if that's because the entities are flipped in the data
which maybe results in different computations somehow
Avian sorts entities in collision data based on the entity ID at the moment
I don't suppose there's a way for you to make that consistent across the server and client?
Or another:
server: point1: Vec2(-9.58982, -11.534095), point2: Vec2(75.88797, 350.0)
client: point1: Vec2(75.88791, 350.0), point2: Vec2(-9.589782, -11.534127)
but even if the entities are flipped when reporting events, then the impulses sent to each entity should be roughly the same?
what are usually causes of non-determinism? order of additions/multiplications?
Getting them to match is pretty hard to do, especially once clients start predicting entities
hmm okay
I guess we could try a branch where we have a CollisionPriority which we use to order entities
and check if I can reproduce it in those conditions
Yeah that could be useful, to test if this could actually be the issue
Could you point me to where avian does the sorting?
src/collision/broad_phase.rs lines 240-244
We could technically remove that ordering, but the X-coordinate based ordering produced by sweep and prune is kinda weird and iirc it caused some collision event and warm starting issues
The warm starting issues would be because it's (e1, e2) sometimes and (e2, e1) other times?
yes
and the order changing also used to count as a separate collision event, even if the objects never actually stopped colliding, for example
Hmmm ... Might want to apply that ordering only to how they are fetched/stored and not to the calculations or something ๐ค
To some extent I think this would also be fixed by just having a contact graph. Intersections between entities would just be edges on the graph, and it wouldn't overwrite edges with a different direction or anything
or I guess we could already kinda do that
would just need to check both (e1, e2) and (e2, e1) combinations every time you add a new collision since the order would no longer be guaranteed
But yeah I think the way this should eventually be handled is:
- Broad phase collects all intersection pairs, no guarantees on order (other than determinism)
- Narrow phase iterates over broad phase pairs, and adds undirected edges for each of them in a contact graph, unless an edge already exists
Then the order of the entities for a given contact is essentially arbitrary, but stable
I just uploaded my current code: https://github.com/nezuo/avian-3d-kcc
Right now I'm working on an issue where you get stuck on sloped ceilings. If you want to see that happening, you can run cargo r -- -p stuck.ron and it will play it back. t to toggle play/pause, r to step one frame
is there a tag for 0.1.1? main is broken for me
How is it broken? ๐ค
error[E0433]: failed to resolve: use of undeclared type Vector
--> /Users/cbournhonesque/Snapchat/dev/rust/.cargo/registry/src/index.crates.io-6f17d22bba15001f/avian2d-0.1.1/src/sync/mod.rs:233:46
|
233 | + accumulated_translation.map_or(Vector::ZERO, |t| {
| ^^^^^^ use of undeclared type Vector
hm it must because of a version issue
ah, it's because i wasn't using the same version in lightyear and in my example, sorry
Okay nice, that should be fixable
how come sorting on aabb.min.x doesn't work? sounds like a good idea to me
It's fine, just you can get both (e1, e2) and (e2, e1) and the collision code would need to be changed to not overwrite an existing collision between the entities if the order given by the broad phase changes
And to avoid duplicate events
I think it might be more efficient if the fetching logic sorts and swaps them for you ๐ค
Why can you get that both (e1, e2) and (e2, e1)? doesn't look like it's the case to me https://github.com/Jondolf/avian/blob/cb920f1214294437e092a178cb7390478b2b8e67/src/collision/broad_phase.rs#L216
But yea I guess this source of error make sense, a - b probably errors in a different direction than b - a ๐ค
Box A is sliding against box B, with a.min.x to the left of b.min.x, Box A moves right, the order changes, box A moves back, and the order changes again
The issue isn't that we can't, but that if you do it naively it creates a lot of issues because (e1, e2) and (e2, e1) would not be the same thing
There's also the fun issue where both have the same min.x 
I don't get it lol
you're saying that we can have (e1, e2) once, and then (e2, e1) in a future FixedUpdate?
that's fine no?
this was also very visible in the sensor example before Avian, you would get CollisionStarted and CollisionEnded by just moving the left side of the character slightly outside the sensor and back in again
It uses (e1, e2) as a key to store/load state and fire events
That's where the problems come from
If they flip the state isn't found and the event fires again despite the collision never ending
and that order also determines which entity is entity1 and which is entity2 in collision data, which resulted in the issue of collision data being flipped between server and client in your case
ah i see, because we want to keep some state to check when the collision ends, I see
Maybe we could also keep the entity-based sorting, but make sure that all the ops after that are still deterministic
Yea, so really we need storage to sort them, but everything else to keep the order based on position (because that's deterministic without relying on entity-order)
Importantly this would also need to include events, otherwise user systems will have the same determinism problem ๐
bit perfect determinism really is all sorts of messed up
btw what is the exact cause of the non-determinism due to the ordering?
What kind of floating-point ops depend on the order between e1 and e2?
is it stuff like this linE? https://github.com/Jondolf/avian/blob/ac1fd0269e04f19f6892c62019cbb9ad9c19444e/src/collision/narrow_phase.rs#L509
Were you referring to this line? https://github.com/Jondolf/avian/blob/ac1fd0269e04f19f6892c62019cbb9ad9c19444e/src/collision/narrow_phase.rs#L560
where we use (e1, e2) to check if it's a new contact or not?
well at least i'm happy that we understand the problem
I don't know exactly which operations, but I imagine a lot of math can produce slightly different results based on the order due to rounding and whatnot. And the entity order also affects which shape is first in Parry's contact algorithms, which might impact results because contacts are essentially computed in the local space of one of the shapes and then transformed back into world space
Contact constraints also use the world-space contact normal on the first entity, so if the order was flipped, the normal would be the opposite. Not sure if this would affect results necessarily though
Yes, and possibly a few other places too I think
It looks to me that this is possible:
- keep storing (e1, e2) in
aabbordering inBroadCollisionPairs - this doesn't seem to be an issue because we're querying for both directions
- the only problem seems to be in here (manifold matching), since the order of entities in the manifolds could be swapped. Just for manifold matching, we use the entity order, but the output of
match_manifoldsstill uses theaabborder
Then the order of computations is always based on aabb, and we can successfully match on past data
collision events still need to be handled as well
Oh right, @vestal minnow how similar would a 30 tickrate with 12 substep simulation be compared to 60 tickrate with 6?
Probably fairly similar but I haven't tested much ๐ค Collisions would be checked less often with 30 Hz, so you'd be relying more on speculative collision or other CCD, and of course movement would be choppier without transform interpolation
Btw @cinder summit have you tested yet if speculative collision helps with the tunneling issues you were having at some point with dashes iirc?
I haven't tested that, tho I haven't recently had that issue even on bevy_xpbd 0.5 at 3 substeps
Maybe I lowered the dash speed after I ran into that or something
it should be fine since you used an ordered IndexMap, and elements are added to it based on the aabb order
Sounds like it might be worth trying considering substeps are cheaper than full frames ... I'm thinking of some ways I could combat lag if I ever were to run into it on my game, and changing my code to support different tickrate/substep combinations seems like a nice idea
Sure, it orders the entries, but it doesn't order the entities within those entries
the entities in Contact would be ordered based on aabb order
The issue is that it will send CollisionStarted(entity1, entity2) or CollisionStarted(entity2, entity1) whenever the aabb order changes, even if the entities were already colliding
It iterates over Collisions
for ((entity1, entity2), contacts) in collisions.get_internal().iter() {
// ...send collision events
}
and entity1 and entity2 would use the aabb order
again with a contact graph we would likely handle this differently and it'd probably be nicer
Wait ... Is Collisions used to generate those events? ๐ค
Currently yes
I really should be rolling that back then I guess ๐
But those CollisionStarted and CollisionEnded events are purely for external use right?
Yes
But getting collision events even when you haven't actually stopped or started colliding is weird and could cause bugs in user code
do we have a unit test for this
i'm not sure I understand. I get the concept, but I'm not sure if the order of entities inside Contact is what determines if the CollisionStarted/Ended events are emitted
i think it's rather the during_previous_frame and during_current_frame, which seem to only depend on the manifold matching
yes it's those during_ properties, but that's unrelated to contact matching
contact matching just transfers the impulses
here's my PR: https://github.com/Jondolf/avian/pull/480
yeah this breaks collision events like I suspected
how do you know?
you can try the sensor example and move the character in and out of the left side of the sensor
and get events every time
and break CollidingEntities as well I think
hm that's weird, the code seems good to me though
i'll test it
you're right
my new version seems to work though ๐
although we'll want more thorough testing
Does that still fix the rollback issue?
That looks like it's just moving the sorting from the broad phase to the narrow phase without removing the sorting issue ๐ค
yep, works for me
No because the entities are sorted in aabb.min.x order inside Contact
just not in the key that is used for Collisions
as well as when computing the contact manifold
probably good to have @cinder summit test it as well
mm I guess it could work, although I don't really love the inconsistency
or no it's probably fine...
methods like Collisions::get already don't care about which order users give entities in, from an API perspective
This would technically be a breaking change if anyone is relying on the entities being sorted by ID for whatever reason though
so I'm not sure if we could ship it in a patch without breaking semver
it's ok it's not really an urgent patch imo
rollbacks don't really cause issues visually, they just lower the perf
yeah, cool
I'll test the PR more tomorrow, I think it should be a decent fix for now
I'd like to "do it properly" once I rework the narrow phase to use a contact graph though
In the contact graph solution, Contacts would still be sorted in order of aabb,
but Collisions would not be an IndexMap anymore, instead it would be a graph where each node is an entity?
yes, each node is an entity, and each (undirected) edge is a collision
Nice! I've studied the code and played around with it a bit, looks more solid than what I have so far. Regarding the getting stuck on the ceiling, I may have found something that improves it? It's hard to tell because I don't know what the exact button inputs were in the replay, but what I did was change
if hit.time_of_impact >= SKIN_WIDTH {
transform.translation += direction * (hit.time_of_impact - SKIN_WIDTH);
}
to just
transform.translation += direction * (hit.time_of_impact - SKIN_WIDTH);
i.e. drop the condition. The reasoning being that in the case when hit.time_of_impact is smaller than the skin we are in a situation that shouldn't happen (being too close to a an obstacle) and what we should do is move backwards by the necessary amount to be at exactly skin_width away from the collider. By dropping the condition hit.time_of_impact - SKIN_WIDTH can become negative and moves us by that required amount (I think, unless I got confused in the math somewhere). Maybe try it out and let me know if that fixes the issue you saw?
Ah but when I enable the floor I get stuck under the ceiling as well when trying to move sideways. I think to fix this the algorithm will have to be expanded to slide along multiple planes, like Freeman mentioned in his post. I'll try to figure out how that works from the Quake source code he linked
https://github.com/id-Software/Quake/blob/bf4ac424ce754894ac8f1dae6a3981954bc9852d/QW/client/pmove.c#L100
You may already know this, but hereโs a paper about the movement code in quake
I did not know, thank you!
Got a very basic broad phase working with Griffin's OBVHS ๐ had to extend it to support proxies and arbitrary leaf data
pretty unoptimal atm and I don't have separate dynamic and static trees yet, but at least I seem to be getting intersections correctly
Avain specific newbie question I posted in #1019697973933899910 : #1269624819972964372 message
@vestal minnow I added resource rollback, added Collisions to it, and set my settings back and it seems to work ๐
I tried adapting Quake's movement logic. I can't say I 100% understand it, and there are some things I'm not happy with (sliding under the ceiling feels wrong somehow), but I have not gotten stuck anywhere using this code. Also made some random changes like adding the floor and some fake gravity. Let me know if you'd like me to make a PR, or you can just copy paste any parts you like manually.
https://github.com/tracefree/avian-3d-kcc
Also do you mind if I show this to Freeman? He may have some more advice on how to make it stable and feel smooth
Thanks for taking a look! You can definitely show it to him. As for the ceiling issue, your suggestion to remove the condition helps but the issue still exists. I did some work on this yesterday and I think this is the issue:
I'm still looking into a way to fix this though. I'll take a look at your quake-like code and see if I can understand it and learn from anything there.
@harsh condor
Let me know if you figure it out, I'd like to understand the code I wrote as well ^^' The paper that Jan linked above really helped, but some details of the algorithm are still a bit unclear to me.
Hmm, I wonder what can be done about this if this is the problem and the quaky version doesn't fix it... perhaps an intersection test in a separate system that teleports characters out of solids?
I think I've seen the quake code with a fix for it, I'll have to look.
quake also has an "unstuck" algorithm that would help but I don't want to add that until the base movement code is good
Makes sense
your code is based on quake 1?
Yeah, that's what I was linked to and what the paper above explains. Probably a good idea to compare to later versions, but think the basic idea behind the algorithm hasn't changed much
so I noticed a difference with your implementation that was confusing me
let mut walk_along_crease = false;
'clip_planes: for i in 0..num_planes {
projected_velocity = extra_velocity.reject_from_normalized(planes[i]);
for j in 0..num_planes {
if j != i && projected_velocity.dot(planes[j]) < 0.0 {
walk_along_crease = true;
break 'clip_planes;
}
}
}
here, you project extra_velocity onto the planes but quake projects the first original_velocity
quake's makes sense because it's basically making all the projected velocities that were seen in the past
There were a few places where I didn't know what I was doing and kind of guessed to be honest :P I just tried changing it to original_velocity and that works too
(i.e. character_controller.velocity * time.delta_seconds())
Got separate trees for active and sleeping/static bodies working, kinda cool to see it visually like this
white = internal node
cyan = active primitive
red = sleeping/static primitive
need to improve a bunch of update logic and maybe handle "fat AABBs" still
then I need to stress test this and compare against both the old SAP broad phase and Rapier
nice to see you working on sleeping improvements! I piggy back on it to exclude some stuff from updates, works nice ๐
The what now
That's what Box2D calls them ๐ I'll use a different term though if I end up implementing them
It's just this (source)
If I do full rebuilds anyway then I probably don't need it though
My current idea which quake 3 actually does is to nudge the velocity by the normal vector of the plane. The one thing I'm unsure about is that in this ceiling case it would cause the velocity to point slightly downwards. This is the quake 3 code:
This does fix the issue though. I'm just not sure what I should do about the y component.
i solved my weird explosive spawn issue btw, was caused by rotations on my player character, not overlapping spawns after despawning something. changing too many things at once ๐ฌ
is it ok to have a child entity with a sensor collider, with a parent that has a normal collider? i just want a sensor to stick out in front and inherit the rotation
i recall that wasn't fully supported in bevy_xpbd a while back
I think that should be okay
great
Not a very thorough benchmark yet, but I'm getting some interesting results in a basic stress test ๐
Setup is a grid of 100,000 bodies with a circle collider shot in random directions, and being slowly pulled back towards the center. The colliders have collision layers set up such that they do not interact, to focus on stress-testing the broad phase for tons of moving dynamic objects. Parallelism is enabled for all of these tests.
- Old SAP broad phase: Starts at 6 fps, goes up to 15 fps as bodies spread out and move less relative to each other.
- New BVH broad phase: Stable 20+ fps with
Collider, and 38 fps with a custom collider. This implies that Parry'sSharedShapemight be a bottleneck? - bevy_rapier: Starts at 7 fps, and degrades over time all the way to below 2 fps. Slowly recovers to 7 fps as bodies get pulled closer again.
bevy_rapier seems very suspicious here, and it's interesting that the performance characteristics are opposite to my old SAP in that performance seems to get worse as objects spread out. Not sure what's going on there, although I know they have their own "Hierarchical-SAP" broad phase.
A better test would probably be to compare against Rapier directly, but it's interesting to see how bevy_rapier seems to struggle here
The collider type having an effect is also interesting, as there isn't much that operates on it with this setup other than computing the AABB and mass properties, and I think my custom collider type is doing basically what Parry does, just without dynamic dispatch
@grizzled depot You may also be interested ^
Interesting if true. I know dynamic dispatch is slow, but I didn't expect it to make this much of a difference!
Here's also the code for the bevy_rapier stress test if you'd like to test it yourself and check that I didn't make any mistakes. Make sure to enable parallel for bevy_rapier and multi_threaded for Bevy (although that doesn't have a large effect)
also add rand
One thing worth noting is that my broad phase can handle BVH traversal to collect the collision pairs in parallel, so it seems to scale decently; I'm not sure if Rapier's broad phase is parallelized, didn't look into how it works too deeply
(I also still haven't even properly optimized it lol)
Also worth testing cases with low numbers of bodies. My raymarcher got a ton slower with BVH with only 5 objects, but it's a lot faster with 500 objects
With 10,000 bodies:
- Old, SAP: 1200-1400 fps
- New, BVH: Stable 1400 fps
- bevy_rapier: 130-140 fps
With 1024 bodies: - Old, SAP: 1600-1700 fps
- New, BVH: 1600-1700 fps
- bevy_rapier: 900-1000 fps
At very small scales the difference is of course smaller and the old SAP might even be faster in some cases if bodies are well spread out along the X-axis, but with such a high fps the difference probably doesn't really matter anyway
Also worth noting that the new BVH broad phase of course has separate dynamic and static trees, so it should generally perform better for scenes with a lot of static geometry. I need to benchmark that separately though
The new BVH might be a bit slower when single-threaded though atm
the BVH building is the biggest bottleneck with this, even though this is using the Bvh2 with fastest_build, which is basically the fastest option based on tray_racing
The building is single threaded? 
Hmm I do have parallel enabled for OBVHS ๐ค
Oh, maybe it just doesn't register things for tracing
parallel only seems to give a few FPS more ๐ค
Just imagine how slow it would be with ploc-bvh ๐
the multithreaded build should be like four times faster
nvm
I was looking at vfb not tfb
apparently single-threaded fastest build is only slightly slower than multi-threaded fastest build
I feel like my numbers are still pretty low compared to a good dynamic BVH though, like what Box2D has
I don't know much about anything in this area but there's this library from Ralith and it may be of interest?
oh I see this library has already been talked about here a month ago
@drifting marsh have you tried it with velocity instead of directly modifying position?
I'm having some issues of my character scaling walls
tried what with velocity?
the weird thing is that Box2D seems to have 3x faster BVH rebuilds even when doing full rebuilds with it
getting 5 ms full rebuilds for about 110,000 bodies with Box2D V3, and 18 ms in mine
modifying the velocity instead of setting the position directly
I guess you'd also not want physics to integrate that
or well hmm
For the traversal I also always have one or two task pools doing way more work than the others, not sure if I can do something to fix that? Tinkering with batching strategies didn't seem to help
do you mean setting the velocity to end_position - start_position where end_position is calculated via collide-and-slide?
Ya
idk why but its broken a lot
trying to figure it out
I can think of cases where that doesn't work
for example this
setting position manually calculates the red vectors, but start to end is blocked by an obstacle
ya true
I mean effectively it shouldn't matter
idk the issues are just weirder stuff like climbing walls
do you mean if you walk into a wall it raises you slightly up or you are actually going to the top of the wall?
I'm working on adding gravity to my controller now and I have the first issue
ya, it raises slightly up
I think the issue is there is a penetrating contact point on the cap of the capsules
but im not entirely sure
I don't see why else it would decide to go upwards
well, this is the next issue I plan to work on so I'll let you know if I figure it out
gotcha ty
Yeah, performance of bevy_rapier compared to rapier is suspiciously bad, I realized that on https://github.com/dimforge/bevy_rapier/pull/551
Hmm yeah this is difficult because the correct nudging direction would need to depend on another shapecast to check if the direction is available and otherwise modify it to slide along the best available plane, but that's what the initial collision algorithm is already doing so it'd be subject to the same errors...
Do you have collisions enabled in addition to the sweep testing? In my local copy I actually removed the collider of the character and spawn a capsule only for the collide and slide algorithm (not attached to any rigidbody). So the body does not get blocked by the corner if you set the velocity instead of the position. I tried it but have not found much benefit though, actually I'm concerned setting the velocity and letting the integration handle the actual movement leads to more floating point errors
Btw Freeman is preparing to send me the results of his research into KCC algorithms, I already received a great tip on how to choose a better direction to slide in when in contact with more than two planes
With CollidingEntities how can I get back the specific entity types that collided in the case where there are multiple colliders per entity? I'm not really sure how best to do this
What do you mean by "specific entity types"?
I'm very interested in the outcome of this, as I love quake-style character controllers ๐
So not sure I have the ECS parlance/understanding right, but how do I get from CollisionEntities to an entity marked with a particular component, say my "Player" component, so I can know "the player has collided with X"?
Eh, I think it's reasonably close with full builds at large scales with lots of bodies moving. Box2D's incremental updates or partial builds are much faster when there's less changes though, and I imagine it'd especially be good for the static tree to make sleeping and waking efficient
Usually that entity will the same as the one holding the RigidBody, which you can get by checking the ColliderParent component of the entity you've collided with
If you want to know what the player collided with, you could query for its CollidingEntities with something like player_query: Query<&CollidingEntities, With<Player>>.
Then if you want to know what those entities are, you could either:
- Have
CollisionLayersfor your entities and check what layer(s) each of the colliding entities belongs to - Check if the colliding entities have some marker component using another query, for example
checkpoint_query: Query<(), With<Checkpoint>>, andcheckpoint_query.contains(entity)
And yeah if you have child colliders, then the rigid body entity can be found in the ColliderParent component
Personally my goal is not to create an exact copy of Quake's movement (I've never even played Quake) but from what I can gather lots of games are inspired by something like it. So I'm looking forward having a controller with very solid and stable collision response that can then be used as a base to create custom solutions :)
That sounds very cool too ๐ Please ping me when there's something to test!
Will do!
Also, do you think the collide-and-slide parts are upstreamable?
I imagine there's some on_ground check as well?
Yeah but I'm not using it extensively yet, for now I just check for it before applying gravity. If the controller turns out general enough I think it would be really cool to include it in Avian eventually, but I've never upstreamed anything so I don't know how that works
The current plan is to not include a full character controller, but generic collide_and_slide and on_ground functions (see godot)
Makes sense
It would be supremely nice if we could use your implementation as a starting point. If you need help upstreaming, let me know ๐
In that case I'll try to rewrite my version into a function instead of a system
@drifting marsh How would you like to continue collaboration? I'll likely play around a lot with different approaches in my local version, should we keep developing at our own pace in separate repos and exchange ideas/code sections occasionally, or would you rather have me make PRs to your repo? Also how would you feel about adding a dual MIT/Apache 2.0 license? (I have a local project that's not a fork of your repo but whose collision response is heavily based on yours plus the multi-plane-sliding, with your permission I'd like to publish that under MIT/Apache)
Yup the current plan is not to upstream a full character controller to the crate itself, but different kinds of character controllers would have examples in the repo so that people can pick what fits their needs or use them as reference for a custom solution. I would like to add built-in collide and slide though, and maybe ground detection
Relevant issue: https://github.com/Jondolf/avian/issues/450
@vestal minnow How does CollidingEntities get updated? Should I be rolling that back too or is it just based on whatever touched in the last physics update? ๐ค
Thanks for the pointers, was able to get what I want working ๐ however I suspect there is a much more concise/correct way than what I ended up with:
fn interact(
player_collisions: Query<&CollidingEntities, With<Player>>,
cp: Query<&ColliderParent>,
interactables: Query<(Entity, &Interactable)>,
) {
for ce in &player_collisions {
for cent in ce.iter() {
let parent = cp.get(*cent).unwrap().get();
if let Ok(interactable) = interactables.get(parent) {
println!("{:?}", interactable);
}
}
}
}
It's updated at the same place as where CollisionStarted and CollisionEnded are sent. Collision starts -> add to CollidingEntities, collision ends -> remove from CollidingEntities
And that logic currently uses the during_current_frame and during_previous_frame properties iterating over Collisions
You can use some iterator functions like filter_map to make it easier to read, but approach-wise, this is the correct way
So if I have CollidingEntities for frame n, roll back 10 frames, chances are it'll be reporting ghost collisions that don't start for another few frames? ๐ค
If I understood what you mean then yeah, I think so
Guess that's one more component for the list then ๐ค
Added it to the GitHub issue ๐
I don't think you have to shapecast to decide on the direction. On the next iteration, the character will shapecast in the nudged direction which will handle collisions with the new direction.
Yes, I'll add a license, I always forget to. I'd say it's better to work in separate repos. The most helpful thing for me would be someone to bounce ideas off of.
How can I enable SweptCcd for a Trimesh-Collider constructed by
ColliderConstructorHierarchy::new(ColliderConstructor::TrimeshFromMesh)
๐
Alright, sounds good!
ColliderConstructorHierarchy generates colliders for a hierarchy of entities, and IIRC swept CCD doesn't really work for child colliders currently. In general it can also be very expensive for trimesh colliders
So I don't think there's really a good way to make that work. What are you trying to do?
Thank you for your answer! Iโm working on a game where the ground is a Trimesh Collider and the player is Kinematic. Whenever the player walks under an object, such as a rock, causing the player to be pushed from top to bottom, the player falls through the ground.
The game: https://x.com/m_morgenthum/status/1820098296976982103?s=46&t=QoSz6HTtQm3FeOZyydFPKQ
hmm
It seems like when the normal of the hit object and the direction you are going are parallel the reject from becomes vertical
which makes sense I suppose?
actually maybe this is just floating point shenanigans
hmm, I'm not entirely sure how to solve this
me when doing anything math related
I'm just going to add a buffer zone around that instability lol
if the length is < 0.05 just ignore, act like its completely flat
does no one actually solve this, just ignores it? I guess you could say that gravity would cancel it out for the most part, but still
I guess really you need a basis
idk still confused on how to solve this, I might just say fuck it and set y = 0 since I don't really care about sliding up
actually wait i solved this before
Trying to debug more system-ordering issues - I have a system that sets:
my_entity.linear_velocity.0 = Vec3::new(20.0, 5.0, 0.0); // fling sidewaysmy_entity.collision_layers.filters = EXCLUDE_PLAYER
so that you launch this sphere collider through the player. But what I'm finding is that if you start close enough, the projectiles seem to "bounce off" the player, even though the filter now excludes the player. It seems like a collision is happening with the new large velocity, but the collision filters haven't been updated yet. Is there a pattern I'm missing here that would make this just work?
Maybe I need to filter the collision during PostProcessCollisions?
This might be of help https://arxiv.org/ftp/arxiv/papers/1211/1211.0059.pdf
i want a circular 2d arena, but i don't think there's a hollow 2d ring/donut shaped collider option available. should i just fake it with a bunch of rectangular colliders overlapping to make an inner circle? or is there another approach?
Do Sensors trigger an event on collision that can be observed using the observer pattern? If not, is that feature planned?
using primitives might be a good idea if you end up dynamically "resizing" it
and at least torus exists but you need to make it from primitive i think
Not yet, but I'd like to figure out an API for this, yes. One problem is that the collision events store both of the entities in the collision, but for observers the trigger already stores the observed entity so it's redundant to have both. Ideally the collision data would also be explicit about which contact point belongs to which entity. So we'd probably need separate events for observers and "normal" buffered events
As for sensors specifically, in the future we might also add a separate intersection graph and send intersection events instead of always computing contact data for sensors since it's more expensive and often unnecessary
There's also an issue about entity events, although that is about bevy_eventlistener
https://github.com/Jondolf/avian/issues/361
I see, thanks! Looking forward to that then. In Godot I'm very used to connecting signals e.g. when the player enters an area around the goal, an intersection graph sounds useful since I don't need contact points in such cases
Yeah I would probably use a bunch of rectangular colliders, you can also use Collider::compound to make it just one collider composed of multiple shapes. You might also be able to make an Annulus mesh, extract the vertices and indices, and use convex decomposition to build the collider, but 2D might not have a nice API to do that for you at the moment ๐ค
But yeah a ring or donut shape would be pretty difficult to add without approximations like this since most collision detection algorithms rely on convexity
(unless you're NiseVoid and have SDF colliders)
alright thanks, i'll probably take the easier option for now and create a bunch of rectangles
hm.. now i kinda want to make that dynamic ring just for fun...
Observers cannot run in parallel FYI, so this may or may not be a performance hazard
You can still trigger them in parallel through ParallelCommands, right (although we currently send events serially)? So on Avian's side it shouldn't be a performance issue. The events also aren't used internally for anything, they're just for users.
Buffered events may also be valuable still, so they would probably continue to exist alongside observer events. Although I would like to reduce event spam and avoid sending events unless necessary. I think contact reporting is opt-in in most engines
Hmm, didnโt know about that. Thatโs cool.
One way to do opt-in, which would be a bit whacky, would be to add a OnCollision(SystemId) component to a collider
That way, you can run a one-shot system on collisions with that specific collider
See this code that uses this pattern for OnPress: https://github.com/TheBevyFlock/bevy_quickstart/blob/main/src/screens/title.rs#L12
I would probably just do that with observers to avoid having to manually register systems, assuming there's a way to check whether a given entity is being observed before triggering the event
Another nice thing about observers is that we can bubble events from child colliders to the parent rigid body
(which may or may not be a good idea)