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

Contact hit events #613

Open
Jondolf opened this issue Jan 1, 2025 · 2 comments
Open

Contact hit events #613

Jondolf opened this issue Jan 1, 2025 · 2 comments
Labels
A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on C-Enhancement New feature or request C-Usability A quality-of-life improvement that makes Avian easier to use

Comments

@Jondolf
Copy link
Owner

Jondolf commented Jan 1, 2025

Note: This probably shouldn't be implemented before some contact improvements I'm working on, like a contact graph and contact type API improvements.

Games often only care about getting contact events for shapes that collide at a significant enough speed (or force). This can be used to trigger some sound effect or particle effect, or to apply damage.

In Box2D, this exists is in the form of "Hit Events":

// Events are only generated if `b2ShapeDef::enableHitEvents` is true
// and the approach speed exceeds `b2WorldDef::hitEventThreshold`.
for (int i = 0; i < contactEvents.hitCount; ++i)
{
    b2ContactHitEvent* hitEvent = contactEvents.hitEvents + i;
    if (hitEvent->approachSpeed > 10.0f)
    {
        // play sound
    }
}

In bevy_rapier, there is instead a ContactForceEvent:

// Events are only generated if `ActiveEvents::COLLISION_EVENTS` is set
// and the force magnitude exceeds the value in the `ContactForceEventThreshold` component.
fn display_events(mut contact_force_events: EventReader<ContactForceEvent>) {
    for contact_force_event in contact_force_events.read() {
        println!("Received contact force event: {:?}", contact_force_event);
    }
}

I think Box2D's approach of using an "approach speed" is generally more intuitive than having to deal with forces, since forces are mass-dependent and can get very large. However, forces or impulses can also be valuable if mass should be taken into account. So we could have both:

// Only send `ContactSpeedEvent` for entities that have a `ContactSpeedEventThreshold` set.
// If both entities have a threshold, the event will be sent if either threshold is met.
#[derive(Component)]
pub struct ContactSpeedEventThreshold(Scalar);

#[derive(Event)]
pub struct ContactSpeedEvent {
    /// The entity of the first collider involved in the contact.
    pub entity1: Entity,
    /// The entity of the second collider involved in the contact.
    pub entity2: Entity,
    /// The world-space contact normal at the point with the largest approach speed.
    pub normal: Vector,
    /// The contact point with the largest approach speed.
    pub point: Vector,
    /// The approach speed of the contact point.
    pub speed: Scalar,
}

// Only send `ContactImpulseEvent` for entities that have a `ContactImpulseEventThreshold` set.
// If both entities have a threshold, the event will be sent if either threshold is met.
#[derive(Component)]
pub struct ContactImpulseEventThreshold(Scalar);

#[derive(Event)]
pub struct ContactImpulseEvent {
    /// The entity of the first collider involved in the contact.
    pub entity1: Entity,
    /// The entity of the second collider involved in the contact.
    pub entity2: Entity,
    /// The world-space contact normal at the point with the largest impulse.
    pub normal: Vector,
    /// The contact point with the largest impulse.
    pub point: Vector,
    /// The magnitude of the largest impulse applied to the first body along the contact normal.
    pub impulse: Scalar,
}

Or alternatively, we could have a single ContactHitEvent that stores both the approach speed and impulse, and a HitEventThreshold component that is an enum or has two thresholds:

// Only send `ContactHitEvent` for entities that have a `HitEventThreshold` set.
// If both entities have a threshold, the event will be sent if either threshold is met.
#[derive(Component)]
pub enum HitEventThreshold {
    // TODO: Should this instead be a struct with two thresholds,
    //       or should this even be two separate components?
    Speed(Scalar),
    Impulse(Scalar),
}

#[derive(Event)]
pub struct ContactHitEvent {
    /// The entity of the first collider involved in the contact.
    pub entity1: Entity,
    /// The entity of the second collider involved in the contact.
    pub entity2: Entity,
    /// The world-space contact normal at the point with the largest approach speed.
    pub normal: Vector,
    /// The contact point with the largest approach speed.
    pub point: Vector,
    /// The approach speed of the contact point.
    pub speed: Scalar,
    /// The impulse applied to the first body along the contact normal at the contact point.
    pub impulse: Scalar,
}

bevy_rapier also provides a total force vector and total force magnitude, but I don't think we need them. We should just have helpers for contact types so that you can query Collisions and compute them with methods there.

@Jondolf Jondolf added C-Enhancement New feature or request A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on C-Usability A quality-of-life improvement that makes Avian easier to use labels Jan 1, 2025
@immmdreza
Copy link

Thank you for your efforts, Do we have any workarounds to calculate impact hits on two colliding entities at the moment?

@Jondolf
Copy link
Owner Author

Jondolf commented Feb 8, 2025

Do we have any workarounds to calculate impact hits on two colliding entities at the moment?

@immmdreza You can manually get the contact data from the Collisions resource and access the normal impulse in the ContactData. It's just a bit complicated since each collision can have more than one contact manifold (basically a contact surface), each with one or more contact points that have their own impulses.

Alternatively, you could manually track previous velocities in some component, and estimate impact strength based on the difference between the previous and current velocity after a collision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Dynamics Relates to rigid body dynamics: motion, mass, constraint solving, joints, CCD, and so on C-Enhancement New feature or request C-Usability A quality-of-life improvement that makes Avian easier to use
Projects
None yet
Development

No branches or pull requests

2 participants