diff --git a/src/game/camera.rs b/src/game/camera.rs index 6503f8d..a16ecc6 100644 --- a/src/game/camera.rs +++ b/src/game/camera.rs @@ -13,6 +13,7 @@ pub struct GameCameraPlugin; impl Plugin for GameCameraPlugin { fn build(&self, app: &mut App) { app.register_inspectable::() + .register_type::() .add_startup_system(camera_setup) .add_system_to_stage(CoreStage::PostUpdate, camera_system); } @@ -31,6 +32,10 @@ impl Default for FollowMovement { } } +#[derive(Default, Component, Reflect, Inspectable)] +#[reflect(Component)] +pub struct GameCamera; + #[derive(Default, Component, Reflect, Inspectable)] #[reflect(Component)] pub struct CameraFollow { @@ -55,6 +60,7 @@ fn camera_setup(mut commands: Commands) { }, ..default() }, + GameCamera, )); } diff --git a/src/terrain2d.rs b/src/terrain2d.rs index 5c078d3..6df1352 100644 --- a/src/terrain2d.rs +++ b/src/terrain2d.rs @@ -3,7 +3,10 @@ use std::collections::{ HashMap, }; -use bevy::prelude::*; +use bevy::{ + prelude::*, + render::{camera::RenderTarget, view::window}, +}; use bevy_prototype_debug_lines::DebugLines; mod chunk2d; @@ -14,7 +17,10 @@ pub use chunk2d::*; pub use terrain_gen2d::*; pub use texel2d::*; -use crate::util::{math::*, Vector2I}; +use crate::{ + game::camera::GameCamera, + util::{math::*, Vector2I}, +}; pub struct Terrain2DPlugin; @@ -23,6 +29,7 @@ impl Plugin for Terrain2DPlugin { app.register_type::() .insert_resource(Terrain2D::new()) .add_event::() + .add_system(debug_painter) .add_system_to_stage( CoreStage::PostUpdate, dirty_rect_visualizer.before(emit_terrain_events), @@ -34,6 +41,70 @@ impl Plugin for Terrain2DPlugin { } } +fn debug_painter( + mut terrain: ResMut, + windows: Res, + input: Res>, + camera_query: Query<(&Camera, &GlobalTransform), With>, +) { + if !input.pressed(MouseButton::Left) && !input.pressed(MouseButton::Right) { + return; + } + + // REM: Dirty and hopefully temporary + // https://bevy-cheatbook.github.io/cookbook/cursor2world.html#2d-games + // get the camera info and transform + // assuming there is exactly one main camera entity, so query::single() is OK + let (camera, camera_transform) = camera_query.single(); + + // get the window that the camera is displaying to (or the primary window) + let window = if let RenderTarget::Window(id) = camera.target { + windows.get(id).unwrap() + } else { + windows.get_primary().unwrap() + }; + + // check if the cursor is inside the window and get its position + let world_pos = if let Some(screen_pos) = window.cursor_position() { + // get the size of the window + let window_size = Vec2::new(window.width() as f32, window.height() as f32); + + // convert screen position [0..resolution] to ndc [-1..1] (gpu coordinates) + let ndc = (screen_pos / window_size) * 2.0 - Vec2::ONE; + + // matrix for undoing the projection and camera transform + let ndc_to_world = camera_transform.compute_matrix() * camera.projection_matrix().inverse(); + + // use it to convert ndc to world-space coordinates + let world_pos = ndc_to_world.project_point3(ndc.extend(-1.0)); + + // reduce it to a 2D value + world_pos.truncate() + } else { + return; + }; + + let origin = Vector2I::from(world_pos); + let radius: i32 = 12; + let id = match ( + input.pressed(MouseButton::Left), + input.pressed(MouseButton::Right), + ) { + (true, false) => 1, + (_, _) => 0, + }; + + for y in origin.y - (radius - 1)..origin.y + radius { + for x in origin.x - (radius - 1)..origin.x + radius { + let dx = (x - origin.x).abs(); + let dy = (y - origin.y).abs(); + if dx * dx + dy * dy <= (radius - 1) * (radius - 1) { + terrain.set_texel(&Vector2I { x, y }, id) + } + } + } +} + /** Visualize dirty rects */ diff --git a/src/terrain2d/chunk2d.rs b/src/terrain2d/chunk2d.rs index d157497..8194e85 100644 --- a/src/terrain2d/chunk2d.rs +++ b/src/terrain2d/chunk2d.rs @@ -553,7 +553,8 @@ pub fn chunk_collision_sync( (With, Changed), >, chunk_query: Query<(Entity, &TerrainChunk2D), With>, - child_collider_query: Query<&Children, With>, + child_query: Query<&Children>, + collider_query: Query<&Collider>, ) { let mut updated_chunks = vec![]; @@ -585,9 +586,11 @@ pub fn chunk_collision_sync( for (entity, chunk) in updated_chunks.iter() { // Remove old colliders - for children in child_collider_query.get(*entity) { + for children in child_query.get(*entity) { for child in children { - commands.entity(*child).despawn_recursive() + if let Ok(_) = collider_query.get(*child) { + commands.entity(*child).despawn_recursive() + } } } diff --git a/src/util/vector2_i32.rs b/src/util/vector2_i32.rs index 147ac8f..43b79cb 100644 --- a/src/util/vector2_i32.rs +++ b/src/util/vector2_i32.rs @@ -17,6 +17,15 @@ impl Vector2I { } } +impl From for Vector2I { + fn from(vec: Vec2) -> Self { + Self { + x: vec.x as i32, + y: vec.y as i32, + } + } +} + impl From for Vec2 { fn from(vec: Vector2I) -> Self { Vec2 {