dimforge / bevy_rapier

Official Rapier plugin for the Bevy game engine.
https://rapier.rs
Apache License 2.0
1.27k stars 259 forks source link

Make it easier to get static snapshots of collider data #85

Open ndarilek opened 3 years ago

ndarilek commented 3 years ago

I've been having a very hard time with this and wonder if there's any way bevy_rapier might make this easier. I'm trying to integrate physics into my pathfinding, which can take more than a single frame. Previously, my AStar calls generated paths that routed entities through areas in which they couldn't physically fit. I'm trying to add shapes to this algorithm such that valid successors to a given tile are only those in which the shape of the source entity can fit. Here's an example of what I have right now:

fn find_path_for_shape(
    pool: &AsyncComputeTaskPool,
    initiator: Entity,
    start: &dyn PointLike,
    destination: &dyn PointLike,
    map: &Map,
    query_pipeline: &QueryPipeline,
    collider_query: &QueryPipelineColliderComponentsQuery,
    shape: &dyn Shape,
    channel: &Sender<Option<Path>>,
) {
    let collider_set = QueryPipelineColliderComponentsSet(&collider_query);
    let shapes = Shapes(
        collider_query
            .iter()
            .map(|(a, b, c, d)| (a, b.clone(), c.clone(), d.clone()))
            .collect::<Vec<(Entity, ColliderPosition, ColliderShape, ColliderFlags)>>(),
    );
    let start = start.i32();
    let destination = destination.i32();
    let map_clone = map.clone();
    let shape_clone = shape.clone();
    let channel_clone = channel.clone();
    pool.spawn(async {
        let path = astar(
            &start,
            |p| {
                let mut successors: Vec<((i32, i32), u32)> = vec![];
                for tile in map_clone.get_available_exits(p.0 as usize, p.1 as usize) {
                    let mut should_push = true;
                    let shape_pos = Isometry::new(vector![tile.0 as f32, tile.1 as f32], 0.);
                    query_pipeline.intersections_with_shape(
                        &shapes,
                        &shape_pos,
                        &*shape_clone,
                        InteractionGroups::all(),
                        Some(&|v| v.entity() != initiator),
                        |handle| {
                            should_push = false;
                            false
                        },
                    );
                    if should_push {
                        successors.push(((tile.0 as i32, tile.1 as i32), (tile.2 * 100.) as u32));
                    }
                }
                successors
            },
            |p| (p.distance_squared(&destination) * 100.) as u32,
            |p| *p == destination.into(),
        );
        channel_clone
            .send(if let Some(path) = path {
                Some(Path(path.0))
            } else {
                None
            })
            .expect("Channel should exist");
    })
    .detach();
}

It was suggested that I make a wrapper type that creates a static snapshots of colliders to pass into the pool. I'm fine with this, as I can probably correct for changes in the system that actually negotiates calculated paths for cases where a path gets blocked. Here is a partial attempt at that:

struct Shapes(Vec<(Entity, ColliderPosition, ColliderShape, ColliderFlags)>);

impl ComponentSet<SharedShape> for Shapes {
    fn size_hint(&self) -> usize {
        self.0.len()
    }

    fn for_each(&self, f: impl FnMut(bevy_rapier2d::rapier::data::Index, &SharedShape)) {
        unimplemented!()
    }
}

impl ComponentSetOption<SharedShape> for Shapes {
    fn get(&self, index: Index) -> std::option::Option<&SharedShape> {
        self.0
            .get(index.into())
            .map(|v: &(Entity, ColliderPosition, ColliderShape, ColliderFlags)| &v.2)
    }
}

impl ComponentSet<ColliderFlags> for Shapes {
    fn size_hint(&self) -> usize {
        self.0.len()
    }

    fn for_each(&self, f: impl FnMut(bevy_rapier2d::rapier::data::Index, &ColliderFlags)) {
        unimplemented!()
    }
}

impl ComponentSetOption<ColliderFlags> for Shapes {
    fn get(
        &self,
        index: bevy_rapier2d::rapier2d::data::Index,
    ) -> std::option::Option<&ColliderFlags> {
        self.0.get(index)
    }
}

impl ComponentSet<ColliderPosition> for Shapes {
    fn size_hint(&self) -> usize {
        self.0.len()
    }

    fn for_each(&self, f: impl FnMut(bevy_rapier2d::rapier::data::Index, &ColliderPosition)) {
        unimplemented!()
    }
}

impl ComponentSetOption<ColliderPosition> for Shapes {
    fn get(
        &self,
        index: bevy_rapier2d::rapier2d::data::Index,
    ) -> std::option::Option<&ColliderPosition> {
        self.0.get(index)
    }
}

Obviously that's incomplete, but here are some of the errors I'm getting:

error[E0284]: type annotations needed: cannot satisfy <_ as SliceIndex<[(bevy::prelude::Entity, bevy_rapier2d::prelude::ColliderPosition, bevy_rapier2d::prelude::SharedShape, bevy_rapier2d::prelude::ColliderFlags)]>>::Output == (bevy::prelude::Entity, bevy_rapier2d::prelude::ColliderPosition, bevy_rapier2d::prelude::SharedShape, bevy_rapier2d::prelude::ColliderFlags) --> src\pathfinding.rs:63:14 | 63 | .get(index.into()) | ^^^ cannot satisfy <_ as SliceIndex<[(bevy::prelude::Entity, bevy_rapier2d::prelude::ColliderPosition, bevy_rapier2d::prelude::SharedShape, bevy_rapier2d::prelude::ColliderFlags)]>>::Output == (bevy::prelude::Entity, bevy_rapier2d::prelude::ColliderPosition, bevy_rapier2d::prelude::SharedShape, bevy_rapier2d::prelude::ColliderFlags)

error[E0621]: explicit lifetime required in the type of shape --> src\pathfinding.rs:129:10 | 114 | shape: &dyn Shape, | ---------- help: add explicit lifetime 'static to the type of shape: &'static (dyn bevy_rapier2d::prelude::Shape + 'static) ... 129 | pool.spawn(async { | ^^^^^ lifetime 'static required

error[E0621]: explicit lifetime required in the type of query_pipeline --> src\pathfinding.rs:129:10 | 112 | query_pipeline: &QueryPipeline, | -------------- help: add explicit lifetime 'static to the type of query_pipeline: &'static bevy_rapier2d::prelude::QueryPipeline ... 129 | pool.spawn(async { | ^^^^^ lifetime 'static required

This is probably a more advanced use case, and I'm not sure what the plugin might do to make this easier. Maybe even an example of snapshotting collision data and accessing it in a pool would be useful? There are some obvious mistakes in my Shapes code--Rust Analyzer's automatic import added a few broken paths, for instance--but I feel like I'm adding mud to an existing mess rather than working toward a solution. Alternately, I'm not sure if there's a better solution to my pathfinding issue than taking physical shapes into account, but I had enough special cases for handling bad data that I thought it might help to prevent that bad data from being generated in the first place.

Thanks.

Vrixyz commented 5 months ago

related issue: https://github.com/dimforge/bevy_rapier/issues/363 ; the situation has probably improved since then but that would be great to have an example.