Remove voxel color field

This commit is contained in:
Elias Stepanik 2025-06-14 01:23:37 +02:00
parent eecd786ccb
commit 496c5bf673
6 changed files with 128 additions and 197 deletions

View File

@ -1,25 +1,21 @@
use std::path::Path;
use rayon::prelude::*;
use crate::plugins::big_space::big_space_plugin::RootGrid;
use crate::plugins::environment::systems::voxels::structure::*;
use rayon::prelude::*;
use std::path::Path;
use bevy::prelude::*;
use bevy::render::mesh::*;
use noise::{NoiseFn, Perlin};
use rand::{thread_rng, Rng};
use rand::{Rng, thread_rng};
pub fn setup(
mut commands: Commands,
root: Res<RootGrid>,
) {
pub fn setup(mut commands: Commands, root: Res<RootGrid>) {
// Octree parameters
let unit_size = 1.0_f32;
let unit_size = 1.0_f32;
let octree_base_size = 64.0 * unit_size;
let octree_depth = 10;
let octree_depth = 10;
let path = Path::new("octree.bin");
let mut octree = if path.exists() {
match SparseVoxelOctree::load_from_file(path) {
Ok(tree) => tree,
@ -30,7 +26,6 @@ pub fn setup(
}
} else {
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?
/*const NUM_SPHERES: usize = 5;
let mut rng = threald_rng();
@ -44,30 +39,22 @@ pub fn setup(
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
};
// Attach octree to the scene graph
commands.entity(root.0).with_children(|parent| {
parent.spawn((Transform::default(), octree));
});
}
pub fn generate_voxel_sphere_parallel(
octree: &mut SparseVoxelOctree,
center: Vec3,
radius: i32,
color: Color,
) {
let step = octree.get_spacing_at_depth(octree.max_depth);
let radius_sq = radius * radius;
pub fn generate_voxel_sphere_parallel(octree: &mut SparseVoxelOctree, center: Vec3, radius: i32) {
let step = octree.get_spacing_at_depth(octree.max_depth);
let radius_sq = radius * radius;
// 1. Collect voxel positions in parallel
let voxels: Vec<(Vec3, Voxel)> = (-radius..=radius)
@ -75,7 +62,7 @@ pub fn generate_voxel_sphere_parallel(
.flat_map_iter(|ix| {
let dx2 = ix * ix;
(-radius..=radius).flat_map(move |iy| {
let dy2 = iy * iy;
let dy2 = iy * iy;
let r2_xy = dx2 + dy2;
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;
(-max_z..=max_z).map(move |iz| {
let pos = Vec3::new(
center.x + ix as f32 * step,
center.y + iy as f32 * step,
center.z + iz as f32 * step,
);
(pos, Voxel { color, textures: [0; 6] })
}).collect::<Vec<_>>()
(-max_z..=max_z)
.map(move |iz| {
let pos = Vec3::new(
center.x + ix as f32 * step,
center.y + iy as f32 * step,
center.z + iz as f32 * step,
);
(pos, Voxel::new([0; 6]))
})
.collect::<Vec<_>>()
})
})
.collect();
@ -101,12 +90,7 @@ pub fn generate_voxel_sphere_parallel(
}
}
fn generate_voxel_sphere(
octree: &mut SparseVoxelOctree,
planet_radius: i32,
voxel_color: Color,
) {
fn generate_voxel_sphere(octree: &mut SparseVoxelOctree, planet_radius: i32) {
// 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
let min = -planet_radius;
@ -131,10 +115,7 @@ fn generate_voxel_sphere(
let position = Vec3::new(wx, wy, wz);
// Insert the voxel
let voxel = Voxel {
color: voxel_color,
textures: [0; 6],
};
let voxel = Voxel::new([0; 6]);
octree.insert(position, voxel);
}
}
@ -142,13 +123,9 @@ fn generate_voxel_sphere(
}
}
/// 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.
fn generate_voxel_rect(
octree: &mut SparseVoxelOctree,
voxel_color: Color,
) {
fn generate_voxel_rect(octree: &mut SparseVoxelOctree) {
// The dimensions of our rectangle: 16 x 256 x 16
let size_x = 16;
let size_y = 256;
@ -173,22 +150,14 @@ fn generate_voxel_rect(
let position = Vec3::new(wx, wy, wz);
// Insert the voxel
let voxel = Voxel {
color: voxel_color,
textures: [0; 6],
};
let voxel = Voxel::new([0; 6]);
octree.insert(position, voxel);
}
}
}
}
fn generate_large_plane(
octree: &mut SparseVoxelOctree,
width: usize,
depth: usize,
color: Color,
) {
fn generate_large_plane(octree: &mut SparseVoxelOctree, width: usize, depth: usize) {
// We'll get the voxel spacing (size at the deepest level).
let step = octree.get_spacing_at_depth(octree.max_depth);
@ -209,21 +178,16 @@ fn generate_large_plane(
let position = Vec3::new(wx, wy, wz);
// Insert the voxel
let voxel = Voxel {
color,
textures: [0; 6],
};
let voxel = Voxel::new([0; 6]);
octree.insert(position, voxel);
}
}
}
pub fn generate_solid_plane_with_noise(
octree: &mut SparseVoxelOctree,
width: usize,
depth: usize,
color: Color,
noise: &Perlin,
frequency: f32,
amplitude: f32,
@ -248,13 +212,9 @@ pub fn generate_solid_plane_with_noise(
// Fill from layer 0 up to max_layer
for iy in 0..=max_layer {
let position = Vec3::new(
x * step,
iy as f32 * step,
z * step,
);
let position = Vec3::new(x * step, iy as f32 * step, z * step);
let voxel = Voxel { color, textures: [0; 6] };
let voxel = Voxel::new([0; 6]);
octree.insert(position, voxel);
}
}

View File

@ -1,6 +1,6 @@
use bevy::asset::RenderAssetUsages;
use bevy::prelude::*;
use bevy::render::texture::{Extent3d, TextureDimension, TextureFormat};
use bevy::render::render_resource::{Extent3d, TextureDimension, TextureFormat};
/// Configuration and handle for the voxel texture atlas.
#[derive(Resource, Clone)]

View File

@ -1,5 +1,5 @@
use bevy::prelude::*;
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
/// 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.
@ -13,8 +13,7 @@ pub fn visualize_octree_system(
// Draw a translucent cuboid for the root
gizmos.cuboid(
Transform::from_translation(octree_tf.translation)
.with_scale(Vec3::splat(octree.size)),
Transform::from_translation(octree_tf.translation).with_scale(Vec3::splat(octree.size)),
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.
gizmos.cuboid(
Transform::from_translation(parent_center)
.with_scale(Vec3::splat(leaf_size)),
voxel.color,
Transform::from_translation(parent_center).with_scale(Vec3::splat(leaf_size)),
Color::WHITE,
);
}
}
@ -100,7 +98,9 @@ pub fn draw_grid(
camera_query: Query<&Transform, With<Camera>>,
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;
for (octree, octree_tf) in octree_query.iter() {
@ -142,4 +142,4 @@ pub fn draw_grid(
gizmos.line(p3, p4, Color::WHITE);
}
}
}
}

View File

@ -1,19 +1,27 @@
use std::collections::{HashMap, HashSet};
use std::path::Path;
use std::io;
use bincode;
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
use crate::plugins::environment::systems::voxels::structure::{
AABB, CHUNK_SIZE, ChunkKey, DirtyVoxel, NEIGHBOR_OFFSETS, OctreeNode, Ray, SparseVoxelOctree,
Voxel,
};
use bevy::asset::Assets;
use bevy::color::Color;
use bevy::math::{DQuat, DVec3};
use bevy::prelude::*;
use bevy::render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
use bevy::render::render_asset::RenderAssetUsages;
use crate::plugins::environment::systems::voxels::helper::chunk_key_from_world;
use crate::plugins::environment::systems::voxels::structure::{DirtyVoxel, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS, CHUNK_SIZE, ChunkKey};
use bincode;
use std::collections::{HashMap, HashSet};
use std::io;
use std::path::Path;
impl SparseVoxelOctree {
/// 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 {
root: OctreeNode::new(),
max_depth,
@ -38,9 +46,7 @@ impl SparseVoxelOctree {
world_center = self.denormalize_voxel_center(aligned);
}
let dirty_voxel = DirtyVoxel{
position: aligned,
};
let dirty_voxel = DirtyVoxel { position: aligned };
self.dirty.push(dirty_voxel);
let key = chunk_key_from_world(self, position);
@ -48,7 +54,6 @@ impl SparseVoxelOctree {
self.mark_neighbor_chunks_dirty(position);
self.occupied_chunks.insert(key);
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.
pub fn mark_neighbors_dirty_from_key(&mut self, key: ChunkKey) {
let offsets = [
(-1, 0, 0), (1, 0, 0),
(0, -1, 0), (0, 1, 0),
(0, 0, -1), (0, 0, 1),
(-1, 0, 0),
(1, 0, 0),
(0, -1, 0),
(0, 1, 0),
(0, 0, -1),
(0, 0, 1),
];
for (dx, dy, dz) in offsets {
let neighbor = ChunkKey(key.0 + dx, key.1 + dy, key.2 + dz);
@ -159,13 +167,7 @@ impl SparseVoxelOctree {
}
}
fn remove_recursive(
node: &mut OctreeNode,
x: f32,
y: f32,
z: f32,
depth: u32,
) -> bool {
fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool {
if depth == 0 {
if node.voxel.is_some() {
node.voxel = None;
@ -222,7 +224,6 @@ impl SparseVoxelOctree {
false
}
fn expand_root(&mut self, _x: f32, _y: f32, _z: f32) {
info!("Root expanding ...");
// 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.
fn collect_voxels_from_node(node: &OctreeNode, old_size: f32) -> Vec<(Vec3, Voxel, u32)> {
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
}
@ -270,14 +279,20 @@ impl SparseVoxelOctree {
let offset_x = if (i & 1) != 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 };
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, Color, u32)> {
pub fn traverse(&self) -> Vec<(Vec3, u32)> {
let mut voxels = Vec::new();
// Start at the normalized center (0.5, 0.5, 0.5) rather than (0,0,0)
Self::traverse_recursive(
@ -296,20 +311,20 @@ impl SparseVoxelOctree {
local_center: Vec3,
size: f32,
depth: u32,
out: &mut Vec<(Vec3, Color, u32)>,
out: &mut Vec<(Vec3, u32)>,
octree: &SparseVoxelOctree,
) {
// If a leaf contains a voxel, record its world-space center
if node.is_leaf {
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 let Some(ref children) = node.children {
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 offset = size / 4.0; // child center offset from parent center
let new_size = size / 2.0; // each child cell's size in normalized space
for (i, child) in children.iter().enumerate() {
// Compute each axis' offset: use +offset if the bit is set, 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).
pub fn get_voxel_at(&self, x: f32, y: f32, z: f32) -> Option<&Voxel> {
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()
}
/// Performs a raycast against the octree and returns the first intersected voxel.
pub fn raycast(&self, ray: &Ray) -> Option<(f32, f32, f32, u32, Vec3)> {
// 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),
max: Vec3::new(half_size as f32, half_size as f32, half_size as f32),
};
self.raycast_recursive(
&self.root,
ray,
&root_bounds,
0,
)
self.raycast_recursive(&self.root, ray, &root_bounds, 0)
}
fn raycast_recursive(
@ -435,7 +442,8 @@ impl SparseVoxelOctree {
let mut hits = Vec::new();
for (i, child) in children.iter().enumerate() {
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);
}
}
@ -445,11 +453,11 @@ impl SparseVoxelOctree {
let dist_a = ((a.0 as f32 - ray.origin.x).powi(2)
+ (a.1 as f32 - ray.origin.y).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)
+ (b.1 as f32 - ray.origin.y).powi(2)
+ (b.2 as f32 - ray.origin.z).powi(2))
.sqrt();
.sqrt();
dist_a.partial_cmp(&dist_b).unwrap()
});
return Some(hits[0]);
@ -462,16 +470,15 @@ impl SparseVoxelOctree {
/// Save the octree to a file using bincode serialization.
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> io::Result<()> {
let data = bincode::serialize(self)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let data = bincode::serialize(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
std::fs::write(path, data)
}
/// Load an octree from a file and rebuild runtime caches.
pub fn load_from_file<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let bytes = std::fs::read(path)?;
let mut tree: Self = bincode::deserialize(&bytes)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
let mut tree: Self =
bincode::deserialize(&bytes).map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
tree.rebuild_cache();
Ok(tree)
}
@ -481,7 +488,7 @@ impl SparseVoxelOctree {
self.dirty.clear();
self.dirty_chunks.clear();
self.occupied_chunks.clear();
let voxels = Self::collect_voxels_from_node(&self.root, self.size);
for (pos, _voxel, _depth) in voxels {
let key = chunk_key_from_world(self, pos);
@ -489,4 +496,3 @@ impl SparseVoxelOctree {
}
}
}

View File

@ -1,32 +1,10 @@
use bevy::color::Color;
use bevy::prelude::*;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet, VecDeque};
fn serialize_color<S>(color: &Color, serializer: S) -> Result<S::Ok, S::Error>
where
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.
/// Represents a single voxel with texture indices for each face.
#[derive(Debug, Clone, Copy, Component, PartialEq, Serialize, Deserialize)]
pub struct Voxel {
#[serde(
serialize_with = "serialize_color",
deserialize_with = "deserialize_color"
)]
pub color: Color,
/// Indexes into the texture atlas for the six faces in the order
/// left, right, bottom, top, back, front.
#[serde(default)]
@ -35,10 +13,7 @@ pub struct Voxel {
impl Default for Voxel {
fn default() -> Self {
Self {
color: Color::WHITE,
textures: [0; 6],
}
Self { textures: [0; 6] }
}
}
@ -90,8 +65,8 @@ impl OctreeNode {
impl Voxel {
/// Creates a new empty octree node.
pub fn new(color: Color, textures: [usize; 6]) -> Self {
Self { color, textures }
pub fn new(textures: [usize; 6]) -> Self {
Self { textures }
}
}

View File

@ -1,12 +1,11 @@
use std::path::Path;
use bevy::prelude::*;
use crate::plugins::environment::systems::camera_system::CameraController;
use crate::plugins::environment::systems::voxels::octree;
use crate::plugins::environment::systems::voxels::structure::*;
use bevy::prelude::*;
use std::path::Path;
///TODO
pub fn voxel_system(
keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_button_input: Res<ButtonInput<MouseButton>>,
mut octree_query: Query<&mut SparseVoxelOctree>,
@ -14,32 +13,33 @@ pub fn voxel_system(
mut query: Query<(&mut Transform, &mut CameraController)>,
mut windows: Query<&mut Window>,
) {
let Ok(mut window) = windows.get_single_mut() else { return };
let Ok((mut transform, _)) = query.get_single_mut() else { return };
let Ok(mut window) = windows.get_single_mut() else {
return;
};
let Ok((mut transform, _)) = query.get_single_mut() else {
return;
};
// =======================
// 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() {
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() {
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() {
octree.insert(
transform.translation,
Voxel::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]),
);
octree.insert(transform.translation, Voxel::new([0; 6]));
}
}
if keyboard_input.just_pressed(KeyCode::F4){
if keyboard_input.just_pressed(KeyCode::F4) {
let path = Path::new("octree.bin");
for octree in octree_query.iter() {
if let Err(e) = octree.save_to_file(path) {
@ -47,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");
if path.exists() {
let path = Path::new("octree.bin");
@ -60,17 +60,18 @@ pub fn voxel_system(
}
}
}
}
}*/
// =======================
// 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)
if let Some(_) = window.cursor_position() {
// Set the ray direction to the camera's forward vector
@ -82,44 +83,33 @@ pub fn voxel_system(
direction: ray_direction,
};
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) {
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 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
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
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 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)
// 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
octree.insert(
offset_position,
Voxel::new(Color::srgb(1.0, 0.0, 0.0), [0; 6]),
);
octree.insert(offset_position, Voxel::new([0; 6]));
}
}
}
}
}
}
}