mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 13:58:30 +00:00
Added Greedy Mesher + tracy profile
This commit is contained in:
parent
922e99f937
commit
3440093284
4
.idea/runConfigurations/Run_horror_game.xml
generated
4
.idea/runConfigurations/Run_horror_game.xml
generated
@ -1,7 +1,7 @@
|
|||||||
<component name="ProjectRunConfigurationManager">
|
<component name="ProjectRunConfigurationManager">
|
||||||
<configuration default="false" name="Run horror-game" type="CargoCommandRunConfiguration" factoryName="Cargo Command" singleton="false">
|
<configuration default="false" name="Run horror-game" type="CargoCommandRunConfiguration" factoryName="Cargo Command" singleton="false">
|
||||||
<option name="buildProfileId" value="dev" />
|
<option name="buildProfileId" value="release" />
|
||||||
<option name="command" value="run --package horror-game --bin horror-game" />
|
<option name="command" value="run --package horror-game --bin horror-game --features bevy/trace_tracy_memory" />
|
||||||
<option name="workingDirectory" value="file://$PROJECT_DIR$/client" />
|
<option name="workingDirectory" value="file://$PROJECT_DIR$/client" />
|
||||||
<envs />
|
<envs />
|
||||||
<option name="emulateTerminal" value="true" />
|
<option name="emulateTerminal" value="true" />
|
||||||
|
|||||||
@ -17,4 +17,5 @@ big_space = "0.9.1"
|
|||||||
noise = "0.9.0"
|
noise = "0.9.0"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
bitvec = "1.0.1"
|
bitvec = "1.0.1"
|
||||||
smallvec = "1.14.0"
|
smallvec = "1.14.0"
|
||||||
|
once_cell = "1.21.3"
|
||||||
@ -23,7 +23,7 @@ impl Plugin for EnvironmentPlugin {
|
|||||||
|
|
||||||
app.insert_resource(ChunkCullingCfg { view_distance_chunks: 10 });
|
app.insert_resource(ChunkCullingCfg { view_distance_chunks: 10 });
|
||||||
app.insert_resource(ChunkBudget { per_frame: 20 });
|
app.insert_resource(ChunkBudget { per_frame: 20 });
|
||||||
|
app.add_systems(Update, log_mesh_count);
|
||||||
app
|
app
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// resources
|
// resources
|
||||||
@ -56,6 +56,11 @@ impl Plugin for EnvironmentPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_mesh_count(meshes: Res<Assets<Mesh>>, time: Res<Time>) {
|
||||||
|
if time.delta_secs_f64() as i32 % 5 == 0 {
|
||||||
|
info!("meshes: {}", meshes.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn should_visualize_octree(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
fn should_visualize_octree(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
||||||
octree_query.single().show_wireframe
|
octree_query.single().show_wireframe
|
||||||
|
|||||||
@ -8,25 +8,34 @@ use crate::plugins::environment::systems::voxels::structure::*;
|
|||||||
/// configured radius
|
/// configured radius
|
||||||
|
|
||||||
pub fn despawn_distant_chunks(
|
pub fn despawn_distant_chunks(
|
||||||
mut commands : Commands,
|
mut commands : Commands,
|
||||||
cam_q : Query<&GlobalTransform, With<Camera>>,
|
cam_q : Query<&GlobalTransform, With<Camera>>,
|
||||||
tree_q : Query<&SparseVoxelOctree>,
|
tree_q : Query<&SparseVoxelOctree>,
|
||||||
mut spawned : ResMut<SpawnedChunks>,
|
mut spawned : ResMut<SpawnedChunks>,
|
||||||
chunk_q : Query<&Chunk>,
|
chunk_q : Query<(Entity,
|
||||||
cfg : Res<ChunkCullingCfg>,
|
&Chunk,
|
||||||
|
&Mesh3d,
|
||||||
|
&MeshMaterial3d<StandardMaterial>)>,
|
||||||
|
mut meshes : ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
cfg : Res<ChunkCullingCfg>,
|
||||||
) {
|
) {
|
||||||
let tree = tree_q.single();
|
let tree = tree_q.single();
|
||||||
let cam = cam_q.single().translation();
|
let cam = cam_q.single().translation();
|
||||||
let center = world_to_chunk(tree, cam);
|
let centre = world_to_chunk(tree, cam);
|
||||||
|
|
||||||
for chunk in chunk_q.iter() {
|
for (ent, chunk, mesh3d, mat3d) in chunk_q.iter() {
|
||||||
let ChunkKey(x, y, z) = chunk.key;
|
let ChunkKey(x, y, z) = chunk.key;
|
||||||
if (x - center.0).abs() > cfg.view_distance_chunks ||
|
if (x - centre.0).abs() > cfg.view_distance_chunks ||
|
||||||
(y - center.1).abs() > cfg.view_distance_chunks ||
|
(y - centre.1).abs() > cfg.view_distance_chunks ||
|
||||||
(z - center.2).abs() > cfg.view_distance_chunks {
|
(z - centre.2).abs() > cfg.view_distance_chunks {
|
||||||
if let Some(ent) = spawned.0.remove(&chunk.key) {
|
|
||||||
commands.entity(ent).despawn_recursive();
|
// free assets – borrow, don't move
|
||||||
}
|
meshes.remove(&mesh3d.0);
|
||||||
|
materials.remove(&mat3d.0);
|
||||||
|
|
||||||
|
commands.entity(ent).despawn_recursive();
|
||||||
|
spawned.0.remove(&chunk.key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -269,3 +269,62 @@ pub fn world_to_chunk(tree: &SparseVoxelOctree, p: Vec3) -> ChunkKey {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AABB {
|
||||||
|
pub fn intersects_aabb(&self, other: &AABB) -> bool {
|
||||||
|
self.min.x <= other.max.x &&
|
||||||
|
self.max.x >= other.min.x &&
|
||||||
|
self.min.y <= other.max.y &&
|
||||||
|
self.max.y >= other.min.y &&
|
||||||
|
self.min.z <= other.max.z &&
|
||||||
|
self.max.z >= other.min.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn center(&self) -> Vec3 {
|
||||||
|
(self.min + self.max) * 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SparseVoxelOctree {
|
||||||
|
pub fn collect_voxels_in_region(&self, min: Vec3, max: Vec3) -> Vec<(Vec3, Voxel)> {
|
||||||
|
let half_size = self.size * 0.5;
|
||||||
|
let root_bounds = AABB {
|
||||||
|
min: Vec3::new(-half_size, -half_size, -half_size),
|
||||||
|
max: Vec3::new(half_size, half_size, half_size),
|
||||||
|
};
|
||||||
|
let mut voxels = Vec::new();
|
||||||
|
self.collect_voxels_in_region_recursive(&self.root, root_bounds, min, max, &mut voxels);
|
||||||
|
voxels
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_voxels_in_region_recursive(
|
||||||
|
&self,
|
||||||
|
node: &OctreeNode,
|
||||||
|
node_bounds: AABB,
|
||||||
|
min: Vec3,
|
||||||
|
max: Vec3,
|
||||||
|
out: &mut Vec<(Vec3, Voxel)>,
|
||||||
|
) {
|
||||||
|
if !node_bounds.intersects_aabb(&AABB { min, max }) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if node.is_leaf {
|
||||||
|
if let Some(voxel) = &node.voxel {
|
||||||
|
let center = node_bounds.center();
|
||||||
|
if center.x >= min.x && center.x <= max.x &&
|
||||||
|
center.y >= min.y && center.y <= max.y &&
|
||||||
|
center.z >= min.z && center.z <= max.z
|
||||||
|
{
|
||||||
|
out.push((center, *voxel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(children) = &node.children {
|
||||||
|
for (i, child) in children.iter().enumerate() {
|
||||||
|
let child_bounds = self.compute_child_bounds(&node_bounds, i);
|
||||||
|
self.collect_voxels_in_region_recursive(child, child_bounds, min, max, out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,7 +3,7 @@ use bevy::prelude::*;
|
|||||||
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues, Mesh};
|
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues, Mesh};
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
|
||||||
pub(crate) fn mesh_chunk(
|
/*pub(crate) fn mesh_chunk(
|
||||||
buffer: &[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
|
buffer: &[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
|
||||||
origin: Vec3,
|
origin: Vec3,
|
||||||
step: f32,
|
step: f32,
|
||||||
@ -296,4 +296,181 @@ pub(crate) fn mesh_chunk(
|
|||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::Float32x2(uvs));
|
mesh.insert_attribute(Mesh::ATTRIBUTE_UV_0, VertexAttributeValues::Float32x2(uvs));
|
||||||
mesh.insert_indices(Indices::U32(indices));
|
mesh.insert_indices(Indices::U32(indices));
|
||||||
mesh
|
mesh
|
||||||
}
|
}*/
|
||||||
|
|
||||||
|
|
||||||
|
pub(crate) fn mesh_chunk(
|
||||||
|
buffer: &[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
|
||||||
|
origin: Vec3,
|
||||||
|
step: f32,
|
||||||
|
tree: &SparseVoxelOctree,
|
||||||
|
) -> Mesh {
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Helpers
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const N: usize = CHUNK_SIZE as usize;
|
||||||
|
|
||||||
|
// Safe voxel query that falls back to the octree for out‑of‑chunk requests.
|
||||||
|
let filled = |x: i32, y: i32, z: i32| -> bool {
|
||||||
|
if (0..CHUNK_SIZE).contains(&x)
|
||||||
|
&& (0..CHUNK_SIZE).contains(&y)
|
||||||
|
&& (0..CHUNK_SIZE).contains(&z)
|
||||||
|
{
|
||||||
|
buffer[x as usize][y as usize][z as usize].is_some()
|
||||||
|
} else {
|
||||||
|
let world = origin + Vec3::new(x as f32 * step, y as f32 * step, z as f32 * step);
|
||||||
|
tree.get_voxel_at_world_coords(world).is_some()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Push a single quad (4 vertices, 6 indices). `base` is the lower‑left
|
||||||
|
// corner in world space; `u`/`v` are the tangent vectors (length 1); `size`
|
||||||
|
// is expressed in world units along those axes; `n` is the face normal.
|
||||||
|
let mut positions = Vec::<[f32; 3]>::new();
|
||||||
|
let mut normals = Vec::<[f32; 3]>::new();
|
||||||
|
let mut uvs = Vec::<[f32; 2]>::new();
|
||||||
|
let mut indices = Vec::<u32>::new();
|
||||||
|
|
||||||
|
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| {
|
||||||
|
let i0 = positions.len() as u32;
|
||||||
|
positions.extend_from_slice(&[
|
||||||
|
(base).into(),
|
||||||
|
(base + u * size.x).into(),
|
||||||
|
(base + u * size.x + v * size.y).into(),
|
||||||
|
(base + v * size.y).into(),
|
||||||
|
]);
|
||||||
|
normals.extend_from_slice(&[[n.x, n.y, n.z]; 4]);
|
||||||
|
uvs.extend_from_slice(&[[0.0, 1.0], [1.0, 1.0], [1.0, 0.0], [0.0, 0.0]]);
|
||||||
|
|
||||||
|
if n.x + n.y + n.z >= 0.0 {
|
||||||
|
indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]);
|
||||||
|
} else {
|
||||||
|
// Flip winding for faces with a negative normal component sum so the
|
||||||
|
// result is still counter‑clockwise.
|
||||||
|
indices.extend_from_slice(&[i0, i0 + 3, i0 + 2, i0 + 2, i0 + 1, i0]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Greedy meshing
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Axes: 0→X, 1→Y, 2→Z. For each axis we process the negative and positive
|
||||||
|
// faces (dir = −1 / +1).
|
||||||
|
for (axis, dir) in [ (0, -1), (0, 1), (1, -1), (1, 1), (2, -1), (2, 1) ] {
|
||||||
|
// Mapping of (u,v) axes and their unit vectors in world space.
|
||||||
|
let (u_axis, v_axis, face_normal, u_vec, v_vec) = match (axis, dir) {
|
||||||
|
(0, d) => (1, 2, Vec3::new(d as f32, 0.0, 0.0), Vec3::Y, Vec3::Z),
|
||||||
|
(1, d) => (2, 0, Vec3::new(0.0, d as f32, 0.0), Vec3::Z, Vec3::X),
|
||||||
|
(2, d) => (0, 1, Vec3::new(0.0, 0.0, d as f32), Vec3::X, Vec3::Y),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over every slice perpendicular to `axis`. Faces can lie on
|
||||||
|
// the 0…N grid lines (inclusive) because the positive‑side faces of the
|
||||||
|
// last voxel sit at slice N.
|
||||||
|
for slice in 0..=N {
|
||||||
|
// Build the face mask for this slice.
|
||||||
|
let mut mask = vec![false; N * N];
|
||||||
|
let idx = |u: usize, v: usize| -> usize { u * N + v };
|
||||||
|
|
||||||
|
for u in 0..N {
|
||||||
|
for v in 0..N {
|
||||||
|
// Translate (u,v,slice) to (x,y,z) voxel coordinates.
|
||||||
|
let mut cell = [0i32; 3];
|
||||||
|
let mut neighbor = [0i32; 3];
|
||||||
|
|
||||||
|
cell [axis] = slice as i32 + if dir == 1 { -1 } else { 0 };
|
||||||
|
neighbor[axis] = cell[axis] + dir;
|
||||||
|
|
||||||
|
cell [u_axis] = u as i32;
|
||||||
|
cell [v_axis] = v as i32;
|
||||||
|
neighbor[u_axis] = u as i32;
|
||||||
|
neighbor[v_axis] = v as i32;
|
||||||
|
|
||||||
|
if filled(cell[0], cell[1], cell[2]) && !filled(neighbor[0], neighbor[1], neighbor[2]) {
|
||||||
|
mask[idx(u, v)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Greedy merge the mask into maximal rectangles.
|
||||||
|
let mut visited = vec![false; N * N];
|
||||||
|
for u0 in 0..N {
|
||||||
|
for v0 in 0..N {
|
||||||
|
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the rectangle width.
|
||||||
|
let mut width = 1;
|
||||||
|
while u0 + width < N && mask[idx(u0 + width, v0)] && !visited[idx(u0 + width, v0)] {
|
||||||
|
width += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the rectangle height.
|
||||||
|
let mut height = 1;
|
||||||
|
'h: while v0 + height < N {
|
||||||
|
for du in 0..width {
|
||||||
|
if !mask[idx(u0 + du, v0 + height)] || visited[idx(u0 + du, v0 + height)] {
|
||||||
|
break 'h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
height += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the rectangle area as visited.
|
||||||
|
for du in 0..width {
|
||||||
|
for dv in 0..height {
|
||||||
|
visited[idx(u0 + du, v0 + dv)] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute world‑space base corner.
|
||||||
|
let mut base = origin;
|
||||||
|
match axis {
|
||||||
|
0 => {
|
||||||
|
base.x += step * slice as f32;
|
||||||
|
base.y += step * u0 as f32;
|
||||||
|
base.z += step * v0 as f32;
|
||||||
|
}
|
||||||
|
1 => {
|
||||||
|
base.x += step * v0 as f32;
|
||||||
|
base.y += step * slice as f32;
|
||||||
|
base.z += step * u0 as f32;
|
||||||
|
}
|
||||||
|
2 => {
|
||||||
|
base.x += step * u0 as f32;
|
||||||
|
base.y += step * v0 as f32;
|
||||||
|
base.z += step * slice as f32;
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = Vec2::new(width as f32 * step, height as f32 * step);
|
||||||
|
push_quad(base, size, face_normal, u_vec, v_vec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
// Final mesh assembly
|
||||||
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
|
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_POSITION,
|
||||||
|
VertexAttributeValues::Float32x3(positions),
|
||||||
|
);
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_NORMAL,
|
||||||
|
VertexAttributeValues::Float32x3(normals),
|
||||||
|
);
|
||||||
|
mesh.insert_attribute(
|
||||||
|
Mesh::ATTRIBUTE_UV_0,
|
||||||
|
VertexAttributeValues::Float32x2(uvs),
|
||||||
|
);
|
||||||
|
mesh.insert_indices(Indices::U32(indices));
|
||||||
|
mesh
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::format;
|
use std::fmt::format;
|
||||||
|
use bevy::pbr::wireframe::Wireframe;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::Mesh;
|
use bevy::render::mesh::Mesh;
|
||||||
use big_space::prelude::GridCell;
|
use big_space::prelude::GridCell;
|
||||||
@ -7,41 +8,41 @@ use itertools::Itertools;
|
|||||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||||
use crate::plugins::environment::systems::voxels::meshing::mesh_chunk;
|
use crate::plugins::environment::systems::voxels::meshing::mesh_chunk;
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
|
||||||
/// rebuilds meshes only for chunks flagged dirty by the octree
|
/// rebuilds meshes only for chunks flagged dirty by the octree
|
||||||
pub fn rebuild_dirty_chunks(
|
pub fn rebuild_dirty_chunks(
|
||||||
mut commands: Commands,
|
mut commands : Commands,
|
||||||
mut octrees: Query<(Entity, &mut SparseVoxelOctree)>,
|
mut octrees : Query<&mut SparseVoxelOctree>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes : ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
chunk_q: Query<(Entity, &Chunk)>,
|
chunk_q : Query<(Entity,
|
||||||
mut spawned : ResMut<SpawnedChunks>,
|
&Chunk,
|
||||||
root: Res<RootGrid>,
|
&Mesh3d,
|
||||||
|
&MeshMaterial3d<StandardMaterial>)>,
|
||||||
|
mut spawned : ResMut<SpawnedChunks>,
|
||||||
|
root : Res<RootGrid>,
|
||||||
) {
|
) {
|
||||||
// map ChunkKey → entity
|
// map ChunkKey → (entity, mesh-handle, material-handle)
|
||||||
|
let existing: HashMap<ChunkKey, (Entity, Handle<Mesh>, Handle<StandardMaterial>)> =
|
||||||
let existing: HashMap<ChunkKey, Entity> =
|
chunk_q
|
||||||
chunk_q.iter().map(|(e, c)| (c.key, e)).collect();
|
.iter()
|
||||||
|
.map(|(e, c, m, mat)| (c.key, (e, m.0.clone(), mat.0.clone())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
for (_tree_ent, mut tree) in &mut octrees {
|
for mut tree in &mut octrees {
|
||||||
if tree.dirty_chunks.is_empty() {
|
if tree.dirty_chunks.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// gather voxel data for every dirty chunk
|
//------------------------------------------------ collect voxel data
|
||||||
let mut chunk_voxel_bufs: Vec<(
|
let mut bufs = Vec::new();
|
||||||
ChunkKey,
|
|
||||||
[[[Option<Voxel>; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize],
|
|
||||||
Vec3, // chunk origin
|
|
||||||
f32, // voxel step
|
|
||||||
)> = Vec::new();
|
|
||||||
|
|
||||||
for key in tree.dirty_chunks.iter().copied() {
|
for key in tree.dirty_chunks.iter().copied() {
|
||||||
let mut buf =
|
let mut buf =
|
||||||
[[[None; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize];
|
[[[None; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize];
|
||||||
|
|
||||||
let half = tree.size * 0.5;
|
let half = tree.size * 0.5;
|
||||||
let step = tree.get_spacing_at_depth(tree.max_depth);
|
let step = tree.get_spacing_at_depth(tree.max_depth);
|
||||||
let start = Vec3::new(
|
let origin = Vec3::new(
|
||||||
key.0 as f32 * CHUNK_SIZE as f32 * step - half,
|
key.0 as f32 * CHUNK_SIZE as f32 * step - half,
|
||||||
key.1 as f32 * CHUNK_SIZE as f32 * step - half,
|
key.1 as f32 * CHUNK_SIZE as f32 * step - half,
|
||||||
key.2 as f32 * CHUNK_SIZE as f32 * step - half,
|
key.2 as f32 * CHUNK_SIZE as f32 * step - half,
|
||||||
@ -50,7 +51,7 @@ pub fn rebuild_dirty_chunks(
|
|||||||
for lx in 0..CHUNK_SIZE {
|
for lx in 0..CHUNK_SIZE {
|
||||||
for ly in 0..CHUNK_SIZE {
|
for ly in 0..CHUNK_SIZE {
|
||||||
for lz in 0..CHUNK_SIZE {
|
for lz in 0..CHUNK_SIZE {
|
||||||
let world = start
|
let world = origin
|
||||||
+ Vec3::new(lx as f32 * step, ly as f32 * step, lz as f32 * step);
|
+ Vec3::new(lx as f32 * step, ly as f32 * step, lz as f32 * step);
|
||||||
if let Some(v) = tree.get_voxel_at_world_coords(world) {
|
if let Some(v) = tree.get_voxel_at_world_coords(world) {
|
||||||
buf[lx as usize][ly as usize][lz as usize] = Some(*v);
|
buf[lx as usize][ly as usize][lz as usize] = Some(*v);
|
||||||
@ -59,28 +60,33 @@ pub fn rebuild_dirty_chunks(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk_voxel_bufs.push((key, buf, start, step));
|
bufs.push((key, buf, origin, step));
|
||||||
}
|
}
|
||||||
|
|
||||||
// build / replace meshes
|
//------------------------------------------------ create / update
|
||||||
for (key, buf, origin, step) in chunk_voxel_bufs {
|
for (key, buf, origin, step) in bufs {
|
||||||
let mesh_handle =
|
if let Some((ent, mesh_h, _mat_h)) = existing.get(&key).cloned() {
|
||||||
meshes.add(mesh_chunk(&buf, origin, step, &tree));
|
// update mesh in-place; keeps old asset id
|
||||||
let mesh_3d = Mesh3d::from(mesh_handle);
|
if let Some(mesh) = meshes.get_mut(&mesh_h) {
|
||||||
let material = MeshMaterial3d::<StandardMaterial>::default();
|
*mesh = mesh_chunk(&buf, origin, step, &tree);
|
||||||
|
}
|
||||||
if let Some(&ent) = existing.get(&key) {
|
|
||||||
commands.entity(ent).insert(mesh_3d);
|
|
||||||
spawned.0.insert(key, ent);
|
spawned.0.insert(key, ent);
|
||||||
} else {
|
} else {
|
||||||
|
// spawn brand-new chunk
|
||||||
|
let mesh_h = meshes.add(mesh_chunk(&buf, origin, step, &tree));
|
||||||
|
let mat_h = materials.add(StandardMaterial::default());
|
||||||
|
|
||||||
commands.entity(root.0).with_children(|p| {
|
commands.entity(root.0).with_children(|p| {
|
||||||
let e = p.spawn((
|
let e = p
|
||||||
mesh_3d,
|
.spawn((
|
||||||
material,
|
Mesh3d::from(mesh_h.clone()),
|
||||||
Transform::default(),
|
MeshMaterial3d(mat_h.clone()),
|
||||||
GridCell::<i64>::ZERO,
|
Transform::default(),
|
||||||
Chunk { key, voxels: Vec::new(), dirty: false },
|
GridCell::<i64>::ZERO,
|
||||||
)).id();
|
Chunk { key, voxels: Vec::new(), dirty: false },
|
||||||
|
/*Wireframe,*/
|
||||||
|
))
|
||||||
|
.id();
|
||||||
spawned.0.insert(key, e);
|
spawned.0.insert(key, e);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,6 +93,7 @@ pub struct Chunk {
|
|||||||
pub key: ChunkKey,
|
pub key: ChunkKey,
|
||||||
pub voxels: Vec<(IVec3, Voxel)>, // local coords 0‥15
|
pub voxels: Vec<(IVec3, Voxel)>, // local coords 0‥15
|
||||||
pub dirty: bool,
|
pub dirty: bool,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user