remove large transform system
This commit is contained in:
parent
3845538d92
commit
59553ec7e8
@ -12,3 +12,5 @@ bevy_reflect = "0.15.0"
|
|||||||
bevy_render = "0.15.0"
|
bevy_render = "0.15.0"
|
||||||
bevy_window = "0.15.0"
|
bevy_window = "0.15.0"
|
||||||
egui_dock = "0.14.0"
|
egui_dock = "0.14.0"
|
||||||
|
bytemuck = "1.13"
|
||||||
|
bevy_mod_debugdump = "0.12.1"
|
||||||
@ -1,8 +1,8 @@
|
|||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy_egui::EguiSet;
|
use bevy_egui::EguiSet;
|
||||||
|
use bevy_render::extract_resource::ExtractResourcePlugin;
|
||||||
use crate::helper::debug_gizmos::debug_gizmos;
|
use crate::helper::debug_gizmos::debug_gizmos;
|
||||||
use crate::helper::egui_dock::{reset_camera_viewport, set_camera_viewport, set_gizmo_mode, show_ui_system, UiState};
|
use crate::helper::egui_dock::{reset_camera_viewport, set_camera_viewport, set_gizmo_mode, show_ui_system, UiState};
|
||||||
use crate::helper::large_transform::DoubleTransform;
|
|
||||||
|
|
||||||
pub struct AppPlugin;
|
pub struct AppPlugin;
|
||||||
|
|
||||||
@ -26,7 +26,6 @@ impl Plugin for AppPlugin {
|
|||||||
app.add_plugins(crate::plugins::ui_plugin::UiPlugin);
|
app.add_plugins(crate::plugins::ui_plugin::UiPlugin);
|
||||||
|
|
||||||
app.add_plugins(crate::plugins::environment_plugin::EnvironmentPlugin);
|
app.add_plugins(crate::plugins::environment_plugin::EnvironmentPlugin);
|
||||||
app.add_plugins(crate::plugins::large_transform_plugin::LargeTransformPlugin);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -50,7 +49,6 @@ impl Plugin for AppPlugin {
|
|||||||
app.add_systems(Update, set_gizmo_mode);
|
app.add_systems(Update, set_gizmo_mode);
|
||||||
app.register_type::<Option<Handle<Image>>>();
|
app.register_type::<Option<Handle<Image>>>();
|
||||||
app.register_type::<AlphaMode>();
|
app.register_type::<AlphaMode>();
|
||||||
app.register_type::<DoubleTransform>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,103 +0,0 @@
|
|||||||
use bevy::math::{DQuat, DVec3};
|
|
||||||
use bevy::prelude::{Commands, Component, GlobalTransform, Query, Reflect, Res, ResMut, Resource, Transform, With, Without};
|
|
||||||
use bevy_render::prelude::Camera;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Resource, Reflect,Default)]
|
|
||||||
pub struct WorldOffset(pub DVec3);
|
|
||||||
|
|
||||||
#[derive(Component, Default,Reflect)]
|
|
||||||
pub struct DoubleTransform {
|
|
||||||
pub translation: DVec3,
|
|
||||||
pub rotation: DQuat,
|
|
||||||
pub scale: DVec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DoubleTransform {
|
|
||||||
pub fn new(translation: DVec3, rotation: DQuat, scale: DVec3) -> Self {
|
|
||||||
Self {
|
|
||||||
translation,
|
|
||||||
rotation,
|
|
||||||
scale,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a unit vector pointing "forward" (negative-Z) based on the rotation
|
|
||||||
pub fn forward(&self) -> DVec3 {
|
|
||||||
self.rotation * DVec3::new(0.0, 0.0, -1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a unit vector pointing "right" (positive-X)
|
|
||||||
pub fn right(&self) -> DVec3 {
|
|
||||||
self.rotation * DVec3::new(1.0, 0.0, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a unit vector pointing "up" (positive-Y)
|
|
||||||
pub fn up(&self) -> DVec3 {
|
|
||||||
self.rotation * DVec3::new(0.0, 1.0, 0.0)
|
|
||||||
}
|
|
||||||
pub fn down(&self) -> DVec3 {
|
|
||||||
self.rotation * DVec3::new(0.0, -1.0, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_true_world_position(
|
|
||||||
offset: &WorldOffset,
|
|
||||||
transform: &DoubleTransform,
|
|
||||||
) -> DVec3 {
|
|
||||||
transform.translation + offset.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(mut commands: Commands) {
|
|
||||||
commands
|
|
||||||
.spawn((
|
|
||||||
DoubleTransform {
|
|
||||||
translation: DVec3::new(100_000.0, 0.0, 0.0),
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
// The standard Bevy Transform (will be updated each frame)
|
|
||||||
Transform::default(),
|
|
||||||
GlobalTransform::default(),
|
|
||||||
// Add your mesh/visibility components, etc.
|
|
||||||
));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn update_render_transform_system(
|
|
||||||
camera_query: Query<&DoubleTransform, With<Camera>>,
|
|
||||||
mut query: Query<(&DoubleTransform, &mut Transform), Without<Camera>>,
|
|
||||||
) {
|
|
||||||
let camera_double_tf = camera_query.single();
|
|
||||||
// The camera offset in double-precision
|
|
||||||
let camera_pos = camera_double_tf.translation;
|
|
||||||
|
|
||||||
for (double_tf, mut transform) in query.iter_mut() {
|
|
||||||
// relative position (double precision)
|
|
||||||
let relative_pos = double_tf.translation - camera_pos;
|
|
||||||
transform.translation = relative_pos.as_vec3(); // convert f64 -> f32
|
|
||||||
transform.rotation = double_tf.rotation.as_quat(); // f64 -> f32
|
|
||||||
transform.scale = double_tf.scale.as_vec3(); // f64 -> f32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn floating_origin_system(
|
|
||||||
mut query: Query<&mut DoubleTransform, Without<Camera>>,
|
|
||||||
mut camera_query: Query<&mut DoubleTransform, With<Camera>>,
|
|
||||||
mut offset: ResMut<WorldOffset>,
|
|
||||||
) {
|
|
||||||
let mut camera_tf = camera_query.single_mut();
|
|
||||||
let camera_pos = camera_tf.translation;
|
|
||||||
|
|
||||||
// If the camera moves any distance, recenter it
|
|
||||||
if camera_pos.length() > 0.001 {
|
|
||||||
offset.0 += camera_pos;
|
|
||||||
// Shift everything so camera ends up back at zero
|
|
||||||
for mut dtf in query.iter_mut() {
|
|
||||||
dtf.translation -= camera_pos;
|
|
||||||
}
|
|
||||||
camera_tf.translation = DVec3::ZERO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +1,2 @@
|
|||||||
pub mod egui_dock;
|
pub mod egui_dock;
|
||||||
pub mod debug_gizmos;
|
pub mod debug_gizmos;
|
||||||
pub mod large_transform;
|
|
||||||
@ -1,22 +1,19 @@
|
|||||||
use std::fs::create_dir;
|
|
||||||
use bevy::app::{App, Plugin, PreUpdate, Startup};
|
use bevy::app::{App, Plugin, Startup};
|
||||||
use bevy::color::palettes::css::{GRAY, RED};
|
use bevy::color::palettes::basic::{GREEN, YELLOW};
|
||||||
use bevy::prelude::{default, Color, Commands, GlobalTransform, IntoSystemConfigs, Query, Res, Update};
|
use bevy::color::palettes::css::RED;
|
||||||
use bevy_render::prelude::ClearColor;
|
use bevy::prelude::*;
|
||||||
use crate::app::InspectorVisible;
|
|
||||||
use crate::systems::environment_system::*;
|
use crate::systems::environment_system::*;
|
||||||
use crate::systems::voxels::structure::{ChunkEntities, SparseVoxelOctree, Voxel};
|
use crate::systems::voxels::structure::{OctreeNode, SparseVoxelOctree};
|
||||||
|
|
||||||
pub struct EnvironmentPlugin;
|
pub struct EnvironmentPlugin;
|
||||||
impl Plugin for EnvironmentPlugin {
|
impl Plugin for EnvironmentPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
/*app.insert_resource(ClearColor(Color::from(GRAY)));*/
|
|
||||||
app.init_resource::<ChunkEntities>();
|
|
||||||
app.add_systems(Startup, (setup).chain());
|
app.add_systems(Startup, (setup).chain());
|
||||||
app.add_systems(Update, (crate::systems::voxels::rendering::render,crate::systems::voxels::debug::visualize_octree.run_if(should_visualize_octree), crate::systems::voxels::debug::draw_grid.run_if(should_draw_grid), crate::systems::voxels::debug::debug_draw_chunks_system.run_if(should_visualize_chunks)).chain());
|
app.add_systems(Update, (crate::systems::voxels::rendering::render,crate::systems::voxels::debug::visualize_octree.run_if(should_visualize_octree), crate::systems::voxels::debug::draw_grid.run_if(should_draw_grid)).chain());
|
||||||
|
|
||||||
app.register_type::<SparseVoxelOctree>();
|
app.register_type::<SparseVoxelOctree>();
|
||||||
app.register_type::<ChunkEntities>();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,3 +32,4 @@ fn should_draw_grid(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
|||||||
fn should_visualize_chunks(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
fn should_visualize_chunks(octree_query: Query<&SparseVoxelOctree>,) -> bool {
|
||||||
octree_query.single().show_chunks
|
octree_query.single().show_chunks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,17 +0,0 @@
|
|||||||
|
|
||||||
use bevy::app::{App, Plugin, PreUpdate, Startup, Update};
|
|
||||||
use bevy::prelude::IntoSystemConfigs;
|
|
||||||
use crate::helper::large_transform::*;
|
|
||||||
|
|
||||||
pub struct LargeTransformPlugin;
|
|
||||||
impl Plugin for LargeTransformPlugin {
|
|
||||||
fn build(&self, app: &mut App) {
|
|
||||||
|
|
||||||
app.insert_resource(WorldOffset::default());
|
|
||||||
app.add_systems(Startup, setup);
|
|
||||||
app.add_systems(Update, floating_origin_system.after(crate::systems::camera_system::camera_controller_system));
|
|
||||||
app.add_systems(Update, update_render_transform_system.after(floating_origin_system));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,5 +1,4 @@
|
|||||||
|
|
||||||
pub mod large_transform_plugin;
|
|
||||||
pub mod camera_plugin;
|
pub mod camera_plugin;
|
||||||
pub mod ui_plugin;
|
pub mod ui_plugin;
|
||||||
pub mod environment_plugin;
|
pub mod environment_plugin;
|
||||||
@ -5,7 +5,6 @@ use bevy::prelude::*;
|
|||||||
use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
|
use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode};
|
||||||
use bevy_window::CursorGrabMode;
|
use bevy_window::CursorGrabMode;
|
||||||
use crate::helper::egui_dock::MainCamera;
|
use crate::helper::egui_dock::MainCamera;
|
||||||
use crate::helper::large_transform::{DoubleTransform, WorldOffset};
|
|
||||||
use crate::InspectorVisible;
|
use crate::InspectorVisible;
|
||||||
use crate::systems::voxels::structure::{Ray, SparseVoxelOctree, Voxel};
|
use crate::systems::voxels::structure::{Ray, SparseVoxelOctree, Voxel};
|
||||||
|
|
||||||
@ -33,12 +32,7 @@ pub fn setup(mut commands: Commands,){
|
|||||||
|
|
||||||
|
|
||||||
commands.spawn((
|
commands.spawn((
|
||||||
DoubleTransform {
|
Transform::from_xyz(0.0, 0.0, 10.0), // initial f32
|
||||||
translation: DVec3::new(0.0, 0.0, 10.0),
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
Transform::from_xyz(0.0, 5.0, 10.0), // initial f32
|
|
||||||
GlobalTransform::default(),
|
GlobalTransform::default(),
|
||||||
Camera3d::default(),
|
Camera3d::default(),
|
||||||
Projection::from(PerspectiveProjection{
|
Projection::from(PerspectiveProjection{
|
||||||
@ -64,13 +58,12 @@ pub fn camera_controller_system(
|
|||||||
// Here we query for BOTH DoubleTransform (f64) and Transform (f32).
|
// Here we query for BOTH DoubleTransform (f64) and Transform (f32).
|
||||||
// We'll update DoubleTransform for the "true" position
|
// We'll update DoubleTransform for the "true" position
|
||||||
// and keep Transform in sync for rendering.a
|
// and keep Transform in sync for rendering.a
|
||||||
mut query: Query<(&mut DoubleTransform, &mut Transform, &mut CameraController)>,
|
mut query: Query<(&mut Transform, &mut CameraController)>,
|
||||||
mut octree_query: Query<&mut SparseVoxelOctree>,
|
mut octree_query: Query<&mut SparseVoxelOctree>,
|
||||||
mut app_exit_events: EventWriter<AppExit>,
|
mut app_exit_events: EventWriter<AppExit>,
|
||||||
world_offset: Res<WorldOffset>,
|
|
||||||
) {
|
) {
|
||||||
let mut window = windows.single_mut();
|
let mut window = windows.single_mut();
|
||||||
let (mut double_tf, mut render_tf, mut controller) = query.single_mut();
|
let (mut transform, mut controller) = query.single_mut();
|
||||||
|
|
||||||
// ====================
|
// ====================
|
||||||
// 1) Handle Mouse Look
|
// 1) Handle Mouse Look
|
||||||
@ -87,10 +80,10 @@ pub fn camera_controller_system(
|
|||||||
let pitch_radians = controller.pitch.to_radians();
|
let pitch_radians = controller.pitch.to_radians();
|
||||||
|
|
||||||
// Build a double-precision quaternion from those angles
|
// Build a double-precision quaternion from those angles
|
||||||
let rot_yaw = DQuat::from_axis_angle(DVec3::Y, yaw_radians as f64);
|
let rot_yaw = Quat::from_axis_angle(Vec3::Y, yaw_radians);
|
||||||
let rot_pitch = DQuat::from_axis_angle(DVec3::X, -pitch_radians as f64);
|
let rot_pitch = Quat::from_axis_angle(Vec3::X, -pitch_radians);
|
||||||
|
|
||||||
double_tf.rotation = rot_yaw * rot_pitch;
|
transform.rotation = rot_yaw * rot_pitch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,30 +102,30 @@ pub fn camera_controller_system(
|
|||||||
// ====================
|
// ====================
|
||||||
// 3) Handle Keyboard Movement (WASD, Space, Shift)
|
// 3) Handle Keyboard Movement (WASD, Space, Shift)
|
||||||
// ====================
|
// ====================
|
||||||
let mut direction = DVec3::ZERO;
|
let mut direction = Vec3::ZERO;
|
||||||
|
|
||||||
// Forward/Back
|
// Forward/Back
|
||||||
if keyboard_input.pressed(KeyCode::KeyW) {
|
if keyboard_input.pressed(KeyCode::KeyW) {
|
||||||
direction += double_tf.forward();
|
direction += transform.forward().as_vec3();
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::KeyS) {
|
if keyboard_input.pressed(KeyCode::KeyS) {
|
||||||
direction -= double_tf.forward();
|
direction -= transform.forward().as_vec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Left/Right
|
// Left/Right
|
||||||
if keyboard_input.pressed(KeyCode::KeyA) {
|
if keyboard_input.pressed(KeyCode::KeyA) {
|
||||||
direction -= double_tf.right();
|
direction -= transform.right().as_vec3();
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::KeyD) {
|
if keyboard_input.pressed(KeyCode::KeyD) {
|
||||||
direction += double_tf.right();
|
direction += transform.right().as_vec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Up/Down
|
// Up/Down
|
||||||
if keyboard_input.pressed(KeyCode::Space) {
|
if keyboard_input.pressed(KeyCode::Space) {
|
||||||
direction += double_tf.up();
|
direction += transform.up().as_vec3();
|
||||||
}
|
}
|
||||||
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight) {
|
if keyboard_input.pressed(KeyCode::ShiftLeft) || keyboard_input.pressed(KeyCode::ShiftRight) {
|
||||||
direction -= double_tf.up();
|
direction -= transform.up().as_vec3();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize direction if needed
|
// Normalize direction if needed
|
||||||
@ -143,7 +136,7 @@ pub fn camera_controller_system(
|
|||||||
// Apply movement in double-precision
|
// Apply movement in double-precision
|
||||||
let delta_seconds = time.delta_secs_f64();
|
let delta_seconds = time.delta_secs_f64();
|
||||||
let distance = controller.speed as f64 * delta_seconds;
|
let distance = controller.speed as f64 * delta_seconds;
|
||||||
double_tf.translation += direction * distance;
|
transform.translation += direction * distance as f32;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -185,7 +178,7 @@ pub fn camera_controller_system(
|
|||||||
}
|
}
|
||||||
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(double_tf.translation.x as f64, double_tf.translation.y as f64, double_tf.translation.z as f64, Voxel::new(Color::srgb(1.0, 0.0, 0.0)));
|
octree.insert(transform.translation.x, transform.translation.y, transform.translation.z, Voxel::new(Color::srgb(1.0, 0.0, 0.0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,12 +191,12 @@ pub fn camera_controller_system(
|
|||||||
// 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
|
||||||
let ray_origin = world_offset.0 + double_tf.translation;
|
let ray_origin = transform.translation;
|
||||||
let ray_direction = double_tf.forward().normalize();
|
let ray_direction = transform.forward().normalize();
|
||||||
|
|
||||||
let ray = Ray {
|
let ray = Ray {
|
||||||
origin: ray_origin.as_vec3(),
|
origin: ray_origin,
|
||||||
direction: ray_direction.as_vec3(),
|
direction: ray_direction,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -229,17 +222,6 @@ pub fn camera_controller_system(
|
|||||||
BLUE,
|
BLUE,
|
||||||
);*/
|
);*/
|
||||||
|
|
||||||
let chunk = octree.compute_chunk_coords(hit_x, hit_y, hit_z);
|
|
||||||
|
|
||||||
info!("Chunk Hit: {},{},{}", chunk.0, chunk.1, chunk.2);
|
|
||||||
|
|
||||||
if let Some(chunk_node) = octree.get_chunk_node(hit_x,hit_y,hit_z) {
|
|
||||||
let has_volume = octree.has_volume(chunk_node);
|
|
||||||
|
|
||||||
info!("Chunk Has Volume: {}", has_volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -258,9 +240,9 @@ pub fn camera_controller_system(
|
|||||||
|
|
||||||
// Align the offset position to the center of the nearest voxel
|
// Align the offset position to the center of the nearest voxel
|
||||||
let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth(
|
let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth(
|
||||||
offset_position.x as f64,
|
offset_position.x,
|
||||||
offset_position.y as f64,
|
offset_position.y,
|
||||||
offset_position.z as f64,
|
offset_position.z,
|
||||||
depth,
|
depth,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -278,9 +260,9 @@ pub fn camera_controller_system(
|
|||||||
|
|
||||||
// Align the offset position to the center of the nearest voxel
|
// Align the offset position to the center of the nearest voxel
|
||||||
let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth(
|
let (new_voxel_x, new_voxel_y, new_voxel_z) = octree.normalize_to_voxel_at_depth(
|
||||||
offset_position.x as f64,
|
offset_position.x,
|
||||||
offset_position.y as f64,
|
offset_position.y,
|
||||||
offset_position.z as f64,
|
offset_position.z,
|
||||||
depth,
|
depth,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -305,14 +287,6 @@ pub fn camera_controller_system(
|
|||||||
app_exit_events.send(Default::default());
|
app_exit_events.send(Default::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================
|
|
||||||
// 8) Convert DoubleTransform -> Bevy Transform
|
|
||||||
// =============================================
|
|
||||||
// The final step is to update the f32 `Transform` that Bevy uses for rendering.
|
|
||||||
// This ensures the camera is visually placed at the correct position.
|
|
||||||
render_tf.translation = double_tf.translation.as_vec3();
|
|
||||||
render_tf.rotation = double_tf.rotation.as_quat();
|
|
||||||
render_tf.scale = double_tf.scale.as_vec3();
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -2,7 +2,6 @@ use bevy::color::palettes::basic::*;
|
|||||||
use bevy::color::palettes::css::{BEIGE, MIDNIGHT_BLUE, ORANGE, ORANGE_RED, SEA_GREEN};
|
use bevy::color::palettes::css::{BEIGE, MIDNIGHT_BLUE, ORANGE, ORANGE_RED, SEA_GREEN};
|
||||||
use bevy::math::*;
|
use bevy::math::*;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use crate::helper::large_transform::DoubleTransform;
|
|
||||||
use crate::systems::voxels::structure::{SparseVoxelOctree, Voxel};
|
use crate::systems::voxels::structure::{SparseVoxelOctree, Voxel};
|
||||||
/*pub fn setup(
|
/*pub fn setup(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
@ -15,7 +14,7 @@ use crate::systems::voxels::structure::{SparseVoxelOctree, Voxel};
|
|||||||
DoubleTransform {
|
DoubleTransform {
|
||||||
translation: DVec3::new(0.0, 0.0, 10.0),
|
translation: DVec3::new(0.0, 0.0, 10.0),
|
||||||
// rotate -90 degrees around X so the circle is on the XY plane
|
// rotate -90 degrees around X so the circle is on the XY plane
|
||||||
rotation: DQuat::from_euler(EulerRot::XYZ, -std::f64::consts::FRAC_PI_2, 0.0, 0.0),
|
rotation: DQuat::from_euler(EulerRot::XYZ, -std::f32::consts::FRAC_PI_2, 0.0, 0.0),
|
||||||
scale: DVec3::ONE,
|
scale: DVec3::ONE,
|
||||||
},
|
},
|
||||||
// Bevy's transform components
|
// Bevy's transform components
|
||||||
@ -65,7 +64,7 @@ pub fn setup(mut commands: Commands,) {
|
|||||||
|
|
||||||
let voxels_per_unit = 16;
|
let voxels_per_unit = 16;
|
||||||
let unit_size = 1.0; // 1 unit in your coordinate space
|
let unit_size = 1.0; // 1 unit in your coordinate space
|
||||||
let voxel_size = unit_size / voxels_per_unit as f64;
|
let voxel_size = unit_size / voxels_per_unit as f32;
|
||||||
|
|
||||||
/*//Octree
|
/*//Octree
|
||||||
let octree_base_size = 64.0;
|
let octree_base_size = 64.0;
|
||||||
@ -76,7 +75,7 @@ pub fn setup(mut commands: Commands,) {
|
|||||||
let octree_depth = 10;
|
let octree_depth = 10;
|
||||||
|
|
||||||
|
|
||||||
let mut octree = SparseVoxelOctree::new(octree_depth, octree_base_size, false, false, false);
|
let mut octree = SparseVoxelOctree::new(octree_depth, octree_base_size as f32, false, false, false);
|
||||||
|
|
||||||
|
|
||||||
let color = Color::rgb(0.2, 0.8, 0.2);
|
let color = Color::rgb(0.2, 0.8, 0.2);
|
||||||
@ -90,11 +89,6 @@ pub fn setup(mut commands: Commands,) {
|
|||||||
|
|
||||||
commands.spawn(
|
commands.spawn(
|
||||||
(
|
(
|
||||||
DoubleTransform {
|
|
||||||
translation: DVec3::new(0.0, 0.0, 0.0),
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
Transform::default(),
|
Transform::default(),
|
||||||
octree
|
octree
|
||||||
)
|
)
|
||||||
@ -104,11 +98,6 @@ pub fn setup(mut commands: Commands,) {
|
|||||||
commands.spawn((
|
commands.spawn((
|
||||||
Transform::default(),
|
Transform::default(),
|
||||||
GlobalTransform::default(),
|
GlobalTransform::default(),
|
||||||
DoubleTransform {
|
|
||||||
translation: DVec3::new(0.0, 0.0, 0.0),
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
PointLight {
|
PointLight {
|
||||||
shadows_enabled: true,
|
shadows_enabled: true,
|
||||||
..default()
|
..default()
|
||||||
@ -125,30 +114,30 @@ pub fn setup(mut commands: Commands,) {
|
|||||||
/// - `voxel_step`: how finely to sample the sphere in the x/y/z loops
|
/// - `voxel_step`: how finely to sample the sphere in the x/y/z loops
|
||||||
fn generate_voxel_sphere(
|
fn generate_voxel_sphere(
|
||||||
octree: &mut SparseVoxelOctree,
|
octree: &mut SparseVoxelOctree,
|
||||||
planet_radius: f64,
|
planet_radius: i32,
|
||||||
voxel_color: Color,
|
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 as i64);
|
let min = -planet_radius;
|
||||||
let max = planet_radius as i64;
|
let max = planet_radius;
|
||||||
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
let step = octree.get_spacing_at_depth(octree.max_depth);
|
||||||
|
|
||||||
for ix in min..=max {
|
for ix in min..=max {
|
||||||
let x = ix as f64;
|
let x = ix;
|
||||||
for iy in min..=max {
|
for iy in min..=max {
|
||||||
let y = iy as f64;
|
let y = iy;
|
||||||
for iz in min..=max {
|
for iz in min..=max {
|
||||||
let z = iz as f64;
|
let z = iz;
|
||||||
|
|
||||||
// Check if within sphere of radius `planet_radius`
|
// Check if within sphere of radius `planet_radius`
|
||||||
let dist2 = x * x + y * y + z * z;
|
let dist2 = x * x + y * y + z * z;
|
||||||
if dist2 <= planet_radius * planet_radius {
|
if dist2 <= planet_radius * planet_radius {
|
||||||
// Convert (x,y,z) to world space, stepping by `voxel_step`.
|
// Convert (x,y,z) to world space, stepping by `voxel_step`.
|
||||||
let wx = x * step;
|
let wx = x as f32 * step;
|
||||||
let wy = y * step;
|
let wy = y as f32 * step;
|
||||||
let wz = z * step;
|
let wz = z as f32 * step;
|
||||||
|
|
||||||
// Insert the voxel
|
// Insert the voxel
|
||||||
let voxel = Voxel {
|
let voxel = Voxel {
|
||||||
@ -180,11 +169,11 @@ fn generate_voxel_rect(
|
|||||||
|
|
||||||
// Triple-nested loop for each voxel in [0..16, 0..256, 0..16]
|
// Triple-nested loop for each voxel in [0..16, 0..256, 0..16]
|
||||||
for ix in 0..size_x {
|
for ix in 0..size_x {
|
||||||
let x = ix as f64;
|
let x = ix as f32;
|
||||||
for iy in 0..size_y {
|
for iy in 0..size_y {
|
||||||
let y = iy as f64;
|
let y = iy as f32;
|
||||||
for iz in 0..size_z {
|
for iz in 0..size_z {
|
||||||
let z = iz as f64;
|
let z = iz as f32;
|
||||||
|
|
||||||
// Convert (x,y,z) to world coordinates
|
// Convert (x,y,z) to world coordinates
|
||||||
let wx = x * step;
|
let wx = x * step;
|
||||||
@ -216,9 +205,9 @@ fn generate_large_plane(
|
|||||||
// Double-nested loop for each voxel in [0..width, 0..depth],
|
// Double-nested loop for each voxel in [0..width, 0..depth],
|
||||||
// with y=0.
|
// with y=0.
|
||||||
for ix in 0..width {
|
for ix in 0..width {
|
||||||
let x = ix as f64;
|
let x = ix as f32;
|
||||||
for iz in 0..depth {
|
for iz in 0..depth {
|
||||||
let z = iz as f64;
|
let z = iz as f32;
|
||||||
// y is always 0.
|
// y is always 0.
|
||||||
let y = 0.0;
|
let y = 0.0;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
use bevy::asset::AssetServer;
|
use bevy::asset::AssetServer;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use crate::helper::large_transform::{DoubleTransform, WorldOffset};
|
|
||||||
use crate::systems::camera_system::CameraController;
|
use crate::systems::camera_system::CameraController;
|
||||||
use crate::systems::voxels::structure::{SparseVoxelOctree};
|
use crate::systems::voxels::structure::{SparseVoxelOctree};
|
||||||
|
|
||||||
@ -44,47 +43,23 @@ pub fn update(
|
|||||||
// Query the camera controller so we can see its speed
|
// Query the camera controller so we can see its speed
|
||||||
query_camera_controller: Query<&CameraController>,
|
query_camera_controller: Query<&CameraController>,
|
||||||
// We also query for the camera's f32 `Transform` and the double `DoubleTransform`
|
// We also query for the camera's f32 `Transform` and the double `DoubleTransform`
|
||||||
camera_query: Query<(&Transform, &DoubleTransform, &Camera)>,
|
camera_query: Query<(&Transform, &Camera)>,
|
||||||
// The global offset resource, if you have one
|
|
||||||
world_offset: Res<WorldOffset>,
|
|
||||||
// The chunk-size logic from the octree, so we can compute chunk coords
|
|
||||||
octree_query: Query<&SparseVoxelOctree>, // or get_single if there's only one octree
|
|
||||||
|
|
||||||
// The UI text entity
|
// The UI text entity
|
||||||
mut query_text: Query<&mut Text, With<SpeedDisplay>>,
|
mut query_text: Query<&mut Text, With<SpeedDisplay>>,
|
||||||
) {
|
) {
|
||||||
let camera_controller = query_camera_controller.single();
|
let camera_controller = query_camera_controller.single();
|
||||||
let (transform, double_tf, _camera) = camera_query.single();
|
let (transform, _camera) = camera_query.single();
|
||||||
let mut text = query_text.single_mut();
|
let mut text = query_text.single_mut();
|
||||||
|
|
||||||
// The global double position: offset + camera's double translation
|
|
||||||
let global_pos = world_offset.0 + double_tf.translation;
|
|
||||||
|
|
||||||
// We'll attempt to get the octree so we can compute chunk coords
|
|
||||||
// If there's no octree, we just show "N/A".
|
|
||||||
/*let (chunk_cx, chunk_cy, chunk_cz) = if let Ok(octree) = octree_query.get_single() {
|
|
||||||
// 1) get voxel step
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
// 2) chunk world size
|
|
||||||
let chunk_world_size = CHUNK_SIZE as f64 * step;
|
|
||||||
// 3) compute chunk coords using global_pos
|
|
||||||
let cx = ((global_pos.x) / chunk_world_size).floor() as i32;
|
|
||||||
let cy = ((global_pos.y) / chunk_world_size).floor() as i32;
|
|
||||||
let cz = ((global_pos.z) / chunk_world_size).floor() as i32;
|
|
||||||
(cx, cy, cz)
|
|
||||||
} else {
|
|
||||||
(0, 0, 0) // or default
|
|
||||||
};*/
|
|
||||||
|
|
||||||
// Format the string to show speed, positions, and chunk coords
|
// Format the string to show speed, positions, and chunk coords
|
||||||
text.0 = format!(
|
text.0 = format!(
|
||||||
"\n Speed: {:.3}\n Position(f32): ({:.2},{:.2},{:.2})\n Position(f64): ({:.2},{:.2},{:.2})",
|
"\n Speed: {:.3}\n Position(f32): ({:.2},{:.2},{:.2})",
|
||||||
camera_controller.speed,
|
camera_controller.speed,
|
||||||
transform.translation.x,
|
transform.translation.x,
|
||||||
transform.translation.y,
|
transform.translation.y,
|
||||||
transform.translation.z,
|
transform.translation.z,
|
||||||
global_pos.x,
|
|
||||||
global_pos.y,
|
|
||||||
global_pos.z,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,29 +1,26 @@
|
|||||||
use bevy::color::palettes::basic::{BLACK, RED, YELLOW};
|
use bevy::color::palettes::basic::{BLACK, RED, YELLOW};
|
||||||
use bevy::color::palettes::css::GREEN;
|
use bevy::color::palettes::css::GREEN;
|
||||||
use bevy::math::{DQuat, DVec3, Vec3};
|
use bevy::math::{DQuat, Vec3};
|
||||||
use bevy::pbr::wireframe::Wireframe;
|
use bevy::pbr::wireframe::Wireframe;
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::render::mesh::{Indices, PrimitiveTopology};
|
use bevy::render::mesh::{Indices, PrimitiveTopology};
|
||||||
use bevy::render::render_asset::RenderAssetUsages;
|
use bevy::render::render_asset::RenderAssetUsages;
|
||||||
use bevy_egui::egui::emath::Numeric;
|
use bevy_egui::egui::emath::Numeric;
|
||||||
use bevy_render::prelude::*;
|
use crate::systems::voxels::structure::{ OctreeNode, SparseVoxelOctree};
|
||||||
use crate::helper::large_transform::DoubleTransform;
|
|
||||||
use crate::systems::voxels::structure::{ChunkEntities, OctreeNode, SparseVoxelOctree};
|
|
||||||
|
|
||||||
pub fn visualize_octree(
|
pub fn visualize_octree(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
camera_query: Query<&DoubleTransform, With<Camera>>,
|
camera_query: Query<&Transform, With<Camera>>,
|
||||||
octree_query: Query<(&SparseVoxelOctree, &DoubleTransform)>,
|
octree_query: Query<(&SparseVoxelOctree, &Transform)>,
|
||||||
) {
|
) {
|
||||||
let camera_tf = camera_query.single(); // your "real" camera position in double precision
|
let camera_tf = camera_query.single(); // your "real" camera position in double precision
|
||||||
let camera_pos = camera_tf.translation; // DVec3
|
let camera_pos = camera_tf.translation; // DVec3
|
||||||
|
|
||||||
for (octree, octree_tf) in octree_query.iter() {
|
for (octree, octree_tf) in octree_query.iter() {
|
||||||
let octree_world_pos = octree_tf.translation;
|
|
||||||
visualize_recursive(
|
visualize_recursive(
|
||||||
&mut gizmos,
|
&mut gizmos,
|
||||||
&octree.root,
|
&octree.root,
|
||||||
octree_world_pos, // octree’s root center
|
octree_tf.translation, // octree’s root center
|
||||||
octree.size,
|
octree.size,
|
||||||
octree.max_depth,
|
octree.max_depth,
|
||||||
camera_pos,
|
camera_pos,
|
||||||
@ -34,10 +31,10 @@ pub fn visualize_octree(
|
|||||||
fn visualize_recursive(
|
fn visualize_recursive(
|
||||||
gizmos: &mut Gizmos,
|
gizmos: &mut Gizmos,
|
||||||
node: &OctreeNode,
|
node: &OctreeNode,
|
||||||
node_center: DVec3,
|
node_center: Vec3,
|
||||||
node_size: f64,
|
node_size: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
camera_pos: DVec3,
|
camera_pos: Vec3,
|
||||||
) {
|
) {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
return;
|
return;
|
||||||
@ -46,26 +43,30 @@ fn visualize_recursive(
|
|||||||
// If you want to draw the bounding box of this node:
|
// If you want to draw the bounding box of this node:
|
||||||
/*let half = node_size as f32 * 0.5;*/
|
/*let half = node_size as f32 * 0.5;*/
|
||||||
// Convert double center -> local f32 position
|
// Convert double center -> local f32 position
|
||||||
let center_f32 = (node_center - camera_pos).as_vec3();
|
let center_f32 = (node_center - camera_pos);
|
||||||
|
|
||||||
// A quick approach: draw a wireframe cube by drawing lines for each edge
|
// A quick approach: draw a wireframe cube by drawing lines for each edge
|
||||||
// Or use "cuboid gizmo" methods in future bevy versions that might exist.
|
// Or use "cuboid gizmo" methods in future bevy versions that might exist.
|
||||||
/*draw_wire_cube(gizmos, center_f32, half, Color::YELLOW);*/
|
/*draw_wire_cube(gizmos, center_f32, half, Color::YELLOW);*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
gizmos.cuboid(
|
gizmos.cuboid(
|
||||||
Transform::from_translation(center_f32).with_scale(Vec3::splat(node_size as f32)),
|
Transform::from_translation(center_f32).with_scale(Vec3::splat(node_size)),
|
||||||
BLACK,
|
BLACK,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Recurse children
|
// Recurse children
|
||||||
if let Some(children) = &node.children {
|
if let Some(children) = &node.children {
|
||||||
let child_size = node_size / 2.0;
|
let child_size = node_size / 2.0;
|
||||||
|
|
||||||
|
|
||||||
for (i, child) in children.iter().enumerate() {
|
for (i, child) in children.iter().enumerate() {
|
||||||
let offset_x = if (i & 1) == 1 { child_size / 2.0 } else { -child_size / 2.0 };
|
let offset_x = if (i & 1) == 1 { child_size / 2.0 } else { -child_size / 2.0 };
|
||||||
let offset_y = if (i & 2) == 2 { child_size / 2.0 } else { -child_size / 2.0 };
|
let offset_y = if (i & 2) == 2 { child_size / 2.0 } else { -child_size / 2.0 };
|
||||||
let offset_z = if (i & 4) == 4 { child_size / 2.0 } else { -child_size / 2.0 };
|
let offset_z = if (i & 4) == 4 { child_size / 2.0 } else { -child_size / 2.0 };
|
||||||
|
|
||||||
let child_center = DVec3::new(
|
let child_center = Vec3::new(
|
||||||
node_center.x + offset_x,
|
node_center.x + offset_x,
|
||||||
node_center.y + offset_y,
|
node_center.y + offset_y,
|
||||||
node_center.z + offset_z,
|
node_center.z + offset_z,
|
||||||
@ -87,8 +88,8 @@ fn visualize_recursive(
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn draw_grid(
|
pub fn draw_grid(
|
||||||
mut gizmos: Gizmos,
|
mut gizmos: Gizmos,
|
||||||
camera_query: Query<&DoubleTransform, With<Camera>>,
|
camera_query: Query<&Transform, With<Camera>>,
|
||||||
octree_query: Query<(&SparseVoxelOctree, &DoubleTransform)>,
|
octree_query: Query<(&SparseVoxelOctree, &Transform)>,
|
||||||
) {
|
) {
|
||||||
// 1) Get the camera’s double transform for offset
|
// 1) Get the camera’s double transform for offset
|
||||||
let camera_tf = camera_query.single();
|
let camera_tf = camera_query.single();
|
||||||
@ -100,19 +101,19 @@ pub fn draw_grid(
|
|||||||
// 2) Octree’s double position
|
// 2) Octree’s double position
|
||||||
let octree_pos = octree_dtf.translation; // e.g. [100_000, 0, 0] in double space
|
let octree_pos = octree_dtf.translation; // e.g. [100_000, 0, 0] in double space
|
||||||
|
|
||||||
// 3) Compute spacing in f64
|
// 3) Compute spacing in f32
|
||||||
let grid_spacing = octree.get_spacing_at_depth(octree.max_depth) as f64;
|
let grid_spacing = octree.get_spacing_at_depth(octree.max_depth) as f32;
|
||||||
let grid_size = (octree.size / grid_spacing) as i32;
|
let grid_size = (octree.size / grid_spacing) as i32;
|
||||||
|
|
||||||
// 4) Start position in local "octree space"
|
// 4) Start position in local "octree space"
|
||||||
// We'll define the bounding region from [-size/2, +size/2]
|
// We'll define the bounding region from [-size/2, +size/2]
|
||||||
let half_size = octree.size * 0.5;
|
let half_size = octree.size * 0.5;
|
||||||
let start_position = -half_size; // f64
|
let start_position = -half_size; // f32
|
||||||
|
|
||||||
// 5) Loop over lines
|
// 5) Loop over lines
|
||||||
for i in 0..=grid_size {
|
for i in 0..=grid_size {
|
||||||
// i-th line offset
|
// i-th line offset
|
||||||
let offset = i as f64 * grid_spacing;
|
let offset = i as f32 * grid_spacing;
|
||||||
|
|
||||||
// a) Lines along Z
|
// a) Lines along Z
|
||||||
// from (start_position + offset, 0, start_position)
|
// from (start_position + offset, 0, start_position)
|
||||||
@ -120,15 +121,15 @@ pub fn draw_grid(
|
|||||||
{
|
{
|
||||||
let x = start_position + offset;
|
let x = start_position + offset;
|
||||||
let z1 = start_position;
|
let z1 = start_position;
|
||||||
let z2 = start_position + (grid_size as f64 * grid_spacing);
|
let z2 = start_position + (grid_size as f32 * grid_spacing);
|
||||||
|
|
||||||
// Convert these points to "world double" by adding octree_pos
|
// Convert these points to "world double" by adding octree_pos
|
||||||
let p1_d = DVec3::new(x, 0.0, z1) + octree_pos;
|
let p1_d = Vec3::new(x, 0.0, z1) + octree_pos;
|
||||||
let p2_d = DVec3::new(x, 0.0, z2) + octree_pos;
|
let p2_d = Vec3::new(x, 0.0, z2) + octree_pos;
|
||||||
|
|
||||||
// Then offset by camera_pos, convert to f32
|
// Then offset by camera_pos, convert to f32
|
||||||
let p1_f32 = (p1_d - camera_pos).as_vec3();
|
let p1_f32 = (p1_d - camera_pos);
|
||||||
let p2_f32 = (p2_d - camera_pos).as_vec3();
|
let p2_f32 = (p2_d - camera_pos);
|
||||||
|
|
||||||
// Draw the line
|
// Draw the line
|
||||||
gizmos.line(p1_f32, p2_f32, Color::WHITE);
|
gizmos.line(p1_f32, p2_f32, Color::WHITE);
|
||||||
@ -140,221 +141,16 @@ pub fn draw_grid(
|
|||||||
{
|
{
|
||||||
let z = start_position + offset;
|
let z = start_position + offset;
|
||||||
let x1 = start_position;
|
let x1 = start_position;
|
||||||
let x2 = start_position + (grid_size as f64 * grid_spacing);
|
let x2 = start_position + (grid_size as f32 * grid_spacing);
|
||||||
|
|
||||||
let p1_d = DVec3::new(x1, 0.0, z) + octree_pos;
|
let p1_d = Vec3::new(x1, 0.0, z) + octree_pos;
|
||||||
let p2_d = DVec3::new(x2, 0.0, z) + octree_pos;
|
let p2_d = Vec3::new(x2, 0.0, z) + octree_pos;
|
||||||
|
|
||||||
let p1_f32 = (p1_d - camera_pos).as_vec3();
|
let p1_f32 = (p1_d - camera_pos);
|
||||||
let p2_f32 = (p2_d - camera_pos).as_vec3();
|
let p2_f32 = (p2_d - camera_pos);
|
||||||
|
|
||||||
gizmos.line(p1_f32, p2_f32, Color::WHITE);
|
gizmos.line(p1_f32, p2_f32, Color::WHITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*#[derive(Component)]
|
|
||||||
pub struct GridMarker;
|
|
||||||
|
|
||||||
pub fn draw_grid(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
query: Query<(Entity, &SparseVoxelOctree)>, // Query to access the octree
|
|
||||||
grid_query: Query<Entity, With<GridMarker>>, // Query to find existing grid entities
|
|
||||||
) {
|
|
||||||
for (_, octree) in query.iter() {
|
|
||||||
if octree.show_world_grid {
|
|
||||||
// If grid should be shown, check if it already exists
|
|
||||||
if grid_query.iter().next().is_none() {
|
|
||||||
// Grid doesn't exist, so create it
|
|
||||||
let grid_spacing = octree.get_spacing_at_depth(octree.max_depth) as f32; // Get spacing at the specified depth
|
|
||||||
let grid_size = (octree.size / grid_spacing as f64) as i32; // Determine the number of lines needed
|
|
||||||
|
|
||||||
let mut positions = Vec::new();
|
|
||||||
let mut indices = Vec::new();
|
|
||||||
|
|
||||||
// Calculate the start position to center the grid
|
|
||||||
let start_position = -(octree.size as f32 / 2.0);
|
|
||||||
|
|
||||||
// Create lines along the X and Z axes based on calculated spacing
|
|
||||||
for i in 0..=grid_size {
|
|
||||||
// Lines along the Z-axis
|
|
||||||
positions.push([start_position + i as f32 * grid_spacing, 0.0, start_position]);
|
|
||||||
positions.push([start_position + i as f32 * grid_spacing, 0.0, start_position + grid_size as f32 * grid_spacing]);
|
|
||||||
|
|
||||||
// Indices for the Z-axis lines
|
|
||||||
let base_index = (i * 2) as u32;
|
|
||||||
indices.push(base_index);
|
|
||||||
indices.push(base_index + 1);
|
|
||||||
|
|
||||||
// Lines along the X-axis
|
|
||||||
positions.push([start_position, 0.0, start_position + i as f32 * grid_spacing]);
|
|
||||||
positions.push([start_position + grid_size as f32 * grid_spacing, 0.0, start_position + i as f32 * grid_spacing]);
|
|
||||||
|
|
||||||
// Indices for the X-axis lines
|
|
||||||
let base_index_x = ((grid_size + 1 + i) * 2) as u32;
|
|
||||||
indices.push(base_index_x);
|
|
||||||
indices.push(base_index_x + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the line mesh
|
|
||||||
let mut mesh = Mesh::new(PrimitiveTopology::LineList, RenderAssetUsages::default());
|
|
||||||
mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, positions);
|
|
||||||
mesh.insert_indices(Indices::U32(indices));
|
|
||||||
|
|
||||||
|
|
||||||
let color = bevy::color::Color::srgba(204.0 / 255.0, 0.0, 218.0 / 255.0, 15.0 / 255.0);
|
|
||||||
|
|
||||||
|
|
||||||
// Spawn the entity with the line mesh
|
|
||||||
commands.spawn(PbrBundle {
|
|
||||||
mesh: meshes.add(mesh).into(),
|
|
||||||
material: materials.add(StandardMaterial {
|
|
||||||
base_color: Color::WHITE,
|
|
||||||
unlit: true, // Makes the lines visible without lighting
|
|
||||||
..Default::default()
|
|
||||||
}).into(),
|
|
||||||
transform: Transform::default(),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.insert(GridMarker); // Add a marker component to identify the grid
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// If grid should not be shown, remove any existing grid
|
|
||||||
for grid_entity in grid_query.iter() {
|
|
||||||
commands.entity(grid_entity).despawn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*#[derive(Component)]
|
|
||||||
pub struct BuildVisualization;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct EphemeralLine {
|
|
||||||
pub start: Vec3,
|
|
||||||
pub end: Vec3,
|
|
||||||
pub color: Color,
|
|
||||||
pub time_left: f32, // in seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
|
||||||
pub struct EphemeralLines {
|
|
||||||
pub lines: Vec<EphemeralLine>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ephemeral_lines_system(
|
|
||||||
mut lines: ResMut<EphemeralLines>,
|
|
||||||
mut gizmos: Gizmos,
|
|
||||||
time: Res<Time>,
|
|
||||||
) {
|
|
||||||
let dt = time.delta_secs();
|
|
||||||
|
|
||||||
// Retain only those with time_left > 0, and while they're active, draw them
|
|
||||||
lines.lines.retain_mut(|line| {
|
|
||||||
line.time_left -= dt;
|
|
||||||
if line.time_left > 0.0 {
|
|
||||||
// Draw the line with gizmos
|
|
||||||
gizmos.line(line.start, line.end, line.color);
|
|
||||||
// Keep it
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
// Time’s up, discard
|
|
||||||
false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// System that draws wireframe boxes around each chunk's bounding region.
|
|
||||||
pub fn debug_draw_chunks_system(
|
|
||||||
chunk_entities: Res<ChunkEntities>,
|
|
||||||
|
|
||||||
// If your chunk placement depends on the octree's transform
|
|
||||||
// query that. Otherwise you can skip if they're always at (0,0,0).
|
|
||||||
octree_query: Query<(&SparseVoxelOctree, &DoubleTransform)>,
|
|
||||||
// Optional: If you want large-world offset for camera, we can subtract camera position.
|
|
||||||
// If you don't have floating-origin logic, you can skip this.
|
|
||||||
camera_query: Query<&DoubleTransform, With<Camera>>,
|
|
||||||
|
|
||||||
mut gizmos: Gizmos,
|
|
||||||
) {
|
|
||||||
// We'll get the octree transform offset if we have only one octree.
|
|
||||||
// Adjust if you have multiple.
|
|
||||||
let (octree, octree_tf) = match octree_query.get_single() {
|
|
||||||
Ok(x) => x,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 1) Determine the world size of a single voxel
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
// chunk_size in world units = 16 voxels * step
|
|
||||||
let chunk_size_world = octree.get_chunk_size() as f64 * step;
|
|
||||||
|
|
||||||
// 2) We'll also get the octree's offset in double precision
|
|
||||||
let octree_pos_d = octree_tf.translation;
|
|
||||||
|
|
||||||
// If you want a floating origin approach, subtract the camera's double position:
|
|
||||||
let camera_tf = match camera_query.get_single() {
|
|
||||||
Ok(tf) => tf,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
let camera_pos_d = camera_tf.translation;
|
|
||||||
|
|
||||||
// For each chunk coordinate
|
|
||||||
for (&(cx, cy, cz), _entity) in chunk_entities.map.iter() {
|
|
||||||
// 4) Chunk bounding box in double precision
|
|
||||||
let chunk_min_d = octree_pos_d
|
|
||||||
+ DVec3::new(
|
|
||||||
cx as f64 * chunk_size_world,
|
|
||||||
cy as f64 * chunk_size_world,
|
|
||||||
cz as f64 * chunk_size_world,
|
|
||||||
);
|
|
||||||
let chunk_max_d = chunk_min_d + DVec3::splat(chunk_size_world);
|
|
||||||
|
|
||||||
// 5) Convert to local f32 near the camera
|
|
||||||
let min_f32 = (chunk_min_d - camera_pos_d).as_vec3();
|
|
||||||
let max_f32 = (chunk_max_d - camera_pos_d).as_vec3();
|
|
||||||
|
|
||||||
// 6) Draw ephemeral lines for the box
|
|
||||||
draw_wire_cube(&mut gizmos, min_f32, max_f32, Color::from(YELLOW));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to draw a wireframe box from `min` to `max` in ephemeral gizmos.
|
|
||||||
fn draw_wire_cube(
|
|
||||||
gizmos: &mut Gizmos,
|
|
||||||
min: Vec3,
|
|
||||||
max: Vec3,
|
|
||||||
color: Color,
|
|
||||||
) {
|
|
||||||
// corners
|
|
||||||
let c0 = Vec3::new(min.x, min.y, min.z);
|
|
||||||
let c1 = Vec3::new(max.x, min.y, min.z);
|
|
||||||
let c2 = Vec3::new(min.x, max.y, min.z);
|
|
||||||
let c3 = Vec3::new(max.x, max.y, min.z);
|
|
||||||
let c4 = Vec3::new(min.x, min.y, max.z);
|
|
||||||
let c5 = Vec3::new(max.x, min.y, max.z);
|
|
||||||
let c6 = Vec3::new(min.x, max.y, max.z);
|
|
||||||
let c7 = Vec3::new(max.x, max.y, max.z);
|
|
||||||
|
|
||||||
// edges
|
|
||||||
// bottom face
|
|
||||||
gizmos.line(c0, c1, color);
|
|
||||||
gizmos.line(c1, c3, color);
|
|
||||||
gizmos.line(c3, c2, color);
|
|
||||||
gizmos.line(c2, c0, color);
|
|
||||||
// top face
|
|
||||||
gizmos.line(c4, c5, color);
|
|
||||||
gizmos.line(c5, c7, color);
|
|
||||||
gizmos.line(c7, c6, color);
|
|
||||||
gizmos.line(c6, c4, color);
|
|
||||||
// verticals
|
|
||||||
gizmos.line(c0, c4, color);
|
|
||||||
gizmos.line(c1, c5, color);
|
|
||||||
gizmos.line(c2, c6, color);
|
|
||||||
gizmos.line(c3, c7, color);
|
|
||||||
}
|
|
||||||
@ -21,24 +21,24 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn get_spacing_at_depth(&self, depth: u32) -> f64 {
|
pub fn get_spacing_at_depth(&self, depth: u32) -> f32 {
|
||||||
// Ensure the depth does not exceed the maximum depth
|
// Ensure the depth does not exceed the maximum depth
|
||||||
let effective_depth = depth.min(self.max_depth);
|
let effective_depth = depth.min(self.max_depth);
|
||||||
|
|
||||||
// Calculate the voxel size at the specified depth
|
// Calculate the voxel size at the specified depth
|
||||||
self.size / (2_u64.pow(effective_depth)) as f64
|
self.size / (2_u32.pow(effective_depth)) as f32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Normalize the world position to the nearest voxel grid position at the specified depth.
|
/// Normalize the world position to the nearest voxel grid position at the specified depth.
|
||||||
pub fn normalize_to_voxel_at_depth(
|
pub fn normalize_to_voxel_at_depth(
|
||||||
&self,
|
&self,
|
||||||
world_x: f64,
|
world_x: f32,
|
||||||
world_y: f64,
|
world_y: f32,
|
||||||
world_z: f64,
|
world_z: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
) -> (f64, f64, f64) {
|
) -> (f32, f32, f32) {
|
||||||
// Calculate the voxel size at the specified depth
|
// Calculate the voxel size at the specified depth
|
||||||
let voxel_size = self.get_spacing_at_depth(depth);
|
let voxel_size = self.get_spacing_at_depth(depth) as f32;
|
||||||
|
|
||||||
// Align the world position to the center of the voxel
|
// Align the world position to the center of the voxel
|
||||||
let aligned_x = (world_x / voxel_size).floor() * voxel_size + voxel_size / 2.0;
|
let aligned_x = (world_x / voxel_size).floor() * voxel_size + voxel_size / 2.0;
|
||||||
@ -106,7 +106,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if a position is within the current octree bounds.
|
/// Checks if a position is within the current octree bounds.
|
||||||
pub fn contains(&self, x: f64, y: f64, z: f64) -> bool {
|
pub fn contains(&self, x: f32, y: f32, z: f32) -> bool {
|
||||||
let half_size = self.size / 2.0;
|
let half_size = self.size / 2.0;
|
||||||
let epsilon = 1e-6; // Epsilon for floating-point precision
|
let epsilon = 1e-6; // Epsilon for floating-point precision
|
||||||
|
|
||||||
@ -115,7 +115,7 @@ impl SparseVoxelOctree {
|
|||||||
(z >= -half_size - epsilon && z < half_size + epsilon)
|
(z >= -half_size - epsilon && z < half_size + epsilon)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_voxel_at_world_coords(&self, world_x: f64, world_y: f64, world_z: f64) -> Option<&Voxel> {
|
pub fn get_voxel_at_world_coords(&self, world_x: f32, world_y: f32, world_z: f32) -> Option<&Voxel> {
|
||||||
// Correct normalization: calculate the position relative to the octree's center
|
// Correct normalization: calculate the position relative to the octree's center
|
||||||
let normalized_x = (world_x + (self.size / 2.0)) / self.size;
|
let normalized_x = (world_x + (self.size / 2.0)) / self.size;
|
||||||
let normalized_y = (world_y + (self.size / 2.0)) / self.size;
|
let normalized_y = (world_y + (self.size / 2.0)) / self.size;
|
||||||
@ -124,25 +124,6 @@ impl SparseVoxelOctree {
|
|||||||
self.get_voxel_at(normalized_x, normalized_y, normalized_z)
|
self.get_voxel_at(normalized_x, normalized_y, normalized_z)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A small helper to compute chunk coords from a voxel's "true" world position
|
|
||||||
pub fn compute_chunk_coords(&self, world_x: f64, world_y: f64, world_z: f64) -> (i64, i64, i64) {
|
|
||||||
// The size of one voxel at max_depth
|
|
||||||
let step = self.get_spacing_at_depth(self.max_depth);
|
|
||||||
|
|
||||||
// Each chunk is 16 voxels => chunk_size_world = 16.0 * step
|
|
||||||
|
|
||||||
let chunk_size = self.get_chunk_size();
|
|
||||||
|
|
||||||
|
|
||||||
let chunk_size_world = chunk_size as f64 * step;
|
|
||||||
|
|
||||||
// Divide the world coords by chunk_size_world, floor => chunk coordinate
|
|
||||||
let cx = (world_x / chunk_size_world).floor();
|
|
||||||
let cy = (world_y / chunk_size_world).floor();
|
|
||||||
let cz = (world_z / chunk_size_world).floor();
|
|
||||||
|
|
||||||
(cx as i64, cy as i64, cz as i64)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn has_volume(&self, node: &OctreeNode) -> bool {
|
pub fn has_volume(&self, node: &OctreeNode) -> bool {
|
||||||
// Check if this node is a leaf with a voxel
|
// Check if this node is a leaf with a voxel
|
||||||
@ -163,35 +144,13 @@ impl SparseVoxelOctree {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_chunk_size(&self) -> u32 {
|
|
||||||
self.max_depth - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_chunk_node(&self, world_x: f64, world_y: f64, world_z: f64) -> Option<&OctreeNode> {
|
|
||||||
|
|
||||||
// Ensure the world position is within the octree's bounds
|
|
||||||
if !self.contains(world_x, world_y, world_z) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize the world position to the octree's space
|
|
||||||
let normalized_x = (world_x + (self.size / 2.0)) / self.size;
|
|
||||||
let normalized_y = (world_y + (self.size / 2.0)) / self.size;
|
|
||||||
let normalized_z = (world_z + (self.size / 2.0)) / self.size;
|
|
||||||
|
|
||||||
|
|
||||||
let chunk_size = self.get_chunk_size();
|
|
||||||
|
|
||||||
// Traverse to the appropriate chunk node
|
|
||||||
Self::get_node_at_depth(&self.root, normalized_x, normalized_y, normalized_z, chunk_size)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper function to recursively traverse the octree to a specific depth.
|
/// Helper function to recursively traverse the octree to a specific depth.
|
||||||
fn get_node_at_depth(
|
fn get_node_at_depth(
|
||||||
node: &OctreeNode,
|
node: &OctreeNode,
|
||||||
x: f64,
|
x: f32,
|
||||||
y: f64,
|
y: f32,
|
||||||
z: f64,
|
z: f32,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
) -> Option<&OctreeNode> {
|
) -> Option<&OctreeNode> {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
@ -205,7 +164,7 @@ impl SparseVoxelOctree {
|
|||||||
+ ((y >= 0.5 - epsilon) as usize * 2)
|
+ ((y >= 0.5 - epsilon) as usize * 2)
|
||||||
+ ((z >= 0.5 - epsilon) as usize * 4);
|
+ ((z >= 0.5 - epsilon) as usize * 4);
|
||||||
|
|
||||||
let adjust_coord = |coord: f64| {
|
let adjust_coord = |coord: f32| {
|
||||||
if coord >= 0.5 - epsilon {
|
if coord >= 0.5 - epsilon {
|
||||||
(coord - 0.5) * 2.0
|
(coord - 0.5) * 2.0
|
||||||
} else {
|
} else {
|
||||||
@ -226,54 +185,13 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn traverse_chunk(
|
|
||||||
&self,
|
|
||||||
node: &OctreeNode,
|
|
||||||
chunk_size: u32,
|
|
||||||
) -> Vec<(f32, f32, f32, Color, u32)> {
|
|
||||||
let mut voxels = Vec::new();
|
|
||||||
Self::traverse_chunk_recursive(node, 0.0, 0.0, 0.0, chunk_size as f32, 0, &mut voxels);
|
|
||||||
voxels
|
|
||||||
}
|
|
||||||
|
|
||||||
fn traverse_chunk_recursive(
|
|
||||||
node: &OctreeNode,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
z: f32,
|
|
||||||
size: f32,
|
|
||||||
depth: u32,
|
|
||||||
voxels: &mut Vec<(f32, f32, f32, Color, u32)>,
|
|
||||||
) {
|
|
||||||
if node.is_leaf {
|
|
||||||
if let Some(voxel) = node.voxel {
|
|
||||||
voxels.push((x, y, z, voxel.color, depth));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ref children) = node.children {
|
|
||||||
let half_size = size / 2.0;
|
|
||||||
for (i, child) in children.iter().enumerate() {
|
|
||||||
let offset = |bit: usize| if (i & bit) == bit { half_size } else { 0.0 };
|
|
||||||
Self::traverse_chunk_recursive(
|
|
||||||
child,
|
|
||||||
x + offset(1),
|
|
||||||
y + offset(2),
|
|
||||||
z + offset(4),
|
|
||||||
half_size,
|
|
||||||
depth + 1,
|
|
||||||
voxels,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the (face_normal, local_offset) for the given neighbor direction.
|
/// Returns the (face_normal, local_offset) for the given neighbor direction.
|
||||||
/// - `dx, dy, dz`: The integer direction of the face (-1,0,0 / 1,0,0 / etc.)
|
/// - `dx, dy, dz`: The integer direction of the face (-1,0,0 / 1,0,0 / etc.)
|
||||||
/// - `voxel_size_f`: The world size of a single voxel (e.g. step as f32).
|
/// - `voxel_size_f`: The world size of a single voxel (e.g. step as f32).
|
||||||
pub fn face_orientation(dx: f64, dy: f64, dz: f64, voxel_size_f: f32) -> (Vec3, Vec3) {
|
pub fn face_orientation(dx: f32, dy: f32, dz: f32, voxel_size_f: f32) -> (Vec3, Vec3) {
|
||||||
// We'll do a match on the direction
|
// We'll do a match on the direction
|
||||||
match (dx, dy, dz) {
|
match (dx, dy, dz) {
|
||||||
// Negative X => face normal is (-1, 0, 0), local offset is -voxel_size/2 in X
|
// Negative X => face normal is (-1, 0, 0), local offset is -voxel_size/2 in X
|
||||||
|
|||||||
@ -5,12 +5,11 @@ 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::helper::large_transform::DoubleTransform;
|
use crate::systems::voxels::structure::{OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, NEIGHBOR_OFFSETS};
|
||||||
use crate::systems::voxels::structure::{ChunkEntities, OctreeNode, Ray, SparseVoxelOctree, Voxel, AABB, CHUNK_BUILD_BUDGET, CHUNK_NEIGHBOR_OFFSETS, CHUNK_RENDER_DISTANCE, NEIGHBOR_OFFSETS};
|
|
||||||
|
|
||||||
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: f64, 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,
|
||||||
@ -18,11 +17,11 @@ impl SparseVoxelOctree {
|
|||||||
show_wireframe,
|
show_wireframe,
|
||||||
show_world_grid,
|
show_world_grid,
|
||||||
show_chunks,
|
show_chunks,
|
||||||
dirty_chunks: HashSet::new(),
|
dirty: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, world_x: f64, world_y: f64, world_z: f64, voxel: Voxel) {
|
pub fn insert(&mut self, world_x: f32, world_y: f32, world_z: f32, voxel: Voxel) {
|
||||||
// Normalize the world coordinates to the nearest voxel grid position
|
// Normalize the world coordinates to the nearest voxel grid position
|
||||||
let (aligned_x, aligned_y, aligned_z) = self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth);
|
let (aligned_x, aligned_y, aligned_z) = self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth);
|
||||||
|
|
||||||
@ -41,21 +40,13 @@ impl SparseVoxelOctree {
|
|||||||
voxel_with_position.position = Vec3::new(world_x as f32, world_y as f32, world_z as f32);
|
voxel_with_position.position = Vec3::new(world_x as f32, world_y as f32, world_z as f32);
|
||||||
|
|
||||||
|
|
||||||
// Actually let's do a small helper:
|
self.dirty = true;
|
||||||
let (cx, cy, cz) = self.compute_chunk_coords(world_x, world_y, world_z);
|
|
||||||
self.dirty_chunks.insert((cx as i32, cy as i32, cz as i32));
|
|
||||||
|
|
||||||
// 5b) Also mark the 6 neighboring chunks dirty to fix boundary faces
|
|
||||||
for &(nx, ny, nz) in CHUNK_NEIGHBOR_OFFSETS.iter() {
|
|
||||||
let neighbor_coord = (cx as i32 + nx, cy as i32 + ny, cz as i32 + nz);
|
|
||||||
self.dirty_chunks.insert(neighbor_coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
SparseVoxelOctree::insert_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, voxel_with_position, self.max_depth);
|
SparseVoxelOctree::insert_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, voxel_with_position, self.max_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_recursive(node: &mut OctreeNode, x: f64, y: f64, z: f64, voxel: Voxel, depth: u32) {
|
fn insert_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, voxel: Voxel, depth: u32) {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
node.voxel = Some(voxel);
|
node.voxel = Some(voxel);
|
||||||
node.is_leaf = true;
|
node.is_leaf = true;
|
||||||
@ -72,7 +63,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref mut children) = node.children {
|
if let Some(ref mut children) = node.children {
|
||||||
let adjust_coord = |coord: f64| {
|
let adjust_coord = |coord: f32| {
|
||||||
if coord >= 0.5 - epsilon {
|
if coord >= 0.5 - epsilon {
|
||||||
(coord - 0.5) * 2.0
|
(coord - 0.5) * 2.0
|
||||||
} else {
|
} else {
|
||||||
@ -83,7 +74,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove(&mut self, world_x: f64, world_y: f64, world_z: f64) {
|
pub fn remove(&mut self, world_x: f32, world_y: f32, world_z: f32) {
|
||||||
// Normalize the world coordinates to the nearest voxel grid position
|
// Normalize the world coordinates to the nearest voxel grid position
|
||||||
let (aligned_x, aligned_y, aligned_z) =
|
let (aligned_x, aligned_y, aligned_z) =
|
||||||
self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth);
|
self.normalize_to_voxel_at_depth(world_x, world_y, world_z, self.max_depth);
|
||||||
@ -93,21 +84,14 @@ impl SparseVoxelOctree {
|
|||||||
let normalized_y = (aligned_y + (self.size / 2.0)) / self.size;
|
let normalized_y = (aligned_y + (self.size / 2.0)) / self.size;
|
||||||
let normalized_z = (aligned_z + (self.size / 2.0)) / self.size;
|
let normalized_z = (aligned_z + (self.size / 2.0)) / self.size;
|
||||||
|
|
||||||
// figure out chunk coords for the removed voxel:
|
self.dirty = true;
|
||||||
let (cx, cy, cz) = self.compute_chunk_coords(world_x, world_y, world_z);
|
|
||||||
self.dirty_chunks.insert((cx as i32, cy as i32, cz as i32));
|
|
||||||
|
|
||||||
for &(nx, ny, nz) in CHUNK_NEIGHBOR_OFFSETS.iter() {
|
|
||||||
let neighbor_coord = (cx as i32 + nx, cy as i32 + ny, cz as i32 + nz);
|
|
||||||
self.dirty_chunks.insert(neighbor_coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Call the recursive remove function
|
// Call the recursive remove function
|
||||||
Self::remove_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, self.max_depth);
|
Self::remove_recursive(&mut self.root, normalized_x, normalized_y, normalized_z, self.max_depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_recursive(node: &mut OctreeNode, x: f64, y: f64, z: f64, depth: u32) -> bool {
|
fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool {
|
||||||
if depth == 0 {
|
if depth == 0 {
|
||||||
// This is the leaf node where the voxel should be
|
// This is the leaf node where the voxel should be
|
||||||
if node.voxel.is_some() {
|
if node.voxel.is_some() {
|
||||||
@ -131,7 +115,7 @@ impl SparseVoxelOctree {
|
|||||||
+ ((y >= 0.5 - epsilon) as usize * 2)
|
+ ((y >= 0.5 - epsilon) as usize * 2)
|
||||||
+ ((z >= 0.5 - epsilon) as usize * 4);
|
+ ((z >= 0.5 - epsilon) as usize * 4);
|
||||||
|
|
||||||
let adjust_coord = |coord: f64| {
|
let adjust_coord = |coord: f32| {
|
||||||
if coord >= 0.5 - epsilon {
|
if coord >= 0.5 - epsilon {
|
||||||
(coord - 0.5) * 2.0
|
(coord - 0.5) * 2.0
|
||||||
} else {
|
} else {
|
||||||
@ -169,7 +153,7 @@ impl SparseVoxelOctree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn expand_root(&mut self, x: f64, y: f64, z: f64) {
|
fn expand_root(&mut self, x: f32, y: f32, z: f32) {
|
||||||
let new_size = self.size * 2.0;
|
let new_size = self.size * 2.0;
|
||||||
let new_depth = self.max_depth + 1;
|
let new_depth = self.max_depth + 1;
|
||||||
|
|
||||||
@ -252,15 +236,15 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
|
|
||||||
/// Retrieves a reference to the voxel at the given normalized coordinates and depth, if it exists.
|
/// Retrieves a reference to the voxel at the given normalized coordinates and depth, if it exists.
|
||||||
pub fn get_voxel_at(&self, x: f64, y: f64, z: f64) -> 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_voxel_recursive(
|
fn get_voxel_recursive(
|
||||||
node: &OctreeNode,
|
node: &OctreeNode,
|
||||||
x: f64,
|
x: f32,
|
||||||
y: f64,
|
y: f32,
|
||||||
z: f64,
|
z: f32,
|
||||||
) -> Option<&Voxel> {
|
) -> Option<&Voxel> {
|
||||||
if node.is_leaf {
|
if node.is_leaf {
|
||||||
return node.voxel.as_ref();
|
return node.voxel.as_ref();
|
||||||
@ -272,7 +256,7 @@ impl SparseVoxelOctree {
|
|||||||
+ ((y >= 0.5 - epsilon) as usize * 2)
|
+ ((y >= 0.5 - epsilon) as usize * 2)
|
||||||
+ ((z >= 0.5 - epsilon) as usize * 4);
|
+ ((z >= 0.5 - epsilon) as usize * 4);
|
||||||
|
|
||||||
let adjust_coord = |coord: f64| {
|
let adjust_coord = |coord: f32| {
|
||||||
if coord >= 0.5 - epsilon {
|
if coord >= 0.5 - epsilon {
|
||||||
(coord - 0.5) * 2.0
|
(coord - 0.5) * 2.0
|
||||||
} else {
|
} else {
|
||||||
@ -295,9 +279,9 @@ impl SparseVoxelOctree {
|
|||||||
/// The offsets are directions (-1, 0, 1) for x, y, z.
|
/// The offsets are directions (-1, 0, 1) for x, y, z.
|
||||||
pub fn has_neighbor(
|
pub fn has_neighbor(
|
||||||
&self,
|
&self,
|
||||||
world_x: f64,
|
world_x: f32,
|
||||||
world_y: f64,
|
world_y: f32,
|
||||||
world_z: f64,
|
world_z: f32,
|
||||||
offset_x: i32,
|
offset_x: i32,
|
||||||
offset_y: i32,
|
offset_y: i32,
|
||||||
offset_z: i32,
|
offset_z: i32,
|
||||||
@ -311,9 +295,9 @@ impl SparseVoxelOctree {
|
|||||||
let voxel_size = self.get_spacing_at_depth(depth);
|
let voxel_size = self.get_spacing_at_depth(depth);
|
||||||
|
|
||||||
// Calculate the neighbor's world position
|
// Calculate the neighbor's world position
|
||||||
let neighbor_x = aligned_x + (offset_x as f64) * voxel_size;
|
let neighbor_x = aligned_x + (offset_x as f32) * voxel_size;
|
||||||
let neighbor_y = aligned_y + (offset_y as f64) * voxel_size;
|
let neighbor_y = aligned_y + (offset_y as f32) * voxel_size;
|
||||||
let neighbor_z = aligned_z + (offset_z as f64) * voxel_size;
|
let neighbor_z = aligned_z + (offset_z as f32) * voxel_size;
|
||||||
|
|
||||||
// Check if the neighbor position is within bounds
|
// Check if the neighbor position is within bounds
|
||||||
if !self.contains(neighbor_x, neighbor_y, neighbor_z) {
|
if !self.contains(neighbor_x, neighbor_y, neighbor_z) {
|
||||||
@ -327,7 +311,7 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
|
|
||||||
/// 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<(f64, f64, f64, u32, Vec3)> {
|
pub fn raycast(&self, ray: &Ray) -> Option<(f32, f32, f32, u32, Vec3)> {
|
||||||
// Start from the root node
|
// Start from the root node
|
||||||
let half_size = self.size / 2.0;
|
let half_size = self.size / 2.0;
|
||||||
let root_bounds = AABB {
|
let root_bounds = AABB {
|
||||||
@ -348,7 +332,7 @@ impl SparseVoxelOctree {
|
|||||||
ray: &Ray,
|
ray: &Ray,
|
||||||
bounds: &AABB,
|
bounds: &AABB,
|
||||||
depth: u32,
|
depth: u32,
|
||||||
) -> Option<(f64, f64, f64, u32, Vec3)> {
|
) -> Option<(f32, f32, f32, u32, Vec3)> {
|
||||||
// Check if the ray intersects this node's bounding box
|
// Check if the ray intersects this node's bounding box
|
||||||
if let Some((t_enter, _, normal)) = self.ray_intersects_aabb_with_normal(ray, bounds) {
|
if let Some((t_enter, _, normal)) = self.ray_intersects_aabb_with_normal(ray, bounds) {
|
||||||
// If this is a leaf node and contains a voxel, return it
|
// If this is a leaf node and contains a voxel, return it
|
||||||
@ -358,9 +342,9 @@ impl SparseVoxelOctree {
|
|||||||
|
|
||||||
// Return the hit position along with depth and normal
|
// Return the hit position along with depth and normal
|
||||||
return Some((
|
return Some((
|
||||||
hit_position.x as f64,
|
hit_position.x as f32,
|
||||||
hit_position.y as f64,
|
hit_position.y as f32,
|
||||||
hit_position.z as f64,
|
hit_position.z as f32,
|
||||||
depth,
|
depth,
|
||||||
normal,
|
normal,
|
||||||
));
|
));
|
||||||
|
|||||||
@ -1,19 +1,17 @@
|
|||||||
// Chunk Rendering
|
use std::collections::HashMap;
|
||||||
|
use bevy::color::palettes::basic::BLUE;
|
||||||
use bevy::math::{DQuat, DVec3};
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use bevy::utils::info;
|
|
||||||
use bevy_asset::RenderAssetUsages;
|
use bevy_asset::RenderAssetUsages;
|
||||||
use bevy_render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
use bevy_render::mesh::{Indices, PrimitiveTopology, VertexAttributeValues};
|
||||||
use crate::helper::large_transform::{DoubleTransform, WorldOffset};
|
use crate::systems::ui_system::SpeedDisplay;
|
||||||
use crate::systems::voxels::structure::{ChunkEntities, ChunkMarker, SparseVoxelOctree, CHUNK_BUILD_BUDGET, CHUNK_RENDER_DISTANCE, NEIGHBOR_OFFSETS};
|
use crate::systems::voxels::structure::{SparseVoxelOctree, NEIGHBOR_OFFSETS};
|
||||||
use crate::helper::large_transform::get_true_world_position;
|
|
||||||
use crate::systems::voxels::helper::face_orientation;
|
|
||||||
|
|
||||||
/*pub fn render(
|
|
||||||
|
|
||||||
|
pub fn render(
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut query: Query<&mut SparseVoxelOctree>,
|
mut query: Query<&mut SparseVoxelOctree>,
|
||||||
mut octree_transform_query: Query<&DoubleTransform, With<SparseVoxelOctree>>,
|
octree_transform_query: Query<&Transform, With<SparseVoxelOctree>>,
|
||||||
render_object_query: Query<Entity, With<VoxelTerrainMarker>>,
|
render_object_query: Query<Entity, With<VoxelTerrainMarker>>,
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
@ -29,7 +27,7 @@ use crate::systems::voxels::helper::face_orientation;
|
|||||||
|
|
||||||
|
|
||||||
// Handle updates to the octree only if it is marked as dirty
|
// Handle updates to the octree only if it is marked as dirty
|
||||||
if !octree.dirty_chunks.is_empty() {
|
if octree.dirty {
|
||||||
// Clear existing render objects
|
// Clear existing render objects
|
||||||
for entity in render_object_query.iter() {
|
for entity in render_object_query.iter() {
|
||||||
commands.entity(entity).despawn();
|
commands.entity(entity).despawn();
|
||||||
@ -41,19 +39,19 @@ use crate::systems::voxels::helper::face_orientation;
|
|||||||
let mut voxel_meshes = Vec::new();
|
let mut voxel_meshes = Vec::new();
|
||||||
|
|
||||||
for (x, y, z, _color, depth) in voxels {
|
for (x, y, z, _color, depth) in voxels {
|
||||||
let voxel_size = octree.get_spacing_at_depth(depth) as f32;
|
let voxel_size = octree.get_spacing_at_depth(depth);
|
||||||
|
|
||||||
// Calculate the world position of the voxel
|
// Calculate the world position of the voxel
|
||||||
let world_position = Vec3::new(
|
let world_position = Vec3::new(
|
||||||
(x * octree.size as f32) + (voxel_size / 2.0) - (octree.size / 2.0) as f32,
|
(x * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0),
|
||||||
(y * octree.size as f32) + (voxel_size / 2.0) - (octree.size / 2.0) as f32,
|
(y * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0),
|
||||||
(z * octree.size as f32) + (voxel_size / 2.0) - (octree.size / 2.0) as f32,
|
(z * octree.size) + (voxel_size / 2.0) - (octree.size / 2.0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Convert world_position components to f64 for neighbor checking
|
// Convert world_position components to f32 for neighbor checking
|
||||||
let world_x = world_position.x as f64;
|
let world_x = world_position.x;
|
||||||
let world_y = world_position.y as f64;
|
let world_y = world_position.y;
|
||||||
let world_z = world_position.z as f64;
|
let world_z = world_position.z;
|
||||||
|
|
||||||
// Iterate over all possible neighbor offsets
|
// Iterate over all possible neighbor offsets
|
||||||
for &(dx, dy, dz) in NEIGHBOR_OFFSETS.iter() {
|
for &(dx, dy, dz) in NEIGHBOR_OFFSETS.iter() {
|
||||||
@ -113,353 +111,24 @@ use crate::systems::voxels::helper::face_orientation;
|
|||||||
base_color: Color::srgb(0.8, 0.7, 0.6),
|
base_color: Color::srgb(0.8, 0.7, 0.6),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})),
|
})),
|
||||||
transform: Default::default(),
|
transform: *octree_transform_query.single(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
VoxelTerrainMarker {},
|
VoxelTerrainMarker {},
|
||||||
DoubleTransform {
|
|
||||||
translation: octree_transform_query.single().translation,
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
|
|
||||||
// Reset the dirty flag once the update is complete
|
// Reset the dirty flag once the update is complete
|
||||||
octree.dirty_chunks.clear()
|
octree.dirty = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct VoxelTerrainMarker;
|
pub struct VoxelTerrainMarker;
|
||||||
|
|
||||||
|
|
||||||
pub fn render(
|
|
||||||
mut commands: Commands,
|
|
||||||
mut octree_query: Query<&mut SparseVoxelOctree>,
|
|
||||||
octree_transform_query: Query<&DoubleTransform, With<SparseVoxelOctree>>,
|
|
||||||
mut chunk_entities: ResMut<ChunkEntities>,
|
|
||||||
mut meshes: ResMut<Assets<Mesh>>,
|
|
||||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
|
||||||
// Use DoubleTransform for the camera
|
|
||||||
camera_query: Query<&DoubleTransform, With<Camera>>,
|
|
||||||
) {
|
|
||||||
let mut octree = match octree_query.get_single_mut() {
|
|
||||||
Ok(o) => o,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
|
|
||||||
let camera_dt = match camera_query.get_single() {
|
|
||||||
Ok(dt) => dt,
|
|
||||||
Err(_) => return,
|
|
||||||
};
|
|
||||||
// Convert camera's double position to f32 for distance calculations.
|
|
||||||
let camera_pos = camera_dt.translation.as_vec3();
|
|
||||||
|
|
||||||
let octree_dt = octree_transform_query.single();
|
|
||||||
let octree_offset = octree_dt.translation.as_vec3();
|
|
||||||
|
|
||||||
// Define chunk sizing.
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
let chunk_world_size = octree.get_chunk_size() as f32 * step as f32;
|
|
||||||
|
|
||||||
// 1) DESPAWN out-of-range chunks.
|
|
||||||
let mut chunks_to_remove = Vec::new();
|
|
||||||
for (&(cx, cy, cz), &entity) in chunk_entities.map.iter() {
|
|
||||||
let chunk_min = Vec3::new(
|
|
||||||
cx as f32 * chunk_world_size,
|
|
||||||
cy as f32 * chunk_world_size,
|
|
||||||
cz as f32 * chunk_world_size,
|
|
||||||
);
|
|
||||||
let chunk_center = chunk_min + Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
let final_center = octree_offset + chunk_center;
|
|
||||||
let dist = camera_pos.distance(final_center);
|
|
||||||
if dist > CHUNK_RENDER_DISTANCE as f32 {
|
|
||||||
chunks_to_remove.push((cx, cy, cz, entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (cx, cy, cz, e) in chunks_to_remove {
|
|
||||||
commands.entity(e).despawn();
|
|
||||||
chunk_entities.map.remove(&(cx, cy, cz));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) LOAD new in-range chunks with nearest-first ordering.
|
|
||||||
let camera_cx = ((camera_pos.x - octree_offset.x) / chunk_world_size).floor() as i32;
|
|
||||||
let camera_cy = ((camera_pos.y - octree_offset.y) / chunk_world_size).floor() as i32;
|
|
||||||
let camera_cz = ((camera_pos.z - octree_offset.z) / chunk_world_size).floor() as i32;
|
|
||||||
|
|
||||||
let half_chunks = (CHUNK_RENDER_DISTANCE / chunk_world_size as f64).ceil() as i32;
|
|
||||||
let mut new_chunks_to_spawn = Vec::new();
|
|
||||||
for dx in -half_chunks..=half_chunks {
|
|
||||||
for dy in -half_chunks..=half_chunks {
|
|
||||||
for dz in -half_chunks..=half_chunks {
|
|
||||||
let cc = (camera_cx + dx, camera_cy + dy, camera_cz + dz);
|
|
||||||
if !chunk_entities.map.contains_key(&cc) {
|
|
||||||
let chunk_min = Vec3::new(
|
|
||||||
cc.0 as f32 * chunk_world_size,
|
|
||||||
cc.1 as f32 * chunk_world_size,
|
|
||||||
cc.2 as f32 * chunk_world_size,
|
|
||||||
);
|
|
||||||
let chunk_center = chunk_min + Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
let final_center = octree_offset + chunk_center;
|
|
||||||
let dist = camera_pos.distance(final_center);
|
|
||||||
if dist <= CHUNK_RENDER_DISTANCE as f32 {
|
|
||||||
new_chunks_to_spawn.push(cc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort candidate chunks by distance (nearest first).
|
|
||||||
new_chunks_to_spawn.sort_by(|a, b| {
|
|
||||||
let pos_a = octree_offset
|
|
||||||
+ Vec3::new(
|
|
||||||
a.0 as f32 * chunk_world_size,
|
|
||||||
a.1 as f32 * chunk_world_size,
|
|
||||||
a.2 as f32 * chunk_world_size,
|
|
||||||
)
|
|
||||||
+ Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
let pos_b = octree_offset
|
|
||||||
+ Vec3::new(
|
|
||||||
b.0 as f32 * chunk_world_size,
|
|
||||||
b.1 as f32 * chunk_world_size,
|
|
||||||
b.2 as f32 * chunk_world_size,
|
|
||||||
)
|
|
||||||
+ Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
camera_pos
|
|
||||||
.distance(pos_a)
|
|
||||||
.partial_cmp(&camera_pos.distance(pos_b))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
let build_budget = 5; // Maximum chunks to build per frame.
|
|
||||||
let mut spawn_count = 0;
|
|
||||||
for cc in new_chunks_to_spawn {
|
|
||||||
if spawn_count >= build_budget {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Compute chunk's world position.
|
|
||||||
let chunk_min = Vec3::new(
|
|
||||||
cc.0 as f32 * chunk_world_size,
|
|
||||||
cc.1 as f32 * chunk_world_size,
|
|
||||||
cc.2 as f32 * chunk_world_size,
|
|
||||||
);
|
|
||||||
let chunk_center = chunk_min + Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
// Check if this chunk has any voxels.
|
|
||||||
if let Some(chunk_node) =
|
|
||||||
octree.get_chunk_node(chunk_center.x as f64, chunk_center.y as f64, chunk_center.z as f64)
|
|
||||||
{
|
|
||||||
if octree.has_volume(chunk_node) {
|
|
||||||
info!("Loading chunk at: {},{},{} (has volume)", cc.0, cc.1, cc.2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
build_and_spawn_chunk(
|
|
||||||
&mut commands,
|
|
||||||
&octree,
|
|
||||||
&mut meshes,
|
|
||||||
&mut materials,
|
|
||||||
&mut chunk_entities,
|
|
||||||
cc,
|
|
||||||
octree_offset,
|
|
||||||
);
|
|
||||||
spawn_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Rebuild dirty chunks (if any) with nearest-first ordering and budget.
|
|
||||||
if !octree.dirty_chunks.is_empty() {
|
|
||||||
let mut dirty = octree.dirty_chunks.drain().collect::<Vec<_>>();
|
|
||||||
dirty.sort_by(|a, b| {
|
|
||||||
let pos_a = octree_offset
|
|
||||||
+ Vec3::new(
|
|
||||||
a.0 as f32 * chunk_world_size,
|
|
||||||
a.1 as f32 * chunk_world_size,
|
|
||||||
a.2 as f32 * chunk_world_size,
|
|
||||||
)
|
|
||||||
+ Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
let pos_b = octree_offset
|
|
||||||
+ Vec3::new(
|
|
||||||
b.0 as f32 * chunk_world_size,
|
|
||||||
b.1 as f32 * chunk_world_size,
|
|
||||||
b.2 as f32 * chunk_world_size,
|
|
||||||
)
|
|
||||||
+ Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
camera_pos
|
|
||||||
.distance(pos_a)
|
|
||||||
.partial_cmp(&camera_pos.distance(pos_b))
|
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut rebuild_count = 0;
|
|
||||||
for chunk_coord in dirty {
|
|
||||||
if rebuild_count >= build_budget {
|
|
||||||
octree.dirty_chunks.insert(chunk_coord);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let chunk_min = Vec3::new(
|
|
||||||
chunk_coord.0 as f32 * chunk_world_size,
|
|
||||||
chunk_coord.1 as f32 * chunk_world_size,
|
|
||||||
chunk_coord.2 as f32 * chunk_world_size,
|
|
||||||
);
|
|
||||||
let chunk_center = chunk_min + Vec3::splat(chunk_world_size * 0.5);
|
|
||||||
let final_center = octree_offset + chunk_center;
|
|
||||||
let dist = camera_pos.distance(final_center);
|
|
||||||
|
|
||||||
if dist <= CHUNK_RENDER_DISTANCE as f32 {
|
|
||||||
if let Some(chunk_node) =
|
|
||||||
octree.get_chunk_node(chunk_center.x as f64, chunk_center.y as f64, chunk_center.z as f64)
|
|
||||||
{
|
|
||||||
if octree.has_volume(chunk_node) {
|
|
||||||
info!(
|
|
||||||
"Rebuilding chunk at: {},{},{} (has volume)",
|
|
||||||
chunk_coord.0, chunk_coord.1, chunk_coord.2
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let Some(e) = chunk_entities.map.remove(&chunk_coord) {
|
|
||||||
commands.entity(e).despawn();
|
|
||||||
}
|
|
||||||
build_and_spawn_chunk(
|
|
||||||
&mut commands,
|
|
||||||
&octree,
|
|
||||||
&mut meshes,
|
|
||||||
&mut materials,
|
|
||||||
&mut chunk_entities,
|
|
||||||
chunk_coord,
|
|
||||||
octree_offset,
|
|
||||||
);
|
|
||||||
rebuild_count += 1;
|
|
||||||
} else {
|
|
||||||
if let Some(e) = chunk_entities.map.remove(&chunk_coord) {
|
|
||||||
commands.entity(e).despawn();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn build_and_spawn_chunk(
|
|
||||||
commands: &mut Commands,
|
|
||||||
octree: &SparseVoxelOctree,
|
|
||||||
meshes: &mut ResMut<Assets<Mesh>>,
|
|
||||||
materials: &mut ResMut<Assets<StandardMaterial>>,
|
|
||||||
chunk_entities: &mut ChunkEntities,
|
|
||||||
chunk_coord: (i32, i32, i32),
|
|
||||||
octree_offset: Vec3,
|
|
||||||
) {
|
|
||||||
let face_meshes = build_chunk_geometry(octree, chunk_coord);
|
|
||||||
if face_meshes.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let merged = merge_meshes(face_meshes);
|
|
||||||
let mesh_handle = meshes.add(merged);
|
|
||||||
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
let chunk_world_size = octree.get_chunk_size() as f64 * step;
|
|
||||||
let chunk_min = Vec3::new(
|
|
||||||
chunk_coord.0 as f32 * chunk_world_size as f32,
|
|
||||||
chunk_coord.1 as f32 * chunk_world_size as f32,
|
|
||||||
chunk_coord.2 as f32 * chunk_world_size as f32,
|
|
||||||
);
|
|
||||||
let final_pos = octree_offset + chunk_min;
|
|
||||||
|
|
||||||
let e = commands.spawn((
|
|
||||||
PbrBundle {
|
|
||||||
mesh: mesh_handle.into(),
|
|
||||||
material: materials.add(StandardMaterial {
|
|
||||||
base_color: Color::rgb(0.8, 0.7, 0.6),
|
|
||||||
..default()
|
|
||||||
}).into(),
|
|
||||||
transform: Transform::from_translation(final_pos),
|
|
||||||
..default()
|
|
||||||
},
|
|
||||||
VoxelTerrainMarker,
|
|
||||||
DoubleTransform {
|
|
||||||
translation: DVec3::from(final_pos),
|
|
||||||
rotation: DQuat::IDENTITY,
|
|
||||||
scale: DVec3::ONE,
|
|
||||||
},
|
|
||||||
))
|
|
||||||
.id();
|
|
||||||
|
|
||||||
chunk_entities.map.insert(chunk_coord, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_chunk_geometry(
|
|
||||||
octree: &SparseVoxelOctree,
|
|
||||||
(cx, cy, cz): (i32, i32, i32),
|
|
||||||
) -> Vec<Mesh> {
|
|
||||||
let mut face_meshes = Vec::new();
|
|
||||||
|
|
||||||
// step in world units for one voxel at max_depth
|
|
||||||
let step = octree.get_spacing_at_depth(octree.max_depth);
|
|
||||||
let chunk_size = octree.get_chunk_size();
|
|
||||||
|
|
||||||
// chunk is 16 voxels => chunk_min in world space:
|
|
||||||
let chunk_min_x = cx as f64 * (chunk_size as f64 * step);
|
|
||||||
let chunk_min_y = cy as f64 * (chunk_size as f64 * step);
|
|
||||||
let chunk_min_z = cz as f64 * (chunk_size as f64 * step);
|
|
||||||
|
|
||||||
// for local offset
|
|
||||||
let chunk_min_f32 = Vec3::new(
|
|
||||||
chunk_min_x as f32,
|
|
||||||
chunk_min_y as f32,
|
|
||||||
chunk_min_z as f32,
|
|
||||||
);
|
|
||||||
let voxel_size_f = step as f32;
|
|
||||||
|
|
||||||
// i in [0..16] => corner is chunk_min_x + i*step
|
|
||||||
// no +0.5 => corners approach
|
|
||||||
for i in 0..chunk_size {
|
|
||||||
let vx = chunk_min_x + i as f64 * step;
|
|
||||||
for j in 0..chunk_size {
|
|
||||||
let vy = chunk_min_y + j as f64 * step;
|
|
||||||
for k in 0..chunk_size {
|
|
||||||
let vz = chunk_min_z + k as f64 * step;
|
|
||||||
|
|
||||||
// check if we have a voxel at that corner
|
|
||||||
if let Some(_) = octree.get_voxel_at_world_coords(vx, vy, vz) {
|
|
||||||
// check neighbors
|
|
||||||
for &(dx, dy, dz) in NEIGHBOR_OFFSETS.iter() {
|
|
||||||
let nx = vx + dx as f64 * step;
|
|
||||||
let ny = vy + dy as f64 * step;
|
|
||||||
let nz = vz + dz as f64 * step;
|
|
||||||
|
|
||||||
if octree.get_voxel_at_world_coords(nx, ny, nz).is_none() {
|
|
||||||
let (normal, local_offset) = crate::systems::voxels::helper::face_orientation(dx, dy, dz, voxel_size_f);
|
|
||||||
|
|
||||||
// The voxel corner in chunk-local coords
|
|
||||||
let voxel_corner_local = Vec3::new(vx as f32, vy as f32, vz as f32)
|
|
||||||
- chunk_min_f32;
|
|
||||||
|
|
||||||
// generate face
|
|
||||||
// e.g. center might be the corner + 0.5 offset, or
|
|
||||||
// we can just treat the corner as the "center" in your face calc
|
|
||||||
// but let's do it carefully:
|
|
||||||
let face_center_local = voxel_corner_local + Vec3::splat(voxel_size_f*0.5);
|
|
||||||
|
|
||||||
let face_mesh = generate_face(
|
|
||||||
normal,
|
|
||||||
local_offset,
|
|
||||||
face_center_local,
|
|
||||||
voxel_size_f / 2.0,
|
|
||||||
);
|
|
||||||
face_meshes.push(face_mesh);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
face_meshes
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fn generate_face(orientation: Vec3, local_position: Vec3, position: Vec3, face_size: f32) -> Mesh {
|
fn generate_face(orientation: Vec3, local_position: Vec3, position: Vec3, face_size: f32) -> Mesh {
|
||||||
// Initialize an empty mesh with triangle topology
|
// Initialize an empty mesh with triangle topology
|
||||||
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
|
let mut mesh = Mesh::new(PrimitiveTopology::TriangleList, RenderAssetUsages::default());
|
||||||
|
|||||||
@ -28,28 +28,13 @@ pub struct SparseVoxelOctree {
|
|||||||
#[reflect(ignore)]
|
#[reflect(ignore)]
|
||||||
pub root: OctreeNode,
|
pub root: OctreeNode,
|
||||||
pub max_depth: u32,
|
pub max_depth: u32,
|
||||||
pub size: f64,
|
pub size: f32,
|
||||||
pub show_wireframe: bool,
|
pub show_wireframe: bool,
|
||||||
pub show_world_grid: bool,
|
pub show_world_grid: bool,
|
||||||
pub show_chunks: bool,
|
pub show_chunks: bool,
|
||||||
pub dirty_chunks: HashSet<(i32, i32, i32)>,
|
pub dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Resource, Reflect)]
|
|
||||||
pub struct ChunkEntities {
|
|
||||||
pub map: HashMap<(i32, i32, i32), Entity>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub struct ChunkMarker {
|
|
||||||
pub(crate) chunk_coords: (i64, i64, i64),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub const CHUNK_RENDER_DISTANCE: f64 = 12.0;
|
|
||||||
pub const CHUNK_BUILD_BUDGET: usize = 10;
|
|
||||||
|
|
||||||
|
|
||||||
impl OctreeNode {
|
impl OctreeNode {
|
||||||
/// Creates a new empty octree node.
|
/// Creates a new empty octree node.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
@ -76,7 +61,7 @@ impl Voxel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub const NEIGHBOR_OFFSETS: [(f64, f64, f64); 6] = [
|
pub const NEIGHBOR_OFFSETS: [(f32, f32, f32); 6] = [
|
||||||
(-1.0, 0.0, 0.0), // Left
|
(-1.0, 0.0, 0.0), // Left
|
||||||
(1.0, 0.0, 0.0), // Right
|
(1.0, 0.0, 0.0), // Right
|
||||||
(0.0, -1.0, 0.0), // Down
|
(0.0, -1.0, 0.0), // Down
|
||||||
@ -85,14 +70,6 @@ pub const NEIGHBOR_OFFSETS: [(f64, f64, f64); 6] = [
|
|||||||
(0.0, 0.0, 1.0), // Front
|
(0.0, 0.0, 1.0), // Front
|
||||||
];
|
];
|
||||||
|
|
||||||
pub const CHUNK_NEIGHBOR_OFFSETS: [(i32, i32, i32); 6] = [
|
|
||||||
(-1, 0, 0),
|
|
||||||
(1, 0, 0),
|
|
||||||
(0, -1, 0),
|
|
||||||
(0, 1, 0),
|
|
||||||
(0, 0, -1),
|
|
||||||
(0, 0, 1),
|
|
||||||
];
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user