mirror of
https://github.com/eliasstepanik/voxel-simulation.git
synced 2026-01-26 13:08:29 +00:00
Compare commits
6 Commits
f5714ff105
...
c511e824d6
| Author | SHA1 | Date | |
|---|---|---|---|
| c511e824d6 | |||
| 93dcdb3379 | |||
| 2a24ae1ee6 | |||
|
|
d48df62b50 | ||
| eb130eab93 | |||
| d131c4f67a |
@ -27,6 +27,7 @@ tools.
|
||||
- **F3** – Toggle world grid
|
||||
- **Q** – Insert a red voxel at the crosshair
|
||||
- **F4** – Save the current octree to `octree.bin`
|
||||
- **F5** – Toggle sphere editing mode
|
||||
- **Escape** – Quit the application
|
||||
|
||||
## Running
|
||||
|
||||
@ -97,7 +97,4 @@ fn register_platform_plugins(app: &mut App) {
|
||||
..default()
|
||||
}));
|
||||
}
|
||||
}
|
||||
fn should_display_inspector(inspector_visible: Res<InspectorVisible>) -> bool {
|
||||
inspector_visible.0
|
||||
}
|
||||
}
|
||||
@ -39,7 +39,7 @@ impl Plugin for EnvironmentPlugin {
|
||||
});
|
||||
app.insert_resource(ChunkBudget { per_frame: 20 });
|
||||
app.init_resource::<PrevCameraChunk>();
|
||||
app.add_systems(Update, log_mesh_count);
|
||||
/* app.add_systems(Update, log_mesh_count);*/
|
||||
app
|
||||
// ------------------------------------------------------------------------
|
||||
// resources
|
||||
@ -72,11 +72,11 @@ impl Plugin for EnvironmentPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn log_mesh_count(meshes: Res<Assets<Mesh>>, time: Res<Time>) {
|
||||
/*fn log_mesh_count(meshes: Res<Assets<Mesh>>, time: Res<Time>) {
|
||||
if time.delta_secs_f64() as i32 % 5 == 0 {
|
||||
info!("meshes: {}", meshes.len());
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
fn should_visualize_octree(octree_query: Query<&SparseVoxelOctree>) -> bool {
|
||||
let Ok(octree) = octree_query.get_single() else {
|
||||
|
||||
@ -33,6 +33,7 @@ pub(crate) fn setup(
|
||||
GlobalTransform::default(),
|
||||
DirectionalLight {
|
||||
shadows_enabled: true,
|
||||
illuminance: 1.0,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
pub mod environment_system;
|
||||
pub mod camera_system;
|
||||
pub mod planet_system;
|
||||
pub mod voxels;
|
||||
pub mod voxel_system;
|
||||
@ -1,116 +0,0 @@
|
||||
use bevy::asset::RenderAssetUsages;
|
||||
use bevy::pbr::wireframe::{Wireframe, WireframeColor};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::*;
|
||||
use big_space::floating_origins::FloatingOrigin;
|
||||
use big_space::prelude::GridCell;
|
||||
use noise::{Fbm, NoiseFn, Perlin};
|
||||
use crate::plugins::big_space::big_space_plugin::RootGrid;
|
||||
use crate::plugins::environment::systems::camera_system::CameraController;
|
||||
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct PlanetMaker;
|
||||
|
||||
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct PlanetNoise(pub Fbm<Perlin>);
|
||||
|
||||
pub fn setup(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
root: Res<RootGrid>,
|
||||
) {
|
||||
// Diameter ~ Earth (~12,742 km) × 2 to exaggerate terrain if desired
|
||||
let radius = 12_742_000.0;
|
||||
let sphere_mesh = meshes.add(
|
||||
SphereMeshBuilder::new(radius, SphereKind::Ico { subdivisions: 100 })
|
||||
.build(),
|
||||
);
|
||||
let material_handle = materials.add(StandardMaterial::from(Color::srgb(0.3, 0.6, 1.0)));
|
||||
|
||||
commands.entity(root.0).with_children(|parent| {
|
||||
parent.spawn((
|
||||
Name::new("Planet"),
|
||||
Mesh3d(sphere_mesh.clone()),
|
||||
MeshMaterial3d(material_handle),
|
||||
GridCell::ZERO,
|
||||
Transform::default(),
|
||||
PlanetMaker,
|
||||
Wireframe,
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn setup_noise(mut commands: Commands) {
|
||||
let fbm_noise = Fbm::<Perlin>::new(0);
|
||||
commands.insert_resource(PlanetNoise(fbm_noise));
|
||||
}
|
||||
|
||||
pub fn deform_planet(
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
noise: Res<PlanetNoise>,
|
||||
query: Query<&Mesh3d, With<PlanetMaker>>,
|
||||
) {
|
||||
let frequency = 4.0 / 12_742_000.0;
|
||||
let amplitude = 100_000.0;
|
||||
|
||||
for mesh3d in query.iter() {
|
||||
let handle: &Handle<Mesh> = &mesh3d.0;
|
||||
|
||||
if let Some(mesh) = meshes.get_mut(handle) {
|
||||
// 1. Immutable borrow to extract normals (or default)
|
||||
let normals: Vec<[f32; 3]> = if let Some(VertexAttributeValues::Float32x3(vals)) =
|
||||
mesh.attribute(Mesh::ATTRIBUTE_NORMAL)
|
||||
{
|
||||
vals.clone()
|
||||
} else {
|
||||
// default normals if none exist
|
||||
let count = mesh
|
||||
.attribute(Mesh::ATTRIBUTE_POSITION)
|
||||
.and_then(|attr| match attr {
|
||||
VertexAttributeValues::Float32x3(v) => Some(v.len()),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(0);
|
||||
vec![[0.0, 1.0, 0.0]; count]
|
||||
};
|
||||
|
||||
// 2. Drop the immutable borrow, then mutable-borrow positions
|
||||
if let Some(VertexAttributeValues::Float32x3(positions)) =
|
||||
mesh.attribute_mut(Mesh::ATTRIBUTE_POSITION)
|
||||
{
|
||||
// Now mutate positions using the pre-fetched normals
|
||||
for (i, pos) in positions.iter_mut().enumerate() {
|
||||
let mut vertex = Vec3::new(pos[0], pos[1], pos[2]);
|
||||
let normal = Vec3::new(
|
||||
normals[i][0],
|
||||
normals[i][1],
|
||||
normals[i][2],
|
||||
);
|
||||
|
||||
let unit_dir = vertex.normalize();
|
||||
let sample = [
|
||||
unit_dir.x as f64 * frequency as f64,
|
||||
unit_dir.y as f64 * frequency as f64,
|
||||
unit_dir.z as f64 * frequency as f64,
|
||||
];
|
||||
let noise_value = noise.0.get(sample) as f32;
|
||||
let offset = normal * (noise_value * amplitude);
|
||||
|
||||
let new_pos = unit_dir * (vertex.length() + offset.length());
|
||||
*pos = [new_pos.x, new_pos.y, new_pos.z];
|
||||
}
|
||||
|
||||
|
||||
mesh.compute_smooth_normals();
|
||||
}
|
||||
|
||||
// Force AABB recalc
|
||||
mesh.remove_attribute(Mesh::ATTRIBUTE_COLOR);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -167,6 +167,54 @@ impl SparseVoxelOctree {
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a sphere of voxels with the given radius (in voxels) and center.
|
||||
pub fn insert_sphere(&mut self, center: Vec3, radius: i32, voxel: Voxel) {
|
||||
let step = self.get_spacing_at_depth(self.max_depth);
|
||||
let r2 = radius * radius;
|
||||
|
||||
for x in -radius..=radius {
|
||||
let dx2 = x * x;
|
||||
for y in -radius..=radius {
|
||||
let dy2 = y * y;
|
||||
for z in -radius..=radius {
|
||||
let dz2 = z * z;
|
||||
if dx2 + dy2 + dz2 <= r2 {
|
||||
let pos = Vec3::new(
|
||||
center.x + x as f32 * step,
|
||||
center.y + y as f32 * step,
|
||||
center.z + z as f32 * step,
|
||||
);
|
||||
self.insert(pos, voxel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove all voxels inside a sphere with the given radius (in voxels).
|
||||
pub fn remove_sphere(&mut self, center: Vec3, radius: i32) {
|
||||
let step = self.get_spacing_at_depth(self.max_depth);
|
||||
let r2 = radius * radius;
|
||||
|
||||
for x in -radius..=radius {
|
||||
let dx2 = x * x;
|
||||
for y in -radius..=radius {
|
||||
let dy2 = y * y;
|
||||
for z in -radius..=radius {
|
||||
let dz2 = z * z;
|
||||
if dx2 + dy2 + dz2 <= r2 {
|
||||
let pos = Vec3::new(
|
||||
center.x + x as f32 * step,
|
||||
center.y + y as f32 * step,
|
||||
center.z + z as f32 * step,
|
||||
);
|
||||
self.remove(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_recursive(node: &mut OctreeNode, x: f32, y: f32, z: f32, depth: u32) -> bool {
|
||||
if depth == 0 {
|
||||
if node.voxel.is_some() {
|
||||
|
||||
@ -2,10 +2,12 @@
|
||||
use bevy::app::{App, Plugin, PreUpdate, Startup};
|
||||
use bevy::ecs::schedule::IntoScheduleConfigs;
|
||||
use bevy::prelude::Update;
|
||||
use crate::plugins::input::systems::voxels::VoxelEditMode;
|
||||
|
||||
pub struct InputPlugin;
|
||||
impl Plugin for InputPlugin {
|
||||
fn build(&self, _app: &mut App) {
|
||||
_app.init_resource::<VoxelEditMode>();
|
||||
_app.add_systems(
|
||||
Update,
|
||||
(
|
||||
|
||||
@ -4,6 +4,20 @@ use crate::plugins::environment::systems::voxels::structure::*;
|
||||
use bevy::prelude::*;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Resource, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VoxelEditMode {
|
||||
Single,
|
||||
Sphere,
|
||||
}
|
||||
|
||||
impl Default for VoxelEditMode {
|
||||
fn default() -> Self {
|
||||
Self::Single
|
||||
}
|
||||
}
|
||||
|
||||
const EDIT_SPHERE_RADIUS: i32 = 8;
|
||||
|
||||
///TODO
|
||||
pub fn voxel_system(
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
@ -12,6 +26,7 @@ pub fn voxel_system(
|
||||
|
||||
mut query: Query<(&mut Transform, &mut CameraController)>,
|
||||
mut windows: Query<&mut Window>,
|
||||
mut edit_mode: ResMut<VoxelEditMode>,
|
||||
) {
|
||||
let Ok(mut window) = windows.get_single_mut() else {
|
||||
return;
|
||||
@ -47,22 +62,13 @@ pub fn voxel_system(
|
||||
}
|
||||
}
|
||||
}
|
||||
/* if keyboard_input.just_pressed(KeyCode::F5){
|
||||
let path = Path::new("octree.bin");
|
||||
if path.exists() {
|
||||
let path = Path::new("octree.bin");
|
||||
|
||||
let mut octree = if path.exists() {
|
||||
match SparseVoxelOctree::load_from_file(path) {
|
||||
Ok(tree) => tree,
|
||||
Err(err) => {
|
||||
error!("failed to load octree: {err}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}*/
|
||||
if keyboard_input.just_pressed(KeyCode::F5) {
|
||||
*edit_mode = match *edit_mode {
|
||||
VoxelEditMode::Single => VoxelEditMode::Sphere,
|
||||
VoxelEditMode::Sphere => VoxelEditMode::Single,
|
||||
};
|
||||
}
|
||||
|
||||
// =======================
|
||||
// 6) Building
|
||||
@ -85,28 +91,37 @@ pub fn voxel_system(
|
||||
|
||||
for mut octree in octree_query.iter_mut() {
|
||||
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));
|
||||
|
||||
// Remove the voxel
|
||||
octree.remove(offset_position);
|
||||
} 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));
|
||||
|
||||
// Insert the new voxel
|
||||
octree.insert(offset_position, Voxel::random_sides());
|
||||
match *edit_mode {
|
||||
VoxelEditMode::Single => {
|
||||
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;
|
||||
let offset_position = hit_position - (normal * Vec3::splat(epsilon));
|
||||
octree.remove(offset_position);
|
||||
} 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;
|
||||
let offset_position = hit_position + (normal * Vec3::splat(epsilon));
|
||||
octree.insert(offset_position, Voxel::random_sides());
|
||||
}
|
||||
}
|
||||
VoxelEditMode::Sphere => {
|
||||
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;
|
||||
let offset = hit_position - normal * Vec3::splat(epsilon);
|
||||
octree.remove_sphere(offset, EDIT_SPHERE_RADIUS);
|
||||
} 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;
|
||||
let offset = hit_position + normal * Vec3::splat(epsilon);
|
||||
octree.insert_sphere(offset, EDIT_SPHERE_RADIUS, Voxel::random_sides());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user