#Avian Physics
1 messages Β· Page 14 of 1
XPBD patent situation update: #1171955566436946031 message
I still intend to work on the solver switch and rebrand regardless, since it should be beneficial anyway, and I'd like the name of the crate to not be tied to a specific simulation method
But XPBD could still be available as an option, maybe even used just for joints and some other things but not for collisions
having the same issue using ldtk and xpbd_2d 4.2, it causes entities with physics to be rendered behind their intended layer
I'm also using ldtk, so it might be related to that - I also noticed that the z changes depending on what layer the entity is on in ldtk, but is only negative when there's a collider
yes
same
usually ldtk has layers increase z by 1 per layer starting from the bottom
but with a collider it seems that now my entities local transform.z gets offset by -N where N = layer
so the global transform ends up being 0
meaning it renders behind everything else
also this didnt happen in bevy 0.12 + xpbd 0.3 for me, only showing up now as ive upgraded to 0.13 + 0.4
oh interesting, I only started using this a couple days ago so I didn't realize it worked on earlier versions
I'm interested to know how people are approaching collision logic. As far as I can tell, you must evaluate collisions both ways in order to cover all your bases. Here's an example of one of my collision systems:
fn despawn_on_collision_system(
mut collision_event_reader: EventReader<CollisionStarted>,
ability_query: Query<&HitMatrix, With<DespawnOnCollision>>,
team_query: Query<&Team>,
mut commands: Commands,
) {
for CollisionStarted(entity_1, entity_2) in collision_event_reader.read() {
despawn_on_collision(&ability_query, *entity_1, *entity_2, &team_query, &mut commands);
despawn_on_collision(&ability_query, *entity_2, *entity_1, &team_query, &mut commands);
}
}
fn despawn_on_collision(ability_query: &Query<&HitMatrix, With<DespawnOnCollision>>, origin_entity: Entity, collided_entity: Entity, team_query: &Query<&Team>, commands: &mut Commands) {
let Ok(origin_hit_matrix) = ability_query.get(origin_entity) else {
return;
};
let Ok(origin_team) = team_query.get(origin_entity) else {
return;
};
if let Ok(collided_with_team) = team_query.get(collided_entity) {
if !origin_hit_matrix.can_hit(origin_team, collided_with_team) {
return;
}
}
commands.entity(origin_entity).despawn();
}
Is this basically what other people are doing? I feel like it'd be more ergonomic if 2 mirrored events were fired for every collision, but I'm sure that'd have performance overhead.
#1124043933886976171 message
An interesting approach. Thank you
@silk mauve this is my approach:
for (a, b) in [(event.0, event.1), (event.1, event.0)] {
hi, is there perhaps some way to temporarily exclude entities from the physics simulation? in my case, when the game actors enter vehicles, I want the actors to be invisible and sync the actor position to the vehicle position at all times, with collisions turned off. it should almost be as though they don't exist, without despawning them because i need some parts of the entities for game logic. things I tried so far: i removed the RigidBody component temporarily, but that keeps collisions going. I removed the Collider component temporarily, and that gives me warnings about low mass. I added the Sleeping component, but that only stops movement, not collision logic. any ideas?
maybe try adding sensor?
hmm maybe not if you want to keep it synced with vehicle tho
removing both RigidBody and Collider does the trick, but I'm getting some glitch with this, makes me wonder whether I'm doing the right thing.. (The glitch is: When the driver exits the vehicle and I add RigidBody back in, then any Transform/Visibility changes I do to the actor in the same frame don't seem to have any effect and get reverted almost immediately. But I can work around this~)
btw
what do you do if like
two objects have multiple collision points with each other?
Then you could end up like doubling the amount of impulse you apply or something right?
do you just like do a convex decomposition so that two objects can't collide at multiple points at once?
Multiple contact points can be very important for stability in many cases, like for stable cube stacks, for example. I haven't thought about this much, but I don't think you generally need to handle it in a special way. Even if the impulses were doubled, you typically wouldn't notice it other than maybe for large impacts.
The impulses do take the relative velocities of the bodies at the contact point into account, so applying an impulse for one contact point will also affect the impulses applied to other contact points.
How are you solving contacts then? Afaik using the relative velocities is basically required
but if you have a doubled impulse and restitution > 0.5 it'd make the thing just like explode right
currently I'm just trying out a penalty force based thing
exert force proportional to penetration
mm yeah
but I'm doing a like grid based thing
so there'll be lots of collisions between two objects at once instead of just 1 or 2
and idk what to do about that
I guess in your case maybe? I haven't had this in any simulations I've seen
I'm applying the collision ignoring all other possible collisions since I have to run it in parallel
so yeah...
you can actually see in this image something like that I think
the second collision the velocity greatly increases
@last panther You might've found this already but this paper seems to have a penalty-based approach with multiple contact points (haven't read it fully)
https://www.researchgate.net/publication/5852586_A_Fast_and_Stable_Penalty_Method_for_Rigid_Body_Simulation
might be a bit mathy though
I think this is the important thing
normalize the force by the number of contact points
yeah
I realized I was misreading the impulse solver paper and you should do multiple iterations of impulse solving anyways
and yeah that could work but I think I'll try impulse-based
also might behave weird if you have like
an object colliding horizontally and vertically at the same time
@vestal minnow right I was reading it
and we have this impulse thing
but idk how should I add restitution to it
I guess just add a portion of the input velocity to the output?
going to have to some sort of thing which doesn't break with multiple steps though
I think the approach Erin Catto takes in the new version of Box2D is to apply restitution in a separate loop after the normal and friction impulses:
// Pseudocode
let constraints = prepare_constraints();
for constraint in constraints.iter_mut() {
constraint.solve_normal_impulses();
constraint.solve_friction_impulses();
}
for constraint in constraints.iter_mut() {
constraint.apply_restitution();
}
apply_restitution would do something like this:
// Iterate through the contact points for the constraint.
// Each point belongs to the same manifold.
for point in constraint.points.iter_mut() {
// Relative velocity at contact point
// Note: You might be able to cache this in the constraint
// since it's computed earlier for normal impulses.
let relative_velocity = body2.velocity_at_point(r2) - body1.velocity_at_point(r1);
let normal_speed = relative_velocity.dot(normal);
if normal_speed > -threshold {
continue;
}
let mut impulse = -point.normal_mass * (normal_speed + restitution * relative_velocity);
// Clamp the accumulated impulse
let new_impulse = (point.normal_impulse + impulse).max(0.0);
impulse = new_impulse - point.normal_impulse;
point.normal_impulse = new_impulse;
// Apply contact impulse
let impulse = impulse * normal;
// ...
}
point.normal_mass is the "projected mass" along the contact normal:
let k_normal = inverse_mass_sum +
inv_inertia1 * r1.cross(normal).powi(2) +
inv_inertia2 * r2.cross(normal).powi(2);
point.normal_mass = if k_normal > 0.0 { 1.0 / k_normal } else { 0.0 };
Box2D (new C version) has restitution here
https://github.com/erincatto/box2c/blob/29adfcfb4dbc371a50e37d8e17b072d990ad7ae6/src/contact_solver.c#L327
I believe you could also modify the velocity bias based on restitution somehow
Yeah that's what the old Box2D does
https://github.com/erincatto/box2d/blob/411acc32eb6d4f2e96fc70ddbdf01fe5f9b16230/src/dynamics/b2_contact_solver.cpp#L214
That might be closer to the implementation those slides have
But I think the old Box2D might do a position-based solve after the velocity solve
(while the new one is just a velocity solve, but substepped and with "soft constraints")
alright thanks!
Running Solver2D locally now, noticing that warm starting makes a very large difference. XPBD is much more stable than any other method without warm starting, but with warm starting other methods (especially TGS Soft) outperform XPBD in almost all tests
Except TGS soft has more flex for chains
It stores the impulses from the previous frame yeah
but it also requires storing the collisions from previous frames hmm
which I can't reallllllly do
implemented it btw
nice
only issue is that like in their thing they apply the impulse after every collision and I can't do that and have to sum it up across all collisions and then apply it after
which means that I have to divide the impulse by the number of collisions to prevent it from exploding
eee
I'm consdering switching to xpbd then actually
since I can't do warm starting
wait, does xpbd apply each constraint in parallel or one after each other
XPBD typically also applies corrections like this (a Gauss-Seidel solve), but summing them up is also viable (a Jacobi solve). It just often has slightly worse convergence
but there are still issues if you have multiple parallel constraints right?
if I remember correctly the paper involves dividing by the number of constraints on each object??
well then what's preventing overshooting
if you have 2 collisions for the same object
honestly not sure, but I haven't noticed it
ok hm
yeah I guess it could be an issue with a Jacobi solve, hmm
yeah non jacobi solver you shouldn't have the issue
https://mmacklin.com/uppfrta_preprint.pdf yeah this one has a solution to the jacobi solver thing
ah yea that divides by the number of constraints
Gauss-Seidel style solvers can also be parallelized with graph coloring, but that's more complex
(also I gotta go for ~30 min)
oh no
any links to that?
alright I implemented just dividing by num constraints
seems to work decently well
Haven't read all of these, but
- https://diglib.eg.org/bitstream/handle/10.2312/egs20221036/073-076.pdf
- https://di.ku.dk/forskning/Publikationer/tekniske_rapporter/tekniske-rapporter-2004/04-06.pdf
- https://profs.etsmtl.ca/sandrews/pdf/xpbdBlockNeo_MIG22_preprint.pdf (this is more for XPBD soft bodies though)
The main idea is to determine independent sets of constraints that can be solved in parallel, where each "color" corresponds to a set
The general description of graph coloring is on Wikipedia
https://en.wikipedia.org/wiki/Graph_coloring
In graph theory, graph coloring is a special case of graph labeling; it is an assignment of labels traditionally called "colors" to elements of a graph subject to certain constraints. In its simplest form, it is a way of coloring the vertices of a graph such that no two adjacent vertices are of the same color; this is called a vertex coloring. S...
alright thanks
you doubled one of your links
definitely not going to implement this though, it seems a bit complex
I probably wouldn't go through the trouble of implementing this unless you really need to yeah
oops
fixed
Actually now that I think about it
Shouldn't you be able to apply restitution trivially
By just taking the total normal impulse applied and multiplying it by some constant afterwards?
Intuitively I'd say yes, but I'm guessing in practice it would have some issues since this isn't how it's typically implement afaik... I'd have to think about it more
should be easy to test
yup
..
and somehow crashes my computer
weird
I don't really get why it explodes though
since when you collide you compute the impulse to make the things equalize their velocities basically
..
and I'm still having an issue of interpenetrating the first frame during collision
Naively deriving the impulse from physics equations; The coefficient of restitution determines the ratio of the relative velocity before and after the collision:
restitution = vel_after / vel_before
solve for vel_after
vel_after = restitution * vel_before
the delta is
vel_before + delta_vel = restitution * vel_before
delta_vel = restitution * vel_before - vel_before
so the total normal impulse magnitude should be
total_impulse = delta_vel * mass
= (restitution * vel_before - vel_before) * mass
(but in the direction of the normal)
so I guess the restitution impulse would be this?
restitution_impulse = total_impulse - contact_impulse
this is very likely wrong in practice for sequential impulses though, idk
what's contact impulse?
But yeah idk it seems right
I feel like I probably ended up double counting something somewhere or smth
contact impulse is just the impulse you'd have without restitution added
so you subtract that from the total impulse you expect based on the coefficient of restitution to get the impulse that is applied to account for restitution
but yeah this naive approach probably wouldn't work
yeah hm
maybe because of multiple bodies?
wait hm
Maybe just apply the position update with the contact impulse and then apply the restitution?
XPBD kinda does that
so you don't have issues with more interpenetration
XPBD has a restitution velocity solve after the position-based solve
You just need to store the pre-solve velocities
(2D) I'd like to have my enemies have parts that are armoured and vulerable spots. Is there a recommended way to go about this in xpbd? If I have a RigidBody::Dynamic, should I add children which are static that have their own local translation? Should I add root level entities with constraints to keep them stuck to the enemy?
You could add the different spots as separate colliders which are children of the parent rigid body
Child colliders should follow the parent
Aha, so the colliders don't need a RigidBody::Static or Dynamic?
Nope
Awesome thank you π
hmm
also @vestal minnow I tried the simple restitution method again and it seems to work?
no explosions
im unsure about how much it conserves momentum tho
I'm noticing sometimes the physics in my game feels like it pauses or skips every second or so... Like it's a really slight hiccup.. is that unusual?
might be fixed update
Off the top of my head, FixedUpdate runs 50Hz but Update runs at 60Hz, so every 6th frame on average won't have seen a physics update
Easy fix is to set FixedUpdate to 60Hz, but for some games that's not an option
I have an RTS that depends on a set tick rate (50Hz) so the solution in that case would probably involve some weird interpolation and 1-frame rendering lag
oh, is FixedUpdate used by bevy_xpbd?
π€
Oh that's a good question
I think the default schedule is fixed update?
Nope
Unless you're manually setting the schedule ig
In that case, I have no idea what's making it skip like that
hmm.. might be something to look more closely at though
Hello! I am using collisions to check if entities are touching or inside of each other. I have many, many entities but I only need to do this on-demand. Is there a way to turn off the collision system and only query specific collisions or collision layers?
This will often lead to a pattern of 2/0/2, which is why it's deliberately not set to 60 π
Oh my. That's not good.
FixedUpdate 59Hz 
Is there a way to have a LinearDamping component only apply laterally? or would i just need to roll my own out?
Currently you'd need to implement your own. I'm pretty sure most physics engines implement it like bevy_xpbd, as a single scalar value, but I'd probably be open to changing it to a vector too
gothca thanks, I'm assuming it shouldn't be too hard for me to figure out, I'm guessing it's mainly just lerping the values towards 0.0 by whatever my damping value is
The damping that LinearDamping uses is like this
lin_vel.0 *= 1.0 / (1.0 + delta_secs * lin_damping.0);
But there are a few different ways you could implement it
The default schedule where physics is run is PostUpdate, but the default timestep used for running the PhysicsSchedule is still a custom fixed timestep. I believe that is 60 Hz by default at the moment
Bevy's FixedUpdate is 64 Hz, but with that I got some weird performance issues or even worse frame skips (don't remember) so I kept it at 60 Hz for now
Unity is the one with 50 Hz iirc
Here are docs for physics time stuff
https://docs.rs/bevy_xpbd_3d/latest/bevy_xpbd_3d/plugins/setup/struct.Physics.html#usage
The clock representing physics time, following Time. Can be configured to use a fixed or variable timestep.
Also I'm aware that this setup is maybe a bit strange. My plan is to eventually make physics just run in FixedUpdate by default, and to potentially remove a lot of the weird custom timestep stuff. This had some issues when I originally played around with this, but I'll return to it at some point
Ah thanks for this. I did not do much research before spewing haha π
hmm, so whatever visual hiccup I'm seeing may not be due to this fixed update if I'm hitting 60fps?
I seem to have a fundamental misunderstanding that I cant figure out.
Im trying to recreate the 3d_scene example with the falling cube.
I copy paste the code and it works as expected.
I want to make a change so that the cube is spawned when I click an egui button. I have a system that listens to a bevy event, and spawns the exact same cube, except this time it seems to move at like 1000x the speed, and i only see it flash on the screen for a frame.
The only difference here is that one system is in the Update step, where as the original example is in Setup.
EDIT: it seems like removing the angular velocity component fixes this. no idea why
I'm Currently attempting to make a third person camera controller, with a sphere as a placeholder for a character or model etc. and I'm ray casting from the origin of the parent (the anchor point of the camera) out and then if there is a hit then setting the camera's position to the hit point, however right now the ray hits the sphere and so i have been trying to use layer masks and collision layers so the ray will skip the player mesh / collision, which i have code for, however it isn't working, and I'm just wondering what I'm doing wrong and also if i should attempt to do it another way, here is the responsible code:
(in the library i created to handle the camera stuff) - Lib.rs:
#[derive(PhysicsLayer, Clone, Copy, Debug)]
pub enum GameLayer {
Player = 0b0001,
Enemy = 0b0010,
Environment = 0b0100,
}
fn cam_setup(mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,) {
assert_eq!(LayerMask(0b0001), GameLayer::Player);
let camera_mask = LayerMask::ALL & !LayerMask(GameLayer::Player as u32);
assert_eq!(camera_mask, LayerMask(!0b0001));
commands.spawn((...RayCaster::new(Vec3::ZERO, Direction3d::Z, )
.with_query_filter(SpatialQueryFilter::from_mask(camera_mask))
,
CameraRay,
));
...
And then over in the main.rs where the player is set up:
commands.spawn((...
},
ThirdPersonCameraTarget,
InterpolatedPosition::from_source(physics_entity),
InterpolatedRotation::from_source(physics_entity),
CollisionLayers::new(GameLayer::Player, [GameLayer::Enemy, GameLayer::Environment, GameLayer::Player]),
));
i apologise that this is so long btw
What is this physics_entity it's using? π€
It's using this for interpolation
https://github.com/rubengrim/bevy_xpbd_interp
So then physics_entity would need the CollisionLayers no? π€
Yep, I think that's probably the issue here
The interpolated entity should be used just for rendering, and all the physics/collision components should be on the physics entity
@vestal minnow
How does your crate deal with scale changes?
It seems to me that you either have to propagate transforms twice per frame (once before applying scale, once after integrating) or to have 1 frame delay on either integration or scale application.
thanks for this, i didn't realize that i put the collision layers on the wrong entity, absolute blunder on my part. everything is working as intended now
so lets say i have character and i want it to jump so i need to check if its touching someting. is it a good idea to create a shapecast under the character collision and check if its touching something?
if its not a good idea, how should i do it?
it's supposed to be the way to do it, but i had ghost collisions with it and had to reduce it to just raycast
Sorry for the late response. Transform propagation is currently done multiple times per frame (I'd like to reduce the overhead though), but I believe colliders are only scaled once at the end of a physics frame
So collider scaling might have a one-frame delay if you change the scale before physics is run
I see, thank you.
Yeah the options are generally
- Shape cast
- Ray cast
- A sensor collider as a child entity, positioned at the bottom of the character
I think a shape cast should be a pretty good and relatively easy way to implement it, dunno if it has issues with ghost collisions like .β§². mentioned though
The main problems with a ray cast would probably be that (1) if you go off of a ledge, you can't jump the moment the center of your character goes over the ledge unless you implement a manual Coyotee time jump, and (2) if you happen to be standing on some small crack in the ground and the ray happens to point at it, you can't jump
coyote time is actually super simple
instead of making grounded a bool, you make a float and reset it to 0 ray hits ground, or add delta time if not. so when you jump, you just check if grounded < coyotetime
shapecast is fine if all you need to care about is that it hit at all. if you need normal for slopes then it's wacky, goes sideways on every triangle edge
Looks like no one answered ... Hitting 60FPS doesn't actually mean your ticks happen exactly once a frame, if you get a 2/0/2 pattern it can be very easy to get hiccups. If it doesn't always happen it's probably either the ticks getting uneven like that or a system ordering issue. bevy's default FixedUpdate rate (64Hz) also has this problem, but the benefit there is that it consistently happens at least 4x per second (at 60FPS) so it won't suddenly catch you by surprise. If you run 2 ticks in 1 frame, chances are you also don't have input for both ticks, which can cause further hiccups
I may have missed that.. what exactly would cause the 2/0/2 pattern if I'm not using fixed update in my code? Is it that the physics isn't tied to the framerate?
bevy_xpbd essentially just runs its own fixed update loop in PostUpdate by default (otherwise the physics become tied to FPS, which is of course very bad)
The 2/0/2 pattern happens because at 60FPS some frames might be just under 1/60th of a second and the next slightly over, and that can cause the first one to run 0 ticks, and the next to run 2
you said
If it doesn't always happen it's probably either the ticks getting uneven like that or a system ordering issue.
assuming my systems have no order issues (π), are you saying this can kinda just happen and I can't really prevent it?
You can't prevent it but you can do things to make sure there's always input and no visual hiccups appear
input: Res<ButtonInput<KeyCode>>,
mut players: Query<&mut ExternalForce, With<Player>>
) {
let input_x: f32 = (input.pressed(KeyCode::KeyD) as i8 - input.pressed(KeyCode::KeyA) as i8) as f32;
for mut player_force in players.iter_mut() {
player_force.apply_force(Vec2::Y * input_x * PLAYER_SPEED);
}
}```Should this work?
I think so yes, but since it's force it probably accumulates by default
It does not work.
I don't know why.
Hmmmm ... Could be that force doesn't affect your player entity at all, due to some missing component or something π€
at least ExternalForce should be added automatically
commands.spawn((
RigidBody::Dynamic,
Collider::capsule(50.0, 20.0),
TransformBundle {
local: Transform {
translation: Vec3::new(0.0, 200.0, 0.0),
..default()
},
..default()
},
ExternalForce::new(Vec2::Y).with_persistence(false),
Player
));```
You could try adding logs in the system to make sure it's running properly
fn player_input_system(
input: Res<ButtonInput<KeyCode>>,
mut players: Query<&mut ExternalForce, With<Player>>
) {
let input_x: f32 = (input.pressed(KeyCode::KeyD) as i8 - input.pressed(KeyCode::KeyA) as i8) as f32;
for mut player_force in players.iter_mut() {
println!("{}", input_x);
player_force.apply_force(Vec2::Y * input_x * PLAYER_SPEED);
}
}
Also maybe the force is just way too small?
I did that and the input is working.
You could try increasing it a bunch, like multiply by 100
I set speed to 128.
If Force per frame or per second? π€
It does not work if it is on.
Newtons, so kg*m/s^2, so it should be per second I believe
How big is the player collider? This is 2D, so if you're using pixels as units, you might need very large forces to move them meaningfully
Is there anything in contact with the collider? Maybe friction ... Or it could be another system overwriting things ... That spawn is missing visibility and global visibility (cause it's TransformBundle and not SpatialBundle) so I'd imagine something adds more component (probably based on the Player marker?), and maybe that keeps overwriting the position?
If it was, let's say 50x50 pixels, 128 Newtons of force would only give it an acceleration of 0.0512 pixels per second, which is tiny in pixels
(assuming 1 pixel = 1 meter and a density of 1 here)
ive got a problem, when i move my character everything looks is fine, when i turn my camera everyhing looks is fine, but when i move and turn at the same time its jittery.
I move character with changing linear velocity of player.
camera is the child of player, so i rotate player on y instead of rotating camera
app.add_systems(
PostUpdate,
camera_follow_player
.after(PhysicsSet::Sync)
.before(TransformSystem::TransformPropagate),
);
ive tried this solution on docs.rs but its still the same
so i dont know what to give you for you to help me better
- The first is exactly the factor that you already handled. Your fn was running before the physics engine could handle the transform calculations
- The second you are utilizing transform rotation on a RigidBody. Guessing that ball. The correct way would be to use angvel with interpolation
Or torque impulses
If you would like I have a function that rotates colliders and rigid bodies without jittering
Damn i would like to see it
So first sample video, just to check if this is what you want
cam_q: Query<&Transform, With<CamInfo>>,
mut vel: Query<&mut Velocity>
){
let mut current_time = 0.0; // Current time, starting from 0
let total_s = 0.5; // Max s value of interpolation in seconds
let dt = 1.0/60.0; // Time step for interpolation, adjust as needed
let current_q = player_q.get_single().unwrap().rotation.normalize();
let target_q = cam_q.get_single().unwrap().rotation.normalize();
for mut v in vel.iter_mut(){
while current_time < total_s {
let s = current_time / total_s;
let interpolated_q = current_q.slerp(target_q, s);
let q_difference = interpolated_q * current_q.inverse();
let (axis,angle) = q_difference.to_axis_angle();
let angvel = (axis[0] * angle / dt, axis[1] * angle / dt, axis[2] * angle / dt);
v.angvel = angvel.into();
current_time += dt;
}
}
}```
Here is the code
Video is something like this
As you can see i am moving player which is a rigid body only using velocity
The slight delay you may notice is something i left purposefully to have that smooth transition you can configure that
Oh btw that was written using rapier but I think the only difference is angvel is disassociated
imma try it
If you want no jitter at all just increase dt and your interpolation will go quickly
Smoothed out
Is there a shortcut to remove all physics components from an entity that I want to no longer participate in the simulation?
I believe you can add the components to your own bundle when spawning and then remove the bundle
Is it possible to use SpatialQuery with another query that requires &mut Position? I'm running into conflicting accesses but I can't modify Transform directly as I have transform_to_position: false in SyncConfig
I think you can instead use Res<SpatialQueryPipeline>
At one point I had the intention of removing the SpatialQuery system parameter in favor of the resource (maybe renaming that to SpatialQuery?), which would fix all conflicts like this, but I either had some issue with that or just never got around to doing it... I might revisit it after the solver rework
Currently SpatialQuery just duplicates all the APIs and forwards method calls, which is rather pointless. The only extra thing it has is an update_pipeline method which requires ECS access, but most users don't need that, and it could just be made a separate system or something
Not a convenient way at the moment... I wonder if we could add a custom command or something that would do this, along with any other potential cleanup π€
Some components are intended to be more "internal" and might even be private currently, so it might not be straightforward to cleanly remove all physics components from the outside
(without despawning the entity of course)
Oh thanks! I didn't even know that resource existed, even though its the first field in the SpatialQuery struct π€¦ββοΈ
recently noticed that an entity spawned with a collider initially has a bigger collider which then becomes the proper size, anyone else experiencing this issue?
Is this a one-frame delay? It could be that transform scale is only applied to colliders at the end of the frame, causing a slight delay
If that's the case, I think it should be pretty easy to fix on the bevy_xpbd side
If I want to make my physics object a sprite do I just add the sprite bundle to it?
Yep, that should work
Anyone has attempted to make a hair that is affected by physics yet? Just curious
Don't think so, but I've seen some cloth physics around which should be somewhat similar
It threw me an error.
Do you have duplicate components or something? For example, SpriteBundle has a transform already, so you should set that directly instead of adding your transform outside the bundle
I think that is added in the physics bundle.
Could you show the code for how you spawn the entity?
I am at school right now but I will when I get home.
Did anyone run bevy_xpbd through a debugger? I'm using vscode LLDB, and I'm getting trash data in the variables with the default optimization on 1.
I don't think I have personally; what do you mean by trash data?
The dbg!() blocks show data different from the data shown in the debugger view. I was implementing a solver, and the debugger was showing tangent_impulse as 5000, and normal_impulse as 0. But then prints the opposite to the console.
Maybe it's to do with the weird workspace setup for the crate.
I test 2d and 3d by using a single member.
Otherwise it doesn't compile.
If you have two bundles and they both add the same component, you can't add them in the same method call, but you can insert the other bundle separately.
// Here, the variables represent the bundles you want to add.
// If they both have (for example) Transform, you can't do this:
commands.spawn((
sprite_bundle,
physics_bundle,
));
// Instead, do this:
commands.spawn(sprite_bundle).insert(physics_bundle);
Huh, that's definitely weird
I'm not sure if that's something I can really fix? Although if it is related to the weird crate setup (which Rapier also uses) for whatever reason, then we could try adding a shared "core" crate like I proposed here
https://github.com/Jondolf/bevy_xpbd/issues/298
Unless it's specifically related to using workspaces, but I really doubt that since they're quite common in Rust
Bevy itself has all of its crates in a large workspace
I've honestly not had a good time debugging with bevy in general. The data usually just doesn't show up at all, which I thought was the problem with bevy getting optimized, but it was never showing garbage. So I got surprised.
So it gives wrong results even with a single member?
Single member is the only way I can build bevy_xpbd, so yeah.
Otherwise I think rust tries to compile both 2d and 3d, but the shared root crate can only support one.
Interesting, for me it works fine (also using VSCode)
For me, it has always chosen either 2D or 3D, but not both at the same time
(which to be fair is a bit annoying when you specifically want the other dimension to be processed by Rust Analyzer)
I believe symlinks could be used to make it nicer but it might also be even more confusing and I'm not sure if all platforms support them
Yeah, should probably not complicate it further.
IIRC NPhysics used symlinks but Rapier doesn't
If poor debugger support is a Bevy-wide problem, it could be useful to make an issue for it unless there's already one
I haven't personally used them much so I don't have a lot of experience
Maybe I'll get annoyed enough at some point and create an issue for it xd
That is annoying.
As for the code it is:// Player let color = "Red"; let shape = "round"; let playerTexture = assetServer.load("textures/character_".to_owned() + shape + color + ".png"); commands.spawn(( RigidBody::Dynamic, Collider::capsule(50.0, 20.0), TransformBundle { local: Transform { translation: Vec3::new(0.0, 200.0, 0.0), ..default() }, ..default() }, ExternalForce::new(Vec2::Y).with_persistence(false), SpriteBundle { texture: playerTexture, ..default() }, Player ));
let color = "Red";
let shape = "round";
let player_texture = asset_server.load("textures/character_".to_owned() + shape + color + ".png");
commands.spawn((
RigidBody::Dynamic,
Collider::capsule(42.0, 28.0),
ExternalForce::new(Vec2::Y).with_persistence(false),
SpriteBundle {
texture: player_texture,
transform: Transform {
translation: Vec3::new(0.0, 200.0, 0.0),
..default()
},
..default()
},
Player
));```How do I lock the rotation?
A component that specifies which translational and rotational axes of a rigid body are locked.
Thanks!
Sorry for the ask.
I did try to figure it out on my own.
no problem! glad I could help
What tilemap should I use with this?
Or how would I create a collider based on an svg image?
I am going to try and figure out how to use the polyline thingy.
Could someone please explane the polyline because I am not getting it.
I think that I just figured it out!
Is there a concave shape that I could use?
Collision detection algorithms don't generally work for concave shapes directly, but you can often represent concave shapes using several convex shapes (this is known as "convex decomposition"). You can make a collider as a combination of shapes using Collider::compound, or in 3D you could also make a collider from a mesh
2D also supports polyline colliders
How would I automatically make several convex shapes based on a concave shape?
2D has the Collider::convex_decomposition method which basically takes the vertices and indices of a polyline and makes a filled compound shape from that
A collider used for detecting collisions and generating contacts.
so that can be concave
Sweet! I will look into that!
I'm having an issue, when I spawn my entity. In the Setup schedule the collider is being created fine but when I do it in the Update schedule no collider is created.
The picture I attached shows 4 chunk entities, the bottom right was created at Setup and the rest at Update. As can be seen only the bottom right one has a collider.
Below is the code that I use for creating the entity:
let raw_mesh = chunk_to_mesh(self);
let mesh = meshes.add(raw_mesh.clone());
let mut chunk_entity = commands.spawn((
ChunkBundle {
chunk: Chunk,
mesh: MaterialMeshBundle {
mesh,
material: voxel_material.clone(),
transform: Transform::from_translation(Vec3::new(
self.world_position.x as f32 * self.size.x as f32,
self.world_position.y as f32 * self.size.y as f32,
self.world_position.z as f32 * self.size.z as f32,
)),
..Default::default()
},
rigid_body: RigidBody::Static,
collision_layers: CollisionLayers::new(
RigidLayer::Ground,
RigidLayer::Player,
),
},
raw_mesh.compute_aabb().unwrap(),
AsyncCollider(ComputedCollider::TriMeshWithFlags(
TriMeshFlags::MERGE_DUPLICATE_VERTICES,
))
));
Hmm, does PreUpdate work?
AsyncColliders are currently initialized in Update
Although with the way the system works, I don't see why that would matter π€
No, it doesn't work
Yeah that is exactly what confuses me, that even if there is a huge difference then it would be picked up the next scheduled cycle
Interesting... for now, you could probably handle the collider creation manually
let raw_mesh = chunk_to_mesh(self);
let collider = Collider::trimesh_from_mesh_with_config(
&raw_mesh,
TriMeshFlags::MERGE_DUPLICATE_VERTICES,
);
let mesh = meshes.add(raw_mesh.clone());
let mut chunk_entity = commands.spawn((
ChunkBundle {
chunk: Chunk,
mesh: MaterialMeshBundle {
mesh,
material: voxel_material.clone(),
transform: Transform::from_translation(Vec3::new(
self.world_position.x as f32 * self.size.x as f32,
self.world_position.y as f32 * self.size.y as f32,
self.world_position.z as f32 * self.size.z as f32,
)),
..Default::default()
},
rigid_body: RigidBody::Static,
collision_layers: CollisionLayers::new(
RigidLayer::Ground,
RigidLayer::Player,
),
},
raw_mesh.compute_aabb().unwrap(),
collider,
));
That worked! Thank you π
I'm still very curious why the AsyncCollider failed but at least I can continue working on my game
Yeah, it's very weird... All the system does is query for meshes for entities with AsyncCollider, build the colliders for those entities, and remove the AsyncCollider component so that the system doesn't run again for those entities the next frame
and there are no other query filters or run conditions
In that case I might have another lead, the AsyncCollider component stays on the entity when I look at it using the egui inspector
I think that would indicate that the entity doesn't have a Handle<Mesh> π€
The system is just this
pub fn init_async_colliders(
mut commands: Commands,
meshes: Res<Assets<Mesh>>,
async_colliders: Query<(Entity, &Handle<Mesh>, &AsyncCollider)>,
) {
for (entity, mesh_handle, async_collider) in async_colliders.iter() {
if let Some(mesh) = meshes.get(mesh_handle) {
let collider = match &async_collider.0 {
ComputedCollider::TriMesh => Collider::trimesh_from_mesh(mesh),
ComputedCollider::TriMeshWithFlags(flags) => {
Collider::trimesh_from_mesh_with_config(mesh, *flags)
}
ComputedCollider::ConvexHull => Collider::convex_hull_from_mesh(mesh),
ComputedCollider::ConvexDecomposition(params) => {
Collider::convex_decomposition_from_mesh_with_config(mesh, params)
}
};
if let Some(collider) = collider {
commands
.entity(entity)
.insert(collider)
.remove::<AsyncCollider>();
} else {
error!("Unable to generate collider from mesh {:?}", mesh);
}
}
}
}
Worth spamming a warning if mesh is missing here IMO.
Yeah, true
Mesh is not missing
And indeed for the Setup entity the AsyncCollider is not present
Yeah it needs to point to an asset in Res<Assets<Mesh>>
That's what I thought in the beginning too but check this image of the working entity
oof
I'm using the standard way of adding meshes:
let mesh = meshes.add(raw_mesh.clone());
Tbh I have no idea what's happening there... I don't see how Startup would differ from Update here
Ok I did some more debugging and looking at the init_async_colliders function and I added a system to my game that would just try to get the mesh fromt the entity and it's indeed failing. This is probably not the place to ask for it but do you might know the reason for the fact that my mesh can't be found in the Assests?
This is the code I used for checking and my console is being spammed with AAAAAAAa
pub fn update_chunk_colliders(
meshes: Res<Assets<Mesh>>,
chunks: Query<(Entity, &Handle<Mesh>), (With<Chunk>)>
) {
for (entity, mesh_handle) in &chunks {
let mesh = match meshes.get(mesh_handle) {
Some(mesh) => mesh,
None => {log::info!("AAAAAAAa"); continue},
};
log::info!("FOund mesh: {:?}", mesh);
}
}
And above in the code for generating the entity I shown how I'm adding it
Unless you remove the mesh from assets somewhere or replace the handle or something, then no I don't really know...
It's strange that it's still rendering it even though the asset seemingly doesn't exist
Exactly, it's in a weird state of existing and not existing
SchrΓΆdinger's chunk
Thank you for the help π
Your crate is super awesome and intuitive to use
No problem, and thanks π lmk if you figure out the mesh weirdness
Will do!
I figured it out, I forgot that I set my mesh to RenderWorld so it would be removed no matter what when entering RenderWorld. Changing it to:
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::RENDER_WORLD | RenderAssetUsages::MAIN_WORLD);
Solved it
Ah, yeah that makes sense. Nice that you figured it out!
Thanks
Has anyone played around with collider generation? I'm trying to get my VHACDParameters for my procedural terrain. Currently the generation near cliffs is incredibly inconsistent, leading to invisible "ramps" near the sides of cliffs. Here's a screenshot that hopefully illustrates what I mean.
Notice my character is floating off the ground and the collider only vaguely follows the cliff.
Am I approaching this incorrectly?
VHACD is from Parry, and I haven't really looked into how it works yet, so I can't help much unfortunately... The parameters should match the ones in this table (or some might be missing), could play around with them to see what improves things
I'll take a look
Do I have to put something into indices?
A collider used for detecting collisions and generating contacts.
@vestal minnow What's your plan for joint improvements?
Gonna do generic 6dof like rapier or something else?
I might explore that option too, but Rapier's generic joints are quite complex (I'd need to really study them) and might rely on some Nalgebra features that we don't have. I haven't looked at the implementation too in-depth yet, but I feel like manual implementations could also be stabler and faster than a fully generic solution. That needs to be tested though.
For the new solver, I'm currently thinking that I might keep XPBD for joints for now, since it's quite stable and straightforward. But the XPBD paper's way of handling joints isn't ideal, so I'm now trying out matrix-based implementations like what Bepu has. So essentially, I'm trying to do 3D joints similarly to Bepu and 2D joints similarly to Box2D, but with XPBD logic.
Locally I have a working implementation of the matrix-based block solve for 2D and 3D point-to-point constraints (used by most joints), and it's significantly less stretchy and jittery than the old impl
I also fixed a 2D joint "explosiveness" issue caused by denormalized rotations
As for other planned improvements:
- Joint motors and servos
- Nicer tuning parameters, like a damping ratio and frequency, or CFM and ERP, instead of just XPBD compliance
- Joint breaking based on some force threshold
- Nicer component-based API (relations would be nice tho)
A generic 6DOF joint isn't off the table either though, and I think it could be nice to have even if the built-in joints have custom implementations
And yeah I'll probably try impulse-based joints too
Oh one that I forgot is predictive joint limits. It might be slightly less trivial with XPBD, but probably doable
That's a lot of things to try!
My biggest gripe with rapier and bxpbd so far have been joint limits not behaving properly. I tested my ragdoll setup in unreal, and it worked fine. Hopefully it's not too hard of a fix.
Yeah, I haven't tried Rapier's joint limits in a while, but XPBD's definitely has some issues. I did fix one bug with 2D revolute joint limits and made a working ragdoll with that. But some things are likely buggy still
I'll probably make the stiffness of the limits configurable as well
Hopefully easily extendable/replacable. I think I may need some custom joint limit behavior in the end.
Did you figure out an API for custom solvers?
The current API with XPBD supports custom constraints already, and I've made some changes/improvements locally too
But supporting fully custom solvers isn't as trivial
Understandable.
On the topic of 6dof: I don't think the linear and angular components are dependent in any way, are they? Could be easier to reason with 2 3dof constaints.
Hmm, potentially yes, but I imagine the constraints would be able to reuse some values so it'd be more efficient for it to be one constraint
Joints typically compute an "effective mass" which is used for computing the linear and angular impulse or correction
Different parts of that effective mass matrix apply to different DOFs, and you could probably store them separately, but I'm not sure if you can do all linear and angular computations fully separately, at least as efficiently
(or rather you compute the Jacobian of the constraint and use that to figure out the effective mass, but I believe you don't always need to explicitly compute the Jacobian)
i think so, i also tried changing the transform scale to 1.0 when spawning and it removed the switch in collider size
Is substepping inherently dangerous?
You can use as many iterations as you want, and it will be fine, while substeps in bevy_xpbd don't update the broadphase, and in Eric's TGS Soft, don't even update the narrow phase, only penetration.
That seems like it would lead to missed collisions the more substeps you use.
No, not really.
With some things left out, the simulation loop with iterations (PGS) looks like this:
broad_phase();
narrow_phase();
integrate_velocities(delta_time);
for _ in 0..iterations {
solve_constraints_with_bias(delta_time);
}
integrate_positions(delta_time);
for _ in 0..iterations {
// Relax
solve_constraints_without_bias(delta_time);
}
finalize_positions();
And with substeps (TGS), it looks like this:
broad_phase();
narrow_phase();
let h = delta_time / substeps;
for _ in 0..substeps {
integrate_velocities(h);
solve_constraints_with_bias(h);
integrate_positions(h);
solve_constraints_without_bias(h);
}
finalize_positions();
(of course you could have iterations within substeps too)
In both cases, the bodies are moved by position integration the same amount between narrow phase runs. With substeps, the movement is just split into smaller individual chunks instead of advancing bodies a ton at once like when using just iterations. So in that aspect, the amount of "missed collisions" is the same.
The current bevy_xpbd actually does run the narrow phase in the substepping loop, and the broad phase accounts for running just once a frame by expanding AABBs based on velocity. This AABB expansion is suggested by the XPBD paper.
However, running the narrow phase in the substepping loop is horrible for performance. It's just not very viable. The new solver runs it outside the loop like Box2D, Rapier, Bepu... basically every engine.
To account for position changes during substepping, contact data is stored in local space and transformed to match the current pose when solving constraints. To account for missed collisions at fast speeds (tunneling), you'd use CCD, just like when not using substepping.
(the "relaxation" here isn't necessary but can help remove extra energy introduced by Baumgarte stabilization or soft constraints)
In both cases, the bodies are moved by position integration the same amount between narrow phase runs.
I see! That makes sense. Thank you.
What looks like an xpbd interpolation bug of some sort. Hard to reproduce because this is after ~10^7 frames. This is showing a discrepancy between the game objects and the PhysicsDebugPlugin.
Yeah I've noticed this as well, could be some rotation denormalization issue? It's hard to tell
Hmm, this is in 2D, I see you're storing rotation as the sin and cos of the angle, those fields are private and there isn't any provision to renormalize them, I think you may be right
Yeah I have local changes where 2D rotations are normalized by the solver, and don't remember seeing this issue with that change, but it's not particularly trivial to reproduce
I'll try and come up with a test case
I think it's coming down to repeated application of this
/// Multiplies the rotation by another rotation. This is equivalent to adding angles.
pub fn mul(&self, rhs: Self) -> Self {
Self {
cos: self.cos * rhs.cos() - self.sin * rhs.sin(),
sin: self.sin * rhs.cos() + self.cos * rhs.sin(),
}
}
To normalize, you'd basically do this
let length_recip = Vec2::new(rot.sin(), rot.cos()).length_recip();
let normalized = Rotation::from_sin_cos(
rot.sin() * length_recip,
rot.cos() * length_recip
);
(locally I have helpers for this)
Gotcha, I'll try that, every N frames replace the Rotation component with a normalized copy. Thanks, will report back later!
Here, only the collider and mesh/sprite glitch out while the body's axes are rendered correctly. For collider rendering and Transforms, Rotation gets converted to a Quat, but for the axes it uses Rotation directly. So it does indicate that the rotation is invalid when converted to a Quat, most likely due to denormalization
Or maybe I'll make my mobs take damage over time proportional to how denormalized their rotation vectors are :D
Left: without renormalization code ||| Right: with
Thanks for testing it, good to get confirmation on the issue :)
That'll be fixed in the next release
Hmm, does 2/0/2 happen in practice without vsync?
Becomes a lot less likely, tho even without vsync your FPS could still end up close to tickrate or a multiple of tickrate and you'd get some funny patterns again
I noticed this crate uses Vec3::Z for some of its forward defaults. It should probably follow bevy's NegZ forward convention.
Is there a simple way to alter the type of a RigidBody (from Dynamic to Kinematic) from a RigidBodyQuery ?
RigidBodyQuery doesn't query RigidBody as mutable, so no, you'd have to do it separately.
Ok thanks that's a bummer I'd have to query the other components manually
Do you have examples of this?
I guess the revolute joint aligned axis is one
And perhaps some of the examples
Yeah, I was looking at joints when I posted that.
What does this do in the revolute joint apply_angle_limits?
let limit_axis = Vector3::new(
self.aligned_axis.z,
self.aligned_axis.x,
self.aligned_axis.y,
);
That's wrong, I have it fixed locally
Could you explain what you thought when you made it initially, and how you fixed it? I'm trying to compare it to the XPBD paper.
It's meant to be this, basically
// [n, n1, n2] = [a1, b1, b2], where [a, b, c] are perpendicular unit axes on the bodies.
let a1 = *body1.rotation * self.aligned_axis;
let b1 = *body1.rotation * self.aligned_axis.any_orthonormal_vector();
let b2 = *body2.rotation * self.aligned_axis.any_orthonormal_vector();
let dq = angle_limit.compute_correction(a1, b1, b2, PI);
oh main doesn't have Mul for rotations either π
I have a lot of unreleased changes locally, I'll probably start making PRs for them soon
Another thing, fixed joint on main cannot allow for arbitrary orientations of bodies.
Rapier makes it work with local frames on the joint as opposed to just anchors.
Honestly not sure what I was thinking, I probably just misinterpreted the paper and tested things until it worked... but it was very broken for specific limits
Understandable xD
Yep, I'll try to support local frames as well, I had nearly forgotten so thanks for reminding me :p
Local frames can probably unify some joint code too.
As they can be used in both spherical and revolute joints.
yeah it should be pretty easy to add
mm I should probably cache the other two axes too
Oh yeah, and spherical joint is missing individual swing axes, seemingly.
I don't easily get what the paper is saying for that joint.
Yeah it's pretty confusing, I'm honestly not 100% sure what it means either. Generally, spherical joints can have a kind of limit cone formed by two axes, like in PhysX
https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/docs/Joints.html#spherical-joint
I should really test the spherical joint limits properly and compare with some other engine
Unreal has a very comprehensive joint system. Their gizmos show all the info about the limits.
And another thing. Joints should be able to specify that no collisions should happen between jointed bodies.
PhysX calls both axes swing axes, and Jolt calls them twist axes π€ Meanwhile XPBD has both
Unreal has Swing1, Swing2 and Twist.
Which makes the most sense to me.
If you have a long rod, it can swing about the 2 axes, and twist about its length.
Yep this is planned too and I had a basic implementation at one point, but I'm just not sure how to implement it efficiently since the bodies would need to know what bodies they're connected to, and joints are their own entities
(relations when π)
And another another thing. Joints should be allowed to be put on the bodies.
I don't think anything is blocking that? Only the Without<RigidBody>, from my understanding.
Yeah, should be doable. And I guess you'd only need to give one Entity in that case since the other one would be inferred
I imagine having a separate filter component would be the easiest without relations. But you'd have to maintain its state as the joints get added/removed. Pretty cringe.
And if you only give one entity, and the joint is not on a rigid body, it could even assume the missing entity to be a fixed anchor point in space
I think some other engine has this
Wouldn't that limit the number of joints for a given body, if you only allow joints to be on a body?
Since components cannot have duplicates.
Maintenance hell D:
maybe :D we'll see
It'd need to get the actual joint since some joints could have collisions disabled while others have them enabled. So every body that is connected to something by a joint would need a component like AttachedJoints, and then for each body that has it, the broad phase would query for those joints and check if the collision should be disabled or not
This (and a lot of other logic) would be nicer with a more component-focused approach to joints
I'll try to find my MVP from some time ago
Roughly like this #1124043933886976171 message
Each joint with collisions disabled could just have a CollisionDisabled marker component
And yeah, this makes sense to me. I think the current XPBD version actually just assumes that Swing1 == Swing2, but is otherwise similar
Anyways, I'll work more on the joint stuff very soon, I'm wrapping up CCD now
Hmm, so far, RigidBodyQuery has primarily been intended for internal usage, but I could perhaps make it more user-friendly and make more components mutable
Welp, I started trying to implement generic joints anyway π I found an old MVP and it has some other refactors that could be nice as well... we'll see if I can get it working
After getting local frames done, it's only a few steps to 6dof lol
The normal joints will still keep custom implementations though, for now at least
custom impls can most likely be more efficient and stable
I see 6dof as a niche thing.
yep
It's nice to have for some uses, but most people just need the basic joints.
yeah... looks nice in a list of supported features though lol
For sure.
I think I got the 2D version (4DOF) working, it can at least reproduce revolute and prismatic joint behavior... Although for weirder configurations it's harder to figure out what the expected behavior even is, like what's a prismatic joint that also allows some rotation π
or hmm, that might be like a pin-slot thing
yeah
I really need proper gizmo rendering for joint limits
You can think of joints like that as a combination of multiple joints. Like a prismatic joint sitting on top of a revolute one.
Wish bevy had solid gizmos. I fear joint gizmos will be confusing with outlines.
Yeah... Could also use a mesh, but that's more painful to manage
I see that joints constrain rotation first, then position, this makes sense because position depends on rotation. But then angular limits are enforced after, modifying rotation after positional correction. That seems incorrect to me.
It might be kinda wrong, yeah. I think I originally used this project as a reference for the joints, and got the order from there
I'll try if it's better with the angular limits before the positional correction
Hey everyone, when using linear velocity instead of transform the movement is kind of blurry is this normal or am I missing something. I'm using 3d and the camera isn't moving.
The change is basically indistinguishable. I tried just a single revolute joint, and the behavior was identical, and also tried a bridge (less stretch is better), and the max stretch is the same.
First image has limits after position constraint, second image before
I'll still change it to be before though since it seems more "correct" to me
It shouldn't make a visual difference at non-extremes, yeah.
But should be stabler with strong forces.
Yeah
Blurry?
Maybe jittery is a better word? It looks like it running on less fps
Is it on every frame or every few frames? Wondering if it could be related to this 2/0/2 pattern #1124043933886976171 message
Physics runs at a fixed 60 Hz by default
OK that might be it thanks, I'm running at 144 fps.
Try different timestep modes/values
That worked perfectly thanks
Local frames for joints are a bit awkward. For the fixed joint it makes perfect sense. But for the revolute joint the aligned axis workflow is a lot nicer. You just need to separate the axis into 2, one for each body, and you will get (almost?) all the freedom of the local bases.
For spherical joint you can construct the basis from the limit axes provided by the user. For the revolute the same is probably a good idea too.
Yeah, I believe all that is pretty close to what Rapier does too, although for the revolute joint it only seems to allow one hinge axis instead of one on both bodies
(Bepu allows two though)
Yep, I believe it should be basically identical
On second thought, requiring the user to provide orthonormal axes for the spherical joint might not be very good. Could just ask for the whole orientation and tell them where the swing and twist axes should be?
Yeah I think usually engines just ask for the basis orientations and name which axes are which
Bepu seems to have swing, twist, and the point constraint as separate, and asks for basis orientations for twist and swing axes for swing
Rapier just asks for local frames
I think PhysX also asks for frames, and defines X as the twist axis that has the limit cone
For the generic 6DOF joint, I got all translational DOFs and revolute joint -like angular DOFs (1 unlocked, 2 locked) working properly. 2 unlocked and 1 locked (universal joint) also seems to mostly work, but the limits are currently unstable at some angles, potentially due to singularities or just some bugs. And for spherical joints, I haven't figured out elliptic cone limits quite yet
(interestingly, Jolt also supports pyramidal limits)
Isn't that just individual limits for each swing axis?
They wouldn't form a cone, but a pyramid naturally.
Btw, Jondolf, the mod.rs usage is not recommended in leu of *mod_name*.rs by the rust book.
Maybe a good idea to change at some point
Personally, I heavily dislike that style and find it a lot more confusing. Also, most projects I've seen, including Bevy and Rapier, don't use it. I generally tend to follow Bevy's style and conventions since the crate is a Bevy crate after all.
Didn't realize bevy used mod.rs Wonder why they chose it
Having the mod file be among the modules of itself always felt weird to me
Also I noticed you have the spherical joint available with 2d feature.
Should probably only have it for 3d
For me it's the opposite, it feels strange that a module would be outside its own folder. And I don't want the module names to all be duplicated in the directory for the folder and file, especially since folders are typically at the top and files at the bottom, which creates a disconnect.
Like, this:
collision/
dynamics/
geometry/
math/
lib.rs
is nicer than this:
collision/
dynamics/
geometry/
math/
collision.rs
dynamics.rs
geometry.rs
lib.rs
math.rs
(I omitted the mod.rs files from the first one of course, but when traversing e.g. GitHub, you also don't see them before opening the folder)
In the latter one, the module files are disconnected from their own folders, which I don't like. And it gets worse with larger directories, and in the mix there could also be modules that don't have sub-modules, so then you can't immediately tell which modules are "parent" modules and which are "standalone" modules (for lack of a better term).
The main benefit to *mod_name*.rs in my eyes is that you can search for the files better and you don't get a bunch of different yet indistinguishable mod.rs tabs in editors, but I see that more as a tooling issue.
I did feel similar when I started with rust, duplicates of the same module name are cringe in the directory structure
And yeah this is fixed locally. The old 2D joints also use 3D logic (Vector3::Z stuff everywhere) for some reason, which I've fixed to be properly 2D
Nice
Another nitpick.
On main, joint limit torque is a vec3 in 3d, but they represent a single axis, and can be Scalars
I also heavily prefer mod.rs personally, I'm not entirely sure why the other way is recommended
It feels weirdly disconnected and maybe thats just my familiarity with the language before the other option existed but meh
good point, I'll change that
If the basis of a body in a joint is it's rotation relative to identity, then doesn't it make sense to have only one basis for a joint? Why does rapier have 2?
So far I've only ever needed to specify one basis
i didnt know they made new option for file hierarch
but i guess i will still use old method
don't need help today, got body chunking working!
Ooh, cool!
xpbd works very nicely with lots of child colliders, very impressed how the center of mass just gets handled for you
It was quite painful to get working and the code is still rather questionable in some places... but I'm glad it works in the end π
It seems to be very robust! Only had issues when spawning debris entities automatically, need a pre-spawn check for overlapping colliders so as to not spawn the debris if it's going to blow up the sim
@vestal minnow How much do you expect external API's to change with the new solver? I've been kind of putting off digging into xpbd because of the imminent change
The core API uses the same components and resources, so that won't really change. The module structure (and some docs) will change quite a bit, but that shouldn't matter if you use the prelude. Joints will most likely get upgrades, but again the general API should largely stay the same.
The biggest changes will be in the internals, so if you relied on some internal system ordering or some specific system sets, there might be breakage. And of course physics behavior might be slightly different (notably, collisions will be a bit "softer" but much more stable and performant).
In terms of breaking changes from a user's perspective, it'll be roughly like a normal bevy_xpbd release. There will be a migration guide
I'm not yet entirely sure how I'll handle the next release; I could just release everything under the new crate name (whatever it'll be), or I could first make one more bevy_xpbd release with everything except the new solver to ease the transition
Is there a way to turn debug rendering on and off globally at runtime?
You can get the PhysicsGizmos gizmo group and set enabled
fn my_system(mut store: ResMut<GizmoConfigStore>) {
let (mut config, config_group) = store.config_mut::<PhysicsGizmos>();
config.enabled = false;
}
cool perfect
If I wanted to write a generic function to be used in multiple places to do this what would be the most ideomatic way do you think?
just something like this
fn my_system<G: GizmoConfigGroup>(mut store: ResMut<GizmoConfigStore>) {
let (mut config, config_group) = store.config_mut::<G>();
// ...
}
or specifically a function and not a system?
I meant more like, I want to toggle_debug_render from systems for ui, bevy_console, possibly a keybind
but I can't just call a system from another system
yeah, custom command or an event I think
come to think of it, maybe what I should do try to make some changes to how bevy_console works, so that the commands actually implement Command
and then use that as an abstraction layer
hm, I'm not sure if commands can actually access resources
cursed solution, I wonder if you could instead run a one-shot system from the command 
that was my thought
I don't think its that cursed
Since there are commands to run systems, I don't see why not.
You'd have to pass the system ID every time though, right? Or can you make it const somehow
I feel like you kinda lose the point of the custom command if you need to get the system ID it depends on from somewhere else every time
Hmm, yeah.
The simplest solution
#[derive(Debug, Default, Resource, Reflect, Serialize, Deserialize)]
#[reflect(Resource)] // <-- forgot this one once again
struct DebugRender(bool);
fn toggle_gizmos(d: Res<DebugRender>, mut store: ResMut<GizmoConfigStore>) {
if d.is_changed(){
let (config, _) = store.config_mut::<PhysicsGizmos>();
config.enabled = d.0;
}
}
kinda redundant to control a resource with a resource but it puts some indirection between my editor/console code and the nitty gritty of what DebugRender actually is
still want to look into the commands thing though
Hey I was wondering if this change was still on schedule for may or is that just when the name change is happening and the changes engine will happen slowly after may.
Probably around the end of May. I have things mostly done, just need to do some more fixes and improvements, updating docs and examples, and so on
Is this on the main branch Im gonna be starting a project pretty soon Id been willing to use it and report anything that comes up.
Not yet, but I'll probably start making actual PRs soon and rolling out the stuff I've been working on
A lot of it isn't directly related to the new solver but are just general improvements (like structural changes, CCD, joint improvements, etc.)
Nice thanks for the quick response.
I'll probably merge the new contact solver last, but I'll try to make a public branch soon-ish
Any gotchas for replication? at the moment I'm just replicating pos,rot,linvel and angvel with bevy_replicon
And transform (this might produce weird conflict since pos+rot is also replicated but none seen yet)
seems to work okay, I just had to make sure my systems for replication ran before all systems for physics
Might not want to replicate that one yea ... One weird one might be the time spent sleeping component ... Tho sleeping is broken anyway and I'm not sure if it'll still exist once it's reworked
oof, sleep is important
anyway, I might try not syncing pos or rot, transform should get sync'd
one question I have is: given the whole position base dynamics thing, does changing the position/transform get interpreted as a force/velocity or other such thing?
because in this case I just want a clean overwrite.
interestingly when I pick up my cube in bevy_editor_pls it flies away
trying to dig through the gizmo code, what a headache
Only if you change the position within the substepping loop, in the constraint solver. So generally not unless you give the systems some very specific system ordering
XPBD basically derives velocity from the change in position caused by constraints
(and of course gravity and external forces)
How does joint restituion work in XPBD?
I see that a paper's implementation has some bounce.
For the joint limits.
I guess you'd need to do a separate restitution calculation in addition to the penetration constraint one.
Same for linear joint friction?
Yep, I imagine it'd need to be similar to how collision restitution is handled, as a separate velocity solve
Joints already have linear and angular damping, which slows down the bodies, but I'm not sure if it's exactly the same as what many engines call joint friction
Yeah, I think damping is just for stability.
Friction in this case would represent a prismatic joint's bodies sliding along eachother.
If you disable collisions that is.
Godot just calls it damping for prismatic (slider) joints and 6DOF joints, or relaxation for many other joints
I guess we'd also want force limits for joints. Should be doable
As in breaking with enough force?
Both that, and just the maximum force a limit can apply, for example
not sure how useful that'd be though... Godot's 6DOF joint has it, but most other joints don't seem to
PhysX has break thresholds at least
That seems difficult in PBD
You can compute the constraint force based on the Lagrange multiplier update. If it exceeds the force threshold, you derive the maximum Lagrange multiplier update from the maximum force and use that instead. Could be wrong, but iirc I got this working some time ago
Oh, I see how that could work.
Naming things lagrange and jacobian has done irrepairable damage to the learnability of realtime physics XD
Yep, haha
You could even cache the max delta Lagrange if the max force and timestep are constant
at the very least, you only need to compute it if the max force is limited, and skip otherwise
Also, for the spherical joint limits I'll probably attempt to support something like this
enum SwingLimit {
Cone {
swing: f32,
},
Pyramid {
min_swing_y: f32,
max_swing_y: f32,
min_swing_z: f32,
max_swing_z: f32,
},
}
(API will likely be different)
I haven't found any sources on elliptical cone swing limits, and PhysX seems to be basically the only mainstream engine that has them, so for now I'd only support the circular cone and pyramid
Can't elliptical limits be represented by an ellipsis with semi-axes being total_allowed_angle/2?
Not sure what you mean. What would the angle constraint be like?
My initial thought is that a cone limit can be represented as a circle with r=max_angle
An elliptical limit would thus be represented by an ellipsis with a=max_angle_x and b=max_angle_y
What's r here? The radius of the circular cross-section in the cone increases the farther you get from the apex
I'll try to explain when I'm done with this game
I'm having so much fun with this crate <3
Haven't tested it yet.
Reminds me of SPAZ.
haha yeah I'm making spaz + terraria
Nice, that could be cool.
Interesting idea, could potentially work... I'll try if I can implement something like this
Holy crab, I think it might be working basically first try @little berry
a = PI/2 β 1.57 (max swing angle 1)
b = a/2 β 0.785 (max swing angle 2)
c = r (current max swing angle)
It's bouncy/explosive when hitting some angles harder, but that might be the restitution issue
Poggers
poggers indeed, thanks for the idea for the limit!
I'll need to test more setups and probably visualize the ellipse to make sure it's accurate, but at least this basic setup seems alright
Also need to test limits above 90 degrees
And IDK if the ellipse represents anything in the 3d space
So visualizing it may not be useful
mostly just looking down from above the cone to see if the body follows the perimeter of the ellipse
Makes sense.
Hey so i am not sure if I'm doing anything wrong but I have set the physics plugin to run in the fixedUpdate schedule and when in presenter mode immediate where im getting ~1500fps the physics seems to run very slowly, like a tick every 2-3seconds. while if in presenter mode vsync i get ~120fps and it doesnt happen.
sorry if im stoopid
I think bevy_xpbd default to fixed timestep.
You'd have to insert the time resource yourself to tell it to run faster.
This is what im currently doing
bevy_xpbd has a custom fixed timestep by default, but in PostUpdate, similar to bevy_rapier (except rapier's timestep isn't fully fixed by default).
Not sure if this is related, but you might want to use Physics::fixed_once_hz when running in FixedUpdate
app.insert_resource(Time::new_with(Physics::fixed_once_hz(64.0)))
oh and make sure your FixedUpdate timestep and physics timestep match
This fixed it, thank you :))
Are there improvements for making physics run in fixed update UX in the next release?
Seems pretty miserable if you don't know how to do it.
Yesterday me and some a friend where playing around with fixed update but it seems to slow down the sim on higher fps monitors.
I think this is exactly the issue i was having
For now we just added this for every system that needs to wait for physics updates
.add_systems(PostUpdate, camera_controller.after(PhysicsSet::Sync).before(bevy::transform::TransformSystem::TransformPropagate))
not ideal but it fixes jitter and lag of the transform
This seems to work without having to force a fixed update
Yeah, it's pretty bad atm. I haven't done improvements yet, but I could at least make it so that you don't need fixed_once_hz and FixedUpdate should work out of the box even with fixed_hz. And I should add better documentation for FixedUpdate usage.
Eventually, we should probably rework the scheduling to just use FixedUpdate by default similar to basically every other game engine. This hasn't been done yet, mostly because of Bevy's FixedUpdate historically having some issues in earlier releases (and it still has some issues afaik). But nowadays I think it should be more viable
Yeah, it was very funny when any load in FU would feedback loop into itself.
Should be fine now, from my limited experience.
Rapier seemed to run fine in fixed update for me.
Yeah. I would personally leave a rework like this to the release after the next release though (i.e. after the solver rework) to avoid too many breaking changes
Fair.
The rebrand seems like a project that stalls your average FOSS project and burns out the author.
So getting it done earlier that later is preferable.
Haha yeah, at least I'm done with school until fall so I don't have that to stress about
I've got another style nitpick.
Instead of having a giant idented if block, it's recommended to use a guard clause.
Instead of
if let Some(angle_limit) = self.angle_limit {
//do many things
}
0.0
do
let Some(angle_limit) = self.angle_limit else {
return 0.0;
};
//do many things
Wish there was a RA action for it tbh. It can already turn the former into a match block.
i am trying to build a kinematic character controller in xpbd and have realized that normals are very imprecise when using shape casts via spacial query's shape_hits function. it gives incorrect values when moving almost orthogonal to colliders, even when they are completely aligned to grid. it isn't by a ton, but it's enough to clip through colliders. i was curious if there were any common pitfalls, or if this is known behavior
i can work around this by getting the collider's transform, and using transform.translation.back().xyz() as a substitute, but i'd much rather have xpbd give me the normal it has already calculated
I ran into this issue as well when making a character controller, would be interested to see if it could be fixed
Seems like a Parry issue if it's happening for shape casts. Not sure if there's much I can do (other than trying to fix it in Parry)
if that's so, it's definitely upsetting
Have you opened a bug with parry?
These sorts of reports are invaluable honestly
Btw the normals should be in local space, so make sure you're transforming them by the rotation of the correct entity if you want global space normals
that's probably not the issue though if it even happens for axis-aligned objects
Perhaps I should rename the data from e.g. point1 and normal1 to local_point1 and local_normal1 to make it more explicit...
i have not. i will do so when i get home
probably a good idea. i had no idea until just now and would have run into that issue myself no doubt
yeah the docs do mention it, but most people probably don't really think about it at the start and just expect it to be in world space
I've seen others get tripped up by it too so it'd probably be good to rename
Or alternatively, just make the data world-space by default. But internally it's still computed in local space, so for cases where the local data is better, there'd be unnecessary conversions
Looks like Unity and Godot just return the world-space point and normal. That might ultimately be the most user-friendly option...
One idea could be to keep the local data, but to also store the transform of the entity that was hit. This way, you could directly get the global data via helper methods without having to separately query for the position of the entity that was hit. I think this could be a really flexible and convenient approach
For prior art, Unity returns the transform, for example
With that approach, this
fn my_system(spatial: SpatialQuery, transforms: Query<&GlobalTransform>) {
if let Some(hit) = spatial.cast_ray(Vec3::ZERO, Direction3d::X, 100.0, true, default()) {
if let Some(transform) = transforms.get(hit.entity) {
println!("{}", transform.rotation() * hit.normal);
}
}
}
would be just something like this
fn my_system(spatial: SpatialQuery) {
if let Some(hit) = spatial.cast_ray(Vec3::ZERO, Direction3d::X, 100.0, true, default()) {
println!("{}", hit.global_normal());
}
}
Looks like Unity and Godot just return the world-space point and normal. That might ultimately be the most user-friendly option...
I guess offer both if most people need the world one?
Yeah my idea was this
One idea could be to keep the local data, but to also store the transform of the entity that was hit.
So e.g.ShapeHitDatawould be roughly like this
pub struct ShapeHitData {
pub entity: Entity,
pub time_of_impact: Scalar,
pub local_point1: Vector,
pub local_point2: Vector,
pub local_normal1: Vector,
pub local_normal2: Vector,
/// The transform of the entity that was hit.
/// (ideally this would be a PhysicsIsometry type)
pub transform: GlobalTransform,
}
impl ShapeHitData {
pub fn global_point(&self) -> Vector {
self.transform.transform_point(self.local_point1)
}
pub fn global_normal(&self) -> Vector {
self.transform.rotation * self.local_normal1
}
// ...
}
Of course storing local data for both colliders uses more memory than just storing the global data though
yeah
Although GlobalTransform doesn't have rotation.
The actual implementation would store the Position and Rotation
Precomputing scale sure is convenient.
But it could be nice to have a wrapper isometry type for this. I've been considering just adding a PhysicsIsometry like Nalgebra's/Rapier's isometries
What's the use case for returning scale in the hit data?
No, I mean that baking scale into the colliders helps avoid so much headache.
Yeah, currently colliders store both the scaled and unscaled version (Rapier does the same). I'm pretty sure only storing the scaled version would be lossy
and have some other issues
But perhaps that'd only be an issue if you scale the same collider multiple times, and at that point we could leave it to the user to cache the unscaled shape
I wonder why parry/rapier stores all the shapes on the heap.
Seems like it'd only be useful for meshes.
I imagine it'd hurt performance and cause lots of other issues if you stored shapes in multiple different places. So if you store meshes and convex polyhedra on the heap, you should probably store other shapes there too.
Or at least in the same component
I guess you could use an enum
I still need to try how viable colliders as assets is, but it's blocked on Parry's shapes not implementing TypePath
I would try it for you but afaik the AnyCollider/ScaledCollider trait signature doesn't let me access assets 
Yeah, to support collider assets we'd need to change every system that queries for colliders to have Assets<MyCollider> in the system signature and query for handles instead. I'm not sure how we could nicely support colliders as both components and assets
I guess technically some kind of generic system parameter could work
Yea you'd probably want to let the trait specify a system param that should be queried for it
Otherwise you'd need spheres as assets in that example which seems a bit silly 
Uhh, I think something roughly like this might work
trait ColliderQuery<'w, 's, C: AnyCollider, F: QueryFilter = ()> {
fn get(&'s self, entity: Entity) -> Option<&'s C>;
}
#[derive(SystemParam)]
pub struct ColliderComponentQuery<'w, 's, C: AnyCollider + Component, F: QueryFilter + 'static = ()>
{
pub query: Query<'w, 's, &'static C, F>,
}
impl<'w, 's, C: AnyCollider + Component + 'static, F: QueryFilter> ColliderQuery<'w, 's, C, F>
for ColliderComponentQuery<'w, 's, C, F>
{
fn get(&'s self, entity: Entity) -> Option<&'s C> {
self.query.get(entity).ok()
}
}
#[derive(SystemParam)]
struct ColliderAssetQuery<'w, 's, C: AnyCollider + Asset, F: QueryFilter + 'static> {
query: Query<'w, 's, &'static Handle<C>, F>,
colliders: Res<'w, Assets<C>>,
}
impl<'w, 's, C: AnyCollider + Asset, F: QueryFilter> ColliderQuery<'w, 's, C, F>
for ColliderAssetQuery<'w, 's, C, F>
{
fn get(&'s self, entity: Entity) -> Option<&'s C> {
self.query
.get(entity)
.ok()
.and_then(|handle| self.colliders.get(handle))
}
}
pub trait AnyCollider: Send + Sync + 'static {
type Query<'w, 's>: ColliderQuery<'w, 's, Self>
where
Self: Sized;
// ...
}
And then use in a system
fn init_colliders<C: AnyCollider>(
mut commands: Commands,
collider_query: C::Query<'_, '_>,
// ...
The lifetimes are ugly though, and this approach adds an extra query for colliders, since if you need other components, you need to do that in a separate query
You could probably add generic QueryData for the query as well, but I'm not sure how I can add the collider component to the same query
I guess 0.14's query joins, but idk about the performance of that. Or maybe QueryState has some nicer approach, I haven't used it
I think the simpler way would be to just add an associated SystemParam as an extra, which would just have Res<Assets<C>> or some query to fetch them or however else they're stored
Since we can just store the handle in Collider, instead of making it Handle<Collider>
If it's useful there could even be separate asset pools for different types of colliders
my brain isn't braining, wdym?
Well you'd just store it on the trait, and since it's a SystemParam you could request Collider::Param (or since rust is probably gonna complain, <Collider as AnyCollider>::Param, then you pass a reference to the value when you do things with AnyCollider)
pub trait AnyCollider: Send + Sync + 'static {
type Param: SystemParam;
So you'd pass the system param to every generic collider method?
fn my_system<C: AnyCollider + Component>(
param: C::Param,
query: Query<&C>,
) {
for collider in &query {
let aabb = collider.aabb(¶m, pos, rot);
// ...
}
}
or something else
I feel like that'd be a bit strange from an API perspective since it'd be nice to be able to use the methods directly without any World access, and if you used multiple methods in the same system, it'd have to fetch the collider separately each time (for assets at least)
Yes or for end user systems:
fn my_system(
param: Collider::Param,
query: Query<&Collider>,
) {
for collider in &query {
let aabb = collider.aabb(¶m, pos, rot);
// ...
}
}
If we need to call multiple methods at once we could do something like a "fetched collider" maybe ... Not sure if that would actually happen however
In this case, if Collider is a component and not an asset then the param wouldn't do anything, right? Since Collider is the collider itself
Yea, () implements SystemParam so that would be used
I guess we could also make Collider just a wrapper around a separate shape struct, and the only method would be fetch or get. So you could do
fn my_system(
param: Collider::Param,
query: Query<&Collider>,
) {
for collider in &query {
let aabb = collider.get(¶m).aabb(pos, rot);
// ...
}
}
or something like that
Yea that would also work, we could introduce a layer before AnyCollider called FetchableCollider that has the system param and a fetched collider maybe ... But if it's not necessary that could complicate some types ... Maybe Collider has is an enum, and stores small variants directly, only holding a handle for Mesh/Compound/whatever π€
Okay this might not be ideal but:
pub trait Fetchable<T> {
type Param: SystemParam;
fn fetch(&self, param: Self::Param) -> T;
}
// Enum for *fetching* collider shapes. Doesn't store large shapes
// like trimeshes, polylines, and other compound shapes directly.
pub enum FetchableShape {
Sphere(Sphere),
Cuboid(Cuboid),
// ...
TriMesh(Handle<TriMesh>),
}
// The actual shape enum storing the data.
pub enum TypedShape {
Sphere(Sphere),
Cuboid(Cuboid),
// ...
TriMesh(TriMesh),
}
impl AnyCollider for TypedShape {
// ...
}
pub struct Collider {
shape: FetchableShape,
}
impl Fetchable<Option<TypedShape>> for Collider {
type Param = Res<'static, Assets<Handle<TriMesh>>>;
fn fetch(&self, param: Self::Param) -> Option<TypedShape> {
match self.shape {
FetchableShape::Sphere(sphere) => Some(TypedShape::Sphere(sphere)),
FetchableShape::Cuboid(cuboid) => Some(TypedShape::Cuboid(cuboid)),
FetchableShape::TriMesh(handle) => Some(TypedShape::TriMesh(param.get(handle).ok()?)),
}
}
}
Yea that could work ... But we'd need to be certain it's actually necessary the fetch is worth it, since it looks like a pain to write, and you also double up on all the matches on enums
yeah, and of course one issue with TypedShape being an enum is that they are all the size of the largest variant (I think?), in this case TriMesh
unless we add dynamic dispatch stuff again / wrap TriMesh in an Arc or something
TriMesh would need to be some reference type anyway I'd imagine
Wouldn't want to clone trimeshes every time you access them for something
yup
One cool part with this fetchable collider approach is that I think we could remove both AsyncCollider and AsyncSceneCollider, and make it possible to just give a Handle<Mesh> directly to a Collider method. A system would go through all of these colliders, build the collider shapes once the meshes are available, add the shapes as assets, and replace the mesh handle with the actual collider shape's handle.
This could also potentially work well with asset preprocessing for convex decomp and such
It would also become a lot more obvious how to reuse things the right way
Colliders are technically backed by a shared value, but it's kind of obscured by the xpbd API so most apps just have many many duplicate instances of the same colliders
Yeah, exactly
Definitely worth trying once Parry supports Reflect / I get peck to a usable state
here's the issue incase anyone wants to follow along
https://github.com/dimforge/parry/issues/193
How would xpbd respond if I dashed into an object far enough for the normal to point up instead of in the direction I came from? Would it just respond to the normals or would it also consider the involved velocities that caused the collision? π€
Like, if you dash fast enough that the character goes inside of the object, which could cause weird normals?
The penetration constraint in XPBD doesn't care about velocity, and just pushes the body based on the normal and penetration depth. So if the normal pointed up, the character would be pushed up. Restitution is applied afterwards though, and that considers relative velocity
That kind of super fast movement is for CCD to fix.
Which I believe Jondolf is working on for the next release.
Nise has SDF collisions so the built-in CCD wouldn't work out of the box
I could always implement CCD tho
yea
CCD sounds a lot more complicated than it is. I was surprised how straightforward the explanation is.
"Just project the bounds lol"
I'll probably try to implement a speculative collisions + (optional) time of impact CCD combo, which seems like what almost every engine is going for
Project the bounds? π€
Shapecast the object's bounds along its velocity.
substepped CCD would be a bit more complex though (and more expensive)
and non-linear toi queries aren't super trivial
(Parry handles that for me tho)
I wonder if CCD for multiple constraints is in any way possible in realtime. Like for super fast spinning joints.
I've read, I think, Erin writing about improving joint limits by predicting them.
Yup, predictive joint limits are pretty nice. Trivial with impulse-based joints for at least prismatic and revolute joints. It's probably doable with XPBD too but not quite as easily
With impulse-based joints I think you literally just slightly modify the bias term
Hey guys, quick question:
I'd like to enable/disable colliders. Currently I'm doing that by adding and removing Sensors to let the player pass trough.
The problem now is, when the sensors is triggered once, it gets removed from the collider entity and the collider also doesn't "work" anymore afterwards (not showing up in debug, but in the hierarchy it's still on the entity). Is this expected behavior? π€
bevy_xpbd doesn't remove colliders or sensors from entities at any point
If a sensor collides, all it should do is send a collision event
What's the position of the entity when this happens?
Makes sense, thanks I'll check if it's something on my end. But when the sensor would be removed and the collider component is still on it, it would normally collide again right?
I don't think I've tried that myself, but I believe it should work normally, yes
ok, was an issue on my end π€¦ββοΈ thanks Jondolf!
Np!
More joint and constraint improvement progress:
- New 3D types:
SwingLimit,TwistLimit,SwingConstraint, andTwistConstraintSwingLimitis an enum with support for circular cones, elliptic cones, and pyramids (pyramid impl WIP)- These constraints can be used on their own, but they're also used by
SphericalJointandGenericJoint, increasing code reuse - Custom swing and twist axes are supported
- Much cleaner and better documented code for the limits
- I'll most likely
the Lagrange multiplier properties from constraints. They confuse people and tie the types to XPBD even though the types could technically be reused by other implementations. The multipliers don't need to be stored if we only use substeps and have no separate position iterations (which is already the case).
Random thoughts:
- It's annoying that the joint types store entity IDs. It ties the types to the ECS and means that composing a constraint (like a spherical joint) out of several other constraints (like a ball-socket constraint and a swing-twist constraint) leads to storing duplicate entity IDs. Storing the entities in a separate component would fix this, and it'd also reduce the need for generic systems in cases where the joint itself isn't needed but the "relationship" is.
- Some engines like PhysX and Jolt define the twist axis (and hinge axis) as the common X axis. I think the Z axis could make more sense to make 2D and 3D consistent. Bepu also defines Z as the twist axis.
- I will probably move the point-to-point constraint used by e.g. revolute and spherical joints to its own constraint type for convenience and code reuse. Bepu calls it a
BallSocket, Godot calls it aPinJoint2D/3D, and Jolt calls it aPointConstraint. Not sure on the ideal name here... - Eventually, we might want to reconsider our other joint names as well. Names like "prismatic" and "revolute" seem to come from engineering, but "slider" and "hinge" are also very common and could be more approachable in the context of game development (although "slider" might be a bit overloaded). Not something I'm changing now, but could be worth considering at some point. For now, I've added these as doc aliases.
pyramid impl WIP
I did mine by just applying the twist limit to different axes. Didn't test it much, but felt like it worked.
I feel like I've tried that and had some weird issues, but I'll try again tomorrow
I think the Z axis could make more sense to make 2D and 3D consistent. Bepu also defines Z as the twist axis.
I do feel Making NegZ the "Forward" of the joint feels right.
And as you say helps with 2d
That would mean that the min/max joint limit is applied the wrong way in 3D
I think
What do you mean?
Bepu calls it a BallSocket, Godot calls it a PinJoint2D/3D, and Jolt calls it a PointConstraint. Not sure on the ideal name here...
Pin sounds the most relevant. Anchor maybe?
Names like "prismatic" and "revolute" seem to come from engineering, but "slider" and "hinge" are also very common and could be more approachable
Yeah, learning the former terms was weird.
A positive rotation is counterclockwise in 2D (because mathematicians). The max limit should be hit when rotating in that direction. But if you used -Z for the axis, I'm pretty sure the max limit would instead be hit in the clockwise direction when viewing from Z towards -Z.
Can invert the limits?
At least right now, with the current impl, 2D and 3D seem to be consistent
That'd be weird since it'd effectively be implicitly inverting the axis itself
But it would be consistent to the user for the joint to "look" towards the bevy default.
I think striving for consistency is key for ease of use.
(even if I hate negz forward that bevy chose)
If I had a joint motor for a revolute joint with a positive angular velocity, I'd expect it to rotate counterclockwise in the XY plane, because that's how rotations in Bevy normally work and how AngularVelocity works. This wouldn't work if I made the axis face the opposite direction. That would be inconsistent.
I guess we think about joints differently. To me the axis is the "main" part, and the corresponding plane is assumed.
I just don't see why this:
commands.spawn(revolute_joint.with_motor_target(0.3));
would have a different rotation direction than this:
commands.spawn(PbrBundle {
mesh,
material,
transform: Transform::from_rotation(Quat::from_rotation_z(0.3)),
..default()
});
and if we just internally made the axis face the opposite direction, it'd break all of my expectations of what the axis is
we'd also only invert e.g. min/max limits in 3D, since 2D doesn't need that
since in 2D, rotation is about Z, not -Z
Wait, how does this
Transform::from_rotation(Quat::from_rotation_z(0.3))
line work exactly?
All my intuition is gone from having negz be forward.
Viewing from Z towards -Z, it's a counterclockwise rotation of 0.3 radians
So bevy accounts for it properly then.
In 2D:
And same in 3D.
With Z as the rotation axis, the min/max limits, motors, etc. work like you'd probably expect. On bevy_xpbd main, 2D joints even use Vector3::Z, but locally I just have raw angles, which produces the same result.
If I were to make a convex_decomposition collider what would be the best way to make a mesh to match it?
Isn't the goal usually for convex_decomposition to match your mesh instead? π€
hey guys. I'm wanting to impelement a network stack in my game, but I need cross platform determinism. How far away is this? I read in the docs that it's already locally deterministic...
I actually want to sync just user inputs, then replay them on the server. Any suggestions would be appreciated.
There's a feature flag for cross platform determinism, I'm not sure if it works perfectly however. I've seen some very slight deviations over time in my game, but if you run into those in practice and can easily reproduce them they are probably fairly easy to fix
Thanks for the reply, and pardon my ignorance. Do you mean a workaround like syncing some properties sometimes, or periodically syncing full state, something like that?
Also, I've never quite understood why ints aren't used for physics engines to gain cross platform determinism...are floats just much faster or something?
Fixed-point numbers are just more painful, and might not be able to take advantage of platform intrinsics and vectorization as well, which can make them significantly slower. They don't really even increase the supported numeric range, and fixed-point math isn't supported by almost any math libraries in Rust (like Glam). And fixed-point numbers aren't even needed for cross-platform determinism, as all you need is scalar implementations for specific functions, mostly transcendental trigonometric operations like sin and cos.
Interesting, thanks for the great answer. Does that mean cross platform determinism is easy to implement? Does the enhanced-determinism feature implement what's required?
Actually, the better question for me is, if the server is running Linux on x86 or an ARM processor and the clients on Windows, will Bevy_XBPD be deterministic?
I assume you mean things like SIMD instructions don't do int.
I'm not sure if bevy_xpbd is fully deterministic even locally at the moment, so the docs might be inaccurate/outdated there; there are some determinism tests and those pass, and generally it seems to produce the same result, but I'm pretty sure I've also noticed tiny inconsistencies in some specific examples. It's a bit hard to debug properly. But once we're 100% locally deterministic, I believe cross-platform determinism should be pretty straightforward
(bevy_rapier also has determinism issues despite having an enhanced-determinism feature)
Any idea how soon this might be coming? Sorry to ask such a question on an OSS project!
Well I'm working on a big solver rework which should be done later this month. I'll test if that is deterministic, and if not, I can try if I can fix it. But I can't make promises, determinism issues are notoriously hard to debug
That's awesome. I don't need it anytime soon, though. Thans so much for your work, sir.
Hmm, pin could technically have naming conflicts with Rust's Pin, although in practice a name like PinJoint would probably be fine. "Anchor" could get somewhat confused with the local anchor properties on joints. What it does is constrain a point on one body to a point on another body, so PointConstraint would make sense, but it might be slightly less user-friendly, and the joint-like constraints generally have a FooJoint naming scheme
Wikipedia says that a pin joint is actually a revolute joint, which makes sense; if you pinned something to a wall, it'd only have one rotational DOF left. So that name isn't ideal
https://en.wikipedia.org/wiki/Mechanical_joint#Pin
It's ultimately a ball joint / ball socket / spherical joint without the angular limits, and I'm not sure if that really exists as its own joint in real life, since ball joints always have a limited range of motion. So I suppose a more abstract PointConstraint name could be fine too
Honestly, I think whatever you choose, it will be insufficient to describe the joint's full functionality by name. No need to think too much about it.
What axes did you use for this? I haven't found a combination that'd work properly yet, I keep getting either cone behavior or overall wonky behavior
fn apply_limits(
&mut self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
dt: Scalar,
) -> Vector3 {
let z1 = -self.joint_forward1;
let (x1, y1) = z1.any_orthonormal_pair();
let z2 = -self.joint_forward2;
let (x2, y2) = z2.any_orthonormal_pair();
let mut swing_lagrange_x = self.swing_lagrange_x;
let x = self.apply_limits_inner(
body1,
body2,
&self.swing_limit_x,
&mut swing_lagrange_x,
x1,
x2,
-z1,
-z2,
dt,
);
self.swing_lagrange_x = swing_lagrange_x;
And for the other swing and twist its y1, y2, x1, x2, and -z1, -z2, -x1, -x2,
Inner fn is just the old twist limit.
fn apply_limits_inner(
&self,
body1: &mut RigidBodyQueryItem,
body2: &mut RigidBodyQueryItem,
limit: &Option<AngleLimit>,
lagrange: &mut Scalar,
a1: Vector3,
a2: Vector3,
b1: Vector3,
b2: Vector3,
dt: Scalar,
) -> Scalar {
if let Some(joint_limit) = limit {
let basis_rot1 = self.compute_rotation_with_body_basis(*body1.rotation);
let a1 = basis_rot1.rotate_vec3(a1);
let a2 = body2.rotation.rotate_vec3(a2);
let b1 = basis_rot1.rotate_vec3(b1);
let b2 = body2.rotation.rotate_vec3(b2);
With basis.
Alright, thanks. I'll try that
is this crate currently wasm friendly?
and if not, can you point me in the direction of a workaround
It should work on wasm
hmm.. maybe it's my browser
What error(s) are you getting?
In theory any non-deterministic behavior should be pretty easy to fix in bevy_xpbd if you manage to create an example that reporudces it in the simplest way possible
The convex_decomposition was done first.
But you give a mesh to convex decomposition right? π€
I'll definitely do that once I get to that point.
@vestal minnow it seems like friction interaction is not quite stable
I'm applying a velocity to a cube at the center of mass, and it's causing rotation due to interaction with the floor
Not too much of an issue but it is kinda annoying
Well if it's touching the floor then isn't it supposed to cause rotation?
no?
I'm changing the velocity in the center of mass
so there should be equal amounts of resistance from all points of the object so it shouldn't rotate
also the velocity is directly in a face direction
so it isn't something with it going diagonally or something
Is this 3D?
Oh then yeah, it shouldn't rotate about the up-axis. I thought you meant something like friction causing a box to tip over when pushing it with high friction
Yeah, makes sense. The new solver will handle friction slightly differently and solves penetration via impulses so hopefully that'll be more stable
I'll test actually
ok
that does seem quite a bit better
I think 3D friction could be improved further by also accounting for friction along the bitangent
and a block solver would help make box stacking a lot more stable, but for 4 contact points it's a lot harder than for 2
- a block solver wouldn't work as well if we want to solve contacts with SIMD eventually
(or it'd be more difficult at least)
ExternalForce would be the correct thing to use for acceleration right?
That, or just add to velocity
I guess ExternalForce gets applied over multiple substeps
My current character controller just does a lerp between current and desired velocity based on some "acceleration" value, but this approach doesn't even really make sense, and is stopping me from reasonably implementing skills that push or pull characters, because they can easily overcome that force because the lerp just goes between bigger values π
Not entirely sure how I'm gonna do the pushing and pulling yet tho, would either need to be a force or some temporary velocity, we don't have a concept for the latter do we? π€
Like, give an entity some velocity for X seconds?
Yea, without the weird edgecases that might happen if you add velocity and remove it later, like bumping into something, friction, etc
Yeah there's no built-in thing for that
Would it just need another copy of the system that applies velocity, but using some temporary velocity component?
You could probably just have a TemporaryVelocity with a Timer and the target velocity, and then set LinearVelocity to that every frame, or do the integration manually yeah
lmao I forgot we have a separate LinearVelocity and not Velocity for some reason
Guess we'll have to call it TemporaryLinearVelocity now 
I wonder what the accurate reperesentation of wind would be anyway ... I guess it's kind of like air pushing you, so I guess it applies velocity? π€
If you think of it as drag, it'd be
F = 1/2 * fluid_density * relative_velocity^2 * C * A
where C is the drag coefficient that depends on the shape, and A is the cross-sectional area
For a capsule, the drag coefficient would probably be close to a sphere, which is 0.47 according to Wikipedia
Surely character models are all capsules π
Yep, you use the drag equations to model wind correctly π
All my character colliders are capsules at least π
Ah but I guess that would still sort of make it acceleration, the force just stops when the relative velocity is 0 π€
hmm
Would it be possible to make some way you could create both the mesh and collider with the same function?
It's kinda annoying to do things and realize that Collider::cylinder takes in the opposite argument list than Cylinder::new
I thought jondolf made a way to construct colliders from primitives π€
yeah you can do Collider::from(cylinder) or cylinder.collider()
So well integrated, you'd almost think you were involved in the development of primitives π
It's annoying that the cylinder and capsule radius and length are reversed for primitives vs. the collider constructors π₯²
Deprecate them π
yeah idk if I should just swap the order to match Bevy, breaking every app using them without producing errors, or deprecate the separate constructors
is always the answer
I made that API vote in #off-topic for xpbd 0.4 but didn't deprecate it yet because everyone gave different answers 
lol
I'd suggest deprecating tbh
Cylinder::new.collider() is not much longer than Collider::cylinder
Yeah, maybe I should do that... Probably not for this release though
This release will mostly focus on the new solver, CCD, joint improvements, and other general stability and performance updates, and the one after that will probably focus more on some UX improvements and API, along with fixing up low-hanging fruit and shrinking the number of issues in the repo
ok
- we need simulation islands and a better broad phase
Performance sounds nice π
We'll see if it's better for your case or just my collision-heavy tests lol
islands would really help and fix sleeping though... I might try if I can get a basic implementation working soon
I mean the performance bottleneck for me was collisions sooo ...
you have only like 3 substeps right?
At the moment yes, I'd like to go back to 6 tho 
I also run the schedule bevy_xpbd runs in 4 times (and that should support higher too) per frame π
Yeah well you hopefully shouldn't be bottlenecked by the narrow phase after the release at least
It's not even really bottlenecked on the narrow phase because sphere on SDF is cheap ... Tho even if I hardcode my collisions that system takes a substantial amount of time
Next bevy release should also clear up more room due to some optimizations to running systems and removing the fairly high runtime of the First schedule with all the event systems
Hmm do your SDF collisions support a "prediction distance"? Basically, computing closest points even for non-touching objects within some margin
To some extent yes, but if I want to push really large prediction distances I might need to change some early out logic a bit
Okay nice, speculative CCD could potentially work out-of-the-box for you then. It's only good for moderately fast-moving objects though and can cause ghost collisions if the prediction distance is too large
(I also haven't implemented it just yet lol, should be pretty easy though)
"SDF collisions with speculative CCD" ... People would surely think we're making stuff up at this point π
TGS Soft with XPBD joints and SDF collisions with speculative CCD
As I understand it, the current broadphase is a sweep-and-prune affair where all the colliders are mixed together in the same sweeps. Pairs that could never intersect because they're on different collision layers will still be sorted against one another. Is this correct?
I could use some help understanding how to create a platform that a dynamic object stands on and moves with. I created a small demo showing what I tried to do and how it didn't work here: https://github.com/jhgarner/platform-repro. The core of it is that I can't figure out how to make the velocity of a Dynamic object follow the position changes of a Static object and I think I'm missing something fundamental about how LinearVelocity interacts with the delta_time. What would be the right way to accomplish what the demo's trying to do?
Yea, there's just one sorted list, it does early out on any pairs that can't collide, but it's probably not the most efficient approach possible
If we're talking moving platforms that a character stands on it usually involves checking what "ground" entity a character is standing on, then applying the difference in velocity it has now vs previous update to the character's velocity ... But if it's for things that need to behave in a more physically accurate way I think some friction and having the platform be a kinematic body might be all that's needed π€
actually im unsure
There's other things which may not be primitives in bevy?
Like the HalfSpace
and having to swap between Collider::half_space and Cylinder::new.collider() would be inconsistent
Then again Collider::from(Cylinder::new()) is pretty consistent so it may be fine
We'll eventually add the missing primitives like HalfSpace to Bevy as well, but I could also just add them as primitives in bevy_xpbd until they're upstreamed
sorryyy wrong ping 
was responding to @last panther
But yeah either way I won't deprecate the methods yet, we can see what we should do later
And yes, it's currently a sweep and prune which has all colliders in the same list regardless of their layers. But I'm pretty sure other algorithms would also not split bodies into separate acceleration structures since it'd probably just be slower to manage and query
And as for sweep and prune, it uses a sorting algorithm that takes advantage of spatial and temporal coherence, meaning that bodies tend not to move that much relative to each other within individual timesteps, which makes sorting cheap
well the thing I'm worried about is if there ends up being things that make sense to exist in bevy_xpbd but not really as primitives separate from it
like if it couldn't be rendered sensibly or something
idk that's probably not true
You offer a function to create it anyways so it isn't really a problem
Primitives aren't just for rendering, colliders are one intended use case. And that's something that will most likely be built-in eventually.
Bevy also does already have a HalfSpace, it's just a rendering-specific type and not a math primitive yet
ah ok
@vestal minnow do you have any suggestions for how I'd run two simulations at once?
Or like rewind the physics to a previous state
like could I just set the position & transform to the previous value
or is there any physics data that would be messed up
Kinda goes without saying
Shit wait the simulation isn't fully determinsitic right
any way to make it so?
I just need local determinism really
It probably isn't 100% deterministic but it's hard to even tell without very specific setups
the thing is I'll be storing the player movement
and then like running it back later for like up to a minute
there's the enhanced-determinism feature but that only really helps with cross-platform deterministic math atm
don't think I need that
hmm... yeah there could potentially be drift if it's for that long
I guess I could just store the positions of everything every second or so
and reset everything then
actually that's a good idea regardless
..and I'm already doing that
I still need to properly test if the new solver is more deterministic
okay yeah no that isn't a problem
@vestal minnow actually remember I gave the friction example?
That may not be deterministic which could be an issue
since it seems like things significantly diverge from what they should be
Thanks. I'm trying to implement the less physically accurate version. My simplified example skips checking the ground entity by just assuming one, and assumes the platform movement is the only thing affecting the velocity so I just set the followers velocity to the derived following velocity. Even with these assumptions though, I still see the behavior shown in the readme (https://github.com/jhgarner/platform-repro).
If you use linear velocity to move the platform as a kinematic body you wouldn't have to mess with the delta times at least ... Just copy the velocity over every frame after the platform updates it π€
Also please rename anglelimits alpha and beta to min and max
this isn't math land
is a breaking change though...
I already have that renamed locally
and the joint code is a lot better commented and structured
Renaming them? No
no I mean more like
have you redone the rest of the joint api or is it just internal changes apart from that
basically how much stuff would I have to rewrite
The properties are different but you'd generally just touch the costructors and helper methods and those are the same. Although I will probably mark some of them as #[deprecated] since there's more configurability now
ok
Like spherical joint limits aren't just one swing limit and one twist limits, there are many different types of swing limits
And different limits can have their own compliances
And many joints support local frames instead of just anchor points
wdym by that?
A rotation + offset instead of just an offset. Like a fixed joint previously locked the rotations to be equal, but now you can set what the relative orientation should be
oh
I thought the fixed joint would have set the rotations to be equal to what they were at time of creation
but whatever
that doesn't really make sense when I think about it
same as PhysX local frames
https://nvidia-omniverse.github.io/PhysX/physx/5.1.0/docs/Joints.html#joints
and yeah we could make it default to that unless the frames are set, I believe e.g. Godot does that
and probably many other engines
by the way, the current SubstepCount documentation has a broken [Time] reference
docs.rs kinda is buggy about external crates sometimes though
I think that should be fixed now, tho that was fixed on a version after xpbd got its last release probably π€
ok
Also I think if you locally build docs it should report anything that is broken, idk if that's in xpbd's CI tho
It runs cargo check and fmt but I'm not sure if that catches broken doc links
Hasn't given an error for that Time reference at least
probably want to cargo doc
Don't think so ... It might be possible to include it with some flag ... Clippy also has rules for some of the formatting stuff iirc, could possibly see what bevy does for CI, since it does check docs stuff
hm
yeah I should probably change it to build docs too
Bevy seems to use
cargo doc --workspace --all-features --no-deps --document-private-items
EventReader<Collision> is still the regular way to check sensors right? π€
Yeah, that, or you can query for CollidingEntities if you don't need the actual contact data
I still have to deal with the delta times because the platform's velocity depends on its current position and where it needs to be. Deriving and setting the LinearVelocity on a kinematic platform works well at low frame rates, but is unstable at high rates. I updated the repo with a minimal example and a short clip showing what's happening https://github.com/jhgarner/platform-repro/tree/kinematic. At least now the two objects move together even when the platform runs off lol.
I would just make the physics fixed rate if that was the main issue π€
Thanks. In that case I'll look into how to split the logic between fixed and normal update.
Hello π
Is it possible to set the collider location to the GlobalTransform of the entity and not it's normal Transform?
Hello π I believe xpbd does this already. I have colliders flying around that are children of other entities, and the colliders properly inherit the GlobalTransform's position
Interesting... my colliders use the Transform component, when I use the debug plugin I see the colliders at the Transform point and not in the GlobalTransform point. Also the ColliderTransform is set to the same coords as the Transform did you change something in your config?
Nope, totally vanilla. Do you have the full spatial bundle in both parent and child?
In the parent I don't have anything, the parent is only used as a container so my Inspector is not full of entities
I believe if you want GlobalTransform to work properly, the whole hierarchy needs both GlobalTransform and Transform
Interesting, I'll try adding them
Setting the GlobalTransform to the parent now makes the children's GlobalTransform relative to the parent which is what I wanted to avoid and the reason I used GlobalTransform
GlobalTransform is not relative to the parent, Transform is
If the root entity has a default Transform, setting the Transforms of its children should be the same as setting their GlobalTransforms
And GlobalTransform in general is intended to be read-only anyways
(GlobalTransform docs)
I see, I will look more into it, thank you π
I have noticed that if you place a raycast component onto a game object its origin is whatever itβs pos was the previous frame or is that an artifact of the debug plugin being a frame late?
Does XPBD use fixed point math? I ask because I've read this:
"Addendum: I am suggesting to keep the size of the world small. With that said, Both OP ans Jibb Smart bring up the point that moving away from the origin floats have less precision. That will have an effect on physics, one that will be seen far earlier than the edge of the world. Fixed point numbers, well, have fixed precision, they will be equally good (or bad, if you prefer) everywhere. Which is good if we want determinism. I also want to mention that the way we usually do physics has the property of amplifying small variations." on this page: https://gamedev.stackexchange.com/questions/174320/how-can-i-perform-a-deterministic-physics-simulation
I'm worried my RTS map will be too big and errors will creep in in the physics calculations.
I don't think I've ever heard anyone imply determinism needs fixed point math ... Determinism isn't usually about deterministic results despite different coordinates π€
For the most part if you get your units right the size of maps doesn't usually become an issue, tho if you're dealing with things like entire planets or solar systems a floating point origin system or something else like that definitely becomes necessary to keep sane behavior ... I remember someone tried to hook bevy_xpbd up to bevy_space, but I'm not sure how that went π€
This SO article is all about writing deterministic physics. He does seem to have some really good pointers. I'm just worried about this idea that float maths gets less precise the larger numbers get.
Here's the specific article he sites on that:
"The Butterfly Effect - Deterministic Physics in The Incredible Machine and Contraption Maker."
https://www.moddb.com/members/kevryan/blogs/the-butterfly-effect-deterministic-physics-in-the-incredible-machine-and-contraption-maker
determinism and fixed point aren't really related
but tbh if your floating point is having issues at whichever distance you're already having something a bit too big
if you really want better precision use the f64 flag
Yeah you can absolutely get cross-platform determinism with floating-point math, you just need to be careful with specific operations that normally would use intrinsics that can give different results on different hardware.
For large worlds, the solution is more-so to either use double-precision (f64) or a floating origin like big_space. In general I'd recommend a floating origin if that works for your use-case. The main challenge would be handling multiple floating origins for multiplayer, but I think even that would be viable if/when we add built-in support for it to the physics engine
One of his points: "No, doubles won't save you!" π
The mentioned issues of cross platform can all be bypassed, but this article does seem to suggest they want things to behave identical regardless of where they are placed, which would indeed require fixed point values
They do increase how much space you can have without getting weird issues, tho those weird issues can still be deterministic, they would just be equally weird on both sides if networked π
Yeah, that much I understand.
And yeah if you wanted things to behave exactly the same regardless of position, you'd need fixed-point precision. But in general it's not worth it
I would think I lobbed an artillery shell in the corner of the map, it would land in the same relative position than if I lobbed it near origin.
if it travelled in the same distance and direction
yes but you don't need that to be exact
The difference is so miniscule that it shouldn't matter for 99% of applications
^^that
OK well, that does sound comforting.
The errors should be far too small to notice that, the issue comes in when you have physics deviations that can keep piling up. The gif in the article shows something where a tiny difference could potentially add up for it to eventually perform noticably different behavior
For very large worlds errors can be clearer, but then you can use a floating origin
But even then that example looks like there's plenty of points that self correct these deviations π€
Not sure what you mean by use floating origin
How self correct?
Basically any system that forces positions or velocities back to some known value would clear any accumulated floating errors
cool
In some cases, even rendering can start showing artifacts before physics has clear issues. Like I once did this test very far from the origin, and the physics seems fine-ish
while the rendering is... uh... yeah
I can't even see if the physics are fine because of the rendering π