Now I'm struggling with collision detection


(Nolan) #1

Hey folks, not sure if I should post this here or on the rustsim forum, so if I need to take it there then I can.

I’m trying to implement a simple collision system for my Asteroids shooter. Everything is modeled as spheres. For the moment, my goal is just to get collision events, then generate game-specific events that I handle in other systems.

I have 3 collision groups. Players/ships are group 0, bullets group 1, asteroids/targets group 2. Players and bullets can only collide with asteroids. Asteroids can collide with everything. For the moment I’m disabling blacklists just to get things working.

Here’s my system with comments throughout:

struct CollisionSystem {
    world: CollisionWorld<f32, Entity>,
    ships: CollisionGroups,
    bullets: CollisionGroups,
    asteroids: CollisionGroups,
    // Make it easier to sync `CollisionWorld` with Amethyst
    handles: HashMap<Entity, CollisionObjectHandle>,
    reader_id: Option<ReaderId<ComponentEvent>>,
}

impl Default for CollisionSystem {
    fn default() -> Self {
        // Not implemented to above spec, I just want to see some collisions
        let mut ships = CollisionGroups::new();
        ships.set_membership(&[0]);
        let mut bullets = CollisionGroups::new();
        bullets.set_membership(&[1]);
        let mut asteroids = CollisionGroups::new();
        asteroids.set_membership(&[2]);
        asteroids.set_whitelist(&[0, 1, 2]); // Should make asteroids at least hit themselves
        // ships.set_blacklist(&[1]);
        ships.set_whitelist(&[2]); // Not sure if needed but asteroids should hit players
        bullets.set_blacklist(&[0, 1]); // Not firing bullets right now so left intact
        bullets.set_whitelist(&[2]);
        let handles = HashMap::new();
        Self {
            world: CollisionWorld::new(0.02),
            ships,
            bullets,
            asteroids,
            handles,
            reader_id: None,
        }
    }
}

impl<'s> System<'s> for CollisionSystem {
    type SystemData = (
        Entities<'s>,
        WriteStorage<'s, Transform>,
        ReadStorage<'s, BoundingVolume>,
        ReadStorage<'s, Player>,
        ReadStorage<'s, Bullet>,
        ReadStorage<'s, Target>,
    );

    fn run(&mut self, (entities, mut transforms, bounding_volumes, player, bullets, targets): Self::SystemData) {
        let events = bounding_volumes.channel().read(self.reader_id.as_mut().unwrap());
        // Sync `CollisionWorld` with anything having a `BoundingVolume` component, just `BoundingVolume::Sphere` for now
        for event in events {
            match event {
                ComponentEvent::Inserted(id) => {
                    info!("Inserting {}", id);
                    let entity = entities.entity(*id);
                    if let (Some(bounds), Some(transform)) = (bounding_volumes.get(entity), transforms.get(entity)) {
                        let collision_groups = if let Some(_) = player.get(entity) {
                            info!("Ship");
                            self.ships
                        } else if let Some(_) = bullets.get(entity) {
                            info!("Bullet");
                            self.bullets
                        } else if let Some(_) = targets.get(entity) {
                            info!("Target");
                            self.asteroids
                        } else {
                            info!("Unknown, making target for now");
                            self.asteroids
                        };
                        let shape = ShapeHandle::new(match bounds {
                            BoundingVolume::Sphere(radius) => {
                                info!("Making sphere of radius {}", radius);
                                Ball::new(*radius)
                            },
                        });
                        // I just care about proximity for now.
                        let geometric_query_type = GeometricQueryType::Proximity(0.0);
                        let obj = self.world.add(*transform.isometry(), shape, collision_groups, geometric_query_type, entity);
                        self.handles.insert(entity, obj.handle());
                        info!("Inserted {:?}", obj.handle());
                    }
                }
                // Other arms not hit, removed for brevity
            }
        }
        // Push entity isometries from Amethyst to `CollisionWorld`
        for (entity, transform, _) in (&entities, &transforms, &bounding_volumes).join() {
            if let Some(handle) = self.handles.get(&entity) {
                // info!("Setting position of {:?}: {:?}", handle, transform.isometry().translation);
                self.world.set_position(*handle, *transform.isometry());
            }
        }
        self.world.update();
        // World updated, pull isometries from `CollisionWorld` back to entities
        // Future optimizations might do this only on contact
        for (entity, transform, _) in (&entities, &mut transforms, &bounding_volumes).join() {
            if let Some(handle) = self.handles.get(&entity) {
                let obj = self.world.collision_object(*handle).unwrap();
                let iso = obj.position();
                let mut new_iso = transform.isometry_mut();
                new_iso.translation = iso.translation;
                new_iso.rotation = iso.rotation;
                // info!("Syncing {:?}: {:?}", handle, new_iso.translation);
            }
        }
        for contact in self.world.contact_events() {
            info!("Bang!");
            // . . . except, not
        }
    }

    fn setup(&mut self, res: &mut Resources) {
        Self::SystemData::setup(res);
        self.reader_id = Some(WriteStorage::<BoundingVolume>::fetch(&res).register_reader());
    }
}

If I uncomment the commented-out info! statements, I see transforms pushed/pulled. But I never get any collision events, and I have no clue why. Here’s a sample of my logs:

[INFO][onslaught] Inserting 2
[INFO][onslaught] Got bounds and transform
[INFO][onslaught] Target
[INFO][onslaught] Making sphere of radius 30
[INFO][onslaught] Inserted CollisionObjectHandle(1)
[INFO][onslaught] Inserting 3
[INFO][onslaught] Got bounds and transform
[INFO][onslaught] Target
[INFO][onslaught] Making sphere of radius 30
[INFO][onslaught] Inserted CollisionObjectHandle(2)
[INFO][onslaught] Inserting 4
[INFO][onslaught] Got bounds and transform
[INFO][onslaught] Target
[INFO][onslaught] Making sphere of radius 30
[INFO][onslaught] Inserted CollisionObjectHandle(3)

You’re seeing it insert 3 asteroids. But even letting the thing run for 10 minutes, and even with asteroids moving, nothing ever collides. I’m wondering if I’m setting up my CollisionGroups incorrectly?

The arena is 300X300. Objects are definitely constrained, and wrap when they move outside the bounds. I also just made asteroids have a radius of 90 rather than 30, and they’re still not colliding despite moving. Or, at least, the event handler is never triggered.

Thanks for any help.