From cc3e43da16bb828ae2b0ed926ba57820822f635f Mon Sep 17 00:00:00 2001 From: Amatsugu Date: Sat, 19 Oct 2024 01:33:03 -0400 Subject: [PATCH] experimenting with water surface mesh --- Cargo.lock | 10 ++ Cargo.toml | 2 +- engine/asset_loader/Cargo.toml | 1 + engine/asset_loader/src/lib.rs | 70 +------------- engine/asset_loader/src/macros.rs | 66 +++++++++++++ engine/asset_loader_proc/Cargo.toml | 13 +++ engine/asset_loader_proc/src/lib.rs | 1 + .../src/generators/mesh_generator.rs | 69 ++++++++++++++ engine/world_generation/src/heightmap.rs | 2 +- engine/world_generation/src/map/map.rs | 5 +- engine/world_generation/src/map/map_utils.rs | 2 +- engine/world_generation/src/map/mesh_chunk.rs | 15 +++ game/main/src/camera_system/camera_plugin.rs | 6 +- game/main/src/map_rendering/chunk_rebuild.rs | 4 +- game/main/src/map_rendering/map_init.rs | 93 +++++++++++-------- game/main/src/prelude.rs | 2 + game/main/src/utlis/chunk_utils.rs | 26 ++++-- 17 files changed, 263 insertions(+), 124 deletions(-) create mode 100644 engine/asset_loader/src/macros.rs create mode 100644 engine/asset_loader_proc/Cargo.toml create mode 100644 engine/asset_loader_proc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f142eba..dee1c96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -264,6 +264,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "asset_loader_proc" +version = "0.1.0" +dependencies = [ + "bevy", + "ron", + "serde", + "serde_json", +] + [[package]] name = "async-broadcast" version = "0.5.1" diff --git a/Cargo.toml b/Cargo.toml index b69da53..4e66375 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ members = [ "game/buildings", "game/shared", "engine/world_generation", - "engine/asset_loader", "game/buildings", "game/shared", "game/units", "engine/data", "game/resources"] + "engine/asset_loader", "game/buildings", "game/shared", "game/units", "engine/data", "game/resources", "engine/asset_loader_proc"] # Enable a small amount of optimization in debug mode [profile.dev] diff --git a/engine/asset_loader/Cargo.toml b/engine/asset_loader/Cargo.toml index 53fd560..8febc77 100644 --- a/engine/asset_loader/Cargo.toml +++ b/engine/asset_loader/Cargo.toml @@ -3,6 +3,7 @@ name = "asset_loader" version = "0.1.0" edition = "2021" + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] diff --git a/engine/asset_loader/src/lib.rs b/engine/asset_loader/src/lib.rs index 091be6c..d28af63 100644 --- a/engine/asset_loader/src/lib.rs +++ b/engine/asset_loader/src/lib.rs @@ -1,69 +1 @@ -pub mod macros { - - #[macro_export] - macro_rules! create_asset_loader { - ( - $plugin_name: ident, - $loader_name: ident, - $asset_type: ident, - $extensions: expr, - $($string_name: ident -> $handle_name: ident)* ; - $($string_array_name: ident -> $handle_array_name: ident)* ? - ) => { - use bevy::prelude::*; - use bevy::asset::{AssetLoader, AssetEvent, AssetEvents, LoadContext, LoadState, AsyncReadExt, io::Reader}; - use bevy::utils::BoxedFuture; - pub struct $plugin_name; - impl Plugin for $plugin_name { - fn build(&self, app: &mut App) { - app.init_asset::<$asset_type>() - .init_asset_loader::<$loader_name>(); - } - } - - #[derive(Default)] - pub struct $loader_name; - - impl AssetLoader for $loader_name { - type Asset = $asset_type; - - type Settings = (); - - type Error = String; - - async fn load<'a>( - &'a self, - reader: &'a mut Reader<'_>, - _settings: &'a Self::Settings, - load_context: &'a mut LoadContext<'_>, - ) -> Result { - let mut bytes = Vec::new(); - let read_result = reader.read_to_end(&mut bytes).await; - if read_result.is_err() { - return Err(read_result.err().unwrap().to_string()); - } - let serialized: Result = - ron::de::from_bytes::(&bytes); - if serialized.is_err() { - return Err(serialized.err().unwrap().to_string()); - } - let mut asset = serialized.unwrap(); - $( - - asset.$handle_name = load_context.load(&asset.$string_name); - )* - $( - for i in 0..asset.$string_array_name.len(){ - asset.$handle_array_name.push(load_context.load(&asset.$string_array_name[i])); - } - )? - return Ok(asset); - } - - fn extensions(&self) -> &[&str] { - $extensions - } - } - }; - } -} +pub mod macros; \ No newline at end of file diff --git a/engine/asset_loader/src/macros.rs b/engine/asset_loader/src/macros.rs new file mode 100644 index 0000000..741c6b5 --- /dev/null +++ b/engine/asset_loader/src/macros.rs @@ -0,0 +1,66 @@ +#[macro_export] +macro_rules! create_asset_loader { + ( + $plugin_name: ident, + $loader_name: ident, + $asset_type: ident, + $extensions: expr, + $($string_name: ident -> $handle_name: ident)* ; + $($string_array_name: ident -> $handle_array_name: ident)* ? + ) => { + use bevy::prelude::*; + use bevy::asset::{AssetLoader, AssetEvent, AssetEvents, LoadContext, LoadState, AsyncReadExt, io::Reader}; + use bevy::utils::BoxedFuture; + pub struct $plugin_name; + impl Plugin for $plugin_name { + fn build(&self, app: &mut App) { + app.init_asset::<$asset_type>() + .init_asset_loader::<$loader_name>(); + } + } + + #[derive(Default)] + pub struct $loader_name; + + impl AssetLoader for $loader_name { + type Asset = $asset_type; + + type Settings = (); + + type Error = String; + + async fn load<'a>( + &'a self, + reader: &'a mut Reader<'_>, + _settings: &'a Self::Settings, + load_context: &'a mut LoadContext<'_>, + ) -> Result { + let mut bytes = Vec::new(); + let read_result = reader.read_to_end(&mut bytes).await; + if read_result.is_err() { + return Err(read_result.err().unwrap().to_string()); + } + let serialized: Result = + ron::de::from_bytes::(&bytes); + if serialized.is_err() { + return Err(serialized.err().unwrap().to_string()); + } + let mut asset = serialized.unwrap(); + $( + + asset.$handle_name = load_context.load(&asset.$string_name); + )* + $( + for i in 0..asset.$string_array_name.len(){ + asset.$handle_array_name.push(load_context.load(&asset.$string_array_name[i])); + } + )? + return Ok(asset); + } + + fn extensions(&self) -> &[&str] { + $extensions + } + } + }; +} diff --git a/engine/asset_loader_proc/Cargo.toml b/engine/asset_loader_proc/Cargo.toml new file mode 100644 index 0000000..6579daa --- /dev/null +++ b/engine/asset_loader_proc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "asset_loader_proc" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +serde = "1.0.204" +serde_json = "1.0.120" +bevy = "0.14.2" +ron = "0.8.1" diff --git a/engine/asset_loader_proc/src/lib.rs b/engine/asset_loader_proc/src/lib.rs new file mode 100644 index 0000000..136d063 --- /dev/null +++ b/engine/asset_loader_proc/src/lib.rs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/engine/world_generation/src/generators/mesh_generator.rs b/engine/world_generation/src/generators/mesh_generator.rs index b7b9a74..d58881a 100644 --- a/engine/world_generation/src/generators/mesh_generator.rs +++ b/engine/world_generation/src/generators/mesh_generator.rs @@ -2,6 +2,7 @@ use crate::hex_utils::HexCoord; use crate::{hex_utils::offset3d_to_world, prelude::*}; #[cfg(feature = "tracing")] use bevy::log::*; +use bevy::math::VectorSpace; use bevy::{ prelude::*, render::{ @@ -94,6 +95,74 @@ fn create_tile( } } +pub fn generate_chunk_water_mesh(chunk: &MeshChunkData, sealevel: f32, map_width: usize, map_height: usize) -> Mesh { + #[cfg(feature = "tracing")] + let _gen_mesh = info_span!("Generate Water Surface Mesh").entered(); + let vertex_count: usize = Chunk::SIZE * Chunk::SIZE * 7; + let mut verts = Vec::with_capacity(vertex_count); + let mut uvs = Vec::with_capacity(vertex_count); + let mut indices = Vec::with_capacity(vertex_count); + let mut normals = Vec::with_capacity(vertex_count); + + for z in 0..Chunk::SIZE { + for x in 0..Chunk::SIZE { + let idx = x + z * Chunk::SIZE; + let height = chunk.heights[idx]; + if height > sealevel { + continue; + } + let off_pos = Vec3::new(x as f32, sealevel, z as f32); + let tile_pos = offset3d_to_world(off_pos); + let coord = HexCoord::from_grid_pos(x, z); + let n = chunk.get_neighbors(&coord); + + create_tile_water_surface(tile_pos, &n, &mut verts, &mut uvs, &mut indices, &mut normals); + } + } + let mesh = Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::MAIN_WORLD | RenderAssetUsages::RENDER_WORLD, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, verts) + .with_inserted_attribute(Mesh::ATTRIBUTE_UV_0, uvs) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_indices(Indices::U32(indices)); + return mesh; +} + +fn create_tile_water_surface( + pos: Vec3, + neighbors: &[f32; 6], + verts: &mut Vec, + uvs: &mut Vec, + indices: &mut Vec, + normals: &mut Vec, +) { + let idx = verts.len() as u32; + //todo: only use triangle fan when on shoreline + verts.push(pos); + uvs.push(Vec2::ZERO); + normals.push(Vec3::Y); + for i in 0..6 { + let p = pos + HEX_CORNERS[i]; + verts.push(p); + let mut uv = Vec2::ZERO; + let n = neighbors[i]; + let nn = neighbors[(i + 5) % 6]; + + if nn > pos.y || n > pos.y { + uv = Vec2::ONE; + } + + indices.push(idx); + indices.push(idx + 1 + i as u32); + indices.push(idx + 1 + ((i as u32 + 1) % 6)); + + uvs.push(uv); + normals.push(Vec3::Y); + } +} + fn create_tile_wall( pos: Vec3, dir: usize, diff --git a/engine/world_generation/src/heightmap.rs b/engine/world_generation/src/heightmap.rs index 62a74dc..f115414 100644 --- a/engine/world_generation/src/heightmap.rs +++ b/engine/world_generation/src/heightmap.rs @@ -39,7 +39,7 @@ pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32, painter: &BiomePain chunks, height: cfg.size.y as usize, width: cfg.size.x as usize, - sea_level: cfg.sea_level as f32, + sealevel: cfg.sea_level as f32, min_level: min, max_level: max, biome_count: painter.biomes.len(), diff --git a/engine/world_generation/src/map/map.rs b/engine/world_generation/src/map/map.rs index 9ac52d3..4224b15 100644 --- a/engine/world_generation/src/map/map.rs +++ b/engine/world_generation/src/map/map.rs @@ -13,7 +13,7 @@ pub struct Map { pub chunks: Vec, pub height: usize, pub width: usize, - pub sea_level: f32, + pub sealevel: f32, pub min_level: f32, pub max_level: f32, pub biome_count: usize, @@ -39,6 +39,7 @@ impl Map { return MeshChunkData { min_height: self.min_level, + sealevel: self.sealevel, heights: chunk.heights.clone(), textures: chunk.textures.clone(), }; @@ -140,7 +141,7 @@ impl Map { pub fn get_center(&self) -> Vec3 { let w = self.get_world_width(); let h = self.get_world_height(); - return Vec3::new(w / 2., self.sea_level, h / 2.); + return Vec3::new(w / 2., self.sealevel, h / 2.); } pub fn get_world_width(&self) -> f32 { diff --git a/engine/world_generation/src/map/map_utils.rs b/engine/world_generation/src/map/map_utils.rs index d98d184..05b6672 100644 --- a/engine/world_generation/src/map/map_utils.rs +++ b/engine/world_generation/src/map/map_utils.rs @@ -68,7 +68,7 @@ pub fn update_map(map: &Map, smooth: f32, image: &mut ImageBuffer [f32; 6] { + let mut data = [self.min_height; 6]; + let n_tiles = coord.get_neighbors(); + for i in 0..6 { + let n = n_tiles[i]; + if !n.is_in_bounds(Chunk::SIZE * width, Chunk::SIZE * height) { + continue; + } + data[i] = self.heights[n.to_index(Chunk::SIZE)]; + } + + return data; + } } diff --git a/game/main/src/camera_system/camera_plugin.rs b/game/main/src/camera_system/camera_plugin.rs index 43b08bf..549e2fe 100644 --- a/game/main/src/camera_system/camera_plugin.rs +++ b/game/main/src/camera_system/camera_plugin.rs @@ -224,7 +224,7 @@ fn sample_ground(pos: Vec3, heightmap: &Map) -> f32 { let mut ground_height = if heightmap.is_in_bounds(&tile_under) { heightmap.sample_height(&tile_under) } else { - heightmap.sea_level + heightmap.sealevel }; for n in neighbors { @@ -234,8 +234,8 @@ fn sample_ground(pos: Vec3, heightmap: &Map) -> f32 { } } } - if ground_height < heightmap.sea_level { - ground_height = heightmap.sea_level; + if ground_height < heightmap.sealevel { + ground_height = heightmap.sealevel; } return ground_height; } diff --git a/game/main/src/map_rendering/chunk_rebuild.rs b/game/main/src/map_rendering/chunk_rebuild.rs index 6783b65..a6b0971 100644 --- a/game/main/src/map_rendering/chunk_rebuild.rs +++ b/game/main/src/map_rendering/chunk_rebuild.rs @@ -33,6 +33,7 @@ fn chunk_rebuilder( heightmap: Res, ) { let pool = AsyncComputeTaskPool::get(); + let map_size = UVec2::new(heightmap.width as u32, heightmap.height as u32); for (chunk_entity, idx) in &chunk_query { #[cfg(feature = "tracing")] @@ -46,7 +47,8 @@ fn chunk_rebuilder( #[cfg(feature = "tracing")] let _spawn_span = info_span!("Rebuild Task").entered(); let mut queue = CommandQueue::default(); - let (mesh, collider_data, _, _) = prepare_chunk_mesh(&chunk_data, chunk_offset, chunk_index); + let (mesh, water_mesh, collider_data, _, _) = + prepare_chunk_mesh(&chunk_data, chunk_data.sealevel, chunk_offset, chunk_index, map_size); #[cfg(feature = "tracing")] let trimesh_span = info_span!("Chunk Trimesh").entered(); let c = Collider::trimesh_with_flags( diff --git a/game/main/src/map_rendering/map_init.rs b/game/main/src/map_rendering/map_init.rs index 4f5b7dd..601a4a7 100644 --- a/game/main/src/map_rendering/map_init.rs +++ b/game/main/src/map_rendering/map_init.rs @@ -32,12 +32,13 @@ use crate::{ chunk_material::ChunkMaterial, water_material::{WaterMaterial, WaterSettings}, }, - utlis::{ - chunk_utils::{paint_map, prepare_chunk_mesh_with_collider}, - }, + utlis::chunk_utils::{paint_map, prepare_chunk_mesh_with_collider}, }; -use super::{chunk_rebuild::ChunkRebuildPlugin, render_distance_system::RenderDistanceVisibility, terraforming_test::TerraFormingTestPlugin}; +use super::{ + chunk_rebuild::ChunkRebuildPlugin, render_distance_system::RenderDistanceVisibility, + terraforming_test::TerraFormingTestPlugin, +}; pub struct MapInitPlugin; @@ -52,7 +53,6 @@ impl Plugin for MapInitPlugin { app.add_plugins(BiomeAssetPlugin); app.add_plugins(ResourceInspectorPlugin::::default()); - app.add_plugins(ResourceInspectorPlugin::::default()); app.register_type::>(); app.register_asset_reflect::>(); app.add_plugins(( @@ -93,12 +93,7 @@ impl Plugin for MapInitPlugin { } } -#[derive(Resource, Reflect, Default)] -#[reflect(Resource)] -struct WaterInspect(Handle>); - fn setup_materials( - mut commands: Commands, mut phos_assets: ResMut, mut water_materials: ResMut>>, ) { @@ -119,7 +114,6 @@ fn setup_materials( ..default() }, }); - commands.insert_resource(WaterInspect(water_material.clone())); phos_assets.water_material = water_material; } @@ -239,12 +233,19 @@ fn spawn_map( ) { paint_map(&mut heightmap, &biome_painter, &tile_assets, &tile_mappers); + let map_size = UVec2::new(heightmap.width as u32, heightmap.height as u32); let chunk_meshes: Vec<_> = heightmap .chunks .par_iter() .map(|chunk: &Chunk| { let index = offset_to_index(chunk.chunk_offset, heightmap.width); - return prepare_chunk_mesh_with_collider(&heightmap.get_chunk_mesh_data(index), chunk.chunk_offset, index); + return prepare_chunk_mesh_with_collider( + &heightmap.get_chunk_mesh_data(index), + heightmap.sealevel, + chunk.chunk_offset, + index, + map_size, + ); }) .collect(); @@ -257,36 +258,52 @@ fn spawn_map( 0., (Chunk::SIZE / 2) as f32 * 1.5, ); - for (mesh, collider, pos, index) in chunk_meshes { + for (chunk_mesh, water_mesh, collider, pos, index) in chunk_meshes { // let mesh_handle = meshes.a - let chunk = commands.spawn(( - MaterialMeshBundle { - mesh: meshes.add(mesh), - material: atlas.chunk_material_handle.clone(), - transform: Transform::from_translation(pos), - ..default() - }, - PhosChunk::new(index), - RenderDistanceVisibility::default().with_offset(visibility_offset), - collider, - )); - registry.chunks.push(chunk.id()); + let chunk = commands + .spawn(( + MaterialMeshBundle { + mesh: meshes.add(chunk_mesh), + material: atlas.chunk_material_handle.clone(), + transform: Transform::from_translation(pos), + ..default() + }, + PhosChunk::new(index), + RenderDistanceVisibility::default().with_offset(visibility_offset), + collider, + )) + .id(); + let water = commands + .spawn(( + MaterialMeshBundle { + mesh: meshes.add(water_mesh), + material: atlas.water_material.clone(), + transform: Transform::from_translation(pos), + ..default() + }, + PhosChunk::new(index), + NotShadowCaster, + RenderDistanceVisibility::default().with_offset(visibility_offset), + )) + .id(); + registry.chunks.push(chunk); + registry.waters.push(water); } } - commands.spawn(( - MaterialMeshBundle { - transform: Transform::from_translation(heightmap.get_center()), - mesh: meshes.add( - Plane3d::default() - .mesh() - .size(heightmap.get_world_width(), heightmap.get_world_height()), - ), - material: atlas.water_material.clone(), - ..default() - }, - NotShadowCaster, - )); + // commands.spawn(( + // MaterialMeshBundle { + // transform: Transform::from_translation(heightmap.get_center()), + // mesh: meshes.add( + // Plane3d::default() + // .mesh() + // .size(heightmap.get_world_width(), heightmap.get_world_height()), + // ), + // material: atlas.water_material.clone(), + // ..default() + // }, + // NotShadowCaster, + // )); commands.insert_resource(registry); generator_state.set(GeneratorState::Idle); diff --git a/game/main/src/prelude.rs b/game/main/src/prelude.rs index 1196012..6ff4870 100644 --- a/game/main/src/prelude.rs +++ b/game/main/src/prelude.rs @@ -30,12 +30,14 @@ impl PhosChunk { #[derive(Resource, Default)] pub struct PhosChunkRegistry { pub chunks: Vec, + pub waters: Vec, } impl PhosChunkRegistry { pub fn new(size: usize) -> Self { return Self { chunks: Vec::with_capacity(size), + waters: Vec::with_capacity(size), }; } } diff --git a/game/main/src/utlis/chunk_utils.rs b/game/main/src/utlis/chunk_utils.rs index e37870e..942e81d 100644 --- a/game/main/src/utlis/chunk_utils.rs +++ b/game/main/src/utlis/chunk_utils.rs @@ -3,14 +3,17 @@ use bevy::log::*; use bevy::{ asset::Assets, ecs::system::Res, - math::{IVec2, Vec3}, + math::{IVec2, UVec2, Vec3}, render::mesh::Mesh, }; use bevy_rapier3d::geometry::{Collider, TriMeshFlags}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use world_generation::{ biome_painter::BiomePainter, - generators::{chunk_colliders::generate_chunk_collider, mesh_generator::generate_chunk_mesh}, + generators::{ + chunk_colliders::generate_chunk_collider, + mesh_generator::{generate_chunk_mesh, generate_chunk_water_mesh}, + }, hex_utils::offset_to_world, prelude::{Chunk, Map, MeshChunkData}, tile_manager::TileAsset, @@ -50,16 +53,20 @@ pub fn paint_chunk( pub fn prepare_chunk_mesh( chunk: &MeshChunkData, + sealevel: f32, chunk_offset: IVec2, chunk_index: usize, -) -> (Mesh, (Vec, Vec<[u32; 3]>), Vec3, usize) { + map_size: UVec2, +) -> (Mesh, Mesh, (Vec, Vec<[u32; 3]>), Vec3, usize) { #[cfg(feature = "tracing")] let _gen_mesh = info_span!("Generate Chunk").entered(); - let mesh = generate_chunk_mesh(chunk); + let chunk_mesh = generate_chunk_mesh(chunk); + let water_mesh = generate_chunk_water_mesh(chunk, sealevel, map_size.x as usize, map_size.y as usize); let col_data = generate_chunk_collider(chunk); return ( - mesh, + chunk_mesh, + water_mesh, col_data, offset_to_world(chunk_offset * Chunk::SIZE as i32, 0.), chunk_index, @@ -68,15 +75,18 @@ pub fn prepare_chunk_mesh( pub fn prepare_chunk_mesh_with_collider( chunk: &MeshChunkData, + sealevel: f32, chunk_offset: IVec2, chunk_index: usize, -) -> (Mesh, Collider, Vec3, usize) { - let (mesh, (col_verts, col_indicies), pos, index) = prepare_chunk_mesh(chunk, chunk_offset, chunk_index); + map_size: UVec2, +) -> (Mesh, Mesh, Collider, Vec3, usize) { + let (chunk_mesh, water_mesh, (col_verts, col_indicies), pos, index) = + prepare_chunk_mesh(chunk, sealevel, chunk_offset, chunk_index, map_size); let collider: Collider; { #[cfg(feature = "tracing")] let _collider_span = info_span!("Create Collider Trimesh").entered(); collider = Collider::trimesh_with_flags(col_verts, col_indicies, TriMeshFlags::DELETE_DUPLICATE_TRIANGLES); } - return (mesh, collider, pos, index); + return (chunk_mesh, water_mesh, collider, pos, index); }