Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7be2443
wip
CuddlyBunion341 Mar 13, 2025
5218b0b
Add perf data?
CuddlyBunion341 Mar 13, 2025
09905bf
Attempt improve performance (crashes instantly)
CuddlyBunion341 Mar 14, 2025
71b5354
Add async std
CuddlyBunion341 Mar 14, 2025
2191fc6
Revert "Add async std"
CuddlyBunion341 Mar 14, 2025
060f285
Revert "Attempt improve performance (crashes instantly)"
CuddlyBunion341 Mar 14, 2025
dfba6e8
Revert "Add perf data?"
CuddlyBunion341 Mar 14, 2025
c969a3c
Revert "wip"
CuddlyBunion341 Mar 14, 2025
088f1ee
wip
CuddlyBunion341 Mar 15, 2025
972004d
Implement async!
CuddlyBunion341 Mar 16, 2025
818290b
Refactor mesh generation, fix performance
CuddlyBunion341 Mar 16, 2025
36c81b8
Fix linter
CuddlyBunion341 Mar 16, 2025
6d5499a
Refactor remove unused code
CuddlyBunion341 Mar 16, 2025
6c40e67
Refactor
CuddlyBunion341 Mar 16, 2025
09af598
Refactor rename
CuddlyBunion341 Mar 16, 2025
ea89014
Use ChatGPT to DRYify mesher task with macro
CuddlyBunion341 Mar 16, 2025
83ddaa3
Refactor, fix linter
CuddlyBunion341 Mar 16, 2025
3280693
Refactor
CuddlyBunion341 Mar 16, 2025
8c87304
Improve dispose
CuddlyBunion341 Mar 16, 2025
5adc654
Update src/client/terrain/systems.rs
CuddlyBunion341 Mar 16, 2025
9541f0a
Update src/client/terrain/systems.rs
CuddlyBunion341 Mar 16, 2025
2940e1d
Join chunk meshing
CuddlyBunion341 Mar 17, 2025
68ab49e
Refactor?
CuddlyBunion341 Mar 17, 2025
90e272b
Refactor systems
CuddlyBunion341 Mar 18, 2025
f9e5cbd
Fix linter
CuddlyBunion341 Mar 18, 2025
7085d21
Refactor rename
CuddlyBunion341 Mar 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/client/terrain/components.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use bevy::ecs::component::Component;

use super::resources::MeshType;

#[derive(Component)]
pub struct ChunkMesh {
pub key: [i32; 3],
pub mesh_type: MeshType,
}
2 changes: 2 additions & 0 deletions src/client/terrain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ impl Plugin for TerrainPlugin {
app.insert_resource(ChunkManager::new());
app.insert_resource(util::TextureManager::new());
app.insert_resource(resources::RenderMaterials::new());
app.insert_resource(resources::MesherTasks::default());
app.add_event::<terrain_events::BlockUpdateEvent>();
app.add_event::<terrain_events::ChunkMeshUpdateEvent>();
app.add_event::<terrain_events::WorldRegenerateEvent>();
Expand All @@ -36,6 +37,7 @@ impl Plugin for TerrainPlugin {
Update,
terrain_systems::handle_terrain_regeneration_events_system,
);
app.add_systems(Update, terrain_systems::handle_chunk_tasks_system);
}
}
}
24 changes: 24 additions & 0 deletions src/client/terrain/resources.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bevy::tasks::Task;

use crate::prelude::*;

#[derive(Resource)]
Expand All @@ -9,6 +11,28 @@ impl SpawnAreaLoaded {
}
}

#[derive(Clone, PartialEq)]
pub enum MeshType {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reminder:

A logical Chunk of terrain has two geometrical representations:
A solid one, for the cubes like stone and dirt and a transparent one for the grass at the moment, perhaps glass and other stuff in the future.

Solid,
Transparent,
}

pub struct ChunkMeshes {
pub cube_mesh: Option<Mesh>,
pub cross_mesh: Option<Mesh>,
}

pub struct MeshTask(pub Task<ChunkMeshes>);
pub struct FutureChunkMesh {
pub position: Vec3,
pub meshes_task: MeshTask,
}

#[derive(Resource, Default)]
pub struct MesherTasks {
pub task_list: Vec<FutureChunkMesh>,
}

#[derive(Resource)]
pub struct RenderMaterials {
pub transparent_material: Option<Handle<StandardMaterial>>,
Expand Down
193 changes: 104 additions & 89 deletions src/client/terrain/systems.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use terrain_resources::RenderMaterials;
use terrain_util::create_cross_mesh_for_chunk;
use bevy::tasks::{futures_lite::future, AsyncComputeTaskPool};
use terrain_components::ChunkMesh;
use terrain_resources::{
ChunkMeshes, FutureChunkMesh, MeshTask, MeshType, MesherTasks, RenderMaterials,
};

use crate::prelude::*;

Expand Down Expand Up @@ -46,7 +49,7 @@ pub fn generate_world_system(
mut client: ResMut<RenetClient>,
mut chunk_manager: ResMut<ChunkManager>,
) {
let render_distance = Vec3::new(4.0, 4.0, 4.0);
let render_distance = Vec3::new(8.0, 4.0, 8.0);

info!("Sending chunk requests for chunks");

Expand All @@ -70,13 +73,10 @@ pub fn generate_world_system(
}

pub fn handle_chunk_mesh_update_events_system(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
chunk_manager: ResMut<ChunkManager>,
mut chunk_mesh_update_events: EventReader<terrain_events::ChunkMeshUpdateEvent>,
mut mesh_query: Query<(Entity, &terrain_components::ChunkMesh)>,
texture_manager: ResMut<terrain_util::TextureManager>,
materials: Res<RenderMaterials>,
mut tasks: ResMut<MesherTasks>,
) {
for event in chunk_mesh_update_events.read() {
info!(
Expand All @@ -86,25 +86,10 @@ pub fn handle_chunk_mesh_update_events_system(
let chunk_option = chunk_manager.get_chunk(event.position);
match chunk_option {
Some(chunk) => {
for (entity, chunk_mesh) in mesh_query.iter_mut() {
if Chunk::key_eq_pos(chunk_mesh.key, chunk.position) {
commands.entity(entity).despawn();
}
}
add_chunk_objects(
&mut commands,
&mut meshes,
chunk,
&texture_manager,
&materials,
);
add_cross_objects(
&mut commands,
chunk,
&materials,
&texture_manager,
&mut meshes,
);
tasks.task_list.push(FutureChunkMesh {
position: chunk.position,
meshes_task: create_mesh_task(chunk, &texture_manager),
});
}
None => {
println!("No chunk found");
Expand All @@ -113,73 +98,103 @@ pub fn handle_chunk_mesh_update_events_system(
}
}

fn add_chunk_objects(
commands: &mut Commands,
meshes: &mut ResMut<Assets<Mesh>>,
chunk: &Chunk,
texture_manager: &terrain_util::TextureManager,
materials: &RenderMaterials,
fn create_mesh_task(chunk: &Chunk, texture_manager: &terrain_util::TextureManager) -> MeshTask {
let task_pool = AsyncComputeTaskPool::get();
let chunk = *chunk;
let texture_manager = texture_manager.clone();
MeshTask(task_pool.spawn(async move {
ChunkMeshes {
cube_mesh: terrain_util::create_cube_mesh_for_chunk(&chunk, &texture_manager),
cross_mesh: terrain_util::create_cross_mesh_for_chunk(&chunk, &texture_manager),
}
}))
}

pub fn handle_chunk_tasks_system(
mut commands: Commands,
materials: Res<RenderMaterials>,
mut tasks: ResMut<MesherTasks>,
mut meshes: ResMut<Assets<Mesh>>,
mut mesh_query: Query<(Entity, &terrain_components::ChunkMesh)>,
) {
if let Some(mesh) = terrain_util::create_chunk_mesh(chunk, texture_manager) {
let material = materials
.chunk_material
.clone()
.expect("Chunk material is loaded");

let meshes: &mut Mut<Assets<Mesh>> = &mut ResMut::reborrow(meshes);
commands.spawn((
Mesh3d(meshes.add(mesh)),
Transform::from_xyz(
chunk.position.x * CHUNK_SIZE as f32,
chunk.position.y * CHUNK_SIZE as f32,
chunk.position.z * CHUNK_SIZE as f32,
),
MeshMaterial3d(material),
player_components::Raycastable,
terrain_components::ChunkMesh {
key: [
chunk.position.x as i32,
chunk.position.y as i32,
chunk.position.z as i32,
],
},
Name::from("Transparent Chunk Mesh"),
));
}
let mut next_poll_indicies: Vec<usize> = Vec::new();
tasks
.task_list
.iter_mut()
.enumerate()
.for_each(|(index, future_chunk)| {
let chunk_position = future_chunk.position;
let task_result =
bevy::tasks::block_on(future::poll_once(&mut future_chunk.meshes_task.0));
if task_result.is_none() {
next_poll_indicies.push(index);
return;
}
let mesh_option = task_result.unwrap();

if mesh_option.cross_mesh.is_some() {
commands.spawn(create_chunk_bundle(
meshes.add(mesh_option.cross_mesh.unwrap()),
chunk_position,
MeshType::Transparent,
materials.transparent_material.clone().unwrap(),
));
}

if mesh_option.cube_mesh.is_some() {
commands
.spawn(create_chunk_bundle(
meshes.add(mesh_option.cube_mesh.unwrap()),
chunk_position,
MeshType::Solid,
materials.chunk_material.clone().unwrap(),
))
.insert(player_components::Raycastable);
}

for (old_chunk, old_mesh) in mesh_query.iter_mut() {
if Chunk::key_eq_pos(old_mesh.key, chunk_position) {
commands.entity(old_chunk).despawn();
}
}
});

let mut index = 0;
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugly index, can't use enumerate() in this context.

tasks.task_list.retain(|_| {
let contains = next_poll_indicies.contains(&index);
index += 1;
contains
})
}

fn add_cross_objects(
commands: &mut Commands,
chunk: &Chunk,
materials: &RenderMaterials,
texture_manager: &terrain_util::TextureManager,
meshes: &mut ResMut<Assets<Mesh>>,
fn create_chunk_bundle(
mesh_handle: Handle<Mesh>,
chunk_position: Vec3,
mesh_type: MeshType,
material_handle: Handle<StandardMaterial>,
) -> (
bevy::prelude::Mesh3d,
bevy::prelude::Transform,
ChunkMesh,
bevy::prelude::MeshMaterial3d<StandardMaterial>,
) {
if let Some(mesh) = create_cross_mesh_for_chunk(chunk, texture_manager) {
let mesh_handle = meshes.add(mesh);

commands.spawn((
Mesh3d(mesh_handle),
MeshMaterial3d(
materials
.transparent_material
.clone()
.expect("Transparent material exists"),
),
Transform::from_xyz(
chunk.position.x * CHUNK_SIZE as f32,
chunk.position.y * CHUNK_SIZE as f32,
chunk.position.z * CHUNK_SIZE as f32,
),
terrain_components::ChunkMesh {
key: [
chunk.position.x as i32,
chunk.position.y as i32,
chunk.position.z as i32,
],
},
));
}
(
Mesh3d(mesh_handle),
Transform::from_xyz(
chunk_position.x * CHUNK_SIZE as f32,
chunk_position.y * CHUNK_SIZE as f32,
chunk_position.z * CHUNK_SIZE as f32,
),
terrain_components::ChunkMesh {
key: [
chunk_position.x as i32,
chunk_position.y as i32,
chunk_position.z as i32,
],
mesh_type,
},
MeshMaterial3d(material_handle),
)
}

fn create_transparent_material(texture_handle: Handle<Image>) -> StandardMaterial {
Expand Down
2 changes: 1 addition & 1 deletion src/client/terrain/util/blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ pub mod client_block {
use client_block::block_properties;
use TextureName::*;

#[derive(Resource)]
#[derive(Resource, Clone)]
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea how to circumvent this.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The clone shouldn't be too expensive though, as we are only cloning a hashmap with a few primitives.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The atlas currently consists of 16 slots, so there are not many UVs and enum values that will be cloned.

pub struct TextureManager {
textures: HashMap<TextureName, TextureUV>,
}
Expand Down
2 changes: 1 addition & 1 deletion src/client/terrain/util/cube_mesher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub fn create_cube_geometry_data(
}
}

pub fn create_chunk_mesh(chunk: &Chunk, texture_manager: &TextureManager) -> Option<Mesh> {
pub fn create_cube_mesh_for_chunk(chunk: &Chunk, texture_manager: &TextureManager) -> Option<Mesh> {
let mut geometry_data = GeometryData {
position: Vec::new(),
uv: Vec::new(),
Expand Down