diff --git a/assets/config/third_person_camera.ron b/assets/config/third_person_camera.ron index 86deeef..77a5f03 100644 --- a/assets/config/third_person_camera.ron +++ b/assets/config/third_person_camera.ron @@ -10,7 +10,7 @@ smoothing_weight: 0.9, target_height_above_feet: 3.0, collision: ( - ball_radius: 2.0, + ball_radius: 4.0, min_obstruction_width: 1.0, min_range_length: 4.0, not_worth_searching_dist: 4.0, diff --git a/src/bin/editor/control/hover_3d.rs b/src/bin/editor/control/hover_3d.rs index 44fb31e..1d5f402 100644 --- a/src/bin/editor/control/hover_3d.rs +++ b/src/bin/editor/control/hover_3d.rs @@ -39,7 +39,7 @@ impl HoverVoxel { /// Returns the normal vector of the face that the ray hit first. pub fn hover_face(&self) -> Point3i { - voxel_containing_point3f(&self.impact.impact.normal.into()) + voxel_containing_point3f(&self.impact.impact.normal.normalize().into()) } /// Returns the point of the adjacent voxel that shares a face with the voxel that was hit by diff --git a/src/bin/editor/voxel_brush.rs b/src/bin/editor/voxel_brush.rs index 014a7a3..d8340fe 100644 --- a/src/bin/editor/voxel_brush.rs +++ b/src/bin/editor/voxel_brush.rs @@ -4,11 +4,9 @@ use crate::{ }; use voxel_mapper::voxel::{ - centered_extent, - chunk_cache_flusher::ChunkCacheFlusher, - chunk_processor::MeshMode, - editor::{EditVoxelsRequest, SetVoxel}, - voxel_containing_point, Voxel, VoxelDistance, VoxelMap, VoxelType, EMPTY_VOXEL, + centered_extent, chunk_cache_flusher::ChunkCacheFlusher, chunk_processor::MeshMode, + double_buffer::EditedChunksBackBuffer, voxel_containing_point, Voxel, VoxelDistance, VoxelMap, + VoxelType, EMPTY_VOXEL, }; use amethyst::{ @@ -61,7 +59,7 @@ impl<'a> System<'a> for VoxelBrushSystem { ReadExpect<'a, ChunkCacheFlusher>, WriteExpect<'a, PaintBrush>, WriteExpect<'a, MeshMode>, - Write<'a, EventChannel>, + WriteExpect<'a, EditedChunksBackBuffer>, CameraData<'a>, ); @@ -75,7 +73,7 @@ impl<'a> System<'a> for VoxelBrushSystem { cache_flusher, mut brush, mut mesh_mode, - mut edit_voxel_requests, + mut voxel_backbuffer, ray_data, ): Self::SystemData, ) { @@ -135,39 +133,39 @@ impl<'a> System<'a> for VoxelBrushSystem { .unwrap() { lock_brush_dist_from_camera = true; - send_request_for_sphere( + edit_sphere( SetVoxelOperation::MakeSolid, brush_center, brush.radius, brush.voxel_type, &map_reader, - &mut edit_voxel_requests, + &mut *voxel_backbuffer, ); } else if input_handler .action_is_down(&ActionBinding::RemoveVoxel) .unwrap() { lock_brush_dist_from_camera = true; - send_request_for_sphere( + edit_sphere( SetVoxelOperation::RemoveSolid, brush_center, brush.radius, VoxelType(0), &map_reader, - &mut edit_voxel_requests, + &mut *voxel_backbuffer, ); } if !lock_brush_dist_from_camera { if let Some((_cam, cam_tfm)) = ray_data.get_main_camera() { - let dist_from_xz_point = objects - .xz_plane - .map(|p| (*cam_tfm.translation() - p.coords).norm()); - brush.dist_from_camera = objects - .voxel - .as_ref() - .map(|v| v.impact.impact.toi) - .or(dist_from_xz_point); + brush.dist_from_camera = + objects + .voxel + .as_ref() + .map(|v| v.impact.impact.toi) + .or(objects + .xz_plane + .map(|p| (*cam_tfm.translation() - p.coords).norm())); } } @@ -175,32 +173,26 @@ impl<'a> System<'a> for VoxelBrushSystem { } } -fn send_request_for_sphere( +fn edit_sphere( operation: SetVoxelOperation, center: Point3i, radius: u32, voxel_type: VoxelType, map_reader: &ChunkMapReader3, - edit_voxel_requests: &mut EventChannel, + voxel_backbuffer: &mut EditedChunksBackBuffer, ) { - let set_voxels = centered_extent(center, radius) - .iter_points() - .filter_map(|p| { + voxel_backbuffer.edit_voxels_out_of_place( + map_reader, + ¢ered_extent(center, radius), + |p: Point3i, v: &mut Voxel| { let diff = p - center; let dist = (diff.dot(&diff) as f32).sqrt() - radius as f32; if dist <= 1.0 { - let old_voxel = map_reader.get(&p); - Some(( - p, - determine_new_voxel(old_voxel, operation, voxel_type, dist), - )) - } else { - // No need to set a voxel this far away from the sphere's surface. - None + let old_voxel = *v; + *v = determine_new_voxel(old_voxel, operation, voxel_type, dist); } - }) - .collect(); - edit_voxel_requests.single_write(EditVoxelsRequest { voxels: set_voxels }); + }, + ); } fn determine_new_voxel( @@ -208,7 +200,7 @@ fn determine_new_voxel( operation: SetVoxelOperation, new_type: VoxelType, new_dist: f32, -) -> SetVoxel { +) -> Voxel { let old_type = old_voxel.voxel_type; let old_dist = VoxelDistance::decode(old_voxel.distance); let old_solid = old_dist < 0.0; @@ -244,14 +236,14 @@ fn determine_new_voxel( let new_solid = new_dist < 0.0; - SetVoxel { + Voxel { voxel_type: if new_solid { new_type } else { // Non-solid voxels can't have non-empty attributes. EMPTY_VOXEL.voxel_type }, - distance: new_dist, + distance: VoxelDistance::encode(new_dist), } } diff --git a/src/voxel.rs b/src/voxel.rs index a7d8b40..a19ae8f 100644 --- a/src/voxel.rs +++ b/src/voxel.rs @@ -6,7 +6,6 @@ pub mod chunk_cache_compressor; pub mod chunk_cache_flusher; pub mod chunk_processor; pub mod double_buffer; -pub mod editor; pub mod map_file; //pub mod map_generators; pub mod meshing; @@ -196,3 +195,13 @@ pub fn upgrade_point(old_p: na::Point3) -> nc_new::na::Point3 { pub fn upgrade_vector(old_v: na::Vector3) -> nc_new::na::Vector3 { nc_new::na::Vector3::::new(old_v.x, old_v.y, old_v.z) } + +pub fn empty_chunk_map() -> ChunkMap3 { + let ambient_value = EMPTY_VOXEL; + + ChunkMap3::new(VOXEL_CHUNK_SHAPE, ambient_value, (), FastLz4 { level: 10 }) +} + +pub fn empty_array(extent: Extent3i) -> Array3 { + Array3::fill(extent, EMPTY_VOXEL) +} diff --git a/src/voxel/bundle.rs b/src/voxel/bundle.rs index f114961..a8d1ea0 100644 --- a/src/voxel/bundle.rs +++ b/src/voxel/bundle.rs @@ -2,11 +2,10 @@ use super::{ chunk_cache_compressor::ChunkCacheCompressorSystem, chunk_cache_flusher::{ChunkCacheFlusher, ChunkCacheFlusherSystem, ChunkCacheReceiver}, chunk_processor::{MeshMode, VoxelChunkProcessorSystem}, - double_buffer::VoxelDoubleBufferingSystem, - editor::VoxelEditorSystemDesc, + double_buffer::{EditedChunksBackBuffer, VoxelDoubleBufferingSystem}, }; -use amethyst::core::{ecs::prelude::*, SystemBundle, SystemDesc}; +use amethyst::core::{ecs::prelude::*, SystemBundle}; use building_blocks::{core::Point3i, partition::OctreeDBVT}; /// Includes the voxel systems necessary for making edits to the `VoxelMap` and generating the @@ -14,9 +13,8 @@ use building_blocks::{core::Point3i, partition::OctreeDBVT}; /// and `VoxelAssets` resources. These can be created using the `load_voxel_map` function and /// `VoxelAssetLoader`. /// -/// In order for edits to be considered by the pipeline of systems, they must be submitted either to -/// the `EventChannel` or the `EditVoxelsRequestBackBuffer` resource. Editing the -/// `VoxelMap` directly will not work. +/// In order for edits to be considered by the pipeline of systems, they must be written to the +/// `EditedChunksBackBuffer`. Editing the `VoxelMap` directly will not work. pub struct VoxelSystemBundle; impl<'a, 'b> SystemBundle<'a, 'b> for VoxelSystemBundle { @@ -27,6 +25,7 @@ impl<'a, 'b> SystemBundle<'a, 'b> for VoxelSystemBundle { ) -> Result<(), amethyst::Error> { world.insert(OctreeDBVT::::default()); world.insert(MeshMode::SurfaceNets); + world.insert(EditedChunksBackBuffer::new()); // Chunk cache maintenance. let (tx, rx) = crossbeam::channel::unbounded(); @@ -36,12 +35,11 @@ impl<'a, 'b> SystemBundle<'a, 'b> for VoxelSystemBundle { dispatcher.add(ChunkCacheCompressorSystem, "chunk_cache_compressor", &[]); // Voxel editing. - dispatcher.add(VoxelEditorSystemDesc.build(world), "voxel_editor", &[]); dispatcher.add(VoxelChunkProcessorSystem, "voxel_chunk_processor", &[]); dispatcher.add( VoxelDoubleBufferingSystem, "voxel_double_buffering", - &["voxel_editor", "voxel_chunk_processor"], + &["voxel_chunk_processor"], ); Ok(()) diff --git a/src/voxel/double_buffer.rs b/src/voxel/double_buffer.rs index 6af7908..e1adb07 100644 --- a/src/voxel/double_buffer.rs +++ b/src/voxel/double_buffer.rs @@ -1,33 +1,62 @@ -use crate::voxel::{editor::EditVoxelsRequest, Voxel, VoxelMap}; +use crate::voxel::{empty_array, empty_chunk_map, Voxel, VoxelMap, VOXEL_CHUNK_SHAPE}; -use amethyst::{core::ecs::prelude::*, shrev::EventChannel}; -use building_blocks::prelude::*; -use std::collections::{HashMap, HashSet}; +use amethyst::core::ecs::prelude::*; +use building_blocks::{prelude::*, storage::compressible_map::MaybeCompressed}; +use std::collections::HashSet; #[cfg(feature = "profiler")] use thread_profiler::profile_scope; -/// Used by systems that want to double buffer their `SetVoxelRequests`, allowing them to run in -/// concurrently with the `VoxelEditorSystem`. Any requests written here in frame T will be written -/// to the `VoxelMap` at the end of frame T+1. -#[derive(Default)] -pub struct EditVoxelsRequestBackBuffer { - requests: Vec, +/// For the sake of pipelining, all voxels edits are first written out of place here. They get +/// merged into the `VoxelMap` by the `VoxelDoubleBufferingSystem` at the end of a frame. +pub struct EditedChunksBackBuffer { + edited_voxels: ChunkMap3, + // Includes the edited chunks as well as their neighbors, all of which need to be re-meshed. + dirty_chunk_keys: HashSet, } -impl EditVoxelsRequestBackBuffer { - pub fn push_request(&mut self, edit: EditVoxelsRequest) { - self.requests.push(edit); +impl EditedChunksBackBuffer { + pub fn new() -> Self { + Self { + edited_voxels: empty_chunk_map(), + dirty_chunk_keys: Default::default(), + } } -} -/// For the sake of pipelining, all voxels edits are first written out of place by the -/// `VoxelEditorSystem`. They get merged into the `VoxelMap` by the `VoxelDoubleBufferingSystem` at -/// the end of a frame. -#[derive(Default)] -pub struct EditedChunksBackBuffer { - pub edited_chunks: HashMap>, - pub neighbor_chunks: Vec, + /// This function does read-modify-write of the voxels in `extent`, reading from `reader` and + /// writing into the backbuffer. This enables parallelism between voxel editors and the chunk + /// processor. All edited chunks and their neighbors will be marked as dirty. + pub fn edit_voxels_out_of_place( + &mut self, + reader: &ChunkMapReader3, + extent: &Extent3i, + edit_func: impl Fn(Point3i, &mut Voxel), + ) { + // Copy any of the overlapping chunks that don't already exist in the backbuffer, i.e. those + // chunks which haven't been modified by this function yet. + for chunk_key in reader.chunk_keys_for_extent(extent) { + self.edited_voxels.chunks.get_or_insert_with(chunk_key, || { + reader + .get_chunk(chunk_key, reader.local_cache) + .cloned() + .unwrap_or(Chunk3::with_array(empty_array( + reader.extent_for_chunk_at_key(&chunk_key), + ))) + }); + } + + // Mark the chunks and their neighbors as dirty. + let extent_with_neighbor_chunks = Extent3i::from_min_and_max( + extent.minimum - VOXEL_CHUNK_SHAPE, + extent.max() + VOXEL_CHUNK_SHAPE, + ); + for chunk_key in reader.chunk_keys_for_extent(&extent_with_neighbor_chunks) { + self.dirty_chunk_keys.insert(chunk_key); + } + + // Edit the backbuffer. + self.edited_voxels.for_each_mut(extent, edit_func); + } } #[derive(Default)] @@ -36,50 +65,42 @@ pub struct DirtyChunks { } /// The system responsible for merging the `EditedChunksBackBuffer` into the `VoxelMap`. This allows -/// the `VoxelChunkProcessorSystem` and `VoxelEditorSystem` to run in parallel at the expense of a -/// single frame of latency. +/// the `VoxelChunkProcessorSystem` and systems that edit the `EditedChunksBackBuffer` to run in +/// parallel at the expense of a single frame of latency. pub struct VoxelDoubleBufferingSystem; impl<'a> System<'a> for VoxelDoubleBufferingSystem { type SystemData = ( - Write<'a, EditVoxelsRequestBackBuffer>, - Write<'a, EventChannel>, - Write<'a, Option>, Write<'a, Option>, + WriteExpect<'a, EditedChunksBackBuffer>, WriteExpect<'a, VoxelMap>, ); - fn run( - &mut self, - ( - mut edit_requests, mut set_voxels_channel, mut edits, mut dirty_chunks, mut map - ): Self::SystemData, - ) { + fn run(&mut self, (mut dirty_chunks, mut edits, mut map): Self::SystemData) { #[cfg(feature = "profiler")] profile_scope!("voxel_double_buffering"); - // Submit the requests to the setter. - set_voxels_channel.drain_vec_write(&mut edit_requests.requests); + // Create a new backbuffer. + let EditedChunksBackBuffer { + edited_voxels, + dirty_chunk_keys, + } = std::mem::replace(&mut *edits, EditedChunksBackBuffer::new()); // Merge the edits into the map. - let EditedChunksBackBuffer { - edited_chunks, - neighbor_chunks, - } = edits.take().unwrap(); - let mut new_dirty_chunks = HashSet::new(); - for (chunk_key, chunk_voxels) in edited_chunks.into_iter() { - map.voxels - .chunks - .insert(chunk_key, Chunk3::with_array(chunk_voxels)); - new_dirty_chunks.insert(chunk_key); + for (chunk_key, chunk) in edited_voxels.chunks.into_iter() { + match chunk { + MaybeCompressed::Compressed(_) => panic!("Never compress the back buffer"), + MaybeCompressed::Decompressed(chunk) => { + map.voxels.chunks.insert(chunk_key, chunk); + } + } } - new_dirty_chunks.extend(neighbor_chunks.into_iter()); // Update the set of dirty chunks so the `ChunkReloaderSystem` can see them on the next // frame. assert!(dirty_chunks.is_none()); *dirty_chunks = Some(DirtyChunks { - chunks: new_dirty_chunks, + chunks: dirty_chunk_keys, }); } } diff --git a/src/voxel/editor.rs b/src/voxel/editor.rs deleted file mode 100644 index ee965f4..0000000 --- a/src/voxel/editor.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::voxel::{ - chunk_cache_flusher::ChunkCacheFlusher, double_buffer::EditedChunksBackBuffer, VoxelDistance, - VoxelMap, VoxelType, EMPTY_VOXEL, VOXEL_CHUNK_SHAPE, -}; - -use amethyst::{core::ecs::prelude::*, derive::SystemDesc, shrev::EventChannel}; -use building_blocks::prelude::*; -use std::collections::HashMap; - -#[cfg(feature = "profiler")] -use thread_profiler::profile_scope; - -// TODO: delete entire chunks when they become empty - -#[derive(Clone, Debug)] -pub struct EditVoxelsRequest { - pub voxels: Vec<(Point3i, SetVoxel)>, -} - -/// The data actually stored in each point of the voxel map. -#[derive(Clone, Copy, Debug, Default, PartialEq)] -pub struct SetVoxel { - /// Points to some palette element. - pub voxel_type: VoxelType, - /// Distance from the isosurface. - pub distance: f32, -} - -/// Writes the `EditVoxelsRequest`s into the affected chunks out of place and puts the chunked edits -/// into the `EditedChunksBackBuffer` to be merged in at the end of the frame. -#[derive(SystemDesc)] -#[system_desc(name(VoxelEditorSystemDesc))] -pub struct VoxelEditorSystem { - #[system_desc(event_channel_reader)] - reader_id: ReaderId, -} - -impl VoxelEditorSystem { - pub fn new(reader_id: ReaderId) -> Self { - VoxelEditorSystem { reader_id } - } -} - -impl<'a> System<'a> for VoxelEditorSystem { - #[allow(clippy::type_complexity)] - type SystemData = ( - ReadExpect<'a, VoxelMap>, - Write<'a, Option>, - Read<'a, EventChannel>, - ReadExpect<'a, ChunkCacheFlusher>, - ); - - fn run(&mut self, (map, mut backbuffer, set_events, cache_flusher): Self::SystemData) { - #[cfg(feature = "profiler")] - profile_scope!("voxel_editor"); - - let local_chunk_cache = LocalChunkCache3::new(); - - let mut edited_chunks = HashMap::new(); - for EditVoxelsRequest { voxels } in set_events.read(&mut self.reader_id) { - for ( - p, - SetVoxel { - voxel_type: new_type, - distance: new_dist, - }, - ) in voxels.into_iter() - { - // Get the chunk containing the point. We only write out of place into the - // backbuffer. - let chunk_key = map.voxels.chunk_key(p); - let chunk = edited_chunks.entry(chunk_key).or_insert_with(|| { - if let Some(c) = map.voxels.get_chunk(chunk_key, &local_chunk_cache) { - c.array.clone() - } else { - Array3::fill(map.voxels.extent_for_chunk_at_key(&chunk_key), EMPTY_VOXEL) - } - }); - - // Set the new voxel value. - let voxel = chunk.get_mut(p); - voxel.distance = VoxelDistance::encode(*new_dist); - voxel.voxel_type = *new_type; - } - } - - // It's necessary to reload neighboring chunks when voxels change close to the boundaries. - // We just always add the neighbors for simplicity. - let mut neighbor_chunks = Vec::new(); - for chunk_key in edited_chunks.keys() { - for offset in Point3i::moore_offsets().iter() { - neighbor_chunks.push(*chunk_key + *offset * VOXEL_CHUNK_SHAPE); - } - } - - assert!(backbuffer.is_none()); - *backbuffer = Some(EditedChunksBackBuffer { - edited_chunks, - neighbor_chunks, - }); - - cache_flusher.flush(local_chunk_cache); - } -} diff --git a/src/voxel/meshing.rs b/src/voxel/meshing.rs index 7feb94b..d894d85 100644 --- a/src/voxel/meshing.rs +++ b/src/voxel/meshing.rs @@ -151,7 +151,7 @@ pub fn generate_mesh_vertices_with_greedy_quads( /// surface chunk. fn material_weights(voxels: &V, surface_strides: &[Stride]) -> Vec<[f32; 4]> where - V: HasArrayIndexer<[i32; 3]> + GetUncheckedRelease, + V: Array<[i32; 3]> + GetUncheckedRelease, { // Precompute the offsets for cube corners. let mut corner_offset_strides = [Stride(0); 8];