From 188a15e878b3770bcdc0a9c7255d7a05efbff60b Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 13:04:05 +0900 Subject: [PATCH 1/7] upd: RELEASE.md --- RELEASE.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/RELEASE.md b/RELEASE.md index f351979..175d493 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,16 +1,19 @@ # Release Process -- leave the Cargo.toml package version alone :) -- trigger the `tag and publish crate [manual]` action and specify the release increment (major, minor, or patch) - - once the action completes, the Cargo.toml will be updated and the crate will be live on crates.io -- create a release (with title, notes, thanks, etc) and tie it to the tag that was created by the action / new crate version +- leave the `Cargo.toml` package version alone :) +- trigger the `tag and publish crate [manual]` action and specify + the release increment (major, minor, or patch) + - once the action completes, the `Cargo.toml` + will be updated and the crate will be live on `crates.io` +- create a release (with title, notes, thanks, etc) + and tie it to the tag that was created by the action / new crate version ## Execution The order of operations for tagging and publishing in the action is this 1. run all checks, i.e. fmt, clippy, tests, etc -1. push change / update to the Cargo.toml +1. push change / update to the `Cargo.toml` 1. push the new tag to the repo 1. publish the new version to crates.io @@ -18,5 +21,9 @@ The order of operations for tagging and publishing in the action is this - if step 1 of execution fails, after addressing the error, you run the action again - if step 2 of execution fails, after addressing the error, you run the action again -- if step 3 of execution fails (the Cargo.toml version was incremented), after addressing the error, you should manually tag (github's ux or the cli) and manually publish the crate with `cargo publish` -- if step 4 of execution fails (the Cargo.toml version was incremented and there's a new corresponding tag), after addressing the error, you should manually publish the crate with `cargo publish` +- if step 3 of execution fails (the `Cargo.toml` version was incremented), + after addressing the error, you should manually tag (github's ui or the cli) + and manually publish the crate with `cargo publish` +- if step 4 of execution fails + (the `Cargo.toml` version was incremented and there's a new corresponding tag), + after addressing the error, you should manually publish the crate with `cargo publish` From 33263f30576f96a1687b3807826ecb9fc45dc56e Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 13:04:20 +0900 Subject: [PATCH 2/7] upd: README.md --- README.md | 68 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 9a4369a..0a2dcc5 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,17 @@ [![Crates.io](https://img.shields.io/crates/d/bevy_collider_gen.svg)](https://crates.io/crates/bevy_collider_gen) [![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/shnewto/bevy_collider_gen#license) -a library for generating 2d colliders, for bevy apps, from images with transparency +A library for generating 2d colliders, for bevy apps, from images with transparency -## specifying your dependency +## Specifying your dependency -by default, both bevy_rapier2d and avian2d (formerly bevy_xpbd_2d) are enabled. this is to help with the out of box experience, specifically, being able to run both examples and tinker. +By default, both bevy_rapier2d and avian2d (formerly bevy_xpbd_2d) are enabled. +This is to help with the out of box experience, specifically, +being able to run both examples and tinker. -but you'll probably only want to just use one of the physics engines supported so when you use it in your own crate fill in in the `bevy_collider_gen` dependencies with something like this for `bevy_rapier2d` +But you'll probably only want to just use one of the physics engines supported +so when you use it in your own crate fill in in the `bevy_collider_gen` +dependencies with something like this for `bevy_rapier2d` ```toml [dependencies.bevy_collider_gen] @@ -18,7 +22,7 @@ but you'll probably only want to just use one of the physics engines supported s version = "*" ``` -or this for `avian2d` +Or this for `avian2d` ```toml [dependencies.bevy_collider_gen] @@ -28,48 +32,54 @@ features = ["avian2d", "parallel"] default-features = false ``` -## example +## Example ![example with a car, terrain, and boulders](https://github.com/shnewto/bevy_collider_gen/blob/main/img/example-default.png?raw=true) -to see this in action you can run the example, with no args it generates a scene with various colliders using pngs in the `assets/sprite` directory +To see this in action you can run the example, with no arguments +it generates a scene with various colliders using PNG's in the `assets/sprite` directory ### bevy_rapier2d -#### note that you must have the rapier2d feature enabled - ```sh cargo run --example rapier2d_colliders -F "bevy_rapier2d/debug-render-2d" ``` ### avian2d -#### note that you must have the avian2d feature enabled - ```sh cargo run --example avian2d_colliders -F "avian2d, avian2d/debug-plugin" ``` -you can also specify a path to an image yourself the example will attempt to generate one or more convex_polyline colliders for the objects it finds +You can also specify a path to an image yourself the example will attempt to +generate one or more `convex_polyline` colliders for the objects it finds -## about / why +## About / why -i was looking for a way to iterate on some 2d scenes with colliders on things with more sophisticated shapes than simple -geometry, i figured there should be enough info in an image with transparency to generate colliders, and... there is! so i +I was looking for a way to iterate on some 2d scenes with colliders +on things with more sophisticated shapes than simple +geometry, I figured there should be enough info in an image with +transparency to generate colliders, and... there is! So i packaged up my approach here in case anyone else could benefit. -## how it works +## How it works 😄 head on over to the edges crate to learn more -## caveats +## Caveats -- as mentioned here and there in these docs, this implementation requires images to have transparency in order to distinguish object from non-object :) -- 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 +- as mentioned here and there in these docs, this implementation requires + images to have transparency in order to distinguish object from non-object :) +- 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 +## Examples of colliders generated for assets/sprite/car.png -(as in pictures of the sort of thing you can expect, not the runnable bevy app example. that's a couple headings up) +(as in pictures of the sort of thing you can expect, +not the runnable bevy app example. That's a couple headings up) ### convex polyline (bevy_raiper2d only) @@ -85,15 +95,19 @@ packaged up my approach here in case anyone else could benefit. ### heightfield -the current implementation does best if the image you're generating a heightfield from is either centered in the image +The current implementation does best if the image +you're generating a heightfield from is either centered in the image or spans the entire width of the image... ![heightfield collider on an upside down car sprite](https://github.com/shnewto/bevy_collider_gen/blob/main/img/heightfield.png?raw=true) ### convex decomposition -I didn't add support for convex decomposition directly because when sprites were small, and collisions were forceful, they were sort of unreliable (occasional panics because of bounds indexing in rapier's dependencies 💀). But if you wanted to use -convex decomposition colliders you could construct them with the edge coordinates from your image with something like this +I didn't add support for convex decomposition directly because when +sprites were small, and collisions were forceful, they were sort of unreliable +(occasional panics because of bounds indexing in rapier's dependencies 💀). +But if you wanted to use convex decomposition colliders you could construct +them with the edge coordinates from your image with something like this ```rust let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); @@ -115,11 +129,11 @@ for coords in edge_coordinate_groups { ![convex decomposition collider on a car sprite](https://github.com/shnewto/bevy_collider_gen/blob/main/img/convex-decomposition.png?raw=true) -## license +## License -all code in this repository is dual-licensed under either: +All code in this repository is dual-licensed under either: - MIT License (LICENSE-MIT or ) - Apache License, Version 2.0 (LICENSE-APACHE or ) -at your option. +At your option. From 48ba1d7b59cbb2a516cf7608a6a5aaff253a32fb Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 13:07:12 +0900 Subject: [PATCH 3/7] upd: less strict version choice --- Cargo.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2b2caf7..552705e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ rapier2d = ["dep:bevy_rapier2d"] parallel = ["dep:rayon", "avian2d/parallel", "bevy_rapier2d/parallel"] [dependencies] -edges = "0.4.0" +edges = "0.4" bevy_math = { version = "0.14", default-features = false } -rayon = { version = "1.10.0", optional = true } +rayon = { version = "1", optional = true } [dependencies.bevy_rapier2d] version = "0.27" @@ -40,7 +40,7 @@ features = ["2d", "parry-f32"] [dev-dependencies] bevy = "0.14" -bevy_prototype_lyon = "0.12.0" +bevy_prototype_lyon = "0.12" indoc = "2.0.4" [[example]] From b9e02e1672aefc0d0776e02990af23a4f036d2bc Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 13:08:03 +0900 Subject: [PATCH 4/7] cleared sources for crates.io --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 552705e..be26089 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ description = "a library for generating colliders, for bevy apps, from images wi keywords = ["bevy", "rapier", "png", "collider", "2d"] readme = "README.md" +exclude = ["assets/*", ".github/*", "examples/*"] +include = ["/LICENSE-APACHE", "/LICENSE-MIT", "/README.md", "/src/"] + [lints.clippy] cast_precision_loss = { level = "allow", priority = 1 } pedantic = { level = "warn", priority = 0 } From 250d34df0b4f06704e118ce7e7480a4a0c860a9d Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 22:16:15 +0900 Subject: [PATCH 5/7] simplified example launch --- Cargo.toml | 14 ++++++++++++-- README.md | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index be26089..0731272 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,12 +46,22 @@ bevy = "0.14" bevy_prototype_lyon = "0.12" indoc = "2.0.4" +[dev-dependencies.bevy_rapier2d] +version = "0.27" +default-features = false +features = ["dim2", "headless", "debug-render-2d"] + +[dev-dependencies.avian2d] +version = "0.1" +default-features = false +features = ["2d", "parry-f32", "debug-plugin"] + [[example]] name = "avian2d_colliders" path = "examples/avian2d_colliders.rs" -required-features = ["avian2d", "avian2d/debug-plugin"] +required-features = ["avian2d"] [[example]] name = "rapier2d_colliders" path = "examples/rapier2d_colliders.rs" -required-features = ["rapier2d", "bevy_rapier2d/debug-render-2d"] +required-features = ["rapier2d"] diff --git a/README.md b/README.md index 0a2dcc5..d4c626e 100644 --- a/README.md +++ b/README.md @@ -42,13 +42,13 @@ it generates a scene with various colliders using PNG's in the `assets/sprite` d ### bevy_rapier2d ```sh -cargo run --example rapier2d_colliders -F "bevy_rapier2d/debug-render-2d" +cargo run --example rapier2d_colliders ``` ### avian2d ```sh -cargo run --example avian2d_colliders -F "avian2d, avian2d/debug-plugin" +cargo run --example avian2d_colliders -F avian2d ``` You can also specify a path to an image yourself the example will attempt to From d02138c6453a23d20f8618d22abfc17855b78444 Mon Sep 17 00:00:00 2001 From: salam Date: Fri, 6 Dec 2024 23:59:11 +0900 Subject: [PATCH 6/7] update: examples --- examples/avian2d_colliders.rs | 312 ++++++++++++------------------ examples/rapier2d_colliders.rs | 344 +++++++++++++-------------------- 2 files changed, 259 insertions(+), 397 deletions(-) diff --git a/examples/avian2d_colliders.rs b/examples/avian2d_colliders.rs index c883e98..1b37c9e 100644 --- a/examples/avian2d_colliders.rs +++ b/examples/avian2d_colliders.rs @@ -1,29 +1,17 @@ #![allow(clippy::needless_pass_by_value)] -use avian2d::{math::Vector, prelude::*}; -use bevy::{ - asset::LoadState, - color::palettes::css, - pbr::wireframe::WireframePlugin, - prelude::*, - render::{ - settings::{RenderCreation, WgpuFeatures, WgpuSettings}, - RenderPlugin, - }, -}; +use avian2d::prelude::*; +use bevy::{asset::LoadState, color::palettes::css, prelude::*}; use bevy_collider_gen::{ avian2d::{generate_collider, generate_colliders}, edges::Edges, ColliderType, }; -use bevy_prototype_lyon::{ - prelude::{Fill, GeometryBuilder, ShapePlugin}, - shapes, -}; +use bevy_prototype_lyon::{prelude::*, shapes}; use indoc::indoc; use std::collections::HashMap; -/// Colliders (or, with no png path specified, Car + Boulder + Terrain) -/// Illustrating how to use PNG files w transparency to generate colliders (and geometry) +/// Colliders: Car + Boulder + Terrain +/// Illustrating how to use PNG files with transparency to generate colliders (and geometry) /// for 2d sprites. /// /// Controls @@ -38,20 +26,18 @@ fn custom_png_spawn( game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("custom_png"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("custom_png") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); - + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let colliders = generate_colliders(sprite_image, ColliderType::ConvexPolyline, true); + for collider in colliders { commands.spawn(( collider.unwrap(), RigidBody::Static, SpriteBundle { - texture: sprite_handle.unwrap().clone(), - transform: Transform::from_xyz(0.0, 0.0, 0.0), + texture: sprite_handle.clone(), ..default() }, DebugRender::default().with_collider_color(css::VIOLET.into()), @@ -69,22 +55,20 @@ fn car_spawn( game_assets: Res, image_assets: Res>, ) { - let initial_xyz = Vec3::new(-200.0, -5.0, 0.0); - let sprite_handle = game_assets.image_handles.get("car_handle"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("car") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let collider = generate_collider(sprite_image, ColliderType::ConvexPolyline, true).unwrap(); commands.spawn(( collider, + RigidBody::Dynamic, SpriteBundle { - texture: sprite_handle.unwrap().clone(), - transform: Transform::from_xyz(initial_xyz.x, initial_xyz.y, initial_xyz.z), + texture: sprite_handle.clone(), + transform: INITIAL_POSITION, ..default() }, Car, - RigidBody::Dynamic, DebugRender::default().with_collider_color(css::VIOLET.into()), )); } @@ -96,17 +80,17 @@ fn terrain_spawn( game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("terrain_handle"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("terrain") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let collider = generate_collider(sprite_image, ColliderType::Heightfield, true).unwrap(); + commands.spawn(( collider, RigidBody::Static, SpriteBundle { - texture: sprite_handle.unwrap().clone(), + texture: sprite_handle.clone(), ..default() }, DebugRender::default().with_collider_color(css::VIOLET.into()), @@ -131,20 +115,20 @@ fn boulders_spawn( let coord_group = edges.multi_image_edge_translated(); let colliders = generate_colliders(sprite_image, ColliderType::ConvexPolyline, true); - for (coords, collider) in coord_group.iter().zip(colliders.into_iter()) { + for (coords, collider) in coord_group.into_iter().zip(colliders.into_iter()) { let shape = shapes::Polygon { - points: coords.clone(), + points: coords, closed: true, }; - let geometry = GeometryBuilder::build_as(&shape); - let fill = Fill::color(Srgba::hex("#545454").unwrap()); - let transform = Transform::from_xyz(0., 40., 0.); commands.spawn(( collider.unwrap(), - geometry, - fill, - transform, + ShapeBundle { + path: GeometryBuilder::build_as(&shape), + ..default() + }, + Fill::color(css::GRAY), + Stroke::new(css::BLACK, 1.), RigidBody::Dynamic, DebugRender::default().with_collider_color(css::VIOLET.into()), )); @@ -162,15 +146,15 @@ pub enum AppState { Running, } -#[derive(Component, Resource, Default)] +#[derive(Resource, Default)] pub struct GameAsset { pub font_handle: Handle, - pub image_handles: HashMap>, + pub image_handles: HashMap<&'static str, Handle>, } fn main() { App::new() - .add_plugins( + .add_plugins(( DefaultPlugins .set(ImagePlugin::default_nearest()) .set(WindowPlugin { @@ -183,24 +167,16 @@ fn main() { .set(AssetPlugin { file_path: ".".to_string(), ..default() - }) - .set(RenderPlugin { - render_creation: RenderCreation::Automatic(WgpuSettings { - features: WgpuFeatures::POLYGON_MODE_LINE, - ..default() - }), - ..default() }), - ) + ShapePlugin, + PhysicsPlugins::default(), + #[cfg(debug_assertions)] + PhysicsDebugPlugin::default(), + )) .init_state::() .insert_resource(GameAsset::default()) - .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0))) - .insert_resource(Gravity(Vector::NEG_Y * 1000.0)) - .add_plugins(ShapePlugin) - .add_plugins(WireframePlugin) - .add_plugins(PhysicsPlugins::default()) - .add_plugins(PhysicsDebugPlugin::default()) - .add_systems(OnEnter(AppState::Loading), load_assets) + .insert_resource(Gravity(Vec2::NEG_Y * 500.)) + .add_systems(Startup, load_assets) .add_systems( OnExit(AppState::Loading), ( @@ -228,154 +204,116 @@ pub fn check_assets( game_assets: Res, mut state: ResMut>, ) { - for h in game_assets.image_handles.values() { - if Some(LoadState::Loaded) != asset_server.get_load_state(h) { - return; - } - } - - if Some(LoadState::Loaded) - != asset_server.get_load_state(&game_assets.font_handle.clone().untyped()) + let all_images_loaded = game_assets.image_handles.values().all(|handle| { + asset_server + .get_load_state(handle) + .is_some_and(|state| matches!(state, LoadState::Loaded)) + }); + let font_load_state = asset_server.get_load_state(&game_assets.font_handle.clone()); + if all_images_loaded && font_load_state.is_some_and(|state| matches!(state, LoadState::Loaded)) { - return; - } - - state.set(AppState::Running); -} - -pub fn camera_spawn(mut commands: Commands) { - commands.spawn(Camera2dBundle::default()); -} - -pub fn camera_movement( - mut query: Query<(&Camera, &mut OrthographicProjection, &mut Transform)>, - keys: Res>, -) { - for (_, mut projection, mut transform) in &mut query { - if keys.pressed(KeyCode::ArrowLeft) { - transform.translation.x -= 10.0; - } - if keys.pressed(KeyCode::ArrowRight) { - transform.translation.x += 10.0; - } - - if keys.pressed(KeyCode::ArrowUp) { - transform.translation.y += 10.0; - } - - if keys.pressed(KeyCode::ArrowDown) { - transform.translation.y -= 10.0; - } - - if keys.pressed(KeyCode::KeyW) { - projection.scale -= 0.01; - } - - if keys.pressed(KeyCode::KeyS) { - projection.scale += 0.01; - } + state.set(AppState::Running); } } pub fn load_assets(asset_server: Res, mut game_assets: ResMut) { - let custom_png_path = std::env::args().nth(1); game_assets.font_handle = asset_server.load("assets/font/NotoSansMono-Bold.ttf"); - - if let Some(png_path) = custom_png_path { - info!("Loading {}", png_path); - game_assets.image_handles = - HashMap::from([("custom_png".into(), asset_server.load(&png_path))]); - return; - } - game_assets.image_handles = HashMap::from([ - ( - "car_handle".into(), - asset_server.load("assets/sprite/car.png"), - ), - ( - "terrain_handle".into(), - asset_server.load("assets/sprite/terrain.png"), - ), - ( - "boulders".into(), - asset_server.load("assets/sprite/boulders.png"), - ), + ("car", asset_server.load("assets/sprite/car.png")), + ("terrain", asset_server.load("assets/sprite/terrain.png")), + ("boulders", asset_server.load("assets/sprite/boulders.png")), ]); + if let Some(png_path) = std::env::args().nth(1) { + info!("Loading {}", png_path); + game_assets + .image_handles + .insert("custom_png", asset_server.load(&png_path)); + } } pub fn controls_text_spawn(mut commands: Commands, game_assets: Res) { - let mut tips_text: String = indoc! {" + let tips_text = indoc! {" controls -------------------- ← ↑ ↓ → (pan camera) w (zoom in) s (zoom out) - "} - .into(); - - if game_assets.image_handles.contains_key("car_handle") { - let car_controls: String = indoc! {" - a d (move car) - 1 (reset car transform to initial) - "} - .into(); - - tips_text.push_str(&car_controls); - } - - let node_bundle = NodeBundle { - style: Style { - width: Val::Px(100.), - height: Val::Px(10.), - position_type: PositionType::Absolute, - justify_content: JustifyContent::FlexStart, - align_items: AlignItems::FlexStart, - left: Val::Px(80.0), - bottom: Val::Px(600.0), - ..default() - }, - ..Default::default() - }; - let text_bundle = TextBundle { - text: Text { - sections: vec![TextSection { - value: tips_text.to_string(), - style: TextStyle { - font: game_assets.font_handle.clone(), - font_size: 20.0, - color: Color::srgb(0.9, 0.9, 0.9), + a d (move car) + 1 (reset car transform to initial) + "}; + + commands + .spawn(NodeBundle { + style: Style { + width: Val::Px(100.), + height: Val::Px(10.), + position_type: PositionType::Absolute, + justify_content: JustifyContent::FlexStart, + align_items: AlignItems::FlexStart, + left: Val::Px(80.), + bottom: Val::Px(600.), + ..default() + }, + ..Default::default() + }) + .with_children(|parent| { + parent.spawn(TextBundle { + text: Text { + sections: vec![TextSection { + value: tips_text.to_string(), + style: TextStyle { + font: game_assets.font_handle.clone(), + font_size: 20., + color: Color::srgb(0.9, 0.9, 0.9), + }, + }], + justify: JustifyText::Left, + ..default() }, - }], - justify: JustifyText::Left, - ..default() - }, - ..Default::default() - }; + ..Default::default() + }); + }); +} - commands.spawn(node_bundle).with_children(|parent| { - parent.spawn(text_bundle); - }); +pub fn camera_spawn(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); } -pub fn car_movement( - mut query: Query<(&mut LinearVelocity, &mut Transform), With>, +pub fn camera_movement( + mut query: Query<(&mut OrthographicProjection, &mut Transform), With>, keys: Res>, ) { - for (mut linear_velocity, mut transform) in &mut query { - if keys.pressed(KeyCode::KeyD) { - linear_velocity.x += 30.0; - } - - if keys.pressed(KeyCode::KeyA) { - linear_velocity.x -= 30.0; + for (mut projection, mut transform) in &mut query { + for key in keys.get_pressed() { + match key { + KeyCode::ArrowUp => transform.translation.y += 10., + KeyCode::ArrowDown => transform.translation.y -= 10., + KeyCode::ArrowLeft => transform.translation.x -= 10., + KeyCode::ArrowRight => transform.translation.x += 10., + KeyCode::KeyW => projection.scale -= 0.01, + KeyCode::KeyS => projection.scale += 0.01, + _ => {} + } } + } +} - if keys.pressed(KeyCode::Digit1) { - linear_velocity.x = 0.0; - linear_velocity.y = 0.0; - *transform = INITIAL_POSITION; +pub fn car_movement( + mut query: Query<(&mut Transform, &mut LinearVelocity), With>, + keys: Res>, +) { + for (mut transform, mut linear_velocity) in &mut query { + for key in keys.get_pressed() { + match key { + KeyCode::KeyA => linear_velocity.x -= 30., + KeyCode::KeyD => linear_velocity.x += 30., + KeyCode::Digit1 => { + *linear_velocity = LinearVelocity::ZERO; + *transform = INITIAL_POSITION; + } + _ => {} + } } } } -const INITIAL_POSITION: Transform = Transform::from_xyz(-200.0, 2.0, 0.0); +const INITIAL_POSITION: Transform = Transform::from_xyz(-200., 2., 0.); diff --git a/examples/rapier2d_colliders.rs b/examples/rapier2d_colliders.rs index 3260e45..39e2127 100644 --- a/examples/rapier2d_colliders.rs +++ b/examples/rapier2d_colliders.rs @@ -1,28 +1,17 @@ #![allow(clippy::needless_pass_by_value)] -use bevy::{ - asset::LoadState, - pbr::wireframe::WireframePlugin, - prelude::*, - render::{ - settings::{RenderCreation, WgpuFeatures, WgpuSettings}, - RenderPlugin, - }, -}; +use bevy::{asset::LoadState, color::palettes::css, prelude::*}; use bevy_collider_gen::{ edges::Edges, rapier2d::{generate_collider, generate_colliders}, ColliderType, }; -use bevy_prototype_lyon::{ - prelude::{Fill, GeometryBuilder, ShapePlugin}, - shapes, -}; +use bevy_prototype_lyon::{prelude::*, shapes}; use bevy_rapier2d::prelude::*; use indoc::indoc; use std::collections::HashMap; -/// Colliders (or, with no png path specified, Car + Boulder + Terrain) -/// Illustrating how to use PNG files w transparency to generate colliders (and geometry) +/// Colliders: Car + Boulder + Terrain +/// Illustrating how to use PNG files with transparency to generate colliders (and geometry) /// for 2d sprites. /// /// Controls @@ -30,49 +19,29 @@ use std::collections::HashMap; /// w (zoom in) /// d (zoom out) -/// Custom PNG: `bevy_rapier2d` `convex_polyline` collider +/// Custom PNG: `convex_polyline` colliders /// from png path specified as cli argument fn custom_png_spawn( mut commands: Commands, game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("custom_png"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("custom_png") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); - + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let colliders = generate_colliders(sprite_image, ColliderType::ConvexPolyline, true); + for collider in colliders { commands.spawn(( collider.unwrap(), RigidBody::Fixed, SpriteBundle { - texture: sprite_handle.unwrap().clone(), - transform: Transform::from_xyz(0.0, 0.0, 0.0), + texture: sprite_handle.clone(), ..default() }, )); } - - // - // An approach to generating convex decomposition colliders for your sprites with this crate - // - - // let edge_coordinate_groups = multi_image_edge_translated(sprite_image); - // 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); - // commands.spawn(( - // collider, - // RigidBody::Fixed, - // SpriteBundle { - // texture: sprite_handle.unwrap().clone(), - // ..default() - // }, - // )); - // } } /// for the movement system @@ -86,18 +55,18 @@ fn car_spawn( game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("car_handle"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("car") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); - let collider = generate_collider(sprite_image, ColliderType::ConvexPolyline, true).unwrap(); + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); + let collider = generate_collider(sprite_image, ColliderType::ConvexPolyline, true); + commands.spawn(( - collider, + collider.unwrap(), RigidBody::Dynamic, Velocity::default(), SpriteBundle { - texture: sprite_handle.unwrap().clone(), + texture: sprite_handle.clone(), transform: INITIAL_POSITION, ..default() }, @@ -112,17 +81,17 @@ fn terrain_spawn( game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("terrain_handle"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("terrain") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let collider = generate_collider(sprite_image, ColliderType::Heightfield, true).unwrap(); + commands.spawn(( collider, RigidBody::Fixed, SpriteBundle { - texture: sprite_handle.unwrap().clone(), + texture: sprite_handle.clone(), ..default() }, )); @@ -136,30 +105,28 @@ fn boulders_spawn( game_assets: Res, image_assets: Res>, ) { - let sprite_handle = game_assets.image_handles.get("boulders"); - if sprite_handle.is_none() { + let Some(sprite_handle) = game_assets.image_handles.get("boulders") else { return; - } - let sprite_image = image_assets.get(sprite_handle.unwrap()).unwrap(); - + }; + let sprite_image = image_assets.get(sprite_handle).unwrap(); let edges = Edges::from(sprite_image); let coord_group = edges.multi_image_edge_translated(); let colliders = generate_colliders(sprite_image, ColliderType::ConvexPolyline, true); - for (coords, collider) in coord_group.iter().zip(colliders.into_iter()) { + for (coords, collider) in coord_group.into_iter().zip(colliders.into_iter()) { let shape = shapes::Polygon { - points: coords.clone(), + points: coords, closed: true, }; - let geometry = GeometryBuilder::build_as(&shape); - let fill = Fill::color(Srgba::hex("#545454").unwrap()); - let transform = Transform::from_xyz(0., 40., 0.); commands.spawn(( - geometry, - fill, - transform, collider.unwrap(), + ShapeBundle { + path: GeometryBuilder::build_as(&shape), + ..default() + }, + Fill::color(css::GRAY), + Stroke::new(css::BLACK, 1.), RigidBody::Dynamic, )); } @@ -176,15 +143,15 @@ pub enum AppState { Running, } -#[derive(Component, Resource, Default)] +#[derive(Resource, Default)] pub struct GameAsset { pub font_handle: Handle, - pub image_handles: HashMap>, + pub image_handles: HashMap<&'static str, Handle>, } fn main() { App::new() - .add_plugins( + .add_plugins(( DefaultPlugins .set(ImagePlugin::default_nearest()) .set(WindowPlugin { @@ -197,38 +164,30 @@ fn main() { .set(AssetPlugin { file_path: ".".to_string(), ..default() - }) - .set(RenderPlugin { - render_creation: RenderCreation::Automatic(WgpuSettings { - features: WgpuFeatures::POLYGON_MODE_LINE, - ..default() - }), - ..default() }), - ) - .init_state::() - .insert_resource(GameAsset::default()) - .insert_resource(ClearColor(Color::srgb(0.0, 0.0, 0.0))) - .add_plugins(ShapePlugin) - .add_plugins(WireframePlugin) - .add_plugins(RapierPhysicsPlugin::::pixels_per_meter(100.0)) - .add_plugins(RapierDebugRenderPlugin { - style: DebugRenderStyle { - collider_fixed_color: [360., 100., 100., 1.], - collider_dynamic_color: [360., 100., 100., 1.], + ShapePlugin, + RapierPhysicsPlugin::::pixels_per_meter(100.), + #[cfg(debug_assertions)] + RapierDebugRenderPlugin { + style: DebugRenderStyle { + collider_fixed_color: [360., 100., 100., 1.], + collider_dynamic_color: [360., 100., 100., 1.], + ..default() + }, ..default() }, - ..default() - }) - .add_systems(OnEnter(AppState::Loading), load_assets) + )) + .init_state::() + .insert_resource(GameAsset::default()) + .add_systems(Startup, load_assets) .add_systems( OnExit(AppState::Loading), ( camera_spawn, - custom_png_spawn, car_spawn, terrain_spawn, boulders_spawn, + custom_png_spawn, controls_text_spawn, ), ) @@ -248,134 +207,98 @@ pub fn check_assets( game_assets: Res, mut state: ResMut>, ) { - for h in game_assets.image_handles.values() { - if Some(LoadState::Loaded) != asset_server.get_load_state(h) { - return; - } - } - - if Some(LoadState::Loaded) - != asset_server.get_load_state(&game_assets.font_handle.clone().untyped()) + let all_images_loaded = game_assets.image_handles.values().all(|handle| { + asset_server + .get_load_state(handle) + .is_some_and(|state| matches!(state, LoadState::Loaded)) + }); + let font_load_state = asset_server.get_load_state(&game_assets.font_handle.clone()); + if all_images_loaded && font_load_state.is_some_and(|state| matches!(state, LoadState::Loaded)) { - return; - } - - state.set(AppState::Running); -} - -pub fn camera_spawn(mut commands: Commands) { - commands.spawn(Camera2dBundle::default()); -} - -pub fn camera_movement( - mut query: Query<(&Camera, &mut OrthographicProjection, &mut Transform)>, - keys: Res>, -) { - for (_, mut projection, mut transform) in &mut query { - if keys.pressed(KeyCode::ArrowLeft) { - transform.translation.x -= 10.0; - } - if keys.pressed(KeyCode::ArrowRight) { - transform.translation.x += 10.0; - } - - if keys.pressed(KeyCode::ArrowUp) { - transform.translation.y += 10.0; - } - - if keys.pressed(KeyCode::ArrowDown) { - transform.translation.y -= 10.0; - } - - if keys.pressed(KeyCode::KeyW) { - projection.scale -= 0.01; - } - - if keys.pressed(KeyCode::KeyS) { - projection.scale += 0.01; - } + state.set(AppState::Running); } } pub fn load_assets(asset_server: Res, mut game_assets: ResMut) { - let custom_png_path = std::env::args().nth(1); game_assets.font_handle = asset_server.load("assets/font/NotoSansMono-Bold.ttf"); - - if let Some(png_path) = custom_png_path { - info!("Loading {}", png_path); - game_assets.image_handles = - HashMap::from([("custom_png".into(), asset_server.load(&png_path))]); - return; - } - game_assets.image_handles = HashMap::from([ - ( - "car_handle".into(), - asset_server.load("assets/sprite/car.png"), - ), - ( - "terrain_handle".into(), - asset_server.load("assets/sprite/terrain.png"), - ), - ( - "boulders".into(), - asset_server.load("assets/sprite/boulders.png"), - ), + ("car", asset_server.load("assets/sprite/car.png")), + ("terrain", asset_server.load("assets/sprite/terrain.png")), + ("boulders", asset_server.load("assets/sprite/boulders.png")), ]); + if let Some(png_path) = std::env::args().nth(1) { + info!("Loading {}", png_path); + game_assets + .image_handles + .insert("custom_png", asset_server.load(&png_path)); + } } pub fn controls_text_spawn(mut commands: Commands, game_assets: Res) { - let mut tips_text: String = indoc! {" + let tips_text = indoc! {" controls -------------------- ← ↑ ↓ → (pan camera) w (zoom in) s (zoom out) - "} - .into(); + a d (move car) + 1 (reset car transform to initial) + "}; + + commands + .spawn(NodeBundle { + style: Style { + width: Val::Px(100.), + height: Val::Px(10.), + position_type: PositionType::Absolute, + justify_content: JustifyContent::FlexStart, + align_items: AlignItems::FlexStart, + left: Val::Px(80.), + bottom: Val::Px(600.), + ..default() + }, + ..Default::default() + }) + .with_children(|parent| { + parent.spawn(TextBundle { + text: Text { + sections: vec![TextSection { + value: tips_text.to_string(), + style: TextStyle { + font: game_assets.font_handle.clone(), + font_size: 20., + color: Color::srgb(0.9, 0.9, 0.9), + }, + }], + justify: JustifyText::Left, + ..default() + }, + ..Default::default() + }); + }); +} - if game_assets.image_handles.contains_key("car_handle") { - let car_controls: String = indoc! {" - a d (move car) - 1 (reset car transform to initial) - "} - .into(); +pub fn camera_spawn(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); +} - tips_text.push_str(&car_controls); +pub fn camera_movement( + mut query: Query<(&mut OrthographicProjection, &mut Transform), With>, + keys: Res>, +) { + for (mut projection, mut transform) in &mut query { + for key in keys.get_pressed() { + match key { + KeyCode::ArrowUp => transform.translation.y += 10., + KeyCode::ArrowDown => transform.translation.y -= 10., + KeyCode::ArrowLeft => transform.translation.x -= 10., + KeyCode::ArrowRight => transform.translation.x += 10., + KeyCode::KeyW => projection.scale -= 0.01, + KeyCode::KeyS => projection.scale += 0.01, + _ => {} + } + } } - - let node_bundle = NodeBundle { - style: Style { - width: Val::Px(100.), - height: Val::Px(10.), - position_type: PositionType::Absolute, - justify_content: JustifyContent::FlexStart, - align_items: AlignItems::FlexStart, - left: Val::Px(80.0), - bottom: Val::Px(600.0), - ..default() - }, - ..Default::default() - }; - let text_bundle = TextBundle { - text: Text { - sections: vec![TextSection { - value: tips_text.to_string(), - style: TextStyle { - font: game_assets.font_handle.clone(), - font_size: 20.0, - color: Color::srgb(0.9, 0.9, 0.9), - }, - }], - justify: JustifyText::Left, - ..default() - }, - ..Default::default() - }; - - commands.spawn(node_bundle).with_children(|parent| { - parent.spawn(text_bundle); - }); } pub fn car_movement( @@ -384,16 +307,17 @@ pub fn car_movement( ) { for (mut transform, mut velocity) in &mut query { let linear_velocity = &mut velocity.linvel; - if keys.pressed(KeyCode::KeyD) { - linear_velocity.x += 30.0; - } - - if keys.pressed(KeyCode::KeyA) { - linear_velocity.x -= 30.0; - } - if keys.pressed(KeyCode::Digit1) { - *transform = INITIAL_POSITION; + for key in keys.get_pressed() { + match key { + KeyCode::KeyA => linear_velocity.x -= 30., + KeyCode::KeyD => linear_velocity.x += 30., + KeyCode::Digit1 => { + *linear_velocity = Vec2::default(); + *transform = INITIAL_POSITION; + } + _ => {} + } } } } -const INITIAL_POSITION: Transform = Transform::from_xyz(-200.0, 2.0, 0.0); +const INITIAL_POSITION: Transform = Transform::from_xyz(-200., 2., 0.); From 8aa201d988cd4b9536c3fcfb470f8d39633a376b Mon Sep 17 00:00:00 2001 From: salam Date: Sat, 7 Dec 2024 22:02:41 +0900 Subject: [PATCH 7/7] add: CHANGELOG.md --- CHANGELOG.md | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d614594 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,98 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [0.3.0](https://github.com/shnewto/bevy_collider_gen/compare/0.2.2...0.3.0) - 2024-11-20 + +### Added + +- Feature `parallel` for parallel work with data + ([594e767](https://github.com/shnewto/bevy_collider_gen/commit/594e767586494e821009f18fe8cbd96fe1b3703e)). +- [`rayon`] dependency for `parallel` features + ([594e767](https://github.com/shnewto/bevy_collider_gen/commit/594e767586494e821009f18fe8cbd96fe1b3703e)) + ([1019ba6](https://github.com/shnewto/bevy_collider_gen/commit/1019ba697f0c3b20a633eaa55af7446193ab6e9e)). +- `ColliderType` enumeration + ([618b5f7](https://github.com/shnewto/bevy_collider_gen/commit/618b5f7d081744a9f1eafee10ce9c0d21b95e1a9)). +- Functions `generate colliders`, `generate collider` for code maintainability + ([618b5f7](https://github.com/shnewto/bevy_collider_gen/commit/618b5f7d081744a9f1eafee10ce9c0d21b95e1a9)) + ([56f3a2f](https://github.com/shnewto/bevy_collider_gen/commit/56f3a2faebab3190c170ecf68e2067fa51b1ce1c)). + +### Changed + +- Upgrade dependencies: [`edges`] 0.4 + ([e4c501f](https://github.com/shnewto/edges/commit/e4c501fa701a47c9ac67bd17e805ede77ad6485a)). +- The `rapier2d`, `parallel` features are enabled and `avian2d` are disabled by default + ([f000104](https://github.com/shnewto/bevy_collider_gen/commit/f0001048d5000b34ef888fca76ccd26f3edeb3e9)). +- Dependencies [`bevy`] replaced by [`bevy_math`] + ([d2079cb](https://github.com/shnewto/bevy_collider_gen/commit/d2079cb293d6aad43588ef849539c2e885de3e0c)) + ([42c750e](https://github.com/shnewto/bevy_collider_gen/commit/42c750ecdd9b4d8f167a20e204a6692ea1cd6bee)). +- `Edges` structure now in public external crate [`edges`] + ([618b5f7](https://github.com/shnewto/bevy_collider_gen/commit/618b5f7d081744a9f1eafee10ce9c0d21b95e1a9)). + +### Removed + +- [`thiserror`] from dependencies + ([2f1f35b](https://github.com/shnewto/bevy_collider_gen/commit/2f1f35b4f6275ad079b1fe76e1a976ba6a2c3b04)). +- Functions are removed for code maintainability + ([618b5f7](https://github.com/shnewto/bevy_collider_gen/commit/618b5f7d081744a9f1eafee10ce9c0d21b95e1a9)): + - `single_polyline_collider_translated` + - `single_polyline_collider_raw` + - `single_convex_polyline_collider_translated` + - `single_convex_polyline_collider_raw` + - etc. + +## [0.2.2](https://github.com/shnewto/bevy_collider_gen/compare/0.2.1...0.2.2) - 2024-08-14 + +### Added + +- Feature `avian2d` and enabled by default for support [`avian2d`] colliders + ([9cd6ac9](https://github.com/shnewto/bevy_collider_gen/commit/9cd6ac9f362fa867e6d1bf38b4f8681ac9d09754)). + +### Changed + +- Upgrade dependencies: [`bevy`] 0.14, [`bevy_prototype_lyon`] 0.12 + ([9cd6ac9](https://github.com/shnewto/bevy_collider_gen/commit/9cd6ac9f362fa867e6d1bf38b4f8681ac9d09754)). + +### Removed + +- Feature `xpbd_2d` and support for [`bevy_xpbd_2d`] colliders + ([9cd6ac9](https://github.com/shnewto/bevy_collider_gen/commit/9cd6ac9f362fa867e6d1bf38b4f8681ac9d09754)). + +## [0.2.1](https://github.com/shnewto/bevy_collider_gen/compare/0.2.0...0.2.1) - 2024-05-13 + +### Changed + +- Upgrade dependencies: [`edges`] 0.3.2 + ([cbb8c5c](https://github.com/shnewto/bevy_collider_gen/commit/cbb8c5c1474f08bed0b405c76da3f99bd2b27540)). + +## [0.2.0](https://github.com/shnewto/bevy_collider_gen/compare/0.1.0...0.2.0) - 2024-03-04 + +### Added + +- Dependencies: [`thiserror`], [`edges`] + ([01dc9be](https://github.com/shnewto/bevy_collider_gen/commit/01dc9be747fb971d3222702d203eb471d5b156d7)). +- `Edges` structure from [`edges`] crate + ([01dc9be](https://github.com/shnewto/bevy_collider_gen/commit/01dc9be747fb971d3222702d203eb471d5b156d7)). + +### Removed + +- Functions ([01dc9be](https://github.com/shnewto/bevy_collider_gen/commit/01dc9be747fb971d3222702d203eb471d5b156d7)): + - `image_to_edges` + - `march_edges` + - `multi_image_edge_translated` + - `multi_image_edges_raw` + - `single_image_edge_raw` + - `single_image_edge_translated` + - `translate_vec` + +[`bevy`]: https://crates.io/crates/bevy +[`bevy_math`]: https://crates.io/crates/bevy_math +[`avian2d`]: https://crates.io/crates/avian2d +[`rayon`]: https://crates.io/crates/rayon +[`edges`]: https://crates.io/crates/edges +[`thiserror`]: https://crates.io/crates/thiserror +[`bevy_prototype_lyon`]: https://crates.io/crates/bevy_prototype_lyon +[`bevy_xpbd_2d`]: https://crates.io/crates/bevy_xpbd_2d