diff --git a/Cargo.lock b/Cargo.lock index 8a5241d..15925f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3991,6 +3991,7 @@ version = "0.1.0" dependencies = [ "bevy", "serde", + "world_generation", ] [[package]] diff --git a/engine/world_generation/src/hex_utils.rs b/engine/world_generation/src/hex_utils.rs index 907e2b5..ba20e2c 100644 --- a/engine/world_generation/src/hex_utils.rs +++ b/engine/world_generation/src/hex_utils.rs @@ -144,6 +144,7 @@ impl HexCoord { }; } + /// Converts this coordinate to it's chunk local equivalent pub fn to_chunk(&self) -> HexCoord { let c_pos = self.to_chunk_pos(); let off = self.to_offset(); @@ -164,14 +165,20 @@ impl HexCoord { return IVec2::new(self.hex.x + (self.hex.y / 2), self.hex.y); } + /// Convert the current coordiante to an index pub fn to_index(&self, width: usize) -> usize { return ((self.hex.x + self.hex.y * width as i32) + (self.hex.y / 2)) as usize; } + + /// Gets the index of this coord in the chunk array. + /// + /// [`width`] is in number of chunks pub fn to_chunk_index(&self, width: usize) -> usize { let pos = self.to_chunk_pos(); return (pos.x + pos.y * width as i32) as usize; } + /// Gets the index of this tile in the chunk pub fn to_chunk_local_index(&self) -> usize { return self.to_chunk().to_index(Chunk::SIZE); } @@ -251,6 +258,37 @@ impl HexCoord { return result; } + pub fn hex_select_bounded( + &self, + radius: usize, + include_center: bool, + height: usize, + width: usize, + ) -> Vec { + assert!(radius != 0, "Radius cannot be zero"); + let mut result = Vec::with_capacity(get_tile_count(radius)); + + if include_center { + if self.is_in_bounds(height, width) { + result.push(*self); + } + } + + for k in 0..(radius + 1) { + let mut p = self.scale(4, k); + for i in 0..6 { + for _j in 0..k { + p = p.get_neighbor(i); + if p.is_in_bounds(height, width) { + result.push(p); + } + } + } + } + + return result; + } + pub fn select_ring(&self, radius: usize) -> Vec { assert!(radius != 0, "Radius cannot be zero"); let mut result = Vec::with_capacity(radius * 6); diff --git a/engine/world_generation/src/map/config.rs b/engine/world_generation/src/map/config.rs index 27582c9..86fe848 100644 --- a/engine/world_generation/src/map/config.rs +++ b/engine/world_generation/src/map/config.rs @@ -1,4 +1,5 @@ use bevy::prelude::*; +use bevy_asset_loader::asset_collection::AssetCollection; use bevy_inspector_egui::InspectorOptions; use serde::{Deserialize, Serialize}; diff --git a/engine/world_generation/src/map/map.rs b/engine/world_generation/src/map/map.rs index 35b2d17..1a9ce59 100644 --- a/engine/world_generation/src/map/map.rs +++ b/engine/world_generation/src/map/map.rs @@ -87,22 +87,19 @@ impl Map { self.chunks[pos.to_chunk_index(self.width)].heights[pos.to_chunk_local_index()] = height; } - pub fn create_crater(&mut self, pos: &HexCoord, radius: usize, depth: f32) -> Vec { + pub fn create_crater(&mut self, pos: &HexCoord, radius: usize, depth: f32) -> Vec<(HexCoord, f32)> { assert!(radius != 0, "Radius cannot be zero"); - let width = self.width; - let mut chunks = self.hex_select_mut(pos, radius, true, |p, h, r| { + let tiles = self.hex_select_mut(pos, radius, true, |p, h, r| { let d = (r as f32) / (radius as f32); let cur = *h; let h2 = cur - depth; *h = h2.lerp(cur, d * d).max(0.); - return p.to_chunk_index(width); + return (*p, *h); }); - chunks.dedup(); - - return chunks; + return tiles; } pub fn hex_select(&self, center: &HexCoord, radius: usize, include_center: bool, op: OP) -> Vec @@ -111,20 +108,25 @@ impl Map { { assert!(radius != 0, "Radius cannot be zero"); + let mut result = if include_center { + Vec::with_capacity(get_tile_count(radius) + 1) + } else { + Vec::with_capacity(get_tile_count(radius)) + }; if include_center { let h = self.sample_height(¢er); - (op)(¢er, h, 0); + result.push((op)(center, h, 0)); } - let mut result = Vec::with_capacity(get_tile_count(radius)); - for k in 0..(radius + 1) { let mut p = center.scale(4, k); for i in 0..6 { for _j in 0..k { p = p.get_neighbor(i); - let h = self.sample_height(&p); - result.push((op)(&p, h, k)); + if self.is_in_bounds(&p) { + let h = self.sample_height(&p); + result.push((op)(&p, h, k)); + } } } } @@ -144,20 +146,25 @@ impl Map { { assert!(radius != 0, "Radius cannot be zero"); + let mut result = if include_center { + Vec::with_capacity(get_tile_count(radius) + 1) + } else { + Vec::with_capacity(get_tile_count(radius)) + }; if include_center { let h = self.sample_height_mut(¢er); - (op)(¢er, h, 0); + result.push((op)(center, h, 0)); } - let mut result = Vec::with_capacity(get_tile_count(radius)); - for k in 0..(radius + 1) { let mut p = center.scale(4, k); for i in 0..6 { for _j in 0..k { p = p.get_neighbor(i); - let h = self.sample_height_mut(&p); - result.push((op)(&p, h, k)); + if self.is_in_bounds(&p) { + let h = self.sample_height_mut(&p); + result.push((op)(&p, h, k)); + } } } } diff --git a/engine/world_generation/src/states.rs b/engine/world_generation/src/states.rs index dfb66ff..a1659a3 100644 --- a/engine/world_generation/src/states.rs +++ b/engine/world_generation/src/states.rs @@ -7,5 +7,5 @@ pub enum GeneratorState { SpawnMap, Idle, Regenerate, + Cleanup, } - diff --git a/game/buildings/src/building_plugin.rs b/game/buildings/src/building_plugin.rs index 785ce4e..9ca45d9 100644 --- a/game/buildings/src/building_plugin.rs +++ b/game/buildings/src/building_plugin.rs @@ -1,4 +1,6 @@ -use bevy::{prelude::*, window::PrimaryWindow}; +use std::f32::consts::E; + +use bevy::{ecs::world::CommandQueue, prelude::*, window::PrimaryWindow}; use bevy_asset_loader::loading_state::{ config::{ConfigureLoadingState, LoadingStateConfig}, LoadingStateAppExt, @@ -6,10 +8,13 @@ use bevy_asset_loader::loading_state::{ use bevy_rapier3d::{parry::transformation::utils::transform, pipeline::QueryFilter, plugin::RapierContext}; use shared::{ despawn::Despawn, + events::TileModifiedEvent, states::{AssetLoadState, GameplayState}, tags::MainCamera, }; -use world_generation::{heightmap, hex_utils::HexCoord, map::map::Map}; +use world_generation::{ + heightmap, hex_utils::HexCoord, map::map::Map, prelude::GenerationConfig, states::GeneratorState, +}; use crate::{ assets::{ @@ -17,7 +22,8 @@ use crate::{ building_database::BuildingDatabase, }, build_queue::{BuildQueue, QueueEntry}, - buildings_map::BuildingMap, + buildings_map::{BuildingEntry, BuildingMap}, + prelude::Building, }; pub struct BuildingPugin; @@ -25,7 +31,6 @@ pub struct BuildingPugin; impl Plugin for BuildingPugin { fn build(&self, app: &mut App) { app.insert_resource(BuildQueue::default()); - app.init_resource::(); app.add_plugins(BuildingAssetPlugin); app.configure_loading_state( @@ -34,11 +39,31 @@ impl Plugin for BuildingPugin { app.add_systems(Update, init.run_if(in_state(AssetLoadState::Loading))); app.add_systems(Update, hq_placement.run_if(in_state(GameplayState::PlaceHQ))); + app.add_systems( + PreUpdate, + prepare_building_map.run_if(in_state(GeneratorState::SpawnMap)), + ); + app.add_systems(Update, regernerate.run_if(in_state(GeneratorState::Regenerate))); + app.add_systems( + PostUpdate, + update_building_heights.run_if(in_state(GeneratorState::Idle)), + ); app.add_systems(PreUpdate, process_build_queue.run_if(in_state(GameplayState::Playing))); } } +fn prepare_building_map(mut commands: Commands, cfg: Res) { + commands.insert_resource(BuildingMap::new(cfg.size)); +} + +fn regernerate(mut commands: Commands, buildings: Query>, cfg: Res) { + for e in buildings.iter() { + commands.entity(e).despawn(); + } + commands.insert_resource(BuildingMap::new(cfg.size)); +} + #[derive(Resource)] struct IndicatorCube(Handle, Handle); @@ -114,6 +139,7 @@ fn process_build_queue( mut commands: Commands, db: Res, building_assets: Res>, + mut building_map: ResMut, heightmap: Res, ) { for item in &queue.queue { @@ -121,12 +147,44 @@ fn process_build_queue( if let Some(building) = building_assets.get(handle.id()) { let h = heightmap.sample_height(&item.pos); println!("Spawning {} at {}", building.name, item.pos); - commands.spawn(SceneBundle { - scene: building.prefab.clone(), - transform: Transform::from_translation(item.pos.to_world(h)), - ..Default::default() - }); + let e = commands.spawn(( + SceneBundle { + scene: building.prefab.clone(), + transform: Transform::from_translation(item.pos.to_world(h)), + ..Default::default() + }, + Building, + )); + + building_map.add_building(BuildingEntry::new(item.pos, e.id())); } } queue.queue.clear(); } + +fn update_building_heights( + mut tile_updates: EventReader, + building_map: Res, + mut commands: Commands, +) { + for event in tile_updates.read() { + match event { + TileModifiedEvent::HeightChanged(coord, new_height) => { + if let Some(building) = building_map.get_building(coord) { + let mut queue = CommandQueue::default(); + let e = building.entity.clone(); + let h = *new_height; + queue.push(move |world: &mut World| { + let mut emut = world.entity_mut(e); + if let Some(mut transform) = emut.get_mut::() { + transform.translation.y = h; + } + }); + + commands.append(&mut queue); + } + } + _ => (), + } + } +} diff --git a/game/buildings/src/buildings_map.rs b/game/buildings/src/buildings_map.rs index 1f4a46c..620463b 100644 --- a/game/buildings/src/buildings_map.rs +++ b/game/buildings/src/buildings_map.rs @@ -1,14 +1,16 @@ use bevy::prelude::*; -use world_generation::hex_utils::HexCoord; +use world_generation::{hex_utils::HexCoord, prelude::Chunk}; -#[derive(Resource, Default)] +#[derive(Resource)] pub struct BuildingMap { pub chunks: Vec, + pub size: UVec2, } impl BuildingMap { pub fn new(size: UVec2) -> Self { let mut db = BuildingMap { + size, chunks: Vec::with_capacity(size.length_squared() as usize), }; @@ -23,19 +25,39 @@ impl BuildingMap { return db; } - pub fn get_buildings_in_range(&self, coord: &HexCoord, radius: usize) -> Option> { + pub fn get_buildings_in_range(&self, coord: &HexCoord, radius: usize) -> Vec<&BuildingEntry> { assert!(radius != 0, "Radius cannot be zero"); - todo!(); + + let w = self.size.x as usize * Chunk::SIZE; + let h = self.size.y as usize * Chunk::SIZE; + let coords = coord.hex_select_bounded(radius, true, h, w); + return self.get_buildings_in_coords(coords); + } + + pub fn get_buildings_in_coords(&self, coords: Vec) -> Vec<&BuildingEntry> { + let mut result = Vec::new(); + for coord in &coords { + if let Some(buidling) = self.get_building(coord) { + result.push(buidling); + } + } + + return result; } pub fn get_building(&self, coord: &HexCoord) -> Option<&BuildingEntry> { - todo!(); + let chunk = &self.chunks[coord.to_chunk_index(self.size.x as usize)]; + return chunk.get_building(coord); + } + + pub fn add_building(&mut self, entry: BuildingEntry) { + let chunk = &mut self.chunks[entry.coord.to_chunk_index(self.size.x as usize)]; + chunk.add_building(entry); } } - pub struct BuildingChunk { - pub entries: Vec, + pub entries: Vec, pub index: usize, pub offset: IVec2, } @@ -50,7 +72,11 @@ impl BuildingChunk { } pub fn get_building(&self, coord: &HexCoord) -> Option<&BuildingEntry> { - todo!(); + return self.entries.iter().find(|b| &b.coord == coord); + } + + pub fn add_building(&mut self, entry: BuildingEntry) { + self.entries.push(entry); } } diff --git a/game/buildings/src/lib.rs b/game/buildings/src/lib.rs index 6909842..9e24bf4 100644 --- a/game/buildings/src/lib.rs +++ b/game/buildings/src/lib.rs @@ -3,4 +3,5 @@ pub mod build_queue; pub mod building_plugin; pub mod buildings_map; pub mod footprint; +pub mod prelude; pub use building_plugin::*; diff --git a/game/buildings/src/prelude.rs b/game/buildings/src/prelude.rs new file mode 100644 index 0000000..3d6fc40 --- /dev/null +++ b/game/buildings/src/prelude.rs @@ -0,0 +1,3 @@ +use bevy::prelude::*; +#[derive(Component)] +pub struct Building; diff --git a/game/main/assets b/game/main/assets index a223382..6a48d91 160000 --- a/game/main/assets +++ b/game/main/assets @@ -1 +1 @@ -Subproject commit a223382d13d1e3cac29ecc1f9152ac81e2b19c74 +Subproject commit 6a48d910aa3826fac04af0b481e741d3b8e2e911 diff --git a/game/main/src/map_rendering/chunk_rebuild.rs b/game/main/src/map_rendering/chunk_rebuild.rs index 44000d9..418aa60 100644 --- a/game/main/src/map_rendering/chunk_rebuild.rs +++ b/game/main/src/map_rendering/chunk_rebuild.rs @@ -4,6 +4,8 @@ use bevy::tasks::*; use bevy::utils::futures; use bevy_rapier3d::geometry::Collider; use bevy_rapier3d::geometry::TriMeshFlags; +use shared::events::ChunkModifiedEvent; +use shared::events::TileModifiedEvent; use world_generation::prelude::Map; use world_generation::states::GeneratorState; @@ -17,18 +19,14 @@ pub struct ChunkRebuildPlugin; impl Plugin for ChunkRebuildPlugin { fn build(&self, app: &mut App) { - app.insert_resource(ChunkRebuildQueue::default()); app.init_resource::(); - app.add_systems(PreUpdate, chunk_rebuilder.run_if(in_state(GeneratorState::SpawnMap))); + app.add_event::(); + app.add_event::(); + app.add_systems(PreUpdate, chunk_rebuilder.run_if(in_state(GeneratorState::Idle))); app.add_systems(PostUpdate, collider_task_resolver); } } -#[derive(Resource, Default)] -pub struct ChunkRebuildQueue { - pub queue: Vec, -} - fn chunk_rebuilder( mut commands: Commands, chunk_query: Query<(Entity, &PhosChunk), (With, Without)>, @@ -39,7 +37,7 @@ fn chunk_rebuilder( for (chunk_entity, idx) in &chunk_query { #[cfg(feature = "tracing")] let _spawn_span = info_span!("Rebuild Chunk").entered(); - + println!("Rebuilding Chunk"); let chunk_index = idx.index; let chunk_data = heightmap.get_chunk_mesh_data(chunk_index); let chunk_offset = heightmap.chunks[chunk_index].chunk_offset; diff --git a/game/main/src/map_rendering/terraforming_test.rs b/game/main/src/map_rendering/terraforming_test.rs index 1525c21..382a804 100644 --- a/game/main/src/map_rendering/terraforming_test.rs +++ b/game/main/src/map_rendering/terraforming_test.rs @@ -1,5 +1,9 @@ -use bevy::{prelude::*, window::PrimaryWindow}; +use bevy::{prelude::*, utils::hashbrown::HashSet, window::PrimaryWindow}; use bevy_rapier3d::{pipeline::QueryFilter, plugin::RapierContext}; +use shared::{ + events::{ChunkModifiedEvent, TileModifiedEvent}, + states::GameplayState, +}; use world_generation::{hex_utils::HexCoord, prelude::Map, states::GeneratorState}; use crate::{ @@ -11,7 +15,12 @@ pub struct TerraFormingTestPlugin; impl Plugin for TerraFormingTestPlugin { fn build(&self, app: &mut App) { - app.add_systems(Update, deform.run_if(in_state(GeneratorState::Idle))); + app.add_systems( + Update, + deform + .run_if(in_state(GeneratorState::Idle)) + .run_if(in_state(GameplayState::Playing)), + ); } } @@ -23,6 +32,8 @@ fn deform( rapier_context: Res, mut heightmap: ResMut, chunks: Res, + mut chunk_modified: EventWriter, + mut tile_modified: EventWriter, ) { let mut multi = 0.; if mouse.just_pressed(MouseButton::Left) { @@ -53,15 +64,22 @@ fn deform( QueryFilter::only_fixed(), ); - if let Some((e, dist)) = collision { + if let Some((_, dist)) = collision { #[cfg(feature = "tracing")] let span = info_span!("Deform Mesh").entered(); let contact_point = cam_ray.get_point(dist); let contact_coord = HexCoord::from_world_pos(contact_point); - let modified_chunks = heightmap.create_crater(&contact_coord, 5, 5. * multi); - for c in modified_chunks { - commands.entity(chunks.chunks[c]).insert(RebuildChunk); + let modified_tiles = heightmap.create_crater(&contact_coord, 5, 5. * multi); + let mut chunk_set: HashSet = HashSet::new(); + for (tile, height) in modified_tiles { + let chunk = tile.to_chunk_index(heightmap.width); + if !chunk_set.contains(&chunk) { + chunk_modified.send(ChunkModifiedEvent { index: chunk }); + chunk_set.insert(chunk); + commands.entity(chunks.chunks[chunk]).insert(RebuildChunk); + } + tile_modified.send(TileModifiedEvent::HeightChanged(tile, height)); } - commands.entity(e).insert(RebuildChunk); + // commands.entity(e).insert(RebuildChunk); } } diff --git a/game/main/src/phos.rs b/game/main/src/phos.rs index 432f798..aba0cf5 100644 --- a/game/main/src/phos.rs +++ b/game/main/src/phos.rs @@ -1,7 +1,7 @@ -use crate::camera_system::camera_plugin::PhosCameraPlugin; use crate::camera_system::components::PhosCamera; use crate::map_rendering::map_init::MapInitPlugin; use crate::utlis::render_distance_system::RenderDistancePlugin; +use crate::{camera_system::camera_plugin::PhosCameraPlugin, utlis::debug_plugin::DebugPlugin}; use bevy::{ pbr::{wireframe::WireframeConfig, CascadeShadowConfig}, prelude::*, @@ -33,6 +33,8 @@ impl Plugin for PhosGamePlugin { RenderDistancePlugin, BuildingPugin, DespawnPuglin, + #[cfg(debug_assertions)] + DebugPlugin, )); //Systems - Startup diff --git a/game/main/src/utlis/debug_plugin.rs b/game/main/src/utlis/debug_plugin.rs new file mode 100644 index 0000000..ca1ecd0 --- /dev/null +++ b/game/main/src/utlis/debug_plugin.rs @@ -0,0 +1,97 @@ +use bevy::{prelude::*, window::PrimaryWindow}; +use bevy_inspector_egui::bevy_egui::EguiContexts; +use bevy_inspector_egui::egui; +use bevy_rapier3d::prelude::*; +use shared::tags::MainCamera; +use world_generation::{ + consts::HEX_CORNERS, + hex_utils::{HexCoord, INNER_RADIUS}, + prelude::Map, + states::GeneratorState, +}; + +pub struct DebugPlugin; + +impl Plugin for DebugPlugin { + fn build(&self, app: &mut App) { + app.insert_state(DebugState::Base); + + app.add_systems( + Update, + show_tile_heights + .run_if(in_state(GeneratorState::Idle)) + .run_if(not(in_state(DebugState::None))), + ); + + app.add_systems( + Update, + verbose_data + .run_if(in_state(GeneratorState::Idle)) + .run_if(in_state(DebugState::Verbose)), + ); + + app.insert_resource(Shape(Polyline3d::new([ + HEX_CORNERS[0], + HEX_CORNERS[1], + HEX_CORNERS[2], + HEX_CORNERS[3], + HEX_CORNERS[4], + HEX_CORNERS[5], + HEX_CORNERS[0], + ]))); + } +} + +#[derive(Resource)] +struct Shape(pub Polyline3d<7>); + +#[derive(States, Debug, Clone, PartialEq, Eq, Hash)] +pub enum DebugState { + Base, + None, + Verbose, +} + +fn show_tile_heights( + cam_query: Query<(&GlobalTransform, &Camera), With>, + window: Query<&Window, With>, + rapier_context: Res, + map: Res, + mut gizmos: Gizmos, + shape: Res, +) { + let win = window.single(); + let (cam_transform, camera) = cam_query.single(); + let Some(cursor_pos) = win.cursor_position() else { + return; + }; + + let Some(cam_ray) = camera.viewport_to_world(cam_transform, cursor_pos) else { + return; + }; + + let collision = rapier_context.cast_ray( + cam_ray.origin, + cam_ray.direction.into(), + 500., + true, + QueryFilter::only_fixed(), + ); + + if let Some((_e, dist)) = collision { + let contact_point = cam_ray.get_point(dist); + let contact_coord = HexCoord::from_world_pos(contact_point); + if map.is_in_bounds(&contact_coord) { + let height = map.sample_height(&contact_coord); + gizmos.primitive_3d( + &shape.0, + contact_coord.to_world(height + 0.01), + Quat::IDENTITY, + Color::WHITE, + ); + } + gizmos.sphere(contact_point, Quat::IDENTITY, 0.1, Srgba::rgb(1., 0., 0.5)); + } +} + +fn verbose_data() {} diff --git a/game/main/src/utlis/mod.rs b/game/main/src/utlis/mod.rs index fc1e23a..b2154a8 100644 --- a/game/main/src/utlis/mod.rs +++ b/game/main/src/utlis/mod.rs @@ -1,2 +1,3 @@ pub mod chunk_utils; pub mod render_distance_system; +pub mod debug_plugin; diff --git a/game/shared/Cargo.toml b/game/shared/Cargo.toml index b19ce07..c394b40 100644 --- a/game/shared/Cargo.toml +++ b/game/shared/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] bevy = "0.14.0" serde = { version = "1.0.204", features = ["derive"] } +world_generation = { path = "../../engine/world_generation" } [features] diff --git a/game/shared/src/events.rs b/game/shared/src/events.rs new file mode 100644 index 0000000..d4ddef7 --- /dev/null +++ b/game/shared/src/events.rs @@ -0,0 +1,13 @@ +use bevy::prelude::*; +use world_generation::hex_utils::*; + +#[derive(Event)] +pub enum TileModifiedEvent { + HeightChanged(HexCoord, f32), + TypeChanged(HexCoord, usize), +} + +#[derive(Event)] +pub struct ChunkModifiedEvent { + pub index: usize, +} diff --git a/game/shared/src/lib.rs b/game/shared/src/lib.rs index d317d7e..244a2c5 100644 --- a/game/shared/src/lib.rs +++ b/game/shared/src/lib.rs @@ -3,3 +3,4 @@ pub mod despawn; pub mod resource; pub mod states; pub mod tags; +pub mod events;