From 782a568eb663d2562f9dea2b4351169640d8624c Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Fri, 21 Feb 2025 11:43:06 +0200 Subject: [PATCH 1/3] Started working on immediate mode drawing for debugging --- Cargo.lock | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + src/debug.rs | 10 ++++-- 3 files changed, 96 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ab118f4..128a118 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1008,6 +1008,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "bevy_prototype_lyon" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e02ff6a3e8b4867eaed81a2bb2cc0bcddc33150849eefa369b4a170ef337aaa8" +dependencies = [ + "bevy", + "lyon_algorithms", + "lyon_tessellation", + "svgtypes", +] + [[package]] name = "bevy_ptr" version = "0.15.1" @@ -2198,6 +2210,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_next_after" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf7cc16383c4b8d58b9905a8509f02926ce3058053c056376248d958c9df1e8" + [[package]] name = "fnv" version = "1.0.7" @@ -2404,6 +2422,7 @@ dependencies = [ "bevy", "bevy-inspector-egui", "bevy_mod_debugdump", + "bevy_prototype_lyon", "bevy_rapier2d", "num-traits", ] @@ -2731,6 +2750,16 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "kurbo" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89234b2cc610a7dd927ebde6b41dd1a5d4214cffaef4cf1fb2195d592f92518f" +dependencies = [ + "arrayvec", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2825,6 +2854,48 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lyon_algorithms" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f13c9be19d257c7d37e70608ed858e8eab4b2afcea2e3c9a622e892acbf43c08" +dependencies = [ + "lyon_path", + "num-traits", +] + +[[package]] +name = "lyon_geom" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8af69edc087272df438b3ee436c4bb6d7c04aa8af665cfd398feae627dbd8570" +dependencies = [ + "arrayvec", + "euclid", + "num-traits", +] + +[[package]] +name = "lyon_path" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0b8aec2f58586f6eef237985b9a9b7cb3a3aff4417c575075cf95bf925252e" +dependencies = [ + "lyon_geom", + "num-traits", +] + +[[package]] +name = "lyon_tessellation" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579d42360a4b09846eff2feef28f538696c7d6c7439bfa65874ff3cbe0951b2c" +dependencies = [ + "float_next_after", + "lyon_path", + "num-traits", +] + [[package]] name = "mach2" version = "0.4.2" @@ -4105,6 +4176,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + [[package]] name = "skrifa" version = "0.22.3" @@ -4218,6 +4295,16 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce5d813d71d82c4cbc1742135004e4a79fd870214c155443451c139c9470a0aa" +[[package]] +name = "svgtypes" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" +dependencies = [ + "kurbo", + "siphasher", +] + [[package]] name = "swash" version = "0.1.19" diff --git a/Cargo.toml b/Cargo.toml index d2856d0..40bf765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2021" bevy = "0.15.1" bevy-inspector-egui = "0.28.1" bevy_mod_debugdump = "0.12.1" +bevy_prototype_lyon = "0.13.0" bevy_rapier2d = "0.28.0" num-traits = "0.2.19" diff --git a/src/debug.rs b/src/debug.rs index 12273ad..891cc46 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -7,14 +7,16 @@ pub struct DebugSet; impl Plugin for DebugPlugin { fn build(&self, app: &mut App) { - app.configure_sets(Last, DebugSet.run_if(is_debug_enabled)); + app.configure_sets(Last, DebugSet.run_if(is_debug_enabled)) + .configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled)); app.insert_resource(DebugMode::off()) .add_plugins(( bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled), bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(), )) - .add_systems(Update, debug_toggle); + .add_systems(Update, debug_toggle) + .add_systems(PostUpdate, draw_debug.in_set(DebugSet)); } } @@ -43,3 +45,7 @@ fn debug_toggle(input: Res>, mut debug_mode: ResMut Date: Mon, 24 Feb 2025 22:02:06 +0200 Subject: [PATCH 2/3] Added drawing debug shapes in immediate mode --- src/debug.rs | 96 +++++++++++++++++++++++++++++++++++--- src/game.rs | 5 +- src/game/systems/demo.rs | 19 ++++++-- src/game/systems/worker.rs | 79 +++++++++++++++++++++++++++---- 4 files changed, 180 insertions(+), 19 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 891cc46..37bebb1 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_prototype_lyon::prelude::*; pub struct DebugPlugin; @@ -11,12 +12,18 @@ impl Plugin for DebugPlugin { .configure_sets(PostUpdate, DebugSet.run_if(is_debug_enabled)); app.insert_resource(DebugMode::off()) - .add_plugins(( - bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled), - bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(), - )) + .insert_resource(DebugDraw::default()); + + app.add_plugins(( + bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled), + bevy_rapier2d::prelude::RapierDebugRenderPlugin::default(), + )); + + app.register_type::() + .add_systems(First, clear_shapes) .add_systems(Update, debug_toggle) - .add_systems(PostUpdate, draw_debug.in_set(DebugSet)); + // TODO: Check if this could be scheduled just before render instead + .add_systems(PostUpdate, draw_shapes); } } @@ -36,6 +43,43 @@ impl DebugMode { } } +/// TODO: Rename to something smarter +#[derive(Clone, Copy, Debug, Reflect)] +pub struct Draw { + pub shape: Shape, + pub color: Srgba, +} + +#[derive(Clone, Copy, Debug, Reflect)] +pub enum Shape { + Line { + from: Vec2, + to: Vec2, + }, + Polygon { + center: Vec2, + sides: usize, + radius: f32, + }, +} + +#[derive(Clone, Debug, Default, Component, Reflect)] +#[reflect(Component)] +#[require(Transform, Visibility)] +pub struct DebugCanvas; + +#[derive(Debug, Default, Resource, Reflect)] +#[reflect(Resource)] +pub struct DebugDraw { + pub draw_queue: Vec, +} + +impl DebugDraw { + pub fn shape(&mut self, draw: Draw) { + self.draw_queue.push(draw); + } +} + pub fn is_debug_enabled(debug_mode: Res) -> bool { debug_mode.enabled } @@ -46,6 +90,44 @@ fn debug_toggle(input: Res>, mut debug_mode: ResMut>) { + for parent in canvas_query.iter() { + commands.entity(parent).despawn_descendants(); + } +} + +fn draw_shapes( + mut commands: Commands, + canvas: Option>>, + mut debug_draw: ResMut, +) { + let canvas = match canvas { + Some(canvas) => *canvas, + None => commands + .spawn((Name::new("Debug Canvas"), DebugCanvas)) + .id(), + }; + commands.entity(canvas).with_children(|builder| { + for draw in debug_draw.draw_queue.drain(..) { + // TODO + let path = match draw.shape { + Shape::Line { from, to } => GeometryBuilder::build_as(&shapes::Line(from, to)), + Shape::Polygon { + center, + sides, + radius, + } => GeometryBuilder::build_as(&shapes::RegularPolygon { + center, + sides, + feature: RegularPolygonFeature::Radius(radius), + }), + }; + + builder.spawn(( + ShapeBundle { path, ..default() }, + Fill::color(Srgba::NONE), + Stroke::color(draw.color), + )); + } + }); } diff --git a/src/game.rs b/src/game.rs index c9fec69..ad13dcd 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,5 +1,6 @@ use crate::{debug, game_setup, util}; use bevy::prelude::*; +use bevy_prototype_lyon::prelude::*; use bevy_rapier2d::prelude::*; mod item; @@ -10,6 +11,7 @@ mod work; pub fn init(app: &mut App) { let app = app.add_plugins(( game_setup::GameSetupPlugin, + ShapePlugin, RapierPhysicsPlugin::::default(), util::UtilPlugin, debug::DebugPlugin, @@ -24,5 +26,6 @@ pub fn init(app: &mut App) { .register_type::() .add_systems(Startup, systems::setup_2d) .add_systems(Update, (systems::demo_2d, systems::work_select)) - .add_systems(PostUpdate, item::update_item_sprite); + .add_systems(PostUpdate, item::update_item_sprite) + .add_systems(Update, (systems::draw_job_targets).in_set(debug::DebugSet)); } diff --git a/src/game/systems/demo.rs b/src/game/systems/demo.rs index a288725..dcd9521 100644 --- a/src/game/systems/demo.rs +++ b/src/game/systems/demo.rs @@ -1,6 +1,9 @@ use bevy::prelude::*; -use crate::game::{item::Item, prefab}; +use crate::game::{ + item::{Inventory, Item, ItemStack}, + prefab, +}; pub fn setup_2d(mut commands: Commands) { commands.spawn(( @@ -9,13 +12,23 @@ pub fn setup_2d(mut commands: Commands) { Transform::from_xyz(0.0, 0.0, 10.0), )); - commands.spawn((Transform::from_xyz(-200.0, 0.0, 0.0), prefab::Glorb)); + commands.spawn(( + Transform::from_xyz(-300.0, 0.0, 0.0), + prefab::Glorb, + Inventory { + items: vec![ItemStack { + item: Item::Wood, + count: 1, + }], + capacity: Some(1), + }, + )); commands.spawn((Transform::from_xyz(-200.0, 100.0, 0.0), prefab::Glorb)); commands.spawn((Transform::from_xyz(-200.0, -100.0, 0.0), prefab::Glorb)); commands.spawn((Transform::from_xyz(200.0, 0.0, 0.0), prefab::Tree)); - commands.spawn((Name::from("Wood"), Item::Wood)); + // commands.spawn((Name::from("Wood"), Item::Wood)); commands.spawn((Transform::from_xyz(-200.0, -150.0, 0.0), prefab::Chest)); } diff --git a/src/game/systems/worker.rs b/src/game/systems/worker.rs index abf267e..c134d05 100644 --- a/src/game/systems/worker.rs +++ b/src/game/systems/worker.rs @@ -1,8 +1,11 @@ -use bevy::prelude::*; +use bevy::{color::palettes::css, prelude::*}; -use crate::game::{ - item::{Inventory, ItemSource, Stockpile}, - work::{Task, WorkType, Worker}, +use crate::{ + debug::{DebugDraw, Draw, Shape}, + game::{ + item::{Inventory, ItemSource, Stockpile}, + work::{Task, WorkType, Worker}, + }, }; pub fn work_select( @@ -26,7 +29,7 @@ pub fn work_select( let stockpile_dist_squared = worker_transform .translation() .distance_squared(stockpile_transform.translation()); - if task_by_distance.map_or(true, |(_, closest_task_dist)| { + if task_by_distance.is_none_or(|(_, closest_task_dist)| { stockpile_dist_squared < closest_task_dist }) { task_by_distance = Some(( @@ -52,9 +55,9 @@ pub fn work_select( let source_dist_squared = worker_transform .translation() .distance_squared(item_source_transform.translation()); - if task_by_distance.map_or(true, |(_, closest_task_dist)| { - source_dist_squared < closest_task_dist - }) { + if task_by_distance + .is_none_or(|(_, closest_task_dist)| source_dist_squared < closest_task_dist) + { task_by_distance = Some(( Task { target: item_source_entity, @@ -70,3 +73,63 @@ pub fn work_select( worker.0 = task_by_distance.map(|(task, _)| task); } } + +/// Example of DebugDraw usage +pub fn draw_job_targets( + mut debug_draw: ResMut, + worker_query: Query<(Entity, &Worker)>, + global_query: Query<&GlobalTransform>, +) { + for (worker_entity, worker) in worker_query.iter() { + let worker_global = global_query.get(worker_entity).unwrap(); + let draws = match worker.0 { + Some(task) => match task.work_type { + WorkType::Gather => vec![ + Draw { + shape: Shape::Polygon { + center: worker_global.translation().xy(), + sides: 3, + radius: 16., + }, + color: css::GREEN, + }, + Draw { + shape: Shape::Line { + from: worker_global.translation().xy(), + to: global_query.get(task.target).unwrap().translation().xy(), + }, + color: css::GREEN, + }, + ], + WorkType::Store(_) => vec![ + Draw { + shape: Shape::Polygon { + center: worker_global.translation().xy(), + sides: 3, + radius: 16., + }, + color: css::YELLOW, + }, + Draw { + shape: Shape::Line { + from: worker_global.translation().xy(), + to: global_query.get(task.target).unwrap().translation().xy(), + }, + color: css::YELLOW, + }, + ], + }, + None => vec![Draw { + shape: Shape::Polygon { + center: worker_global.translation().xy(), + sides: 3, + radius: 16., + }, + color: css::RED, + }], + }; + for draw in draws { + debug_draw.shape(draw); + } + } +} From dadea6135260f7ee542981aebb626901387282f8 Mon Sep 17 00:00:00 2001 From: hheik <4469778+hheik@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:13:07 +0200 Subject: [PATCH 3/3] Renamed Draw to ColoredShape and merged shape clearing system to draw_shapes --- src/debug.rs | 26 +++++++++++--------------- src/game/systems/worker.rs | 18 +++++++++--------- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/debug.rs b/src/debug.rs index 37bebb1..14014d6 100644 --- a/src/debug.rs +++ b/src/debug.rs @@ -20,7 +20,6 @@ impl Plugin for DebugPlugin { )); app.register_type::() - .add_systems(First, clear_shapes) .add_systems(Update, debug_toggle) // TODO: Check if this could be scheduled just before render instead .add_systems(PostUpdate, draw_shapes); @@ -43,9 +42,8 @@ impl DebugMode { } } -/// TODO: Rename to something smarter #[derive(Clone, Copy, Debug, Reflect)] -pub struct Draw { +pub struct ColoredShape { pub shape: Shape, pub color: Srgba, } @@ -71,12 +69,12 @@ pub struct DebugCanvas; #[derive(Debug, Default, Resource, Reflect)] #[reflect(Resource)] pub struct DebugDraw { - pub draw_queue: Vec, + pub draw_queue: Vec, } impl DebugDraw { - pub fn shape(&mut self, draw: Draw) { - self.draw_queue.push(draw); + pub fn colored_shape(&mut self, colored_shape: ColoredShape) { + self.draw_queue.push(colored_shape); } } @@ -90,17 +88,15 @@ fn debug_toggle(input: Res>, mut debug_mode: ResMut>) { - for parent in canvas_query.iter() { - commands.entity(parent).despawn_descendants(); - } -} - fn draw_shapes( mut commands: Commands, canvas: Option>>, mut debug_draw: ResMut, ) { + // TODO: Re-use old shapes + if let Some(canvas) = canvas.as_ref() { + commands.entity(**canvas).despawn_descendants(); + } let canvas = match canvas { Some(canvas) => *canvas, None => commands @@ -108,9 +104,9 @@ fn draw_shapes( .id(), }; commands.entity(canvas).with_children(|builder| { - for draw in debug_draw.draw_queue.drain(..) { + for ColoredShape { shape, color } in debug_draw.draw_queue.drain(..) { // TODO - let path = match draw.shape { + let path = match shape { Shape::Line { from, to } => GeometryBuilder::build_as(&shapes::Line(from, to)), Shape::Polygon { center, @@ -126,7 +122,7 @@ fn draw_shapes( builder.spawn(( ShapeBundle { path, ..default() }, Fill::color(Srgba::NONE), - Stroke::color(draw.color), + Stroke::color(color), )); } }); diff --git a/src/game/systems/worker.rs b/src/game/systems/worker.rs index c134d05..c441f2f 100644 --- a/src/game/systems/worker.rs +++ b/src/game/systems/worker.rs @@ -1,7 +1,7 @@ use bevy::{color::palettes::css, prelude::*}; use crate::{ - debug::{DebugDraw, Draw, Shape}, + debug::{ColoredShape, DebugDraw, Shape}, game::{ item::{Inventory, ItemSource, Stockpile}, work::{Task, WorkType, Worker}, @@ -82,10 +82,10 @@ pub fn draw_job_targets( ) { for (worker_entity, worker) in worker_query.iter() { let worker_global = global_query.get(worker_entity).unwrap(); - let draws = match worker.0 { + let colored_shapes = match worker.0 { Some(task) => match task.work_type { WorkType::Gather => vec![ - Draw { + ColoredShape { shape: Shape::Polygon { center: worker_global.translation().xy(), sides: 3, @@ -93,7 +93,7 @@ pub fn draw_job_targets( }, color: css::GREEN, }, - Draw { + ColoredShape { shape: Shape::Line { from: worker_global.translation().xy(), to: global_query.get(task.target).unwrap().translation().xy(), @@ -102,7 +102,7 @@ pub fn draw_job_targets( }, ], WorkType::Store(_) => vec![ - Draw { + ColoredShape { shape: Shape::Polygon { center: worker_global.translation().xy(), sides: 3, @@ -110,7 +110,7 @@ pub fn draw_job_targets( }, color: css::YELLOW, }, - Draw { + ColoredShape { shape: Shape::Line { from: worker_global.translation().xy(), to: global_query.get(task.target).unwrap().translation().xy(), @@ -119,7 +119,7 @@ pub fn draw_job_targets( }, ], }, - None => vec![Draw { + None => vec![ColoredShape { shape: Shape::Polygon { center: worker_global.translation().xy(), sides: 3, @@ -128,8 +128,8 @@ pub fn draw_job_targets( color: css::RED, }], }; - for draw in draws { - debug_draw.shape(draw); + for colored_shape in colored_shapes { + debug_draw.colored_shape(colored_shape); } } }