mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-11 05:48:29 +00:00
Merge pull request #26 from eliasstepanik/codex/build-texture-atlas-for-voxel-world
Add texture atlas for voxels
This commit is contained in:
commit
f5714ff105
@ -9,6 +9,7 @@ use crate::plugins::environment::systems::voxels::queue_systems::{
|
|||||||
enqueue_visible_chunks, process_chunk_queue,
|
enqueue_visible_chunks, process_chunk_queue,
|
||||||
};
|
};
|
||||||
use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks;
|
use crate::plugins::environment::systems::voxels::render_chunks::rebuild_dirty_chunks;
|
||||||
|
use crate::plugins::environment::systems::voxels::atlas::{VoxelTextureAtlas};
|
||||||
use crate::plugins::environment::systems::voxels::structure::{
|
use crate::plugins::environment::systems::voxels::structure::{
|
||||||
ChunkBudget, ChunkCullingCfg, ChunkQueue, MeshBufferPool, PrevCameraChunk, SparseVoxelOctree,
|
ChunkBudget, ChunkCullingCfg, ChunkQueue, MeshBufferPool, PrevCameraChunk, SparseVoxelOctree,
|
||||||
SpawnedChunks,
|
SpawnedChunks,
|
||||||
@ -22,6 +23,7 @@ impl Plugin for EnvironmentPlugin {
|
|||||||
app.add_systems(
|
app.add_systems(
|
||||||
Startup,
|
Startup,
|
||||||
(
|
(
|
||||||
|
setup_texture_atlas,
|
||||||
crate::plugins::environment::systems::camera_system::setup,
|
crate::plugins::environment::systems::camera_system::setup,
|
||||||
crate::plugins::environment::systems::environment_system::setup
|
crate::plugins::environment::systems::environment_system::setup
|
||||||
.after(crate::plugins::environment::systems::camera_system::setup),
|
.after(crate::plugins::environment::systems::camera_system::setup),
|
||||||
@ -89,3 +91,8 @@ fn should_draw_grid(octree_query: Query<&SparseVoxelOctree>) -> bool {
|
|||||||
};
|
};
|
||||||
octree.show_world_grid
|
octree.show_world_grid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_texture_atlas(mut commands: Commands, mut images: ResMut<Assets<Image>>) {
|
||||||
|
let atlas = VoxelTextureAtlas::generate(&mut images);
|
||||||
|
commands.insert_resource(atlas);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,25 +1,21 @@
|
|||||||
use std::path::Path;
|
|
||||||
use rayon::prelude::*;
|
|
||||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use rayon::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::*;
|
use bevy::render::mesh::*;
|
||||||
use noise::{NoiseFn, Perlin};
|
use noise::{NoiseFn, Perlin};
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{Rng, thread_rng};
|
||||||
|
|
||||||
pub fn setup(
|
pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
|
||||||
mut commands: Commands,
|
|
||||||
root: Res<RootGrid>,
|
|
||||||
) {
|
|
||||||
// Octree parameters
|
// Octree parameters
|
||||||
let unit_size = 1.0_f32;
|
let unit_size = 1.0_f32;
|
||||||
let octree_base_size = 64.0 * unit_size;
|
let octree_base_size = 64.0 * unit_size;
|
||||||
let octree_depth = 10;
|
let octree_depth = 10;
|
||||||
|
|
||||||
let path = Path::new("octree.bin");
|
let path = Path::new("octree.bin");
|
||||||
|
|
||||||
|
|
||||||
let mut octree = if path.exists() {
|
let mut octree = if path.exists() {
|
||||||
match SparseVoxelOctree::load_from_file(path) {
|
match SparseVoxelOctree::load_from_file(path) {
|
||||||
Ok(tree) => tree,
|
Ok(tree) => tree,
|
||||||
@ -30,7 +26,6 @@ pub fn setup(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut tree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false);
|
let mut tree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false);
|
||||||
let color = Color::srgb(0.2, 0.8, 0.2);
|
|
||||||
// How many random spheres?
|
// How many random spheres?
|
||||||
/*const NUM_SPHERES: usize = 5;
|
/*const NUM_SPHERES: usize = 5;
|
||||||
let mut rng = threald_rng();
|
let mut rng = threald_rng();
|
||||||
@ -44,30 +39,22 @@ pub fn setup(
|
|||||||
|
|
||||||
let radius = rng.gen_range(20..=150); // voxels
|
let radius = rng.gen_range(20..=150); // voxels
|
||||||
|
|
||||||
generate_voxel_sphere_parallel(&mut tree, center, radius, color);
|
generate_voxel_sphere_parallel(&mut tree, center, radius);
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
generate_voxel_sphere(&mut tree, 200, color);
|
generate_voxel_sphere(&mut tree, 200);
|
||||||
tree
|
tree
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Attach octree to the scene graph
|
// Attach octree to the scene graph
|
||||||
commands.entity(root.0).with_children(|parent| {
|
commands.entity(root.0).with_children(|parent| {
|
||||||
parent.spawn((Transform::default(), octree));
|
parent.spawn((Transform::default(), octree));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_voxel_sphere_parallel(
|
pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) {
|
||||||
octree: &mut SparseVoxelOctree,
|
let step = octree.get_spacing_at_depth(octree.max_depth);
|
||||||
center: Vec3,
|
let radius_sq = radius * radius;
|
||||||
radius: i32,
|
|
||||||
color: Color,
|
|
||||||
) {
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
let radius_sq = radius * radius;
|
|
||||||
|
|
||||||
// 1. Collect voxel positions in parallel
|
// 1. Collect voxel positions in parallel
|
||||||
let voxels: Vec<(Vec3, Voxel)> = (-radius..=radius)
|
let voxels: Vec<(Vec3, Voxel)> = (-radius..=radius)
|
||||||
@ -75,7 +62,7 @@ pub fn generate_voxel_sphere_parallel(
|
|||||||
.flat_map_iter(|ix| {
|
.flat_map_iter(|ix| {
|
||||||
let dx2 = ix * ix;
|
let dx2 = ix * ix;
|
||||||
(-radius..=radius).flat_map(move |iy| {
|
(-radius..=radius).flat_map(move |iy| {
|
||||||
let dy2 = iy * iy;
|
let dy2 = iy * iy;
|
||||||
let r2_xy = dx2 + dy2;
|
let r2_xy = dx2 + dy2;
|
||||||
|
|
||||||
if r2_xy > radius_sq {
|
if r2_xy > radius_sq {
|
||||||
@ -83,14 +70,16 @@ pub fn generate_voxel_sphere_parallel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let max_z = ((radius_sq - r2_xy) as f32).sqrt() as i32;
|
let max_z = ((radius_sq - r2_xy) as f32).sqrt() as i32;
|
||||||
(-max_z..=max_z).map(move |iz| {
|
(-max_z..=max_z)
|
||||||
let pos = Vec3::new(
|
.map(move |iz| {
|
||||||
center.x + ix as f32 * step,
|
let pos = Vec3::new(
|
||||||
center.y + iy as f32 * step,
|
center.x + ix as f32 * step,
|
||||||
center.z + iz as f32 * step,
|
center.y + iy as f32 * step,
|
||||||
);
|
center.z + iz as f32 * step,
|
||||||
(pos, Voxel { color })
|
);
|
||||||
}).collect::<Vec<_>>()
|
(pos, Voxel::random_sides())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@ -101,12 +90,7 @@ pub fn generate_voxel_sphere_parallel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_voxel_sphere(octree: &mut SparseVoxelOctree, planet_radius: i32) {
|
||||||
fn generate_voxel_sphere(
|
|
||||||
octree: &mut SparseVoxelOctree,
|
|
||||||
planet_radius: i32,
|
|
||||||
voxel_color: Color,
|
|
||||||
) {
|
|
||||||
// For simplicity, we center the sphere around (0,0,0).
|
// For simplicity, we center the sphere around (0,0,0).
|
||||||
// We'll loop over a cubic region [-planet_radius, +planet_radius] in x, y, z
|
// We'll loop over a cubic region [-planet_radius, +planet_radius] in x, y, z
|
||||||
let min = -planet_radius;
|
let min = -planet_radius;
|
||||||
@ -131,9 +115,7 @@ fn generate_voxel_sphere(
|
|||||||
let position = Vec3::new(wx, wy, wz);
|
let position = Vec3::new(wx, wy, wz);
|
||||||
|
|
||||||
// Insert the voxel
|
// Insert the voxel
|
||||||
let voxel = Voxel {
|
let voxel = Voxel::random_sides();
|
||||||
color: voxel_color,
|
|
||||||
};
|
|
||||||
octree.insert(position, voxel);
|
octree.insert(position, voxel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,13 +123,9 @@ fn generate_voxel_sphere(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Inserts a 16x256x16 "column" of voxels into the octree at (0,0,0) corner.
|
/// Inserts a 16x256x16 "column" of voxels into the octree at (0,0,0) corner.
|
||||||
/// If you want it offset or centered differently, just adjust the for-loop ranges or offsets.
|
/// If you want it offset or centered differently, just adjust the for-loop ranges or offsets.
|
||||||
fn generate_voxel_rect(
|
fn generate_voxel_rect(octree: &mut SparseVoxelOctree) {
|
||||||
octree: &mut SparseVoxelOctree,
|
|
||||||
voxel_color: Color,
|
|
||||||
) {
|
|
||||||
// The dimensions of our rectangle: 16 x 256 x 16
|
// The dimensions of our rectangle: 16 x 256 x 16
|
||||||
let size_x = 16;
|
let size_x = 16;
|
||||||
let size_y = 256;
|
let size_y = 256;
|
||||||
@ -172,21 +150,14 @@ fn generate_voxel_rect(
|
|||||||
let position = Vec3::new(wx, wy, wz);
|
let position = Vec3::new(wx, wy, wz);
|
||||||
|
|
||||||
// Insert the voxel
|
// Insert the voxel
|
||||||
let voxel = Voxel {
|
let voxel = Voxel::random_sides();
|
||||||
color: voxel_color,
|
|
||||||
};
|
|
||||||
octree.insert(position, voxel);
|
octree.insert(position, voxel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_large_plane(
|
fn generate_large_plane(octree: &mut SparseVoxelOctree, width: usize, depth: usize) {
|
||||||
octree: &mut SparseVoxelOctree,
|
|
||||||
width: usize,
|
|
||||||
depth: usize,
|
|
||||||
color: Color,
|
|
||||||
) {
|
|
||||||
// We'll get the voxel spacing (size at the deepest level).
|
// We'll get the voxel spacing (size at the deepest level).
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
let step = octree.get_spacing_at_depth(octree.max_depth);
|
||||||
|
|
||||||
@ -207,20 +178,16 @@ fn generate_large_plane(
|
|||||||
let position = Vec3::new(wx, wy, wz);
|
let position = Vec3::new(wx, wy, wz);
|
||||||
|
|
||||||
// Insert the voxel
|
// Insert the voxel
|
||||||
let voxel = Voxel {
|
let voxel = Voxel::random_sides();
|
||||||
color,
|
|
||||||
};
|
|
||||||
octree.insert(position, voxel);
|
octree.insert(position, voxel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn generate_solid_plane_with_noise(
|
pub fn generate_solid_plane_with_noise(
|
||||||
octree: &mut SparseVoxelOctree,
|
octree: &mut SparseVoxelOctree,
|
||||||
width: usize,
|
width: usize,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
color: Color,
|
|
||||||
noise: &Perlin,
|
noise: &Perlin,
|
||||||
frequency: f32,
|
frequency: f32,
|
||||||
amplitude: f32,
|
amplitude: f32,
|
||||||
@ -245,13 +212,9 @@ pub fn generate_solid_plane_with_noise(
|
|||||||
|
|
||||||
// Fill from layer 0 up to max_layer
|
// Fill from layer 0 up to max_layer
|
||||||
for iy in 0..=max_layer {
|
for iy in 0..=max_layer {
|
||||||
let position = Vec3::new(
|
let position = Vec3::new(x * step, iy as f32 * step, z * step);
|
||||||
x * step,
|
|
||||||
iy as f32 * step,
|
|
||||||
z * step,
|
|
||||||
);
|
|
||||||
|
|
||||||
let voxel = Voxel { color };
|
let voxel = Voxel::random_sides();
|
||||||
octree.insert(position, voxel);
|
octree.insert(position, voxel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
71
client/src/plugins/environment/systems/voxels/atlas.rs
Normal file
71
client/src/plugins/environment/systems/voxels/atlas.rs
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
use bevy::asset::RenderAssetUsages;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
|
||||||
|
|
||||||
|
/// Configuration and handle for the voxel texture atlas.
|
||||||
|
#[derive(Resource, Clone)]
|
||||||
|
pub struct VoxelTextureAtlas {
|
||||||
|
pub handle: Handle<Image>,
|
||||||
|
pub columns: usize,
|
||||||
|
pub rows: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VoxelTextureAtlas {
|
||||||
|
/// Create a simple procedural atlas with solid colors.
|
||||||
|
pub fn generate(images: &mut Assets<Image>) -> Self {
|
||||||
|
let tile_size = 16u32;
|
||||||
|
let columns = 2;
|
||||||
|
let rows = 3;
|
||||||
|
let width = tile_size * columns as u32;
|
||||||
|
let height = tile_size * rows as u32;
|
||||||
|
let mut data = vec![0u8; (width * height * 4) as usize];
|
||||||
|
let colors = [
|
||||||
|
[255, 0, 0, 255], // 0: red
|
||||||
|
[0, 0, 0, 255], // 1: black
|
||||||
|
[0, 255, 0, 255], // 2: green
|
||||||
|
[0, 0, 255, 255], // 3: blue
|
||||||
|
[255, 255, 0, 255], // 4: yellow
|
||||||
|
[255, 0, 255, 255], // 5: magenta
|
||||||
|
];
|
||||||
|
for (i, col) in colors.iter().enumerate() {
|
||||||
|
let cx = (i % columns) as u32 * tile_size;
|
||||||
|
let cy = (i / columns) as u32 * tile_size;
|
||||||
|
for y in 0..tile_size {
|
||||||
|
for x in 0..tile_size {
|
||||||
|
let idx = (((cy + y) * width + (cx + x)) * 4) as usize;
|
||||||
|
data[idx..idx + 4].copy_from_slice(col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let image = Image::new_fill(
|
||||||
|
Extent3d {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
},
|
||||||
|
TextureDimension::D2,
|
||||||
|
&data,
|
||||||
|
TextureFormat::Rgba8UnormSrgb,
|
||||||
|
RenderAssetUsages::default(),
|
||||||
|
);
|
||||||
|
let handle = images.add(image);
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
columns,
|
||||||
|
rows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute UV coordinates for the given atlas index.
|
||||||
|
pub fn uv_rect(&self, index: usize) -> [[f32; 2]; 4] {
|
||||||
|
let col = index % self.columns;
|
||||||
|
let row = index / self.columns;
|
||||||
|
let cols = self.columns as f32;
|
||||||
|
let rows = self.rows as f32;
|
||||||
|
let u0 = col as f32 / cols;
|
||||||
|
let v0 = row as f32 / rows;
|
||||||
|
let u1 = (col + 1) as f32 / cols;
|
||||||
|
let v1 = (row + 1) as f32 / rows;
|
||||||
|
[[u0, v1], [u1, v1], [u1, v0], [u0, v0]]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
use bevy::prelude::*;
|
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
/// Visualize each node of the octree as a scaled cuboid, **center-based**.
|
/// Visualize each node of the octree as a scaled cuboid, **center-based**.
|
||||||
/// `octree_tf.translation` is the world-space center of the root bounding box.
|
/// `octree_tf.translation` is the world-space center of the root bounding box.
|
||||||
@ -13,8 +13,7 @@ pub fn visualize_octree_system(
|
|||||||
|
|
||||||
// Draw a translucent cuboid for the root
|
// Draw a translucent cuboid for the root
|
||||||
gizmos.cuboid(
|
gizmos.cuboid(
|
||||||
Transform::from_translation(octree_tf.translation)
|
Transform::from_translation(octree_tf.translation).with_scale(Vec3::splat(octree.size)),
|
||||||
.with_scale(Vec3::splat(octree.size)),
|
|
||||||
Color::srgba(1.0, 1.0, 0.0, 0.15),
|
Color::srgba(1.0, 1.0, 0.0, 0.15),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -85,9 +84,8 @@ fn visualize_recursive_center(
|
|||||||
|
|
||||||
// Draw a small cuboid at the same center as the parent node.
|
// Draw a small cuboid at the same center as the parent node.
|
||||||
gizmos.cuboid(
|
gizmos.cuboid(
|
||||||
Transform::from_translation(parent_center)
|
Transform::from_translation(parent_center).with_scale(Vec3::splat(leaf_size)),
|
||||||
.with_scale(Vec3::splat(leaf_size)),
|
Color::WHITE,
|
||||||
voxel.color,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +98,9 @@ pub fn draw_grid(
|
|||||||
camera_query: Query<&Transform, With<Camera>>,
|
camera_query: Query<&Transform, With<Camera>>,
|
||||||
octree_query: Query<(&SparseVoxelOctree, &Transform)>,
|
octree_query: Query<(&SparseVoxelOctree, &Transform)>,
|
||||||
) {
|
) {
|
||||||
let Ok(camera_tf) = camera_query.get_single() else { return };
|
let Ok(camera_tf) = camera_query.get_single() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
let camera_pos = camera_tf.translation;
|
let camera_pos = camera_tf.translation;
|
||||||
|
|
||||||
for (octree, octree_tf) in octree_query.iter() {
|
for (octree, octree_tf) in octree_query.iter() {
|
||||||
@ -142,4 +142,4 @@ pub fn draw_grid(
|
|||||||
gizmos.line(p3, p4, Color::WHITE);
|
gizmos.line(p3, p4, Color::WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use crate::plugins::environment::systems::voxels::atlas::VoxelTextureAtlas;
|
||||||
use bevy::asset::RenderAssetUsages;
|
use bevy::asset::RenderAssetUsages;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
|
use bevy::render::mesh::{Indices, Mesh, PrimitiveTopology, VertexAttributeValues};
|
||||||
@ -304,6 +305,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
step: f32,
|
step: f32,
|
||||||
tree: &SparseVoxelOctree,
|
tree: &SparseVoxelOctree,
|
||||||
pool: &mut MeshBufferPool,
|
pool: &mut MeshBufferPool,
|
||||||
|
atlas: &VoxelTextureAtlas,
|
||||||
) -> Option<Mesh> {
|
) -> Option<Mesh> {
|
||||||
// ────────────────────────────────────────────────────────────────────────────
|
// ────────────────────────────────────────────────────────────────────────────
|
||||||
// Helpers
|
// Helpers
|
||||||
@ -313,15 +315,15 @@ pub(crate) fn mesh_chunk(
|
|||||||
const MASK_LEN: usize = N * N;
|
const MASK_LEN: usize = N * N;
|
||||||
|
|
||||||
// Safe voxel query that falls back to the octree for out‑of‑chunk requests.
|
// Safe voxel query that falls back to the octree for out‑of‑chunk requests.
|
||||||
let filled = |x: i32, y: i32, z: i32| -> bool {
|
let get_voxel = |x: i32, y: i32, z: i32| -> Option<Voxel> {
|
||||||
if (0..CHUNK_SIZE).contains(&x)
|
if (0..CHUNK_SIZE).contains(&x)
|
||||||
&& (0..CHUNK_SIZE).contains(&y)
|
&& (0..CHUNK_SIZE).contains(&y)
|
||||||
&& (0..CHUNK_SIZE).contains(&z)
|
&& (0..CHUNK_SIZE).contains(&z)
|
||||||
{
|
{
|
||||||
buffer[x as usize][y as usize][z as usize].is_some()
|
buffer[x as usize][y as usize][z as usize]
|
||||||
} else {
|
} else {
|
||||||
let world = origin + Vec3::new(x as f32 * step, y as f32 * step, z as f32 * step);
|
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()
|
tree.get_voxel_at_world_coords(world).copied()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -341,7 +343,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
let uvs = &mut pool.uvs;
|
let uvs = &mut pool.uvs;
|
||||||
let indices = &mut pool.indices;
|
let indices = &mut pool.indices;
|
||||||
|
|
||||||
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3| {
|
let mut push_quad = |base: Vec3, size: Vec2, n: Vec3, u: Vec3, v: Vec3, tex_id: usize| {
|
||||||
let i0 = positions.len() as u32;
|
let i0 = positions.len() as u32;
|
||||||
positions.extend_from_slice(&[
|
positions.extend_from_slice(&[
|
||||||
(base).into(),
|
(base).into(),
|
||||||
@ -350,7 +352,8 @@ pub(crate) fn mesh_chunk(
|
|||||||
(base + v * size.y).into(),
|
(base + v * size.y).into(),
|
||||||
]);
|
]);
|
||||||
normals.extend_from_slice(&[[n.x, n.y, n.z]; 4]);
|
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]]);
|
let uv_rect = atlas.uv_rect(tex_id);
|
||||||
|
uvs.extend_from_slice(&uv_rect);
|
||||||
|
|
||||||
if n.x + n.y + n.z >= 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]);
|
indices.extend_from_slice(&[i0, i0 + 1, i0 + 2, i0 + 2, i0 + 3, i0]);
|
||||||
@ -382,7 +385,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
for slice in 0..=N {
|
for slice in 0..=N {
|
||||||
// Build the face mask for this slice using a fixed-size array to
|
// Build the face mask for this slice using a fixed-size array to
|
||||||
// avoid heap allocations.
|
// avoid heap allocations.
|
||||||
let mut mask = [false; MASK_LEN];
|
let mut mask = [None::<usize>; MASK_LEN];
|
||||||
let mut visited = [false; MASK_LEN];
|
let mut visited = [false; MASK_LEN];
|
||||||
let idx = |u: usize, v: usize| -> usize { u * N + v };
|
let idx = |u: usize, v: usize| -> usize { u * N + v };
|
||||||
|
|
||||||
@ -400,10 +403,19 @@ pub(crate) fn mesh_chunk(
|
|||||||
neighbor[u_axis] = u as i32;
|
neighbor[u_axis] = u as i32;
|
||||||
neighbor[v_axis] = v as i32;
|
neighbor[v_axis] = v as i32;
|
||||||
|
|
||||||
if filled(cell[0], cell[1], cell[2])
|
if let Some(vox) = get_voxel(cell[0], cell[1], cell[2]) {
|
||||||
&& !filled(neighbor[0], neighbor[1], neighbor[2])
|
if get_voxel(neighbor[0], neighbor[1], neighbor[2]).is_none() {
|
||||||
{
|
let face_idx = match (axis, dir) {
|
||||||
mask[idx(u, v)] = true;
|
(0, -1) => 0,
|
||||||
|
(0, 1) => 1,
|
||||||
|
(1, -1) => 2,
|
||||||
|
(1, 1) => 3,
|
||||||
|
(2, -1) => 4,
|
||||||
|
(2, 1) => 5,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
mask[idx(u, v)] = Some(vox.textures[face_idx]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -411,14 +423,15 @@ pub(crate) fn mesh_chunk(
|
|||||||
// Greedy merge the mask into maximal rectangles.
|
// Greedy merge the mask into maximal rectangles.
|
||||||
for u0 in 0..N {
|
for u0 in 0..N {
|
||||||
for v0 in 0..N {
|
for v0 in 0..N {
|
||||||
if !mask[idx(u0, v0)] || visited[idx(u0, v0)] {
|
if visited[idx(u0, v0)] {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let Some(tex_id) = mask[idx(u0, v0)] else { continue };
|
||||||
|
|
||||||
// Determine the rectangle width.
|
// Determine the rectangle width.
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
while u0 + width < N
|
while u0 + width < N
|
||||||
&& mask[idx(u0 + width, v0)]
|
&& mask[idx(u0 + width, v0)] == Some(tex_id)
|
||||||
&& !visited[idx(u0 + width, v0)]
|
&& !visited[idx(u0 + width, v0)]
|
||||||
{
|
{
|
||||||
width += 1;
|
width += 1;
|
||||||
@ -428,7 +441,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
let mut height = 1;
|
let mut height = 1;
|
||||||
'h: while v0 + height < N {
|
'h: while v0 + height < N {
|
||||||
for du in 0..width {
|
for du in 0..width {
|
||||||
if !mask[idx(u0 + du, v0 + height)]
|
if mask[idx(u0 + du, v0 + height)] != Some(tex_id)
|
||||||
|| visited[idx(u0 + du, v0 + height)]
|
|| visited[idx(u0 + du, v0 + height)]
|
||||||
{
|
{
|
||||||
break 'h;
|
break 'h;
|
||||||
@ -466,7 +479,7 @@ pub(crate) fn mesh_chunk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let size = Vec2::new(width as f32 * step, height as f32 * step);
|
let size = Vec2::new(width as f32 * step, height as f32 * step);
|
||||||
push_quad(base, size, face_normal, u_vec, v_vec);
|
push_quad(base, size, face_normal, u_vec, v_vec, tex_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,3 +10,4 @@ mod meshing;
|
|||||||
pub mod meshing_gpu;
|
pub mod meshing_gpu;
|
||||||
pub mod queue_systems;
|
pub mod queue_systems;
|
||||||
pub mod render_chunks;
|
pub mod render_chunks;
|
||||||
|
pub mod atlas;
|
||||||
|
|||||||
@ -1,19 +1,27 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
|
||||||
use std::path::Path;
|
use crate::plugins::environment::systems::voxels::structure::{
|
||||||
use std::io;
|
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree,
|
||||||
use bincode;
|
Voxel,
|
||||||
|
};
|
||||||
use bevy::asset::Assets;
|
use bevy::asset::Assets;
|
||||||
use bevy::color::Color;
|
|
||||||
use bevy::math::{DQuat, DVec3};
|
use bevy::math::{DQuat, DVec3};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
||||||
use bevy::render::render_asset::RenderAssetUsages;
|
use bevy::render::render_asset::RenderAssetUsages;
|
||||||
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
|
use bincode;
|
||||||
use crate::plugins::environment::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS, CHUNK_SIZE, ChunkKey};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
impl SparseVoxelOctree {
|
impl SparseVoxelOctree {
|
||||||
/// Creates a new octree with the specified max depth, size, and wireframe visibility.
|
/// Creates a new octree with the specified max depth, size, and wireframe visibility.
|
||||||
pub fn new(max_depth: u32, size: f32, show_wireframe: bool, show_world_grid: bool, show_chunks: bool) -> Self {
|
pub fn new(
|
||||||
|
max_depth: u32,
|
||||||
|
size: f32,
|
||||||
|
show_wireframe: bool,
|
||||||
|
show_world_grid: bool,
|
||||||
|
show_chunks: bool,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
root: OctreeNode::new(),
|
root: OctreeNode::new(),
|
||||||
max_depth,
|
max_depth,
|
||||||
@ -38,9 +46,7 @@ impl SparseVoxelOctree {
|
|||||||
world_center = self.denormalize_voxel_center(aligned);
|
world_center = self.denormalize_voxel_center(aligned);
|
||||||
}
|
}
|
||||||
|
|
||||||
let dirty_voxel = DirtyVoxel{
|
let dirty_voxel = DirtyVoxel { position: aligned };
|
||||||
position: aligned,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.dirty.push(dirty_voxel);
|
self.dirty.push(dirty_voxel);
|
||||||
let key = chunk_key_from_world(self, position);
|
let key = chunk_key_from_world(self, position);
|
||||||
@ -48,7 +54,6 @@ impl SparseVoxelOctree {
|
|||||||
self.mark_neighbor_chunks_dirty(position);
|
self.mark_neighbor_chunks_dirty(position);
|
||||||
self.occupied_chunks.insert(key);
|
self.occupied_chunks.insert(key);
|
||||||
|
|
||||||
|
|
||||||
Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth);
|
Self::insert_recursive(&mut self.root, aligned, voxel, self.max_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,9 +152,12 @@ impl SparseVoxelOctree {
|
|||||||
/// Mark all six neighbor chunks of the given key as dirty if they exist.
|
/// Mark all six neighbor chunks of the given key as dirty if they exist.
|
||||||
pub fn mark_neighbors_dirty_from_key(&mut self, key: ChunkKey) {
|
pub fn mark_neighbors_dirty_from_key(&mut self, key: ChunkKey) {
|
||||||
let offsets = [
|
let offsets = [
|
||||||
(-1, 0, 0), (1, 0, 0),
|
(-1, 0, 0),
|
||||||
(0, -1, 0), (0, 1, 0),
|
(1, 0, 0),
|
||||||
(0, 0, -1), (0, 0, 1),
|
(0, -1, 0),
|
||||||
|
(0, 1, 0),
|
||||||
|
(0, 0, -1),
|
||||||
|
(0, 0, 1),
|
||||||
];
|
];
|
||||||
for (dx, dy, dz) in offsets {
|
for (dx, dy, dz) in offsets {
|
||||||
let neighbor = ChunkKey(key.0 + dx, key.1 + dy, key.2 + dz);
|
let neighbor = ChunkKey(key.0 + dx, key.1 + dy, key.2 + dz);
|
||||||
@ -159,13 +167,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_recursive(
|
fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool {
|
||||||
node: &mut OctreeNode,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
z: f32,
|
|
||||||
depth: u32,
|
|
||||||
) -> bool {
|
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
if node.voxel.is_some() {
|
if node.voxel.is_some() {
|
||||||
node.voxel = None;
|
node.voxel = None;
|
||||||
@ -222,7 +224,6 @@ impl SparseVoxelOctree {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) {
|
fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) {
|
||||||
info!("Root expanding ...");
|
info!("Root expanding ...");
|
||||||
// Save the old root and its size.
|
// Save the old root and its size.
|
||||||
@ -244,7 +245,15 @@ impl SparseVoxelOctree {
|
|||||||
/// The coordinate system here assumes the node covers [–old_size/2, +old_size/2] in each axis.
|
/// The coordinate system here assumes the node covers [–old_size/2, +old_size/2] in each axis.
|
||||||
fn collect_voxels_from_node(node: &OctreeNode, old_size: f32) -> Vec<(Vec3, Voxel, u32)> {
|
fn collect_voxels_from_node(node: &OctreeNode, old_size: f32) -> Vec<(Vec3, Voxel, u32)> {
|
||||||
let mut voxels = Vec::new();
|
let mut voxels = Vec::new();
|
||||||
Self::collect_voxels_recursive(node, -old_size / 2.0, -old_size / 2.0, -old_size / 2.0, old_size, 0, &mut voxels);
|
Self::collect_voxels_recursive(
|
||||||
|
node,
|
||||||
|
-old_size / 2.0,
|
||||||
|
-old_size / 2.0,
|
||||||
|
-old_size / 2.0,
|
||||||
|
old_size,
|
||||||
|
0,
|
||||||
|
&mut voxels,
|
||||||
|
);
|
||||||
voxels
|
voxels
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,14 +279,20 @@ impl SparseVoxelOctree {
|
|||||||
let offset_x = if (i & 1) != 0 { half } else { 0.0 };
|
let offset_x = if (i & 1) != 0 { half } else { 0.0 };
|
||||||
let offset_y = if (i & 2) != 0 { half } else { 0.0 };
|
let offset_y = if (i & 2) != 0 { half } else { 0.0 };
|
||||||
let offset_z = if (i & 4) != 0 { half } else { 0.0 };
|
let offset_z = if (i & 4) != 0 { half } else { 0.0 };
|
||||||
Self::collect_voxels_recursive(child, x + offset_x, y + offset_y, z + offset_z, half, depth + 1, out);
|
Self::collect_voxels_recursive(
|
||||||
|
child,
|
||||||
|
x + offset_x,
|
||||||
|
y + offset_y,
|
||||||
|
z + offset_z,
|
||||||
|
half,
|
||||||
|
depth + 1,
|
||||||
|
out,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn traverse(&self) -> Vec<(Vec3, u32)> {
|
||||||
|
|
||||||
pub fn traverse(&self) -> Vec<(Vec3, Color, u32)> {
|
|
||||||
let mut voxels = Vec::new();
|
let mut voxels = Vec::new();
|
||||||
// Start at the normalized center (0.5, 0.5, 0.5) rather than (0,0,0)
|
// Start at the normalized center (0.5, 0.5, 0.5) rather than (0,0,0)
|
||||||
Self::traverse_recursive(
|
Self::traverse_recursive(
|
||||||
@ -296,20 +311,20 @@ impl SparseVoxelOctree {
|
|||||||
local_center: Vec3,
|
local_center: Vec3,
|
||||||
size: f32,
|
size: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
out: &mut Vec<(Vec3, Color, u32)>,
|
out: &mut Vec<(Vec3, u32)>,
|
||||||
octree: &SparseVoxelOctree,
|
octree: &SparseVoxelOctree,
|
||||||
) {
|
) {
|
||||||
// If a leaf contains a voxel, record its world-space center
|
// If a leaf contains a voxel, record its world-space center
|
||||||
if node.is_leaf {
|
if node.is_leaf {
|
||||||
if let Some(voxel) = node.voxel {
|
if let Some(voxel) = node.voxel {
|
||||||
out.push((octree.denormalize_voxel_center(local_center), voxel.color, depth));
|
out.push((octree.denormalize_voxel_center(local_center), depth));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the node has children, subdivide the cell into 8 subcells.
|
// If the node has children, subdivide the cell into 8 subcells.
|
||||||
if let Some(ref children) = node.children {
|
if let Some(ref children) = node.children {
|
||||||
let offset = size / 4.0; // child center offset from parent center
|
let offset = size / 4.0; // child center offset from parent center
|
||||||
let new_size = size / 2.0; // each child cell's size in normalized space
|
let new_size = size / 2.0; // each child cell's size in normalized space
|
||||||
for (i, child) in children.iter().enumerate() {
|
for (i, child) in children.iter().enumerate() {
|
||||||
// Compute each axis' offset: use +offset if the bit is set, else -offset.
|
// Compute each axis' offset: use +offset if the bit is set, else -offset.
|
||||||
let dx = if (i & 1) != 0 { offset } else { -offset };
|
let dx = if (i & 1) != 0 { offset } else { -offset };
|
||||||
@ -322,8 +337,6 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Retrieve a voxel from the octree if it exists (x,y,z in [-0.5..+0.5] range).
|
/// Retrieve a voxel from the octree if it exists (x,y,z in [-0.5..+0.5] range).
|
||||||
pub fn get_voxel_at(&self, x: f32, y: f32, z: f32) -> Option<&Voxel> {
|
pub fn get_voxel_at(&self, x: f32, y: f32, z: f32) -> Option<&Voxel> {
|
||||||
Self::get_voxel_recursive(&self.root, x, y, z)
|
Self::get_voxel_recursive(&self.root, x, y, z)
|
||||||
@ -388,7 +401,6 @@ impl SparseVoxelOctree {
|
|||||||
self.get_voxel_at_world_coords(neighbor_world).is_some()
|
self.get_voxel_at_world_coords(neighbor_world).is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Performs a raycast against the octree and returns the first intersected voxel.
|
/// Performs a raycast against the octree and returns the first intersected voxel.
|
||||||
pub fn raycast(&self, ray: &Ray) -> Option<(f32, f32, f32, u32, Vec3)> {
|
pub fn raycast(&self, ray: &Ray) -> Option<(f32, f32, f32, u32, Vec3)> {
|
||||||
// Start from the root node
|
// Start from the root node
|
||||||
@ -397,12 +409,7 @@ impl SparseVoxelOctree {
|
|||||||
min: Vec3::new(-half_size as f32, -half_size as f32, -half_size as f32),
|
min: Vec3::new(-half_size as f32, -half_size as f32, -half_size as f32),
|
||||||
max: Vec3::new(half_size as f32, half_size as f32, half_size as f32),
|
max: Vec3::new(half_size as f32, half_size as f32, half_size as f32),
|
||||||
};
|
};
|
||||||
self.raycast_recursive(
|
self.raycast_recursive(&self.root, ray, &root_bounds, 0)
|
||||||
&self.root,
|
|
||||||
ray,
|
|
||||||
&root_bounds,
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn raycast_recursive(
|
fn raycast_recursive(
|
||||||
@ -435,7 +442,8 @@ impl SparseVoxelOctree {
|
|||||||
let mut hits = Vec::new();
|
let mut hits = Vec::new();
|
||||||
for (i, child) in children.iter().enumerate() {
|
for (i, child) in children.iter().enumerate() {
|
||||||
let child_bounds = self.compute_child_bounds(bounds, i);
|
let child_bounds = self.compute_child_bounds(bounds, i);
|
||||||
if let Some(hit) = self.raycast_recursive(child, ray, &child_bounds, depth + 1) {
|
if let Some(hit) = self.raycast_recursive(child, ray, &child_bounds, depth + 1)
|
||||||
|
{
|
||||||
hits.push(hit);
|
hits.push(hit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -445,11 +453,11 @@ impl SparseVoxelOctree {
|
|||||||
let dist_a = ((a.0 as f32 - ray.origin.x).powi(2)
|
let dist_a = ((a.0 as f32 - ray.origin.x).powi(2)
|
||||||
+ (a.1 as f32 - ray.origin.y).powi(2)
|
+ (a.1 as f32 - ray.origin.y).powi(2)
|
||||||
+ (a.2 as f32 - ray.origin.z).powi(2))
|
+ (a.2 as f32 - ray.origin.z).powi(2))
|
||||||
.sqrt();
|
.sqrt();
|
||||||
let dist_b = ((b.0 as f32 - ray.origin.x).powi(2)
|
let dist_b = ((b.0 as f32 - ray.origin.x).powi(2)
|
||||||
+ (b.1 as f32 - ray.origin.y).powi(2)
|
+ (b.1 as f32 - ray.origin.y).powi(2)
|
||||||
+ (b.2 as f32 - ray.origin.z).powi(2))
|
+ (b.2 as f32 - ray.origin.z).powi(2))
|
||||||
.sqrt();
|
.sqrt();
|
||||||
dist_a.partial_cmp(&dist_b).unwrap()
|
dist_a.partial_cmp(&dist_b).unwrap()
|
||||||
});
|
});
|
||||||
return Some(hits[0]);
|
return Some(hits[0]);
|
||||||
@ -462,16 +470,15 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
/// Save the octree to a file using bincode serialization.
|
/// Save the octree to a file using bincode serialization.
|
||||||
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
|
||||||
let data = bincode::serialize(self)
|
let data = bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
|
||||||
std::fs::write(path, data)
|
std::fs::write(path, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load an octree from a file and rebuild runtime caches.
|
/// Load an octree from a file and rebuild runtime caches.
|
||||||
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
|
||||||
let bytes = std::fs::read(path)?;
|
let bytes = std::fs::read(path)?;
|
||||||
let mut tree: Self = bincode::deserialize(&bytes)
|
let mut tree: Self =
|
||||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
bincode::deserialize(&bytes).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
tree.rebuild_cache();
|
tree.rebuild_cache();
|
||||||
Ok(tree)
|
Ok(tree)
|
||||||
}
|
}
|
||||||
@ -481,7 +488,7 @@ impl SparseVoxelOctree {
|
|||||||
self.dirty.clear();
|
self.dirty.clear();
|
||||||
self.dirty_chunks.clear();
|
self.dirty_chunks.clear();
|
||||||
self.occupied_chunks.clear();
|
self.occupied_chunks.clear();
|
||||||
|
|
||||||
let voxels = Self::collect_voxels_from_node(&self.root, self.size);
|
let voxels = Self::collect_voxels_from_node(&self.root, self.size);
|
||||||
for (pos, _voxel, _depth) in voxels {
|
for (pos, _voxel, _depth) in voxels {
|
||||||
let key = chunk_key_from_world(self, pos);
|
let key = chunk_key_from_world(self, pos);
|
||||||
@ -489,4 +496,3 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
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::*;
|
||||||
|
use crate::plugins::environment::systems::voxels::atlas::VoxelTextureAtlas;
|
||||||
use bevy::pbr::wireframe::Wireframe;
|
use bevy::pbr::wireframe::Wireframe;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::Mesh;
|
use bevy::render::mesh::Mesh;
|
||||||
@ -25,6 +26,7 @@ pub fn rebuild_dirty_chunks(
|
|||||||
mut spawned: ResMut<SpawnedChunks>,
|
mut spawned: ResMut<SpawnedChunks>,
|
||||||
mut pool: ResMut<MeshBufferPool>,
|
mut pool: ResMut<MeshBufferPool>,
|
||||||
root: Res<RootGrid>,
|
root: Res<RootGrid>,
|
||||||
|
atlas: Res<VoxelTextureAtlas>,
|
||||||
) {
|
) {
|
||||||
// map ChunkKey → (entity, mesh-handle, material-handle)
|
// map ChunkKey → (entity, mesh-handle, material-handle)
|
||||||
let existing: HashMap<ChunkKey, (Entity, Handle<Mesh>, Handle<StandardMaterial>, u32)> =
|
let existing: HashMap<ChunkKey, (Entity, Handle<Mesh>, Handle<StandardMaterial>, u32)> =
|
||||||
@ -87,7 +89,7 @@ pub fn rebuild_dirty_chunks(
|
|||||||
for (key, buf, origin, step, lod) in bufs {
|
for (key, buf, origin, step, lod) in bufs {
|
||||||
if let Some((ent, mesh_h, _mat_h, _)) = existing.get(&key).cloned() {
|
if let Some((ent, mesh_h, _mat_h, _)) = existing.get(&key).cloned() {
|
||||||
// update mesh in-place; keeps old asset id
|
// update mesh in-place; keeps old asset id
|
||||||
match mesh_chunk(&buf, origin, step, &tree, &mut pool) {
|
match mesh_chunk(&buf, origin, step, &tree, &mut pool, &atlas) {
|
||||||
Some(new_mesh) => {
|
Some(new_mesh) => {
|
||||||
if let Some(mesh) = meshes.get_mut(&mesh_h) {
|
if let Some(mesh) = meshes.get_mut(&mesh_h) {
|
||||||
*mesh = new_mesh;
|
*mesh = new_mesh;
|
||||||
@ -100,10 +102,13 @@ pub fn rebuild_dirty_chunks(
|
|||||||
spawned.0.remove(&key);
|
spawned.0.remove(&key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(mesh) = mesh_chunk(&buf, origin, step, &tree, &mut pool) {
|
} else if let Some(mesh) = mesh_chunk(&buf, origin, step, &tree, &mut pool, &atlas) {
|
||||||
// spawn brand-new chunk only if mesh has faces
|
// spawn brand-new chunk only if mesh has faces
|
||||||
let mesh_h = meshes.add(mesh);
|
let mesh_h = meshes.add(mesh);
|
||||||
let mat_h = materials.add(StandardMaterial::default());
|
let mat_h = materials.add(StandardMaterial {
|
||||||
|
base_color_texture: Some(atlas.handle.clone()),
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
commands.entity(root.0).with_children(|p| {
|
commands.entity(root.0).with_children(|p| {
|
||||||
let e = p
|
let e = p
|
||||||
|
|||||||
@ -1,32 +1,21 @@
|
|||||||
use bevy::color::Color;
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use rand::Rng;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
|
||||||
fn serialize_color<S>(color: &Color, serializer: S) -> Result<S::Ok, S::Error>
|
/// Represents a single voxel with texture indices for each face.
|
||||||
where
|
#[derive(Debug, Clone, Copy, Component, PartialEq, Serialize, Deserialize)]
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let [r, g, b, a] = color.to_linear().to_f32_array();
|
|
||||||
[r, g, b, a].serialize(serializer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deserialize_color<'de, D>(deserializer: D) -> Result<Color, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let arr: [f32; 4] = Deserialize::deserialize(deserializer)?;
|
|
||||||
Ok(Color::linear_rgba(arr[0], arr[1], arr[2], arr[3]))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Represents a single voxel with a color.
|
|
||||||
#[derive(Debug, Clone, Copy, Component, PartialEq, Default, Serialize, Deserialize)]
|
|
||||||
pub struct Voxel {
|
pub struct Voxel {
|
||||||
#[serde(
|
/// Indexes into the texture atlas for the six faces in the order
|
||||||
serialize_with = "serialize_color",
|
/// left, right, bottom, top, back, front.
|
||||||
deserialize_with = "deserialize_color"
|
#[serde(default)]
|
||||||
)]
|
pub textures: [usize; 6],
|
||||||
pub color: Color,
|
}
|
||||||
|
|
||||||
|
impl Default for Voxel {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { textures: [0; 6] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@ -77,8 +66,23 @@ impl OctreeNode {
|
|||||||
|
|
||||||
impl Voxel {
|
impl Voxel {
|
||||||
/// Creates a new empty octree node.
|
/// Creates a new empty octree node.
|
||||||
pub fn new(color: Color) -> Self {
|
pub fn new(textures: [usize; 6]) -> Self {
|
||||||
Self { color }
|
Self { textures }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a voxel with a red top, black bottom and random colors on
|
||||||
|
/// all remaining faces. Assumes the atlas uses index 0 for red, index 1
|
||||||
|
/// for black and indices >=2 for random colors.
|
||||||
|
pub fn random_sides() -> Self {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let mut textures = [0usize; 6];
|
||||||
|
// Face order: left, right, bottom, top, back, front
|
||||||
|
textures[3] = 0; // top is red
|
||||||
|
textures[2] = 1; // bottom is black
|
||||||
|
for &i in &[0usize, 1usize, 4usize, 5usize] {
|
||||||
|
textures[i] = rng.gen_range(2..6);
|
||||||
|
}
|
||||||
|
Self { textures }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
use std::path::Path;
|
|
||||||
use bevy::prelude::*;
|
|
||||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||||
use crate::plugins::environment::systems::voxels::octree;
|
use crate::plugins::environment::systems::voxels::octree;
|
||||||
use crate::plugins::environment::systems::voxels::structure::*;
|
use crate::plugins::environment::systems::voxels::structure::*;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
///TODO
|
///TODO
|
||||||
pub fn voxel_system(
|
pub fn voxel_system(
|
||||||
|
|
||||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
mouse_button_input: Res<ButtonInput<MouseButton>>,
|
mouse_button_input: Res<ButtonInput<MouseButton>>,
|
||||||
mut octree_query: Query<&mut SparseVoxelOctree>,
|
mut octree_query: Query<&mut SparseVoxelOctree>,
|
||||||
@ -14,29 +13,33 @@ pub fn voxel_system(
|
|||||||
mut query: Query<(&mut Transform, &mut CameraController)>,
|
mut query: Query<(&mut Transform, &mut CameraController)>,
|
||||||
mut windows: Query<&mut Window>,
|
mut windows: Query<&mut Window>,
|
||||||
) {
|
) {
|
||||||
let Ok(mut window) = windows.get_single_mut() else { return };
|
let Ok(mut window) = windows.get_single_mut() else {
|
||||||
let Ok((mut transform, _)) = query.get_single_mut() else { return };
|
return;
|
||||||
|
};
|
||||||
|
let Ok((mut transform, _)) = query.get_single_mut() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// 5) Octree Keys
|
// 5) Octree Keys
|
||||||
// =======================
|
// =======================
|
||||||
if keyboard_input.just_pressed(KeyCode::F2){
|
if keyboard_input.just_pressed(KeyCode::F2) {
|
||||||
for mut octree in octree_query.iter_mut() {
|
for mut octree in octree_query.iter_mut() {
|
||||||
octree.show_wireframe = !octree.show_wireframe;
|
octree.show_wireframe = !octree.show_wireframe;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keyboard_input.just_pressed(KeyCode::F3){
|
if keyboard_input.just_pressed(KeyCode::F3) {
|
||||||
for mut octree in octree_query.iter_mut() {
|
for mut octree in octree_query.iter_mut() {
|
||||||
octree.show_world_grid = !octree.show_world_grid;
|
octree.show_world_grid = !octree.show_world_grid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false{
|
if keyboard_input.just_pressed(KeyCode::KeyQ) && window.cursor_options.visible == false {
|
||||||
for mut octree in octree_query.iter_mut() {
|
for mut octree in octree_query.iter_mut() {
|
||||||
octree.insert(transform.translation, Voxel::new(Color::srgb(1.0, 0.0, 0.0)));
|
octree.insert(transform.translation, Voxel::random_sides());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if keyboard_input.just_pressed(KeyCode::F4){
|
if keyboard_input.just_pressed(KeyCode::F4) {
|
||||||
let path = Path::new("octree.bin");
|
let path = Path::new("octree.bin");
|
||||||
for octree in octree_query.iter() {
|
for octree in octree_query.iter() {
|
||||||
if let Err(e) = octree.save_to_file(path) {
|
if let Err(e) = octree.save_to_file(path) {
|
||||||
@ -44,7 +47,7 @@ pub fn voxel_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* if keyboard_input.just_pressed(KeyCode::F5){
|
/* if keyboard_input.just_pressed(KeyCode::F5){
|
||||||
let path = Path::new("octree.bin");
|
let path = Path::new("octree.bin");
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
let path = Path::new("octree.bin");
|
let path = Path::new("octree.bin");
|
||||||
@ -57,17 +60,18 @@ pub fn voxel_system(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
|
||||||
// =======================
|
// =======================
|
||||||
// 6) Building
|
// 6) Building
|
||||||
// =======================
|
// =======================
|
||||||
|
|
||||||
if (mouse_button_input.just_pressed(MouseButton::Left) || mouse_button_input.just_pressed(MouseButton::Right)) && !window.cursor_options.visible {
|
if (mouse_button_input.just_pressed(MouseButton::Left)
|
||||||
|
|| mouse_button_input.just_pressed(MouseButton::Right))
|
||||||
|
&& !window.cursor_options.visible
|
||||||
|
{
|
||||||
// Get the mouse position in normalized device coordinates (-1 to 1)
|
// Get the mouse position in normalized device coordinates (-1 to 1)
|
||||||
if let Some(_) = window.cursor_position() {
|
if let Some(_) = window.cursor_position() {
|
||||||
// Set the ray direction to the camera's forward vector
|
// Set the ray direction to the camera's forward vector
|
||||||
@ -79,44 +83,33 @@ pub fn voxel_system(
|
|||||||
direction: ray_direction,
|
direction: ray_direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
for mut octree in octree_query.iter_mut() {
|
for mut octree in octree_query.iter_mut() {
|
||||||
if let Some((hit_x, hit_y, hit_z, depth,normal)) = octree.raycast(&ray) {
|
if let Some((hit_x, hit_y, hit_z, depth, normal)) = octree.raycast(&ray) {
|
||||||
|
|
||||||
|
|
||||||
if mouse_button_input.just_pressed(MouseButton::Right) {
|
if mouse_button_input.just_pressed(MouseButton::Right) {
|
||||||
|
|
||||||
let voxel_size = octree.get_spacing_at_depth(depth);
|
let voxel_size = octree.get_spacing_at_depth(depth);
|
||||||
let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32);
|
let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32);
|
||||||
let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size)
|
let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size)
|
||||||
|
|
||||||
// Offset position by epsilon in the direction of the normal
|
// Offset position by epsilon in the direction of the normal
|
||||||
let offset_position = hit_position - (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32));
|
let offset_position = hit_position
|
||||||
|
- (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32));
|
||||||
|
|
||||||
// Remove the voxel
|
// Remove the voxel
|
||||||
octree.remove(offset_position);
|
octree.remove(offset_position);
|
||||||
|
} else if mouse_button_input.just_pressed(MouseButton::Left) {
|
||||||
|
|
||||||
}
|
|
||||||
else if mouse_button_input.just_pressed(MouseButton::Left) {
|
|
||||||
|
|
||||||
let voxel_size = octree.get_spacing_at_depth(depth);
|
let voxel_size = octree.get_spacing_at_depth(depth);
|
||||||
let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32);
|
let hit_position = Vec3::new(hit_x as f32, hit_y as f32, hit_z as f32);
|
||||||
let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size)
|
let epsilon = voxel_size * 0.1; // Adjust this value as needed (e.g., 0.1 times the voxel size)
|
||||||
|
|
||||||
// Offset position by epsilon in the direction of the normal
|
// Offset position by epsilon in the direction of the normal
|
||||||
let offset_position = hit_position + (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32));
|
let offset_position = hit_position
|
||||||
|
+ (normal * Vec3::new(epsilon as f32, epsilon as f32, epsilon as f32));
|
||||||
|
|
||||||
// Insert the new voxel
|
// Insert the new voxel
|
||||||
octree.insert(
|
octree.insert(offset_position, Voxel::random_sides());
|
||||||
offset_position,
|
|
||||||
Voxel::new(Color::srgb(1.0, 0.0, 0.0)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user