experimenting with water surface mesh

This commit is contained in:
2024-10-19 01:33:03 -04:00
parent b61dacf5e2
commit cc3e43da16
17 changed files with 263 additions and 124 deletions

10
Cargo.lock generated
View File

@@ -264,6 +264,16 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "asset_loader_proc"
version = "0.1.0"
dependencies = [
"bevy",
"ron",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.5.1" version = "0.5.1"

View File

@@ -5,7 +5,7 @@ members = [
"game/buildings", "game/buildings",
"game/shared", "game/shared",
"engine/world_generation", "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 # Enable a small amount of optimization in debug mode
[profile.dev] [profile.dev]

View File

@@ -3,6 +3,7 @@ name = "asset_loader"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]

View File

@@ -1,69 +1 @@
pub mod macros { 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<Self::Asset, Self::Error> {
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<Self::Asset, _> =
ron::de::from_bytes::<Self::Asset>(&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
}
}
};
}
}

View File

@@ -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<Self::Asset, Self::Error> {
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<Self::Asset, _> =
ron::de::from_bytes::<Self::Asset>(&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
}
}
};
}

View File

@@ -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"

View File

@@ -0,0 +1 @@

View File

@@ -2,6 +2,7 @@ use crate::hex_utils::HexCoord;
use crate::{hex_utils::offset3d_to_world, prelude::*}; use crate::{hex_utils::offset3d_to_world, prelude::*};
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
use bevy::log::*; use bevy::log::*;
use bevy::math::VectorSpace;
use bevy::{ use bevy::{
prelude::*, prelude::*,
render::{ 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<Vec3>,
uvs: &mut Vec<Vec2>,
indices: &mut Vec<u32>,
normals: &mut Vec<Vec3>,
) {
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( fn create_tile_wall(
pos: Vec3, pos: Vec3,
dir: usize, dir: usize,

View File

@@ -39,7 +39,7 @@ pub fn generate_heightmap(cfg: &GenerationConfig, seed: u32, painter: &BiomePain
chunks, chunks,
height: cfg.size.y as usize, height: cfg.size.y as usize,
width: cfg.size.x as usize, width: cfg.size.x as usize,
sea_level: cfg.sea_level as f32, sealevel: cfg.sea_level as f32,
min_level: min, min_level: min,
max_level: max, max_level: max,
biome_count: painter.biomes.len(), biome_count: painter.biomes.len(),

View File

@@ -13,7 +13,7 @@ pub struct Map {
pub chunks: Vec<Chunk>, pub chunks: Vec<Chunk>,
pub height: usize, pub height: usize,
pub width: usize, pub width: usize,
pub sea_level: f32, pub sealevel: f32,
pub min_level: f32, pub min_level: f32,
pub max_level: f32, pub max_level: f32,
pub biome_count: usize, pub biome_count: usize,
@@ -39,6 +39,7 @@ impl Map {
return MeshChunkData { return MeshChunkData {
min_height: self.min_level, min_height: self.min_level,
sealevel: self.sealevel,
heights: chunk.heights.clone(), heights: chunk.heights.clone(),
textures: chunk.textures.clone(), textures: chunk.textures.clone(),
}; };
@@ -140,7 +141,7 @@ impl Map {
pub fn get_center(&self) -> Vec3 { pub fn get_center(&self) -> Vec3 {
let w = self.get_world_width(); let w = self.get_world_width();
let h = self.get_world_height(); 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 { pub fn get_world_width(&self) -> f32 {

View File

@@ -68,7 +68,7 @@ pub fn update_map(map: &Map, smooth: f32, image: &mut ImageBuffer<image::Rgba<u8
let height = map.sample_height(&coord); let height = map.sample_height(&coord);
let mut color = Hsla::hsl(138.0, 1.0, 0.4); let mut color = Hsla::hsl(138.0, 1.0, 0.4);
if height < map.sea_level { if height < map.sealevel {
color.hue = 217.0; color.hue = 217.0;
} }

View File

@@ -6,6 +6,7 @@ pub struct MeshChunkData {
pub heights: [f32; Chunk::AREA], pub heights: [f32; Chunk::AREA],
pub textures: [[u32; 2]; Chunk::AREA], pub textures: [[u32; 2]; Chunk::AREA],
pub min_height: f32, pub min_height: f32,
pub sealevel: f32,
} }
impl MeshChunkData { impl MeshChunkData {
@@ -22,4 +23,18 @@ impl MeshChunkData {
return data; return data;
} }
pub fn get_neighbors_map_bounded(&self, coord: &HexCoord, width: usize, height: usize) -> [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;
}
} }

View File

@@ -224,7 +224,7 @@ fn sample_ground(pos: Vec3, heightmap: &Map) -> f32 {
let mut ground_height = if heightmap.is_in_bounds(&tile_under) { let mut ground_height = if heightmap.is_in_bounds(&tile_under) {
heightmap.sample_height(&tile_under) heightmap.sample_height(&tile_under)
} else { } else {
heightmap.sea_level heightmap.sealevel
}; };
for n in neighbors { for n in neighbors {
@@ -234,8 +234,8 @@ fn sample_ground(pos: Vec3, heightmap: &Map) -> f32 {
} }
} }
} }
if ground_height < heightmap.sea_level { if ground_height < heightmap.sealevel {
ground_height = heightmap.sea_level; ground_height = heightmap.sealevel;
} }
return ground_height; return ground_height;
} }

View File

@@ -33,6 +33,7 @@ fn chunk_rebuilder(
heightmap: Res<Map>, heightmap: Res<Map>,
) { ) {
let pool = AsyncComputeTaskPool::get(); let pool = AsyncComputeTaskPool::get();
let map_size = UVec2::new(heightmap.width as u32, heightmap.height as u32);
for (chunk_entity, idx) in &chunk_query { for (chunk_entity, idx) in &chunk_query {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
@@ -46,7 +47,8 @@ fn chunk_rebuilder(
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _spawn_span = info_span!("Rebuild Task").entered(); let _spawn_span = info_span!("Rebuild Task").entered();
let mut queue = CommandQueue::default(); 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")] #[cfg(feature = "tracing")]
let trimesh_span = info_span!("Chunk Trimesh").entered(); let trimesh_span = info_span!("Chunk Trimesh").entered();
let c = Collider::trimesh_with_flags( let c = Collider::trimesh_with_flags(

View File

@@ -32,12 +32,13 @@ use crate::{
chunk_material::ChunkMaterial, chunk_material::ChunkMaterial,
water_material::{WaterMaterial, WaterSettings}, water_material::{WaterMaterial, WaterSettings},
}, },
utlis::{ utlis::chunk_utils::{paint_map, prepare_chunk_mesh_with_collider},
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; pub struct MapInitPlugin;
@@ -52,7 +53,6 @@ impl Plugin for MapInitPlugin {
app.add_plugins(BiomeAssetPlugin); app.add_plugins(BiomeAssetPlugin);
app.add_plugins(ResourceInspectorPlugin::<GenerationConfig>::default()); app.add_plugins(ResourceInspectorPlugin::<GenerationConfig>::default());
app.add_plugins(ResourceInspectorPlugin::<WaterInspect>::default());
app.register_type::<ExtendedMaterial<StandardMaterial, WaterMaterial>>(); app.register_type::<ExtendedMaterial<StandardMaterial, WaterMaterial>>();
app.register_asset_reflect::<ExtendedMaterial<StandardMaterial, WaterMaterial>>(); app.register_asset_reflect::<ExtendedMaterial<StandardMaterial, WaterMaterial>>();
app.add_plugins(( app.add_plugins((
@@ -93,12 +93,7 @@ impl Plugin for MapInitPlugin {
} }
} }
#[derive(Resource, Reflect, Default)]
#[reflect(Resource)]
struct WaterInspect(Handle<ExtendedMaterial<StandardMaterial, WaterMaterial>>);
fn setup_materials( fn setup_materials(
mut commands: Commands,
mut phos_assets: ResMut<PhosAssets>, mut phos_assets: ResMut<PhosAssets>,
mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, WaterMaterial>>>, mut water_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, WaterMaterial>>>,
) { ) {
@@ -119,7 +114,6 @@ fn setup_materials(
..default() ..default()
}, },
}); });
commands.insert_resource(WaterInspect(water_material.clone()));
phos_assets.water_material = water_material; phos_assets.water_material = water_material;
} }
@@ -239,12 +233,19 @@ fn spawn_map(
) { ) {
paint_map(&mut heightmap, &biome_painter, &tile_assets, &tile_mappers); 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 let chunk_meshes: Vec<_> = heightmap
.chunks .chunks
.par_iter() .par_iter()
.map(|chunk: &Chunk| { .map(|chunk: &Chunk| {
let index = offset_to_index(chunk.chunk_offset, heightmap.width); 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(); .collect();
@@ -257,36 +258,52 @@ fn spawn_map(
0., 0.,
(Chunk::SIZE / 2) as f32 * 1.5, (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 mesh_handle = meshes.a
let chunk = commands.spawn(( let chunk = commands
MaterialMeshBundle { .spawn((
mesh: meshes.add(mesh), MaterialMeshBundle {
material: atlas.chunk_material_handle.clone(), mesh: meshes.add(chunk_mesh),
transform: Transform::from_translation(pos), material: atlas.chunk_material_handle.clone(),
..default() transform: Transform::from_translation(pos),
}, ..default()
PhosChunk::new(index), },
RenderDistanceVisibility::default().with_offset(visibility_offset), PhosChunk::new(index),
collider, RenderDistanceVisibility::default().with_offset(visibility_offset),
)); collider,
registry.chunks.push(chunk.id()); ))
.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(( // commands.spawn((
MaterialMeshBundle { // MaterialMeshBundle {
transform: Transform::from_translation(heightmap.get_center()), // transform: Transform::from_translation(heightmap.get_center()),
mesh: meshes.add( // mesh: meshes.add(
Plane3d::default() // Plane3d::default()
.mesh() // .mesh()
.size(heightmap.get_world_width(), heightmap.get_world_height()), // .size(heightmap.get_world_width(), heightmap.get_world_height()),
), // ),
material: atlas.water_material.clone(), // material: atlas.water_material.clone(),
..default() // ..default()
}, // },
NotShadowCaster, // NotShadowCaster,
)); // ));
commands.insert_resource(registry); commands.insert_resource(registry);
generator_state.set(GeneratorState::Idle); generator_state.set(GeneratorState::Idle);

View File

@@ -30,12 +30,14 @@ impl PhosChunk {
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct PhosChunkRegistry { pub struct PhosChunkRegistry {
pub chunks: Vec<Entity>, pub chunks: Vec<Entity>,
pub waters: Vec<Entity>,
} }
impl PhosChunkRegistry { impl PhosChunkRegistry {
pub fn new(size: usize) -> Self { pub fn new(size: usize) -> Self {
return Self { return Self {
chunks: Vec::with_capacity(size), chunks: Vec::with_capacity(size),
waters: Vec::with_capacity(size),
}; };
} }
} }

View File

@@ -3,14 +3,17 @@ use bevy::log::*;
use bevy::{ use bevy::{
asset::Assets, asset::Assets,
ecs::system::Res, ecs::system::Res,
math::{IVec2, Vec3}, math::{IVec2, UVec2, Vec3},
render::mesh::Mesh, render::mesh::Mesh,
}; };
use bevy_rapier3d::geometry::{Collider, TriMeshFlags}; use bevy_rapier3d::geometry::{Collider, TriMeshFlags};
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator}; use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use world_generation::{ use world_generation::{
biome_painter::BiomePainter, 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, hex_utils::offset_to_world,
prelude::{Chunk, Map, MeshChunkData}, prelude::{Chunk, Map, MeshChunkData},
tile_manager::TileAsset, tile_manager::TileAsset,
@@ -50,16 +53,20 @@ pub fn paint_chunk(
pub fn prepare_chunk_mesh( pub fn prepare_chunk_mesh(
chunk: &MeshChunkData, chunk: &MeshChunkData,
sealevel: f32,
chunk_offset: IVec2, chunk_offset: IVec2,
chunk_index: usize, chunk_index: usize,
) -> (Mesh, (Vec<Vec3>, Vec<[u32; 3]>), Vec3, usize) { map_size: UVec2,
) -> (Mesh, Mesh, (Vec<Vec3>, Vec<[u32; 3]>), Vec3, usize) {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _gen_mesh = info_span!("Generate Chunk").entered(); 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); let col_data = generate_chunk_collider(chunk);
return ( return (
mesh, chunk_mesh,
water_mesh,
col_data, col_data,
offset_to_world(chunk_offset * Chunk::SIZE as i32, 0.), offset_to_world(chunk_offset * Chunk::SIZE as i32, 0.),
chunk_index, chunk_index,
@@ -68,15 +75,18 @@ pub fn prepare_chunk_mesh(
pub fn prepare_chunk_mesh_with_collider( pub fn prepare_chunk_mesh_with_collider(
chunk: &MeshChunkData, chunk: &MeshChunkData,
sealevel: f32,
chunk_offset: IVec2, chunk_offset: IVec2,
chunk_index: usize, chunk_index: usize,
) -> (Mesh, Collider, Vec3, usize) { map_size: UVec2,
let (mesh, (col_verts, col_indicies), pos, index) = prepare_chunk_mesh(chunk, chunk_offset, chunk_index); ) -> (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; let collider: Collider;
{ {
#[cfg(feature = "tracing")] #[cfg(feature = "tracing")]
let _collider_span = info_span!("Create Collider Trimesh").entered(); let _collider_span = info_span!("Create Collider Trimesh").entered();
collider = Collider::trimesh_with_flags(col_verts, col_indicies, TriMeshFlags::DELETE_DUPLICATE_TRIANGLES); 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);
} }