Skip to content

Commit

Permalink
Merge pull request #14 from shnewto/migrate-to-edges-crate
Browse files Browse the repository at this point in the history
migrate to edges crate
  • Loading branch information
shnewto committed Mar 4, 2024
2 parents 4525620 + 7e654e2 commit 26dee55
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 260 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,16 @@ keywords = ["bevy", "rapier", "png", "collider", "2d"]
readme = "README.md"

[features]
default = ["xpbd_2d","rapier2d"]
default = ["xpbd_2d", "rapier2d"]
xpbd_2d = ["dep:bevy_xpbd_2d"]
rapier2d = ["dep:bevy_rapier2d"]

[dependencies]
bevy = "0.13.0"
bevy_rapier2d = { version = "0.25.0", optional = true }
bevy_xpbd_2d = { version = "0.4.2", optional = true }
edges = { version = "0.3.0", features = ["bevy"] }
thiserror = "1.0.57"

[dev-dependencies]
bevy_prototype_lyon = "0.11.0"
Expand Down
17 changes: 6 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ but you'll probably only want to just use one of the physics engines supported s

```toml
[dependencies.bevy_collider_gen]
# replace "*" with the most recent version of bevy_collider_gen
version = "*"
features = ["rapier2d"]
default-features = false
Expand All @@ -23,6 +24,7 @@ or this for `bevy_xpbd_2d`

```toml
[dependencies.bevy_collider_gen]
# replace "*" with the most recent version of bevy_collider_gen
version = "*"
features = ["xpbd_2d"]
default-features = false
Expand Down Expand Up @@ -60,19 +62,11 @@ packaged up my approach here in case anyone else could benefit.

## how it works

i was inspired by [a coding train (or, coding in the cabana rather) on an implementation of "marching squares"](<https://youtu.be/0ZONMNUKTfU>).
so this crate takes a "march through all the values" approach to find edges, i.e. pixels with at least 1 empty neighboring pixel, but
instead of drawing a contour in place, it just keeps track of all the actual pixel coordinates. to determine "empty" I bitwise
or all the bytes for each pixel and, in images with transparency, "empty" is a zero value for the pixel.

after that, we need to put the coordinates in some kind of "drawing order" so whatever we pass all the points to, knows how we want the object constructed. for this, the
crate collects all pixels, in order, that are a distance of 1 from eachother. if there are pixels that have a distance greater than 1
from any pixel in an existing group, that pixel begins a new group.
😄 head on over to the edges crate to learn more <https://github.com/shnewto/edges>

## caveats

- as mentioned here and there in these docs, this implementation requires images to have transparency in order to distinguish object from non-object :)
- there's no reason we couldn't generate colliders / geometry without transparency, it's just not implemented. if you've got a compelling case, raise an issue! or even better, create a pr!
- i imagine for generating things at a larger scale, i.e. colliders for sets of sprites bigger than pixel counts in the hundreds, this implementation won't be performant to do at runtime. i'll suggest serializing the colliders you like and deserializing in your app instead of doing all the number crunching on load when you need a performance boost

## examples of colliders generated for assets/sprite/car.png
Expand Down Expand Up @@ -105,7 +99,8 @@ convex decomposition colliders you could construct them with the edge coordinate

```rust
let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap();
let edge_coordinate_groups = multi_image_edge_translated(sprite_image);
let edges = Edges::from(sprite_image)
let edge_coordinate_groups = edges.multi_image_edge_translated();
for coords in edge_coordinate_groups {
let indices: Vec<[u32; 2]> = (0..coords.len()).map(|i| [i as u32, i as u32]).collect();
let collider = Collider::convex_decomposition(&coords, &indices);
Expand All @@ -120,7 +115,7 @@ for coords in edge_coordinate_groups {
}
```

![convex decomposition collider on an upside down car sprite](<https://github.com/shnewto/bevy_collider_gen/blob/main/img/convex-decomposition.png?raw=true>)
![convex decomposition collider on a car sprite](<https://github.com/shnewto/bevy_collider_gen/blob/main/img/convex-decomposition.png?raw=true>)

## license

Expand Down
13 changes: 8 additions & 5 deletions examples/rapier2d_colliders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use bevy::pbr::wireframe::WireframePlugin;
use bevy::prelude::*;
use bevy::render::settings::{RenderCreation, WgpuFeatures, WgpuSettings};
use bevy::render::RenderPlugin;
use bevy_collider_gen::multi_image_edge_translated;
use bevy_collider_gen::rapier2d::{
multi_convex_polyline_collider_translated, single_convex_polyline_collider_translated,
single_heightfield_collider_translated,
use bevy_collider_gen::{
rapier2d::{
multi_convex_polyline_collider_translated, single_convex_polyline_collider_translated,
single_heightfield_collider_translated,
},
Edges,
};
use bevy_prototype_lyon::prelude::{Fill, GeometryBuilder, ShapePlugin};
use bevy_prototype_lyon::shapes;
Expand Down Expand Up @@ -137,7 +139,8 @@ pub fn boulders_spawn(
}
let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap();

let coord_group = multi_image_edge_translated(sprite_image);
let edges = Edges::from(sprite_image);
let coord_group = edges.multi_image_edge_translated();
let colliders = multi_convex_polyline_collider_translated(sprite_image);

for (coords, collider) in coord_group.iter().zip(colliders.into_iter()) {
Expand Down
13 changes: 8 additions & 5 deletions examples/xpbd_2d_colliders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ use bevy::pbr::wireframe::WireframePlugin;
use bevy::prelude::*;
use bevy::render::settings::{RenderCreation, WgpuFeatures, WgpuSettings};
use bevy::render::RenderPlugin;
use bevy_collider_gen::multi_image_edge_translated;
use bevy_collider_gen::xpbd_2d::{
multi_convex_polyline_collider_translated, single_convex_polyline_collider_translated,
single_heightfield_collider_translated,
use bevy_collider_gen::{
xpbd_2d::{
multi_convex_polyline_collider_translated, single_convex_polyline_collider_translated,
single_heightfield_collider_translated,
},
Edges,
};
use bevy_prototype_lyon::prelude::{Fill, GeometryBuilder, ShapePlugin};
use bevy_prototype_lyon::shapes;
Expand Down Expand Up @@ -126,7 +128,8 @@ pub fn boulders_spawn(
}
let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap();

let coord_group = multi_image_edge_translated(sprite_image);
let edges = Edges::from(sprite_image);
let coord_group = edges.multi_image_edge_translated();
let colliders = multi_convex_polyline_collider_translated(sprite_image);

for (coords, collider) in coord_group.iter().zip(colliders.into_iter()) {
Expand Down
67 changes: 40 additions & 27 deletions src/collider/rapier2d.rs
Original file line number Diff line number Diff line change
@@ -1,82 +1,90 @@
use crate::{
multi_image_edge_translated, multi_image_edges_raw, single_image_edge_raw,
single_image_edge_translated,
};
use bevy::prelude::{Image, Vec2};
use bevy_rapier2d::prelude::{Collider, Real};
use edges::Edges;

/// Generate a single bevy_rapier2d polyline collider from the image,
/// coordinates translated to either side of (0, 0)
pub fn single_polyline_collider_translated(image: &Image) -> Collider {
Collider::polyline(single_image_edge_translated(image), None)
let e = Edges::from(image);
Collider::polyline(e.single_image_edge_translated(), None)
}

/// Generate a single bevy_rapier2d polyline collider from the image,
/// coordinates left alone and all in positive x and y
pub fn single_polyline_collider_raw(image: &Image) -> Collider {
Collider::polyline(single_image_edge_raw(image), None)
let e = Edges::from(image);
Collider::polyline(e.single_image_edge_raw(), None)
}

/// Generate a single bevy_rapier2d convex_polyline collider from the image,
/// coordinates translated to either side of (0, 0)
pub fn single_convex_polyline_collider_translated(image: &Image) -> Option<Collider> {
Collider::convex_polyline(single_image_edge_translated(image))
let e = Edges::from(image);
Collider::convex_polyline(e.single_image_edge_translated())
}

/// Generate a single bevy_rapier2d convex_polyline collider from the image,
/// coordinates left alone and all in positive x and y
pub fn single_convex_polyline_collider_raw(image: &Image) -> Option<Collider> {
Collider::convex_polyline(single_image_edge_raw(image))
let e = Edges::from(image);
Collider::convex_polyline(e.single_image_edge_raw())
}

/// Generate a single bevy_rapier2d convex_hull collider from the image,
/// coordinates translated to either side of (0, 0)
pub fn single_convex_hull_collider_translated(image: &Image) -> Option<Collider> {
let points = single_image_edge_translated(image);
let e = Edges::from(image);
let points = e.single_image_edge_translated();
Collider::convex_hull(&points)
}

/// Generate a single bevy_rapier2d convex_hull collider from the image,
/// coordinates left alone and all in positive x and y
pub fn single_convex_hull_collider_raw(image: &Image) -> Option<Collider> {
let points = single_image_edge_translated(image);
let e = Edges::from(image);
let points = e.single_image_edge_translated();
Collider::convex_hull(&points)
}

/// Generate a single bevy_rapier2d heightfield collider from the image,
/// coordinates translated to either side of (0, 0)
pub fn single_heightfield_collider_translated(image: &Image) -> Collider {
heightfield_collider_from_points(&single_image_edge_translated(image))
let e = Edges::from(image);
heightfield_collider_from_points(&e.single_image_edge_translated())
}

/// Generate a single bevy_rapier2d heightfield collider from the image,
/// coordinates left alone and all in positive x and y
pub fn single_heightfield_collider_raw(image: &Image) -> Collider {
heightfield_collider_from_points(&single_image_edge_raw(image))
let e = Edges::from(image);
heightfield_collider_from_points(&e.single_image_edge_raw())
}

/// Generate as many bevy_rapier2d polyline colliders as it can find in the image,
/// coordinates translated to either side of (0, 0)
pub fn multi_polyline_collider_translated(image: &Image) -> Vec<Collider> {
multi_image_edge_translated(image)
let e = Edges::from(image);
e.multi_image_edge_translated()
.into_iter()
.map(|e| Collider::polyline(e, None))
.map(|v| Collider::polyline(v, None))
.collect()
}

/// Generate as many bevy_rapier2d polyline colliders as it can find in the image,
/// coordinates left alone and all in positive x and y
pub fn multi_polyline_collider_raw(image: &Image) -> Vec<Collider> {
multi_image_edges_raw(image)
let e = Edges::from(image);
e.multi_image_edges_raw()
.into_iter()
.map(|e| Collider::polyline(e, None))
.map(|v| Collider::polyline(v, None))
.collect()
}

/// Generate as many bevy_rapier2d convex_polyline colliders as it can find in the image,
/// coordinates translated to either side of (0, 0)
pub fn multi_convex_polyline_collider_translated(image: &Image) -> Vec<Option<Collider>> {
multi_image_edge_translated(image)
let e = Edges::from(image);
e.multi_image_edge_translated()
.into_iter()
.map(Collider::convex_polyline)
.collect()
Expand All @@ -85,7 +93,8 @@ pub fn multi_convex_polyline_collider_translated(image: &Image) -> Vec<Option<Co
/// Generate as many bevy_rapier2d convex_polyline colliders as it can find in the image,
/// coordinates left alone and all in positive x and y
pub fn multi_convex_polyline_collider_raw(image: &Image) -> Vec<Option<Collider>> {
multi_image_edges_raw(image)
let e = Edges::from(image);
e.multi_image_edges_raw()
.into_iter()
.map(Collider::convex_polyline)
.collect()
Expand All @@ -94,36 +103,40 @@ pub fn multi_convex_polyline_collider_raw(image: &Image) -> Vec<Option<Collider>
/// Generate as many bevy_rapier2d heightfield colliders as it can find in the image,
/// coordinates translated to either side of (0, 0)
pub fn multi_heightfield_collider_translated(image: &Image) -> Vec<Collider> {
multi_image_edge_translated(image)
let e = Edges::from(image);
e.multi_image_edge_translated()
.into_iter()
.map(|e| heightfield_collider_from_points(&e))
.map(|v| heightfield_collider_from_points(&v))
.collect()
}

/// Generate as many bevy_rapier2d heightfield colliders as it can find in the image,
/// coordinates left alone and all in positive x and y
pub fn multi_heightfield_collider_raw(image: &Image) -> Vec<Collider> {
multi_image_edges_raw(image)
let e = Edges::from(image);
e.multi_image_edges_raw()
.into_iter()
.map(|e| heightfield_collider_from_points(&e))
.map(|v| heightfield_collider_from_points(&v))
.collect()
}

/// Generate as many bevy_rapier2d convex_hull colliders as it can find in the image,
/// coordinates translated to either side of (0, 0)
pub fn multi_convex_hull_collider_translated(image: &Image) -> Vec<Option<Collider>> {
multi_image_edge_translated(image)
let e = Edges::from(image);
e.multi_image_edge_translated()
.into_iter()
.map(|e| Collider::convex_hull(&e))
.map(|v| Collider::convex_hull(&v))
.collect()
}

/// Generate as many bevy_rapier2d convex_hull colliders as it can find in the image,
/// coordinates left alone and all in positive x and y
pub fn multi_convex_hull_collider_raw(image: &Image) -> Vec<Option<Collider>> {
multi_image_edges_raw(image)
let e = Edges::from(image);
e.multi_image_edges_raw()
.into_iter()
.map(|e| Collider::convex_hull(&e))
.map(|v| Collider::convex_hull(&v))
.collect()
}

Expand Down Expand Up @@ -151,5 +164,5 @@ fn heights_from_points(points: &[Vec2]) -> Vec<Real> {
}
}

heights.iter().map(|e| e.y).collect::<Vec<Real>>()
heights.iter().map(|v| v.y).collect::<Vec<Real>>()
}
Loading

0 comments on commit 26dee55

Please sign in to comment.