Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to get the collision point coordinates from CollidingEntities? #552

Closed
ufoscout opened this issue Nov 9, 2024 · 2 comments
Closed
Labels
A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality question Further information is requested

Comments

@ufoscout
Copy link

ufoscout commented Nov 9, 2024

Hi, first of all, thank you for this beautiful project. I went through the docs but can't figure out how to get the coordinates of a collision point. Here for example I need the coordinates of where an entity and a wall collided:

pub fn wall_collisions(mut commands: Commands, map: Query<(&Wall, &CollidingEntities)>) {
    for (_map, collisions) in map.iter() {
        collisions.iter().for_each(|entity| {
            commands.entity(entity).insert(CollidedWithWall { contact_x: ??, contact_y:?? });
        });
    }
}

Is this possible? I need both the absolute coordinates and those relative to the entity positions.
The Wall is RigidBody::Static and the entities are RigidBody::Kinematic

@Jondolf Jondolf added question Further information is requested A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality labels Nov 11, 2024
@Jondolf
Copy link
Owner

Jondolf commented Nov 11, 2024

So, this is a bit more involved than one might think; apologies for the long message 😅

You can get the contact data from the Collisions resource, for example using collisions.get(wall_entity, other_entity). So you could iterate over each entity in CollidingEntities, and get the contacts for each one.

Next, this gets a bit complicated:

  1. Every collision can actually have multiple contact manifolds ("contact surfaces" with a specific normal), each of which can have multiple contact points. For example, a cuboid lying flat on the ground might have four contact points, one at each corner of the contact surface.
  2. When two shapes overlap, there isn't a "single contact point". Instead, each contact has a contact point on the surfaces of both objects.
  3. The contact data is stored in local space. This is because it's more efficient to compute, and it's also the representation needed by the engine to solve contacts.

For (1), if you don't want to worry about all contacts, you could just find the contact with the largest penetration depth. I realized there is no method for this yet, so I made a PR to add it (#556). You could copy the methods from there or wait for next release where they'll be included.

For (2), you could always just get e.g. the contact point on the surface of the first entity, contact.point1. Or the other one, whichever you prefer.

For (3), you said you wanted the points in both absolute coordinates and relative to entity positions. To get the point in world-space, but relative to the entity position, you can just apply the entity's rotation like transform1.rotation * contact.point1. To get the point in absolute/global coordinates, just add the position of the entity to that.

Finally, there's still one more potential issue: there's no guarantee which entity in the contact data is which. contact.entity1 could be your wall entity or the entity it was hit by. There's a few ways you could handle this, for example you could check if wall_entity == contacts.entity1, and get contact.point1 or contact.point2 based on the result. I also just opened a PR (#557) to add flip and flipped helpers to flip contact data, which could make this nicer.

With all of that in place, and the methods I added, you could do something like this:

pub fn wall_collisions(
    mut commands: Commands,
    map: Query<(Entity, &Wall, &CollidingEntities, &Transform)>,
    collisions: Res<Collisions>,
) {
    for (&wall_entity, _map, colliding, transform) in map.iter() {
        for &other_entity in colliding.iter() {
            let Some(contacts) = collisions.get(wall_entity, other_entity) else {
                continue;
            };
            let Some(mut deepest_contact) = contacts.find_deepest_contact().copied() else {
                continue;
            };

            // Make sure that `wall_entity` is the first entity in the contact data.
            if wall_entity != contacts.entity1 {
                deepest_contact.flip();
            }

            // Compute the contact point relative to the wall, and in global space.
            let point = transform.rotation * deepest_contact.point1;
            let global_point = transform.translation + point;

            commands.entity(wall_entity).insert(CollidedWithWall {
                contact_x: global_point.x,
                contact_y: global_point.y,
            });
        }
    }
}

(I haven't tested the code, so it may require tweaks)

@ufoscout
Copy link
Author

Wow! Thank you, this is incredibly enlightening. I owe you a beer!

@Jondolf Jondolf closed this as completed Nov 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants