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

avian2d reports collisions between non-intersecting HalfSpace and Rectangle #586

Open
tgiannak opened this issue Dec 9, 2024 · 1 comment
Labels
A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality C-Bug Something isn't working

Comments

@tgiannak
Copy link

tgiannak commented Dec 9, 2024

avian2d (including release v0.1.2 and e23d070) reports collisions between non-intersecting (and in fact, far apart) half-space and rectangle (as well as other) colliders.

I believe this is because for most other collider pairs, parry2d reports invalid normals while computing the contact manifolds, and that is what Avian uses to filter out contact manifolds that do not include contacts.

However, even when there are no contacts (i.e., manifold.points.is_empty() == true), parry2d still reports valid normals for collider pairs such as half-space and rectangle. I have also seen this behavior with half-space and segment.

The parry2d documentation isn't sufficient for me to easily determine whether additionally (or only) filtering based on reported contact points is the correct way to fix the false collision reports.

The following code demonstrates the bug in Avian. Since the results of contact_manifolds are used to populate the list of collisions with no further processing (they go from here to here to here), this results in collisions being reported for the rectangle but not the circle. In this case, neither should have collisions reported.

use avian2d::{collision::contact_query::contact_manifolds, math::Vector2, prelude::Collider};

fn main() {
    let bottom_half = Collider::half_space(Vector2::Y);
    let ball = Collider::circle(10.0);
    let rect = Collider::rectangle(10.0, 10.0);

    let center = Vector2::new(0.0, 0.0);
    let above_center = Vector2::new(0.0, 500.0);

    {
        let manifolds = contact_manifolds(&bottom_half, center, 0.0, &ball, above_center, 0.0, 0.0);
        assert_eq!(manifolds.len(), 0);
    }

    {
        let manifolds = contact_manifolds(&bottom_half, center, 0.0, &rect, above_center, 0.0, 0.0);
        assert_eq!(manifolds.len(), 1);
    }
}

The following code demonstrates the behavior of parry2d.

use parry2d::{
    na::{Isometry2, Vector2},
    query::{
        intersection_test, ContactManifold, DefaultQueryDispatcher, PersistentQueryDispatcher,
    },
    shape::{Ball, Cuboid, HalfSpace},
};

fn main() {
    // A half space covering the bottom half of the screen. (The normal points to the uncovered part.)
    let bottom_space = HalfSpace::new(Vector2::y_axis());

    // A half space covering the top half of the screen. (The normal points to the uncovered part.)
    let top_space = HalfSpace::new(-Vector2::y_axis());

    // Some small shapes.
    let rect = Cuboid::new(Vector2::new(10.0, 10.0));
    let ball = Ball::new(10.0);

    let above_center = Isometry2::translation(0.0, 500.0);
    let center = Isometry2::identity();

    // Confirm that the direction of the half-spaces.
    assert!(!intersection_test(&center, &bottom_space, &above_center, &rect).unwrap());
    assert!(intersection_test(&center, &top_space, &above_center, &rect).unwrap());

    {
        // Put the ball in the top half with the bottom-half-space and there are
        // no collision points and the normals are not valid.
        let mut manifolds: Vec<ContactManifold<(), ()>> = vec![];

        let result = DefaultQueryDispatcher.contact_manifolds(
            &above_center,
            &bottom_space,
            &ball,
            0.0,
            &mut manifolds,
            &mut None,
        );

        assert!(result.is_ok());
        assert_eq!(manifolds.len(), 1);
        assert_eq!(manifolds[0].points.len(), 0);
        assert_eq!(manifolds[0].local_n1, Vector2::new(0.0, 0.0));
        assert_eq!(manifolds[0].local_n2, Vector2::new(0.0, 0.0));
    }

    {
        // Put the ball in the top half with the top-half-space and there are
        // collision points and the normals are valid.
        let mut manifolds: Vec<ContactManifold<(), ()>> = vec![];

        let result = DefaultQueryDispatcher.contact_manifolds(
            &above_center,
            &top_space,
            &ball,
            0.0,
            &mut manifolds,
            &mut None,
        );

        assert!(result.is_ok());
        assert_eq!(manifolds.len(), 1);
        assert_ne!(manifolds[0].points.len(), 0);
        assert_eq!(manifolds[0].local_n1, Vector2::new(0.0, -1.0));
        assert_eq!(manifolds[0].local_n2, Vector2::new(0.0, 1.0));
    }

    {
        // Put the ball and rect near each other and confirm that there are no
        // collision points and the norms are not valid.
        let mut manifolds: Vec<ContactManifold<(), ()>> = vec![];
        let result = DefaultQueryDispatcher.contact_manifolds(
            &above_center,
            &rect,
            &ball,
            0.0,
            &mut manifolds,
            &mut None,
        );

        assert!(result.is_ok());
        assert_eq!(manifolds.len(), 1);
        assert_eq!(manifolds[0].points.len(), 0);
        assert_eq!(manifolds[0].local_n1, Vector2::new(0.0, 0.0));
        assert_eq!(manifolds[0].local_n2, Vector2::new(0.0, 0.0));
    }

    {
        // Put the rect in the top half with the bottom-half-space and there are
        // no collision points, but the normals *are* valid.
        let mut manifolds: Vec<ContactManifold<(), ()>> = vec![];
        let result = DefaultQueryDispatcher.contact_manifolds(
            &above_center,
            &bottom_space,
            &rect,
            0.0,
            &mut manifolds,
            &mut None,
        );

        assert!(result.is_ok());
        assert_eq!(manifolds.len(), 1);
        assert_eq!(manifolds[0].points.len(), 0);
        assert_eq!(manifolds[0].local_n1, Vector2::new(0.0, 1.0));
        assert_eq!(manifolds[0].local_n2, Vector2::new(0.0, -1.0));
    }
}
@Jondolf Jondolf added C-Bug Something isn't working A-Collision Relates to the broad phase, narrow phase, colliders, or other collision functionality labels Dec 10, 2024
@rectalogic
Copy link

Also happens with 3D (bevy 0.15, avian3d b47c30c)

use avian3d::prelude::*;
use bevy::{
    app::{App, Startup},
    prelude::*,
    DefaultPlugins,
};

#[derive(Component)]
pub struct Cube;

#[derive(Component)]
pub struct Floor;

fn main() {
    App::new()
        .add_plugins((
            DefaultPlugins,
            PhysicsPlugins::default(),
            PhysicsDebugPlugin::default(),
        ))
        .add_systems(Startup, setup)
        .add_systems(PostProcessCollisions, log_collisions.run_if(run_once))
        .run();
}

pub fn setup(mut commands: Commands) {
    commands.spawn((Cube, RigidBody::Dynamic, Collider::cuboid(1.0, 1.0, 1.0)));
    commands.spawn((
        Floor,
        RigidBody::Static,
        Transform::from_xyz(0., -2.0, 0.),
        Collider::half_space(Vec3::Y),
    ));
}

fn log_collisions(
    collisions: Res<Collisions>,
    floor: Single<Entity, With<Floor>>,
    cube: Single<Entity, With<Cube>>,
) {
    if let Some(contacts) = collisions.get(*floor, *cube) {
        dbg!(&contacts);
    }
}

This logs the contact with empty contacts arrays:

[src/main.rs:42:9] &contacts = Contacts {
    entity1: 15v1#4294967311,
    entity2: 14v1#4294967310,
    body_entity1: Some(
        15v1#4294967311,
    ),
    body_entity2: Some(
        14v1#4294967310,
    ),
    manifolds: [
        ContactManifold {
            contacts: [],
            normal1: Vec3(
                0.0,
                1.0,
                0.0,
            ),
            normal2: Vec3(
                0.0,
                -1.0,
                0.0,
            ),
            index: 0,
        },
    ],
    is_sensor: false,
    during_current_frame: true,
    during_previous_frame: false,
    total_normal_impulse: 0.0,
    total_tangent_impulse: Vec2(
        0.0,
        0.0,
    ),
}

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 C-Bug Something isn't working
Projects
None yet
Development

No branches or pull requests

3 participants