Skip to content

Commit

Permalink
replace the VoxelEditorSystem with the EditedChunksBackBuffer::edit_v…
Browse files Browse the repository at this point in the history
…oxels_out_of_place method; this should be more efficient that sending a set of points to be written into a voxel map
  • Loading branch information
bonsairobo committed Nov 6, 2020
1 parent 7976835 commit 95f0d55
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 200 deletions.
2 changes: 1 addition & 1 deletion assets/config/third_person_camera.ron
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion src/bin/editor/control/hover_3d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 30 additions & 38 deletions src/bin/editor/voxel_brush.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -61,7 +59,7 @@ impl<'a> System<'a> for VoxelBrushSystem {
ReadExpect<'a, ChunkCacheFlusher>,
WriteExpect<'a, PaintBrush>,
WriteExpect<'a, MeshMode>,
Write<'a, EventChannel<EditVoxelsRequest>>,
WriteExpect<'a, EditedChunksBackBuffer>,
CameraData<'a>,
);

Expand All @@ -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,
) {
Expand Down Expand Up @@ -135,80 +133,74 @@ 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()));
}
}

cache_flusher.flush(local_cache);
}
}

fn send_request_for_sphere(
fn edit_sphere(
operation: SetVoxelOperation,
center: Point3i,
radius: u32,
voxel_type: VoxelType,
map_reader: &ChunkMapReader3<Voxel>,
edit_voxel_requests: &mut EventChannel<EditVoxelsRequest>,
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,
&centered_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(
old_voxel: 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;
Expand Down Expand Up @@ -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),
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/voxel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -196,3 +195,13 @@ pub fn upgrade_point(old_p: na::Point3<f32>) -> nc_new::na::Point3<f32> {
pub fn upgrade_vector(old_v: na::Vector3<f32>) -> nc_new::na::Vector3<f32> {
nc_new::na::Vector3::<f32>::new(old_v.x, old_v.y, old_v.z)
}

pub fn empty_chunk_map() -> ChunkMap3<Voxel> {
let ambient_value = EMPTY_VOXEL;

ChunkMap3::new(VOXEL_CHUNK_SHAPE, ambient_value, (), FastLz4 { level: 10 })
}

pub fn empty_array(extent: Extent3i) -> Array3<Voxel> {
Array3::fill(extent, EMPTY_VOXEL)
}
14 changes: 6 additions & 8 deletions src/voxel/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,19 @@ 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
/// corresponding entities in real time. Before dispatching, the `World` must contain a `VoxelMap`
/// 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<EditVoxelsRequest>` 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 {
Expand All @@ -27,6 +25,7 @@ impl<'a, 'b> SystemBundle<'a, 'b> for VoxelSystemBundle {
) -> Result<(), amethyst::Error> {
world.insert(OctreeDBVT::<Point3i>::default());
world.insert(MeshMode::SurfaceNets);
world.insert(EditedChunksBackBuffer::new());

// Chunk cache maintenance.
let (tx, rx) = crossbeam::channel::unbounded();
Expand All @@ -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(())
Expand Down
113 changes: 67 additions & 46 deletions src/voxel/double_buffer.rs
Original file line number Diff line number Diff line change
@@ -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<EditVoxelsRequest>,
/// 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<Voxel>,
// Includes the edited chunks as well as their neighbors, all of which need to be re-meshed.
dirty_chunk_keys: HashSet<Point3i>,
}

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<Point3i, Array3<Voxel>>,
pub neighbor_chunks: Vec<Point3i>,
/// 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<Voxel>,
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)]
Expand All @@ -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<EditVoxelsRequest>>,
Write<'a, Option<EditedChunksBackBuffer>>,
Write<'a, Option<DirtyChunks>>,
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,
});
}
}
Loading

0 comments on commit 95f0d55

Please sign in to comment.