diff --git a/Cargo.toml b/Cargo.toml
index e8bf7cc2..8d3ad698 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -62,6 +62,7 @@ building_blocks_mesh = { path = "crates/building_blocks_mesh", version = "0.6.0"
building_blocks_search = { path = "crates/building_blocks_search", version = "0.6.0", default-features = false, optional = true }
[dev-dependencies]
+bevy_fly_camera = "0.7"
simdnoise = "3.1"
# Common code for tests and examples.
@@ -104,3 +105,8 @@ required-features = ["mesh"]
name = "lod_terrain"
path = "examples/lod_terrain/lod_terrain.rs"
required-features = ["glam", "mesh"]
+
+[[example]]
+name = "blocky_ambient_occlusion"
+path = "examples/blocky_ambient_occlusion/blocky_ambient_occlusion.rs"
+required-features = ["mesh"]
diff --git a/crates/building_blocks_mesh/src/greedy_quads.rs b/crates/building_blocks_mesh/src/greedy_quads.rs
index bb476d72..c817fc50 100644
--- a/crates/building_blocks_mesh/src/greedy_quads.rs
+++ b/crates/building_blocks_mesh/src/greedy_quads.rs
@@ -50,19 +50,56 @@ pub struct QuadCoordinateConfig {
pub u_flip_face: Axis3,
}
-pub const RIGHT_HANDED_Y_UP_CONFIG: QuadCoordinateConfig = QuadCoordinateConfig {
- // Y is always in the V direction when it's not the normal. When Y is the normal, right-handedness determines that
- // we must use Yzx permutations.
- faces: [
- OrientedCubeFace::new(-1, Axis3Permutation::Xzy),
- OrientedCubeFace::new(-1, Axis3Permutation::Yzx),
- OrientedCubeFace::new(-1, Axis3Permutation::Zxy),
- OrientedCubeFace::new(1, Axis3Permutation::Xzy),
- OrientedCubeFace::new(1, Axis3Permutation::Yzx),
- OrientedCubeFace::new(1, Axis3Permutation::Zxy),
- ],
- u_flip_face: Axis3::X,
-};
+pub fn right_handed_y_up_config() -> QuadCoordinateConfig {
+ QuadCoordinateConfig {
+ faces: [
+ // Front
+ OrientedCubeFace::new(PointN([1, 1, 1]), Axis3Permutation::Zxy),
+ // Top
+ OrientedCubeFace::new(PointN([1, 1, -1]), Axis3Permutation::Yxz),
+ // Right
+ OrientedCubeFace::new(PointN([1, -1, 1]), Axis3Permutation::Xzy),
+ // Back
+ OrientedCubeFace::new(PointN([-1, -1, 1]), Axis3Permutation::Zxy),
+ // Bottom
+ OrientedCubeFace::new(PointN([-1, 1, 1]), Axis3Permutation::Yxz),
+ // Left
+ OrientedCubeFace::new(PointN([-1, 1, 1]), Axis3Permutation::Xzy),
+ ],
+ u_flip_face: Axis3::X,
+ }
+}
+
+pub fn oriented_cube_face_to_face_strides(
+ voxels: &A,
+ oriented_cube_face: &OrientedCubeFace,
+) -> FaceStrides
+where
+ A: IndexedArray<[i32; 3]>,
+{
+ let OrientedCubeFace { n, u, v, .. } = oriented_cube_face;
+ let n_stride = voxels.stride_from_local_point(Local(*n));
+ let u_stride = voxels.stride_from_local_point(Local(*u));
+ let v_stride = voxels.stride_from_local_point(Local(*v));
+ let max_stride = Stride(voxels.extent().shape.volume() as usize);
+ FaceStrides {
+ n_stride,
+ u_stride,
+ v_stride,
+ // The offset to the voxel sharing this cube face.
+ visibility_offset: n_stride,
+ u_offset: if u_stride > max_stride {
+ Stride(0) - u_stride
+ } else {
+ Stride(0)
+ },
+ v_offset: if v_stride > max_stride {
+ Stride(0) - v_stride
+ } else {
+ Stride(0)
+ },
+ }
+}
impl QuadCoordinateConfig {
pub fn quad_groups(self) -> [QuadGroup; 6] {
@@ -172,55 +209,43 @@ fn greedy_quads_for_group(
Merger: MergeStrategy,
{
visited.reset_values(false);
+ println!("ORIENTED CUBE FACE: {:?}", quad_group.face);
- let QuadGroup {
- quads,
- face:
- OrientedCubeFace {
- n_sign,
- permutation,
- n,
- u,
- v,
- ..
- },
- } = quad_group;
-
- let [n_axis, u_axis, v_axis] = permutation.axes();
+ let (n, u, v) = (quad_group.face.n, quad_group.face.u, quad_group.face.v);
+ let [n_axis, u_axis, v_axis] = quad_group.face.permutation.axes();
let i_n = n_axis.index();
let i_u = u_axis.index();
let i_v = v_axis.index();
let num_slices = interior.shape.at(i_n);
- let slice_shape = *n + *u * interior.shape.at(i_u) + *v * interior.shape.at(i_v);
- let mut slice_extent = Extent3i::from_min_and_shape(interior.minimum, slice_shape);
+ let (positive_n, positive_u, positive_v) = (n * n, u * u, v * v);
+ let slice_shape =
+ positive_n + positive_u * interior.shape.at(i_u) + positive_v * interior.shape.at(i_v);
+ let mut slice_extent = Extent3i::from_min_and_shape(
+ interior.minimum
+ + if n != positive_n {
+ (num_slices - 1) * positive_n
+ } else {
+ PointN([0, 0, 0])
+ },
+ slice_shape,
+ );
- let n_stride = voxels.stride_from_local_point(Local(*n));
- let u_stride = voxels.stride_from_local_point(Local(*u));
- let v_stride = voxels.stride_from_local_point(Local(*v));
- let face_strides = FaceStrides {
- n_stride,
- u_stride,
- v_stride,
- // The offset to the voxel sharing this cube face.
- visibility_offset: if *n_sign > 0 {
- n_stride
- } else {
- Stride(0) - n_stride
- },
- };
+ let face_strides = oriented_cube_face_to_face_strides(voxels, &quad_group.face);
for _ in 0..num_slices {
let slice_ub = slice_extent.least_upper_bound();
let u_ub = slice_ub.at(i_u);
let v_ub = slice_ub.at(i_v);
+ // FIXME: The min needs to start from the correct point according to the u and v directions
+ // otherwise for negative u / v, no merging will happen along those directions
voxels.for_each(
&slice_extent,
- |(quad_min, quad_min_stride): (Point3i, Stride), quad_min_voxel| {
+ |(voxel_min, voxel_min_stride): (Point3i, Stride), voxel| {
if !face_needs_mesh(
- &quad_min_voxel,
- quad_min_stride,
+ &voxel,
+ voxel_min_stride,
face_strides.visibility_offset,
voxels,
visited,
@@ -230,12 +255,12 @@ fn greedy_quads_for_group(
// We have at least one face that needs a mesh. We'll try to expand that face into the biggest quad we can find.
// These are the boundaries on quad width and height so it is contained in the slice.
- let max_width = u_ub - quad_min.at(i_u);
- let max_height = v_ub - quad_min.at(i_v);
+ let max_width = u_ub - voxel_min.at(i_u);
+ let max_height = v_ub - voxel_min.at(i_v);
let (quad_width, quad_height) = Merger::find_quad(
- quad_min_stride,
- &quad_min_voxel,
+ voxel_min_stride,
+ &voxel,
max_width,
max_height,
&face_strides,
@@ -248,12 +273,26 @@ fn greedy_quads_for_group(
debug_assert!(quad_height <= max_height);
// Mark the quad as visited.
- let quad_extent =
- Extent3i::from_min_and_shape(quad_min, *n + *u * quad_width + *v * quad_height);
+ let extent_minimum = voxel_min
+ + if u != positive_u {
+ u * (quad_width - 1)
+ } else {
+ PointN([0, 0, 0])
+ }
+ + if v != positive_v {
+ v * (quad_height - 1)
+ } else {
+ PointN([0, 0, 0])
+ };
+ let quad_extent = Extent3i::from_min_and_shape(
+ extent_minimum,
+ positive_n + positive_u * quad_width + positive_v * quad_height,
+ );
+ println!("MARKING {:?} VISITED", quad_extent);
visited.fill_extent(&quad_extent, true);
- quads.push(UnorientedQuad {
- minimum: quad_min,
+ quad_group.quads.push(UnorientedQuad {
+ minimum: voxel_min,
width: quad_width,
height: quad_height,
});
@@ -261,7 +300,7 @@ fn greedy_quads_for_group(
);
// Move to the next slice.
- slice_extent += *n;
+ slice_extent += n;
}
}
@@ -276,27 +315,44 @@ fn face_needs_mesh(
visited: &Array3x1,
) -> bool
where
- A: Get,
+ A: Get + IndexedArray<[i32; 3]>,
T: IsEmpty + IsOpaque,
{
if voxel.is_empty() || visited.get(voxel_stride) {
+ // println!(
+ // "face_needs_mesh: false -> voxel.is_empty(): {}, visited.get(voxel_stride): {}",
+ // voxel.is_empty(),
+ // visited.get(voxel_stride)
+ // );
return false;
}
let adjacent_voxel = voxels.get(voxel_stride + visibility_offset);
if adjacent_voxel.is_empty() {
+ println!(
+ "face_needs_mesh: true -> adjacent_voxel.is_empty(): {:?}",
+ stride_to_point(voxels.extent(), voxel_stride + visibility_offset)
+ );
// Must be visible, opaque or transparent.
return true;
}
if adjacent_voxel.is_opaque() {
+ println!(
+ "face_needs_mesh: true -> adjacent_voxel.is_opaque(): {:?}",
+ stride_to_point(voxels.extent(), voxel_stride + visibility_offset)
+ );
// Fully occluded.
return false;
}
// TODO: If the face lies between two transparent voxels, we choose not to mesh it. We might need to extend the IsOpaque
// trait with different levels of transparency to support this.
+ println!(
+ "face_needs_mesh: {} -> voxel.is_opaque()",
+ voxel.is_opaque()
+ );
voxel.is_opaque()
}
@@ -341,11 +397,14 @@ pub trait MergeStrategy {
Self::Voxel: IsEmpty + IsOpaque;
}
+#[derive(Debug)]
pub struct FaceStrides {
pub n_stride: Stride,
pub u_stride: Stride,
pub v_stride: Stride,
pub visibility_offset: Stride,
+ pub u_offset: Stride,
+ pub v_offset: Stride,
}
/// A per-voxel value used for merging quads.
@@ -377,7 +436,7 @@ where
visited: &Array3x1,
) -> (i32, i32)
where
- A: Get,
+ A: Get + IndexedArray<[i32; 3]>,
{
// Greedily search for the biggest visible quad where all merge values are the same.
let quad_value = min_value.voxel_merge_value();
@@ -430,7 +489,7 @@ impl VoxelMerger {
max_width: i32,
) -> i32
where
- A: Get,
+ A: Get + IndexedArray<[i32; 3]>,
T: IsEmpty + IsOpaque + MergeVoxel,
{
let mut quad_width = 0;
@@ -459,5 +518,742 @@ impl VoxelMerger {
quad_width
}
}
+pub struct VoxelAOMerger {
+ marker: std::marker::PhantomData,
+}
+
+impl MergeStrategy for VoxelAOMerger
+where
+ T: MergeVoxel + IsEmpty + IsOpaque,
+{
+ type Voxel = T;
+
+ fn find_quad(
+ min_stride: Stride,
+ min_value: &T,
+ mut max_width: i32,
+ max_height: i32,
+ face_strides: &FaceStrides,
+ voxels: &A,
+ visited: &Array3x1,
+ ) -> (i32, i32)
+ where
+ A: IndexedArray<[i32; 3]> + Get,
+ {
+ println!(">>> find_quad");
+ // Greedily search for the biggest visible quad where all merge values are the same.
+ let quad_value = min_value.voxel_merge_value();
+
+ println!("face_strides: {:?}", face_strides);
+ let cube_face = face_strides_to_cube_face(face_strides);
+ println!("cube_face: {:?}", cube_face);
+ println!(
+ "find_quad for {:?}",
+ stride_to_point(voxels.extent(), min_stride)
+ );
+
+ // Start by finding the widest quad in the U direction.
+ let mut row_start_stride = min_stride;
+ let vaos = [
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::TopLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::BottomLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::TopRight,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::BottomRight,
+ ),
+ ];
+ let quad_width = Self::get_row_width(
+ voxels,
+ visited,
+ &quad_value,
+ &vaos,
+ face_strides.visibility_offset,
+ row_start_stride,
+ face_strides.u_stride,
+ face_strides,
+ cube_face,
+ max_width,
+ );
+
+ max_width = max_width.min(quad_width);
+ let mut quad_height = 1;
+
+ // If the 0th row ambient occlusion values are constant along V, we can merge in V
+ if vaos[0] == vaos[1] && vaos[2] == vaos[3] {
+ // Now see how tall we can make the quad in the V direction without changing the width.
+ row_start_stride += face_strides.v_stride;
+ while quad_height < max_height {
+ let p = stride_to_point(voxels.extent(), row_start_stride);
+ println!("quad_height: {}, p: {:?}", quad_height, p);
+ // Check if minimum U edge AO values match, if so we can merge vertically, otherwise we're done
+ let row_vaos = [
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::TopLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::BottomLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::TopRight,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_start_stride,
+ cube_face,
+ FaceVertex::BottomRight,
+ ),
+ ];
+
+ if vaos[1] != row_vaos[0]
+ || vaos[3] != row_vaos[2]
+ || row_vaos[0] != row_vaos[1]
+ || row_vaos[0] != row_vaos[2]
+ || row_vaos[2] != row_vaos[3]
+ {
+ // AO values not constant along V
+ println!("AO values not constant along V");
+ break;
+ }
+ let row_width = Self::get_row_width(
+ voxels,
+ visited,
+ &quad_value,
+ &row_vaos,
+ face_strides.visibility_offset,
+ row_start_stride,
+ face_strides.u_stride,
+ face_strides,
+ cube_face,
+ max_width,
+ );
+ if row_width < quad_width {
+ println!("row_width {} < quad_width {}", row_width, quad_width);
+ break;
+ }
+ quad_height += 1;
+ row_start_stride += face_strides.v_stride;
+ }
+ }
+
+ println!("quad_width: {}, quad_height: {}", quad_width, quad_height);
+ println!("<<< find_quad");
+ (quad_width, quad_height)
+ }
+}
+
+impl VoxelAOMerger {
+ fn get_row_width(
+ voxels: &A,
+ visited: &Array3x1,
+ quad_merge_voxel_value: &T::VoxelValue,
+ vaos: &[i32],
+ visibility_offset: Stride,
+ start_stride: Stride,
+ delta_stride: Stride,
+ face_strides: &FaceStrides,
+ cube_face: CubeFace,
+ max_width: i32,
+ ) -> i32
+ where
+ A: IndexedArray<[i32; 3]> + Get,
+ T: IsEmpty + IsOpaque + MergeVoxel,
+ {
+ println!(">>> get_row_width");
+ println!(
+ "start stride: {:?} = {:?}",
+ stride_to_point(voxels.extent(), start_stride),
+ vaos
+ );
+ let mut quad_width = 0;
+ let mut row_stride = start_stride;
+ while quad_width < max_width {
+ let padded_extent = *voxels.extent();
+ let p = stride_to_point(&padded_extent, row_stride) + padded_extent.minimum;
+ println!("p: {:?}", p);
+
+ if visited.get(row_stride) {
+ // Already have a quad for this voxel face.
+ println!("<<< Visited: {:?}", p);
+ break;
+ }
+
+ let voxel = voxels.get(row_stride);
+
+ if !face_needs_mesh(&voxel, row_stride, visibility_offset, voxels, visited) {
+ println!("Face does not need mesh: {:?}", p);
+ break;
+ }
+
+ if !voxel.voxel_merge_value().eq(quad_merge_voxel_value) {
+ // Voxel needs to be non-empty and match the quad merge value.
+ println!("Voxel value differs: {:?}", p);
+ break;
+ }
+
+ let next_vaos = [
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_stride,
+ cube_face,
+ FaceVertex::TopLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_stride,
+ cube_face,
+ FaceVertex::BottomLeft,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_stride,
+ cube_face,
+ FaceVertex::TopRight,
+ ),
+ get_ao_at_vert(
+ voxels,
+ face_strides,
+ row_stride,
+ cube_face,
+ FaceVertex::BottomRight,
+ ),
+ ];
+ if vaos[2] != next_vaos[0]
+ || vaos[3] != next_vaos[1]
+ || next_vaos[0] != next_vaos[2]
+ || next_vaos[1] != next_vaos[3]
+ {
+ println!(
+ "AO values differ: {:?} {:?}",
+ p, face_strides.visibility_offset
+ );
+ // Ambient occlusion values must be constant across the row
+ // They are not so we cannot proceed to the next quad
+ if quad_width == 0 {
+ quad_width = 1;
+ }
+ break;
+ }
+
+ quad_width += 1;
+ row_stride += delta_stride;
+ }
+
+ println!("quad_width: {}", quad_width);
+ println!("<<< get_row_width");
+ quad_width
+ }
+}
+
+fn stride_to_point(extent: &Extent3i, stride: Stride) -> Point3i {
+ let mut rem = stride.0 as i32;
+
+ let xy_area = extent.shape.x() * extent.shape.y();
+ let z = rem / xy_area;
+ rem %= xy_area;
+
+ let y = rem / extent.shape.x();
+ rem %= extent.shape.x();
+
+ let x = rem;
+
+ PointN([x, y, z]) + extent.minimum
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum CubeFace {
+ Top,
+ Bottom,
+ Left,
+ Right,
+ Back,
+ Front,
+}
+
+#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
+pub enum FaceVertex {
+ BottomLeft,
+ BottomRight,
+ TopRight,
+ TopLeft,
+}
+
+fn face_strides_to_cube_face(face_strides: &FaceStrides) -> CubeFace {
+ let (ns, us, vs, vis) = (
+ face_strides.n_stride,
+ face_strides.u_stride,
+ face_strides.v_stride,
+ face_strides.visibility_offset,
+ );
+ let n_sign = if vis == ns { 1 } else { -1 };
+ let (xs, ys, zs) = if ns >= us && us >= vs {
+ (vs, us, ns)
+ } else if ns >= vs && vs >= us {
+ (us, vs, ns)
+ } else if us >= ns && ns >= vs {
+ (vs, ns, us)
+ } else if us >= vs && vs >= ns {
+ (ns, vs, us)
+ } else if vs >= ns && ns >= us {
+ (us, ns, vs)
+ } else if vs >= us && us >= ns {
+ (ns, us, vs)
+ } else {
+ unreachable!();
+ };
+ if n_sign > 0 {
+ if ns == xs {
+ CubeFace::Right
+ } else if ns == ys {
+ CubeFace::Top
+ } else {
+ CubeFace::Front
+ }
+ } else {
+ if ns == xs {
+ CubeFace::Left
+ } else if ns == ys {
+ CubeFace::Bottom
+ } else {
+ CubeFace::Back
+ }
+ }
+}
+
+pub fn get_ao_at_vert(
+ voxels: &A,
+ face_strides: &FaceStrides,
+ stride: Stride,
+ cube_face: CubeFace,
+ face_vertex: FaceVertex,
+) -> i32
+where
+ A: IndexedArray<[i32; 3]> + Get,
+ T: IsEmpty + IsOpaque + MergeVoxel,
+{
+ let (n, u, v, vis, uoff, voff) = (
+ face_strides.n_stride,
+ face_strides.u_stride,
+ face_strides.v_stride,
+ face_strides.visibility_offset,
+ face_strides.u_offset,
+ face_strides.v_offset,
+ );
+ let s = stride + vis + uoff + voff;
+ let (side1_s, side2_s, corner_s) = match face_vertex {
+ FaceVertex::BottomLeft => (s - v, s - u, s - u - v),
+ FaceVertex::BottomRight => (s + u, s - v, s + u - v),
+ FaceVertex::TopRight => (s + v, s + u, s + u + v),
+ FaceVertex::TopLeft => (s - u, s + v, s - u + v),
+ };
+ let (side1, side2, corner) = (
+ !voxels.get(side1_s).is_empty(),
+ !voxels.get(side2_s).is_empty(),
+ !voxels.get(corner_s).is_empty() && voxels.get(corner_s).is_opaque(),
+ );
+ // println!("side1: {}, side2: {}, corner: {}", side1, side2, corner);
+ let ao_value = if side1 && side2 {
+ 0
+ } else {
+ 3 - (side1 as i32 + side2 as i32 + corner as i32)
+ };
+
+ println!(
+ "AO value for {:?} {:?} {:?} = {}",
+ stride_to_point(voxels.extent(), stride),
+ cube_face,
+ face_vertex,
+ ao_value
+ );
+
+ ao_value
+}
+
+// // ████████╗███████╗███████╗████████╗
+// // ╚══██╔══╝██╔════╝██╔════╝╚══██╔══╝
+// // ██║ █████╗ ███████╗ ██║
+// // ██║ ██╔══╝ ╚════██║ ██║
+// // ██║ ███████╗███████║ ██║
+// // ╚═╝ ╚══════╝╚══════╝ ╚═╝
+
+// #[cfg(test)]
+// mod tests {
+// use std::collections::HashSet;
+
+// use super::*;
+
+// #[derive(Clone)]
+// struct Voxel(u8);
+
+// impl IsEmpty for Voxel {
+// fn is_empty(&self) -> bool {
+// self.0 == 0
+// }
+// }
+
+// impl IsOpaque for Voxel {
+// fn is_opaque(&self) -> bool {
+// true
+// }
+// }
+
+// impl MergeVoxel for Voxel {
+// type VoxelValue = u8;
+
+// fn voxel_merge_value(&self) -> Self::VoxelValue {
+// self.0
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_one_voxel() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(ambient_occlusions[i] == 0.0f32);
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_two_voxels_a() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 1])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || ((*position == [1.0, 2.0, 1.0] || *position == [1.0, 2.0, 2.0])
+// && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_two_voxels_b() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 0])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || (*position == [1.0, 2.0, 1.0] && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_two_voxels_c() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([1, 2, 0])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || ((*position == [1.0, 2.0, 1.0] || *position == [2.0, 2.0, 1.0])
+// && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_three_voxels_a() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 0])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 1])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || (*position == [1.0, 2.0, 1.0] && ambient_occlusions[i] == 2.0)
+// || (*position == [1.0, 2.0, 2.0] && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_three_voxels_b() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 0])) = Voxel(1);
+// *array.get_mut(PointN([1, 2, 0])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || (*position == [1.0, 2.0, 1.0] && ambient_occlusions[i] == 2.0)
+// || (*position == [2.0, 2.0, 1.0] && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_three_voxels_c() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([1, 2, 0])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 1])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || (*position == [1.0, 2.0, 1.0] && ambient_occlusions[i] == 3.0)
+// || ((*position == [1.0, 2.0, 2.0]
+// || *position == [2.0, 2.0, 1.0]
+// || *position == [1.0, 3.0, 1.0])
+// && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_four_voxels() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([4, 4, 4]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// *array.get_mut(PointN([1, 1, 1])) = Voxel(1);
+// *array.get_mut(PointN([1, 2, 0])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 1])) = Voxel(1);
+// *array.get_mut(PointN([0, 2, 0])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
+
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let mut ambient_occlusions = [0f32; 4];
+// for (i, vertex) in group.face.quad_corners(quad).iter().enumerate() {
+// ambient_occlusions[i] = get_ao_at_vert_pos(*vertex, &array, &extent) as f32;
+// }
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for (i, position) in positions.iter().enumerate() {
+// println!("position: {:?} = {}", position, ambient_occlusions[i]);
+// assert!(
+// ambient_occlusions[i] == 0.0
+// || (*position == [1.0, 2.0, 1.0] && ambient_occlusions[i] == 3.0)
+// || ((*position == [1.0, 2.0, 2.0]
+// || *position == [2.0, 2.0, 1.0]
+// || *position == [1.0, 3.0, 1.0])
+// && ambient_occlusions[i] == 1.0)
+// );
+// }
+// }
+// }
+// }
+
+// #[test]
+// fn ambient_occlusion_merge_corners() {
+// let extent = Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([8, 8, 8]));
+// let mut array = Array3x1::fill(extent, Voxel(0));
+// // Looks like a 2-seater sofa and we want to check that the faces of the seat and backrests are NOT merged
+// // as the ambient occlusion values on the edges are different than in the middle
+// // Left armrest
+// *array.get_mut(PointN([1, 2, 2])) = Voxel(1);
+// // Right armrest
+// *array.get_mut(PointN([4, 2, 2])) = Voxel(1);
+// // Seat
+// *array.get_mut(PointN([2, 1, 2])) = Voxel(1);
+// *array.get_mut(PointN([3, 1, 2])) = Voxel(1);
+// // Back
+// *array.get_mut(PointN([2, 2, 1])) = Voxel(1);
+// *array.get_mut(PointN([3, 2, 1])) = Voxel(1);
+
+// let mut output = GreedyQuadsBuffer::new(extent, RIGHT_HANDED_Y_UP_CONFIG.quad_groups());
+// greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+// &array,
+// &extent,
+// &mut output,
+// );
-// TODO: implement a MergeStrategy for voxels with an ambient occlusion value at each vertex
+// let mut positions_set = HashSet::new();
+// for group in output.quad_groups.iter() {
+// for quad in group.quads.iter() {
+// let positions = group.face.quad_mesh_positions(quad, 1.0);
+// for position in positions.iter() {
+// println!("position: {:?}", position);
+// positions_set.insert(PointN([
+// position[0] as i32,
+// position[1] as i32,
+// position[2] as i32,
+// ]));
+// }
+// }
+// }
+// // Front center of seat
+// assert!(positions_set.contains(&PointN([3, 2, 3])));
+// // Back center of seat
+// assert!(positions_set.contains(&PointN([3, 2, 2])));
+// // Center of back
+// assert!(positions_set.contains(&PointN([3, 3, 2])));
+// }
+// }
diff --git a/crates/building_blocks_mesh/src/quad.rs b/crates/building_blocks_mesh/src/quad.rs
index eb379e6b..aa08000e 100644
--- a/crates/building_blocks_mesh/src/quad.rs
+++ b/crates/building_blocks_mesh/src/quad.rs
@@ -8,37 +8,33 @@ use building_blocks_core::{
/// Metadata that's used to aid in the geometric calculations for one of the 6 possible cube faces.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct OrientedCubeFace {
- /// Determines the orientation of the plane.
- pub n_sign: i32,
-
/// Determines the {N, U, V} <--> {X, Y, Z} relation.
pub permutation: Axis3Permutation,
- /// First in the `permutation` of +X, +Y, and +Z.
+ /// Direction of the face normal
pub n: Point3i,
- /// Second in the `permutation` of +X, +Y, and +Z.
+ /// Direction of the face-space right-pointing U axis
pub u: Point3i,
- /// Third in the `permutation` of +X, +Y, and +Z.
+ /// Direction of the face-space up-pointing V axis
pub v: Point3i,
}
impl OrientedCubeFace {
- pub const fn new(n_sign: i32, permutation: Axis3Permutation) -> Self {
+ pub fn new(axis_signs: Point3i, permutation: Axis3Permutation) -> Self {
let [n_axis, u_axis, v_axis] = permutation.axes();
Self {
- n_sign,
permutation,
- n: n_axis.get_unit_vector(),
- u: u_axis.get_unit_vector(),
- v: v_axis.get_unit_vector(),
+ n: axis_signs.x() * n_axis.get_unit_vector(),
+ u: axis_signs.y() * u_axis.get_unit_vector(),
+ v: axis_signs.z() * v_axis.get_unit_vector(),
}
}
/// A cube face, using axes with an even permutation.
pub fn canonical(normal: SignedAxis3) -> Self {
Self::new(
- normal.sign,
+ PointN([1, 1, 1]),
Axis3Permutation::even_with_normal_axis(normal.axis),
)
}
@@ -56,7 +52,7 @@ impl OrientedCubeFace {
}
pub fn signed_normal(&self) -> Point3i {
- self.n * self.n_sign
+ self.n
}
pub fn mesh_normal(&self) -> Point3f {
@@ -82,11 +78,18 @@ impl OrientedCubeFace {
let w_vec = self.u * quad.width;
let h_vec = self.v * quad.height;
- let minu_minv = if self.n_sign > 0 {
- quad.minimum + self.n
- } else {
- quad.minimum
- };
+ let minu_minv = quad.minimum
+ + if self.n.x() == 1 {
+ self.n - self.u
+ } else if self.n.y() == 1 {
+ self.n - self.v
+ } else if self.n.z() == 1 {
+ self.n
+ } else if self.n.z() == -1 {
+ -self.u
+ } else {
+ PointN([0, 0, 0])
+ };
let maxu_minv = minu_minv + w_vec;
let minu_maxv = minu_minv + h_vec;
let maxu_maxv = minu_minv + w_vec + h_vec;
@@ -110,16 +113,19 @@ impl OrientedCubeFace {
}
/// Returns the 6 vertex indices for the quad in order to make two triangles in a mesh. Winding order depends on both the
- /// sign of the surface normal and the permutation of the UVs.
- pub fn quad_mesh_indices(&self, start: u32) -> [u32; 6] {
- quad_indices(start, self.n_sign * self.permutation.sign() > 0)
+ /// sign of the surface normal and the permutation of the UVs. Swapping the diagonal can be used for altering the triangle
+ /// orientation to achieve more isotropic interpolation of vertex values such as ambient occlusion.
+ pub fn quad_mesh_indices(&self, start: u32, swap_diagonal: bool) -> [u32; 6] {
+ quad_indices(
+ start,
+ true,
+ swap_diagonal,
+ )
}
/// Returns the UV coordinates of the 4 corners of the quad. Returns vertices in the same order as
/// `OrientedCubeFace::quad_corners`.
///
- /// `u_flip_face` should correspond to the field on `QuadCoordinateConfig`. See the docs there for more info.
- ///
/// This is just one way of assigning UVs to voxel quads. It assumes that each material has a single tile texture with
/// wrapping coordinates, and each voxel face should show the entire texture. It also assumes a particular orientation for
/// the texture. This should be sufficient for minecraft-style meshing.
@@ -127,43 +133,14 @@ impl OrientedCubeFace {
/// If you need to use a texture atlas, you must calculate your own coordinates from the `Quad`.
pub fn tex_coords(
&self,
- u_flip_face: Axis3,
- flip_v: bool,
quad: &UnorientedQuad,
) -> [[f32; 2]; 4] {
- let face_normal_axis = self.permutation.axes()[0];
- let flip_u = if self.n_sign < 0 {
- u_flip_face != face_normal_axis
- } else {
- u_flip_face == face_normal_axis
- };
-
- match (flip_u, flip_v) {
- (false, false) => [
- [0.0, 0.0],
- [quad.width as f32, 0.0],
- [0.0, quad.height as f32],
- [quad.width as f32, quad.height as f32],
- ],
- (true, false) => [
- [quad.width as f32, 0.0],
- [0.0, 0.0],
- [quad.width as f32, quad.height as f32],
- [0.0, quad.height as f32],
- ],
- (false, true) => [
- [0.0, quad.height as f32],
- [quad.width as f32, quad.height as f32],
- [0.0, 0.0],
- [quad.width as f32, 0.0],
- ],
- (true, true) => [
- [quad.width as f32, quad.height as f32],
- [0.0, quad.height as f32],
- [quad.width as f32, 0.0],
- [0.0, 0.0],
- ],
- }
+ [
+ [0.0, 0.0],
+ [quad.width as f32, 0.0],
+ [0.0, quad.height as f32],
+ [quad.width as f32, quad.height as f32],
+ ]
}
/// Extends `mesh` with the given `quad` that belongs to this face.
@@ -178,7 +155,7 @@ impl OrientedCubeFace {
.extend_from_slice(&self.quad_mesh_positions(quad, voxel_size));
mesh.normals.extend_from_slice(&self.quad_mesh_normals());
mesh.indices
- .extend_from_slice(&self.quad_mesh_indices(start_index));
+ .extend_from_slice(&self.quad_mesh_indices(start_index, false));
}
/// Extends `mesh` with the given `quad` that belongs to this face.
@@ -186,8 +163,6 @@ impl OrientedCubeFace {
/// The texture coordinates come from `Quad::tex_coords`.
pub fn add_quad_to_pos_norm_tex_mesh(
&self,
- u_flip_face: Axis3,
- flip_v: bool,
quad: &UnorientedQuad,
voxel_size: f32,
mesh: &mut PosNormTexMesh,
@@ -197,19 +172,27 @@ impl OrientedCubeFace {
.extend_from_slice(&self.quad_mesh_positions(quad, voxel_size));
mesh.normals.extend_from_slice(&self.quad_mesh_normals());
mesh.tex_coords
- .extend_from_slice(&self.tex_coords(u_flip_face, flip_v, quad));
+ .extend_from_slice(&self.tex_coords(quad));
mesh.indices
- .extend_from_slice(&self.quad_mesh_indices(start_index));
+ .extend_from_slice(&self.quad_mesh_indices(start_index, false));
}
}
/// Returns the vertex indices for a single quad (two triangles). The triangles may have either clockwise or counter-clockwise
/// winding. `start` is the first index.
-fn quad_indices(start: u32, counter_clockwise: bool) -> [u32; 6] {
+fn quad_indices(start: u32, counter_clockwise: bool, swap_diagonal: bool) -> [u32; 6] {
if counter_clockwise {
- [start, start + 1, start + 2, start + 1, start + 3, start + 2]
+ if swap_diagonal {
+ [start, start + 1, start + 2, start + 1, start + 3, start + 2]
+ } else {
+ [start, start + 3, start + 2, start, start + 1, start + 3]
+ }
} else {
- [start, start + 2, start + 1, start + 1, start + 2, start + 3]
+ if swap_diagonal {
+ [start, start + 2, start + 3, start + 1, start, start + 3]
+ } else {
+ [start, start + 2, start + 1, start + 1, start + 2, start + 3]
+ }
}
}
diff --git a/crates/building_blocks_storage/src/array/coords.rs b/crates/building_blocks_storage/src/array/coords.rs
index 4bb96ac0..3b7b88e1 100644
--- a/crates/building_blocks_storage/src/array/coords.rs
+++ b/crates/building_blocks_storage/src/array/coords.rs
@@ -61,7 +61,7 @@ impl Deref for Local {
}
/// The most efficient coordinates for slice-backed lattice maps. A single number that translates directly to a slice offset.
-#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
+#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Stride(pub usize);
impl Zero for Stride {
diff --git a/examples/blocky_ambient_occlusion/ambient_occlusion.frag b/examples/blocky_ambient_occlusion/ambient_occlusion.frag
new file mode 100644
index 00000000..12c0f7d6
--- /dev/null
+++ b/examples/blocky_ambient_occlusion/ambient_occlusion.frag
@@ -0,0 +1,423 @@
+// MIT License
+
+// Copyright (c) 2020 Carter Anderson
+// Copyright (c) 2021 Robert Swain
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// Default bevy PBR shaders with added vertex attribute for texture layer
+// and using an array texture for the base colour
+//
+// NOTE: These are from bevy v0.5.0 exactly and must be updated when bevy is
+// updated!
+
+// From the Filament design doc
+// https://google.github.io/filament/Filament.html#table_symbols
+// Symbol Definition
+// v View unit vector
+// l Incident light unit vector
+// n Surface normal unit vector
+// h Half unit vector between l and v
+// f BRDF
+// f_d Diffuse component of a BRDF
+// f_r Specular component of a BRDF
+// α Roughness, remapped from using input perceptualRoughness
+// σ Diffuse reflectance
+// Ω Spherical domain
+// f0 Reflectance at normal incidence
+// f90 Reflectance at grazing angle
+// χ+(a) Heaviside function (1 if a>0 and 0 otherwise)
+// nior Index of refraction (IOR) of an interface
+// ⟨n⋅l⟩ Dot product clamped to [0..1]
+// ⟨a⟩ Saturated value (clamped to [0..1])
+
+// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material
+// and consists of two components, the diffuse component (f_d) and the specular component (f_r):
+// f(v,l) = f_d(v,l) + f_r(v,l)
+//
+// The form of the microfacet model is the same for diffuse and specular
+// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm
+//
+// In which:
+// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets
+// G models the visibility (or occlusion or shadow-masking) of the microfacets
+// f_m is the microfacet BRDF and differs between specular and diffuse components
+//
+// The above integration needs to be approximated.
+
+#version 450
+
+const int MAX_LIGHTS = 10;
+
+struct Light {
+ mat4 proj;
+ vec4 pos;
+ vec4 color;
+};
+
+layout(location = 0) in vec3 v_WorldPosition;
+layout(location = 1) in vec3 v_WorldNormal;
+layout(location = 2) in vec2 v_Uv;
+
+#ifdef STANDARDMATERIAL_NORMAL_MAP
+layout(location = 3) in vec4 v_WorldTangent;
+#endif
+
+layout(location = 4) in float v_AO;
+
+layout(location = 0) out vec4 o_Target;
+
+layout(set = 0, binding = 0) uniform CameraViewProj {
+ mat4 ViewProj;
+};
+layout(std140, set = 0, binding = 1) uniform CameraPosition {
+ vec4 CameraPos;
+};
+
+layout(std140, set = 1, binding = 0) uniform Lights {
+ vec4 AmbientColor;
+ uvec4 NumLights;
+ Light SceneLights[MAX_LIGHTS];
+};
+
+layout(set = 3, binding = 0) uniform StandardMaterial_base_color {
+ vec4 base_color;
+};
+
+#ifdef STANDARDMATERIAL_BASE_COLOR_TEXTURE
+layout(set = 3, binding = 1) uniform texture2D StandardMaterial_base_color_texture;
+layout(set = 3,
+ binding = 2) uniform sampler StandardMaterial_base_color_texture_sampler;
+#endif
+
+#ifndef STANDARDMATERIAL_UNLIT
+
+layout(set = 3, binding = 3) uniform StandardMaterial_roughness {
+ float perceptual_roughness;
+};
+
+layout(set = 3, binding = 4) uniform StandardMaterial_metallic {
+ float metallic;
+};
+
+# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE
+layout(set = 3, binding = 5) uniform texture2D StandardMaterial_metallic_roughness_texture;
+layout(set = 3,
+ binding = 6) uniform sampler StandardMaterial_metallic_roughness_texture_sampler;
+# endif
+
+layout(set = 3, binding = 7) uniform StandardMaterial_reflectance {
+ float reflectance;
+};
+
+# ifdef STANDARDMATERIAL_NORMAL_MAP
+layout(set = 3, binding = 8) uniform texture2D StandardMaterial_normal_map;
+layout(set = 3,
+ binding = 9) uniform sampler StandardMaterial_normal_map_sampler;
+# endif
+
+# if defined(STANDARDMATERIAL_OCCLUSION_TEXTURE)
+layout(set = 3, binding = 10) uniform texture2D StandardMaterial_occlusion_texture;
+layout(set = 3,
+ binding = 11) uniform sampler StandardMaterial_occlusion_texture_sampler;
+# endif
+
+layout(set = 3, binding = 12) uniform StandardMaterial_emissive {
+ vec4 emissive;
+};
+
+# if defined(STANDARDMATERIAL_EMISSIVE_TEXTURE)
+layout(set = 3, binding = 13) uniform texture2D StandardMaterial_emissive_texture;
+layout(set = 3,
+ binding = 14) uniform sampler StandardMaterial_emissive_texture_sampler;
+# endif
+
+# define saturate(x) clamp(x, 0.0, 1.0)
+const float PI = 3.141592653589793;
+
+float pow5(float x) {
+ float x2 = x * x;
+ return x2 * x2 * x;
+}
+
+// distanceAttenuation is simply the square falloff of light intensity
+// combined with a smooth attenuation at the edge of the light radius
+//
+// light radius is a non-physical construct for efficiency purposes,
+// because otherwise every light affects every fragment in the scene
+float getDistanceAttenuation(const vec3 posToLight, float inverseRadiusSquared) {
+ float distanceSquare = dot(posToLight, posToLight);
+ float factor = distanceSquare * inverseRadiusSquared;
+ float smoothFactor = saturate(1.0 - factor * factor);
+ float attenuation = smoothFactor * smoothFactor;
+ return attenuation * 1.0 / max(distanceSquare, 1e-4);
+}
+
+// Normal distribution function (specular D)
+// Based on https://google.github.io/filament/Filament.html#citation-walter07
+
+// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 }
+
+// Simple implementation, has precision problems when using fp16 instead of fp32
+// see https://google.github.io/filament/Filament.html#listing_speculardfp16
+float D_GGX(float roughness, float NoH, const vec3 h) {
+ float oneMinusNoHSquared = 1.0 - NoH * NoH;
+ float a = NoH * roughness;
+ float k = roughness / (oneMinusNoHSquared + a * a);
+ float d = k * k * (1.0 / PI);
+ return d;
+}
+
+// Visibility function (Specular G)
+// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) }
+// such that f_r becomes
+// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0)
+// where
+// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) }
+// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv
+float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) {
+ float a2 = roughness * roughness;
+ float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
+ float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
+ float v = 0.5 / (lambdaV + lambdaL);
+ return v;
+}
+
+// Fresnel function
+// see https://google.github.io/filament/Filament.html#citation-schlick94
+// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5
+vec3 F_Schlick(const vec3 f0, float f90, float VoH) {
+ // not using mix to keep the vec3 and float versions identical
+ return f0 + (f90 - f0) * pow5(1.0 - VoH);
+}
+
+float F_Schlick(float f0, float f90, float VoH) {
+ // not using mix to keep the vec3 and float versions identical
+ return f0 + (f90 - f0) * pow5(1.0 - VoH);
+}
+
+vec3 fresnel(vec3 f0, float LoH) {
+ // f_90 suitable for ambient occlusion
+ // see https://google.github.io/filament/Filament.html#lighting/occlusion
+ float f90 = saturate(dot(f0, vec3(50.0 * 0.33)));
+ return F_Schlick(f0, f90, LoH);
+}
+
+// Specular BRDF
+// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf
+
+// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m
+// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) }
+vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL,
+ float NoH, float LoH) {
+ float D = D_GGX(roughness, NoH, h);
+ float V = V_SmithGGXCorrelated(roughness, NoV, NoL);
+ vec3 F = fresnel(f0, LoH);
+
+ return (D * V) * F;
+}
+
+// Diffuse BRDF
+// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf
+// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm
+
+// simplest approximation
+// float Fd_Lambert() {
+// return 1.0 / PI;
+// }
+//
+// vec3 Fd = diffuseColor * Fd_Lambert();
+
+// Disney approximation
+// See https://google.github.io/filament/Filament.html#citation-burley12
+// minimal quality difference
+float Fd_Burley(float roughness, float NoV, float NoL, float LoH) {
+ float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
+ float lightScatter = F_Schlick(1.0, f90, NoL);
+ float viewScatter = F_Schlick(1.0, f90, NoV);
+ return lightScatter * viewScatter * (1.0 / PI);
+}
+
+// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile
+vec3 EnvBRDFApprox(vec3 f0, float perceptual_roughness, float NoV) {
+ const vec4 c0 = { -1, -0.0275, -0.572, 0.022 };
+ const vec4 c1 = { 1, 0.0425, 1.04, -0.04 };
+ vec4 r = perceptual_roughness * c0 + c1;
+ float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
+ vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
+ return f0 * AB.x + AB.y;
+}
+
+float perceptualRoughnessToRoughness(float perceptualRoughness) {
+ // clamp perceptual roughness to prevent precision problems
+ // According to Filament design 0.089 is recommended for mobile
+ // Filament uses 0.045 for non-mobile
+ float clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0);
+ return clampedPerceptualRoughness * clampedPerceptualRoughness;
+}
+
+// from https://64.github.io/tonemapping/
+// reinhard on RGB oversaturates colors
+vec3 reinhard(vec3 color) {
+ return color / (1.0 + color);
+}
+
+vec3 reinhard_extended(vec3 color, float max_white) {
+ vec3 numerator = color * (1.0f + (color / vec3(max_white * max_white)));
+ return numerator / (1.0 + color);
+}
+
+// luminance coefficients from Rec. 709.
+// https://en.wikipedia.org/wiki/Rec._709
+float luminance(vec3 v) {
+ return dot(v, vec3(0.2126, 0.7152, 0.0722));
+}
+
+vec3 change_luminance(vec3 c_in, float l_out) {
+ float l_in = luminance(c_in);
+ return c_in * (l_out / l_in);
+}
+
+vec3 reinhard_luminance(vec3 color) {
+ float l_old = luminance(color);
+ float l_new = l_old / (1.0f + l_old);
+ return change_luminance(color, l_new);
+}
+
+vec3 reinhard_extended_luminance(vec3 color, float max_white_l) {
+ float l_old = luminance(color);
+ float numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l)));
+ float l_new = numerator / (1.0f + l_old);
+ return change_luminance(color, l_new);
+}
+
+#endif
+
+void main() {
+ vec4 output_color = base_color;
+#ifdef STANDARDMATERIAL_BASE_COLOR_TEXTURE
+ output_color *= texture(sampler2D(StandardMaterial_base_color_texture,
+ StandardMaterial_base_color_texture_sampler),
+ v_Uv);
+#endif
+
+#ifndef STANDARDMATERIAL_UNLIT
+ // calculate non-linear roughness from linear perceptualRoughness
+# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE
+ vec4 metallic_roughness = texture(sampler2D(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler), v_Uv);
+ // Sampling from GLTF standard channels for now
+ float metallic = metallic * metallic_roughness.b;
+ float perceptual_roughness = perceptual_roughness * metallic_roughness.g;
+# endif
+
+ float roughness = perceptualRoughnessToRoughness(perceptual_roughness);
+
+ vec3 N = normalize(v_WorldNormal);
+
+# ifdef STANDARDMATERIAL_NORMAL_MAP
+ vec3 T = normalize(v_WorldTangent.xyz);
+ vec3 B = cross(N, T) * v_WorldTangent.w;
+# endif
+
+# ifdef STANDARDMATERIAL_DOUBLE_SIDED
+ N = gl_FrontFacing ? N : -N;
+# ifdef STANDARDMATERIAL_NORMAL_MAP
+ T = gl_FrontFacing ? T : -T;
+ B = gl_FrontFacing ? B : -B;
+# endif
+# endif
+
+# ifdef STANDARDMATERIAL_NORMAL_MAP
+ mat3 TBN = mat3(T, B, N);
+ N = TBN * normalize(texture(sampler2D(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler), v_Uv).rgb * 2.0 - 1.0);
+# endif
+
+# ifdef STANDARDMATERIAL_OCCLUSION_TEXTURE
+ float occlusion = texture(sampler2D(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler), v_Uv).r;
+# else
+ float occlusion = 1.0;
+# endif
+
+# ifdef STANDARDMATERIAL_EMISSIVE_TEXTURE
+ vec4 emissive = emissive;
+ // TODO use .a for exposure compensation in HDR
+ emissive.rgb *= texture(sampler2D(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler), v_Uv).rgb;
+# endif
+
+ vec3 V = normalize(CameraPos.xyz - v_WorldPosition.xyz);
+ // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
+ float NdotV = max(dot(N, V), 1e-4);
+
+ // Remapping [0,1] reflectance to F0
+ // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping
+ vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic;
+
+ // Diffuse strength inversely related to metallicity
+ vec3 diffuseColor = output_color.rgb * (1.0 - metallic);
+
+ // accumulate color
+ vec3 light_accum = vec3(0.0);
+ for (int i = 0; i < int(NumLights.x) && i < MAX_LIGHTS; ++i) {
+ Light light = SceneLights[i];
+
+ vec3 lightDir = light.pos.xyz - v_WorldPosition.xyz;
+ vec3 L = normalize(lightDir);
+
+ float rangeAttenuation =
+ getDistanceAttenuation(lightDir, light.pos.w);
+
+ vec3 H = normalize(L + V);
+ float NoL = saturate(dot(N, L));
+ float NoH = saturate(dot(N, H));
+ float LoH = saturate(dot(L, H));
+
+ vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH);
+ vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH);
+
+ // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩
+ // where
+ // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color
+ // Φ is light intensity
+
+ // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius
+ // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out
+
+ // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation
+ // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance
+ // light.color.rgb is premultiplied with light.intensity on the CPU
+ light_accum +=
+ ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL);
+ }
+
+ vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV);
+ vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV);
+
+ output_color.rgb = light_accum;
+ output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor.xyz * occlusion * v_AO;
+ output_color.rgb += emissive.rgb * output_color.a;
+
+ // tone_mapping
+ output_color.rgb = reinhard_luminance(output_color.rgb);
+ // Gamma correction.
+ // Not needed with sRGB buffer
+ // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2));
+#endif
+
+ o_Target = output_color;
+}
diff --git a/examples/blocky_ambient_occlusion/ambient_occlusion.vert b/examples/blocky_ambient_occlusion/ambient_occlusion.vert
new file mode 100644
index 00000000..79460127
--- /dev/null
+++ b/examples/blocky_ambient_occlusion/ambient_occlusion.vert
@@ -0,0 +1,77 @@
+// MIT License
+
+// Copyright (c) 2020 Carter Anderson
+// Copyright (c) 2021 Robert Swain
+
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
+
+// Default bevy PBR shaders with added vertex attribute for texture layer
+// and using an array texture for the base colour
+
+#version 450
+
+layout(location = 0) in vec3 Vertex_Position;
+layout(location = 1) in vec3 Vertex_Normal;
+layout(location = 2) in vec2 Vertex_Uv;
+
+#ifdef STANDARDMATERIAL_NORMAL_MAP
+layout(location = 3) in vec4 Vertex_Tangent;
+#endif
+
+layout(location = 4) in float Vertex_AO;
+
+layout(location = 0) out vec3 v_WorldPosition;
+layout(location = 1) out vec3 v_WorldNormal;
+layout(location = 2) out vec2 v_Uv;
+
+layout(set = 0, binding = 0) uniform CameraViewProj {
+ mat4 ViewProj;
+};
+
+#ifdef STANDARDMATERIAL_NORMAL_MAP
+layout(location = 3) out vec4 v_WorldTangent;
+#endif
+
+layout(location = 4) out float v_AO;
+
+layout(set = 2, binding = 0) uniform Transform {
+ mat4 Model;
+};
+
+void main() {
+ vec4 world_position = Model * vec4(Vertex_Position, 1.0);
+ v_WorldPosition = world_position.xyz;
+ v_WorldNormal = mat3(Model) * Vertex_Normal;
+ v_Uv = Vertex_Uv;
+#ifdef STANDARDMATERIAL_NORMAL_MAP
+ v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w);
+#endif
+ gl_Position = ViewProj * world_position;
+
+ vec4 ao_curve = vec4(0.0, 0.2, 0.4, 1.0);
+ if (Vertex_AO == 0.0) {
+ v_AO = ao_curve.x;
+ } else if (Vertex_AO == 1.0) {
+ v_AO = ao_curve.y;
+ } else if (Vertex_AO == 2.0) {
+ v_AO = ao_curve.z;
+ } else {
+ v_AO = ao_curve.w;
+ }
+}
diff --git a/examples/blocky_ambient_occlusion/blocky_ambient_occlusion.rs b/examples/blocky_ambient_occlusion/blocky_ambient_occlusion.rs
new file mode 100644
index 00000000..b5c64e00
--- /dev/null
+++ b/examples/blocky_ambient_occlusion/blocky_ambient_occlusion.rs
@@ -0,0 +1,1922 @@
+use std::collections::HashMap;
+
+use bevy::{
+ input::system::exit_on_esc_system,
+ pbr::AmbientLight,
+ prelude::{shape, *},
+ render::{
+ mesh::Indices,
+ pipeline::{PipelineDescriptor, PrimitiveTopology, RenderPipeline},
+ shader::{ShaderStage, ShaderStages},
+ wireframe::{WireframeConfig, WireframePlugin},
+ },
+ wgpu::{WgpuFeature, WgpuFeatures, WgpuOptions},
+};
+use bevy_fly_camera::{FlyCamera, FlyCameraPlugin};
+use building_blocks::core::prelude::*;
+use building_blocks::mesh::{
+ greedy_quads_with_merge_strategy, right_handed_y_up_config, GreedyQuadsBuffer, IsOpaque,
+ MergeVoxel, PosNormTexMesh, VoxelAOMerger,
+};
+use building_blocks::storage::{
+ access_traits::{Get, GetMut},
+ Array3x1, IsEmpty,
+};
+use building_blocks_core::Axis3Permutation;
+use building_blocks_mesh::{
+ get_ao_at_vert, greedy_quads, oriented_cube_face_to_face_strides, CubeFace, FaceStrides,
+ FaceVertex, OrientedCubeFace, QuadGroup, UnorientedQuad,
+};
+use building_blocks_storage::{IndexedArray, Local, Stride};
+
+fn main() {
+ App::build()
+ .insert_resource(WgpuOptions {
+ features: WgpuFeatures {
+ // The Wireframe requires NonFillPolygonMode feature
+ features: vec![WgpuFeature::NonFillPolygonMode],
+ },
+ ..Default::default()
+ })
+ .add_plugins(DefaultPlugins)
+ .add_plugin(WireframePlugin)
+ .insert_resource(WireframeConfig {
+ global: true,
+ ..Default::default()
+ })
+ .add_plugin(FlyCameraPlugin)
+ .add_system(exit_on_esc_system.system())
+ .add_startup_system(setup.system())
+ .add_system(toggle_fly_camera.system())
+ .run();
+}
+
+/// Basic voxel type with one byte of texture layers
+#[derive(Default, Clone, Copy)]
+struct Voxel(bool);
+
+impl MergeVoxel for Voxel {
+ type VoxelValue = bool;
+
+ fn voxel_merge_value(&self) -> Self::VoxelValue {
+ self.0
+ }
+}
+
+impl IsOpaque for Voxel {
+ fn is_opaque(&self) -> bool {
+ true
+ }
+}
+
+impl IsEmpty for Voxel {
+ fn is_empty(&self) -> bool {
+ !self.0
+ }
+}
+
+fn cube_face_to_quad_corners_as_face_vertices(cube_face: CubeFace) -> [FaceVertex; 4] {
+ match cube_face {
+ CubeFace::Top => [
+ FaceVertex::TopLeft,
+ FaceVertex::BottomLeft,
+ FaceVertex::TopRight,
+ FaceVertex::BottomRight,
+ ],
+ CubeFace::Bottom => [
+ FaceVertex::TopRight,
+ FaceVertex::BottomRight,
+ FaceVertex::TopLeft,
+ FaceVertex::BottomLeft,
+ ],
+ CubeFace::Left => [
+ FaceVertex::BottomLeft,
+ FaceVertex::BottomRight,
+ FaceVertex::TopLeft,
+ FaceVertex::TopRight,
+ ],
+ CubeFace::Right => [
+ FaceVertex::BottomRight,
+ FaceVertex::BottomLeft,
+ FaceVertex::TopRight,
+ FaceVertex::TopLeft,
+ ],
+ CubeFace::Back => [
+ FaceVertex::BottomRight,
+ FaceVertex::BottomLeft,
+ FaceVertex::TopRight,
+ FaceVertex::TopLeft,
+ ],
+ CubeFace::Front => [
+ FaceVertex::BottomLeft,
+ FaceVertex::BottomRight,
+ FaceVertex::TopLeft,
+ FaceVertex::TopRight,
+ ],
+ }
+}
+
+#[derive(Debug, Clone)]
+struct MeshBuf {
+ pub positions: Vec<[f32; 3]>,
+ pub normals: Vec<[f32; 3]>,
+ pub tex_coords: Vec<[f32; 2]>,
+ pub ambient_occlusion: Vec,
+ pub indices: Vec,
+ pub extent: Extent3i,
+}
+
+impl Default for MeshBuf {
+ fn default() -> Self {
+ Self {
+ positions: Vec::new(),
+ normals: Vec::new(),
+ tex_coords: Vec::new(),
+ ambient_occlusion: Vec::new(),
+ indices: Vec::new(),
+ extent: Extent3i::from_min_and_shape(PointN([0, 0, 0]), PointN([0, 0, 0])),
+ }
+ }
+}
+
+impl MeshBuf {
+ fn add_quad(
+ &mut self,
+ face: &OrientedCubeFace,
+ quad: &UnorientedQuad,
+ voxel_size: f32,
+ ambient_occlusions: &[f32; 4],
+ ) {
+ let start_index = self.positions.len() as u32;
+ self.positions
+ .extend_from_slice(&face.quad_mesh_positions(quad, voxel_size));
+ self.normals.extend_from_slice(&face.quad_mesh_normals());
+
+ self.tex_coords
+ .extend_from_slice(&face.tex_coords(quad));
+
+ self.ambient_occlusion.extend_from_slice(ambient_occlusions);
+ self.indices.extend_from_slice(&face.quad_mesh_indices(
+ start_index,
+ false, // FIXME: Fix flipping! It will need to be different logic for different faces!!!
+ //ambient_occlusions[0] + ambient_occlusions[3]
+ //> ambient_occlusions[1] + ambient_occlusions[2],
+ ));
+ }
+}
+
+fn oriented_cube_face_to_cube_face(oriented_cube_face: OrientedCubeFace) -> CubeFace {
+ match oriented_cube_face.permutation {
+ Axis3Permutation::Zxy => {
+ if oriented_cube_face.n.z() == 1 {
+ CubeFace::Front
+ } else if oriented_cube_face.n.z() == -1 {
+ CubeFace::Back
+ } else {
+ unreachable!();
+ }
+ }
+ Axis3Permutation::Xzy => {
+ if oriented_cube_face.n.x() == 1 {
+ CubeFace::Top
+ } else if oriented_cube_face.n.x() == -1 {
+ CubeFace::Bottom
+ } else {
+ unreachable!();
+ }
+ }
+ Axis3Permutation::Yxz => {
+ if oriented_cube_face.n.y() == 1 {
+ CubeFace::Top
+ } else if oriented_cube_face.n.y() == -1 {
+ CubeFace::Bottom
+ } else {
+ unreachable!();
+ }
+ }
+ _ => {
+ unreachable!();
+ }
+ }
+}
+
+fn setup(
+ mut commands: Commands,
+ mut pipelines: ResMut>,
+ mut shaders: ResMut>,
+ mut meshes: ResMut>,
+ mut materials: ResMut>,
+) {
+ let voxels = set_up_voxels();
+
+ let quad_coordinate_config = right_handed_y_up_config();
+
+ let mut greedy_buffer =
+ GreedyQuadsBuffer::new(*voxels.extent(), quad_coordinate_config.quad_groups());
+ greedy_quads(&voxels, voxels.extent(), &mut greedy_buffer);
+ // greedy_quads_with_merge_strategy::<_, _, VoxelAOMerger>(
+ // &voxels,
+ // voxels.extent(),
+ // &mut greedy_buffer,
+ // );
+
+ let voxel_size = 1.0;
+ let mut mesh_buf = MeshBuf::default();
+ for group in greedy_buffer.quad_groups.iter() {
+ let cube_face = oriented_cube_face_to_cube_face(group.face);
+ let face_strides = oriented_cube_face_to_face_strides(&voxels, &group.face);
+ for quad in group.quads.iter() {
+ let stride =
+ voxels.stride_from_local_point(Local(quad.minimum - voxels.extent().minimum));
+ println!("quad corners: {:?}", group.face.quad_corners(quad));
+ // FIXME - THESE HAVE TO USE THE QUAD WIDTH AND QUAD HEIGHT!!!
+ let ao_values = [
+ get_ao_at_vert(
+ &voxels,
+ &face_strides,
+ stride,
+ cube_face,
+ FaceVertex::BottomLeft,
+ ) as f32,
+ get_ao_at_vert(
+ &voxels,
+ &face_strides,
+ stride,
+ cube_face,
+ FaceVertex::BottomRight,
+ ) as f32,
+ get_ao_at_vert(
+ &voxels,
+ &face_strides,
+ stride,
+ cube_face,
+ FaceVertex::TopLeft,
+ ) as f32,
+ get_ao_at_vert(
+ &voxels,
+ &face_strides,
+ stride,
+ cube_face,
+ FaceVertex::TopRight,
+ ) as f32,
+ ];
+ mesh_buf.add_quad(
+ &group.face,
+ quad,
+ voxel_size,
+ &ao_values,
+ );
+ }
+ }
+
+ // Create the bevy mesh
+ let mut render_mesh = Mesh::new(PrimitiveTopology::TriangleList);
+
+ let MeshBuf {
+ positions,
+ normals,
+ tex_coords,
+ indices,
+ ambient_occlusion,
+ ..
+ } = mesh_buf;
+
+ render_mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, positions);
+ render_mesh.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals);
+ render_mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, tex_coords);
+ render_mesh.set_attribute("Vertex_AO", ambient_occlusion);
+ render_mesh.set_indices(Some(Indices::U32(indices)));
+
+ // Configure the custom PBR shader with ambient occlusion technique
+ let pipeline = pipelines.add(PipelineDescriptor::default_config(ShaderStages {
+ vertex: shaders.add(Shader::from_glsl(
+ ShaderStage::Vertex,
+ include_str!("ambient_occlusion.vert"),
+ )),
+ fragment: Some(shaders.add(Shader::from_glsl(
+ ShaderStage::Fragment,
+ include_str!("ambient_occlusion.frag"),
+ ))),
+ }));
+ commands.spawn_bundle(PbrBundle {
+ mesh: meshes.add(render_mesh),
+ material: materials.add(StandardMaterial {
+ base_color: Color::SEA_GREEN,
+ ..Default::default()
+ }),
+ render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new(pipeline)]),
+ ..Default::default()
+ });
+
+ // Axes
+ let axis_mesh = meshes.add(shape::Cube { size: 1.0 }.into());
+ let axis_length = 50.0;
+ commands.spawn_bundle(PbrBundle {
+ mesh: axis_mesh.clone(),
+ material: materials.add(StandardMaterial {
+ base_color: Color::RED,
+ ..Default::default()
+ }),
+ transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
+ Vec3::new(axis_length, 0.1, 0.1),
+ Quat::IDENTITY,
+ Vec3::new(0.5 * axis_length, 0.0, 0.0),
+ )),
+ ..Default::default()
+ });
+ commands.spawn_bundle(PbrBundle {
+ mesh: axis_mesh.clone(),
+ material: materials.add(StandardMaterial {
+ base_color: Color::GREEN,
+ ..Default::default()
+ }),
+ transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
+ Vec3::new(0.1, axis_length, 0.1),
+ Quat::IDENTITY,
+ Vec3::new(0.0, 0.5 * axis_length, 0.0),
+ )),
+ ..Default::default()
+ });
+ commands.spawn_bundle(PbrBundle {
+ mesh: axis_mesh,
+ material: materials.add(StandardMaterial {
+ base_color: Color::BLUE,
+ ..Default::default()
+ }),
+ transform: Transform::from_matrix(Mat4::from_scale_rotation_translation(
+ Vec3::new(0.1, 0.1, axis_length),
+ Quat::IDENTITY,
+ Vec3::new(0.0, 0.0, 0.5 * axis_length),
+ )),
+ ..Default::default()
+ });
+
+ // NOTE: Ambient occlusion only applies to diffuse and specular scattering of ambient light
+ // other light sources will easily overpower it so this demo only uses ambient light for the
+ // sake of focusing on the ambient occlusion
+ commands.insert_resource(AmbientLight {
+ color: Color::WHITE,
+ brightness: 0.6,
+ });
+ let xy_offset = Vec3::new(0.1 * axis_length, 0.1 * axis_length, 0.0);
+ commands
+ .spawn_bundle(PerspectiveCameraBundle {
+ transform: Transform::from_matrix(Mat4::face_toward(
+ Vec3::new(0.0, 0.0, 0.5 * axis_length) + xy_offset,
+ xy_offset,
+ Vec3::Y,
+ )),
+ ..Default::default()
+ })
+ .insert(FlyCamera::default());
+}
+
+const CUBE_FACES: [CubeFace; 6] = [
+ CubeFace::Front,
+ CubeFace::Top,
+ CubeFace::Right,
+ CubeFace::Back,
+ CubeFace::Bottom,
+ CubeFace::Left,
+];
+
+const FACE_VERTICES: [FaceVertex; 4] = [
+ FaceVertex::TopLeft,
+ FaceVertex::TopRight,
+ FaceVertex::BottomRight,
+ FaceVertex::BottomLeft,
+];
+
+fn populate_and_verify(
+ voxels: &mut Array3x1,
+ positions: &[Point3i],
+ offset: Point3i,
+ vertex_aos: &HashMap<(Point3i, CubeFace, FaceVertex), i32>,
+ verify: bool,
+) {
+ let quad_coordinate_config = right_handed_y_up_config();
+ for position in positions {
+ *voxels.get_mut(offset + *position) = Voxel(true);
+ }
+ if verify {
+ for position in positions {
+ let stride =
+ voxels.stride_from_local_point(Local(offset + *position - voxels.extent().minimum));
+ for (cube_face, oriented_cube_face) in
+ CUBE_FACES.iter().zip(quad_coordinate_config.faces.iter())
+ {
+ let face_strides = oriented_cube_face_to_face_strides(&*voxels, oriented_cube_face);
+ for face_vertex in &FACE_VERTICES {
+ assert!(
+ get_ao_at_vert(&*voxels, &face_strides, stride, *cube_face, *face_vertex)
+ == *vertex_aos
+ .get(&(*position, *cube_face, *face_vertex))
+ .unwrap_or(&3)
+ );
+ }
+ }
+ }
+ }
+}
+
+fn set_up_voxels() -> Array3x1 {
+ println!(">>> set_up_voxels");
+ let interior_extent = Extent3i::from_min_and_shape(PointN([0; 3]), PointN([64; 3]));
+ let full_extent = interior_extent.padded(1);
+ let mut voxels = Array3x1::fill(full_extent, Voxel::default());
+
+ let row_step = PointN([0, 5, 0]);
+ let col_step = PointN([5, 0, 0]);
+ let mut offset = PointN([1, 1, 1]);
+
+ let tests = 0xffff; // 1 << 4;
+ let mut shift = 0;
+ let verify = false;
+ // .
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1])];
+ populate_and_verify(&mut voxels, &positions, offset, &HashMap::new(), verify);
+ offset += row_step;
+ }
+ shift += 1;
+
+ // . . .
+ // . . .
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([0, 2, 1])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 1, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += col_step;
+ }
+ shift += 1;
+
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([0, 2, 0])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ (
+ (PointN([0, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += col_step;
+ }
+ shift += 1;
+
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([1, 2, 0])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ *offset.x_mut() = 1;
+ offset += row_step;
+ }
+ shift += 1;
+
+ // .. ..
+ // . .
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([0, 2, 0]), PointN([0, 2, 1])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ (
+ (PointN([1, 1, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 1),
+ (
+ (PointN([0, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This is a hidden face
+ (
+ (PointN([0, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += col_step;
+ }
+ shift += 1;
+
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([0, 2, 0]), PointN([1, 2, 0])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ (
+ (PointN([0, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This is a hidden face
+ (
+ (PointN([0, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ *offset.x_mut() = 1;
+ offset += row_step;
+ }
+ shift += 1;
+
+ // . ..
+ // .. ..
+ if (tests >> shift) & 1 == 1 {
+ let positions = [PointN([1, 1, 1]), PointN([0, 2, 1]), PointN([1, 2, 0])];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 0),
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 1, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([0, 2, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ (
+ (PointN([0, 2, 1]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 2, 0]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 0,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 2, 0]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 2, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += col_step;
+ }
+ shift += 1;
+
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ PointN([1, 1, 1]),
+ PointN([0, 2, 0]),
+ PointN([0, 2, 1]),
+ PointN([1, 2, 0]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 0),
+ ((PointN([1, 1, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 1, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 1),
+ ((PointN([1, 1, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([0, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([0, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([0, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 2, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([0, 2, 1]), CubeFace::Back, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([0, 2, 1]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 2, 0]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 0,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 2, 0]), CubeFace::Left, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ *offset.x_mut() = 1;
+ offset += row_step;
+ }
+ shift += 1;
+
+ // Looks like a 2-seater sofa and we want to check that the faces of the seat and backrests are NOT merged
+ // as the ambient occlusion values on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ // Left armrest
+ PointN([0, 1, 1]),
+ // Seat
+ PointN([1, 0, 1]),
+ PointN([2, 0, 1]),
+ // Back
+ PointN([1, 1, 0]),
+ PointN([2, 1, 0]),
+ // Right armrest
+ PointN([3, 1, 1]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ // Left armrest
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([0, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ (
+ (PointN([0, 1, 1]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Left seat
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 0),
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([1, 0, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ // Right seat
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 0),
+ (
+ (PointN([2, 0, 1]), CubeFace::Top, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([2, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Left back
+ ((PointN([1, 1, 0]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 0,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([1, 1, 0]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ // Right back
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([2, 1, 0]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Right arm rest
+ (
+ (PointN([3, 1, 1]), CubeFace::Bottom, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 1]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([3, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([3, 1, 1]), CubeFace::Back, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([3, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ (
+ (PointN([3, 1, 1]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 1]), CubeFace::Left, FaceVertex::BottomLeft),
+ 0,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += PointN([6, 0, 0]);
+ }
+ shift += 1;
+
+ // Looks like a 4-seater sofa and we want to check that the faces of the seat and backrests are
+ // merged for the center two seats but not the left/right seats as the ambient occlusion values
+ // on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ // Left armrest
+ PointN([0, 1, 1]),
+ // Seat
+ PointN([1, 0, 1]),
+ PointN([2, 0, 1]),
+ PointN([3, 0, 1]),
+ PointN([4, 0, 1]),
+ // Back
+ PointN([1, 1, 0]),
+ PointN([2, 1, 0]),
+ PointN([3, 1, 0]),
+ PointN([4, 1, 0]),
+ // Right armrest
+ PointN([5, 1, 1]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ // Left armrest
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([0, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ (
+ (PointN([0, 1, 1]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Left seat
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 0),
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([1, 0, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ // Second seat
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopRight), // NOTE: This face is hidden.
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([2, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Third seat
+ ((PointN([3, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([3, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([3, 0, 1]), CubeFace::Right, FaceVertex::TopRight), // NOTE: This face is hidden.
+ 2,
+ ),
+ ((PointN([3, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([3, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([3, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Right seat
+ ((PointN([4, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([4, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 0),
+ (
+ (PointN([4, 0, 1]), CubeFace::Top, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([4, 0, 1]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([4, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ ((PointN([4, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([4, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([4, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Left back
+ ((PointN([1, 1, 0]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 0,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([1, 1, 0]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ // Second back
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Third back
+ (
+ (PointN([3, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([3, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Right back
+ (
+ (PointN([4, 1, 0]), CubeFace::Front, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([4, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([4, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([4, 1, 0]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([4, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([4, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([4, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([4, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Right arm rest
+ (
+ (PointN([5, 1, 1]), CubeFace::Bottom, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([5, 1, 1]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([5, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([5, 1, 1]), CubeFace::Back, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([5, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ (
+ (PointN([5, 1, 1]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([5, 1, 1]), CubeFace::Left, FaceVertex::BottomLeft),
+ 0,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += PointN([8, 0, 0]);
+ }
+ shift += 1;
+
+ // Looks like a 2-seater sofa without arm rests and we want to check that the faces of the seat and backrests are NOT merged
+ // as the ambient occlusion values on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ // Seat
+ PointN([0, 0, 1]),
+ PointN([1, 0, 1]),
+ // Back
+ PointN([0, 1, 0]),
+ PointN([1, 1, 0]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ // Left seat
+ ((PointN([0, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ ((PointN([0, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([0, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([0, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([0, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ // Right seat
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Left back
+ (
+ (PointN([0, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([0, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ // Right back
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += PointN([4, 0, 0]);
+ }
+ shift += 1;
+
+ // Looks like a 4-seater sofa without arm rests and we want to check that the faces of the seat and backrests are
+ // merged for the center two seats but not the left/right seats as the ambient occlusion values
+ // on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ // Seat
+ PointN([0, 0, 1]),
+ PointN([1, 0, 1]),
+ PointN([2, 0, 1]),
+ PointN([3, 0, 1]),
+ // Back
+ PointN([0, 1, 0]),
+ PointN([1, 1, 0]),
+ PointN([2, 1, 0]),
+ PointN([3, 1, 0]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ // Left seat
+ ((PointN([0, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ ((PointN([0, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([0, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([0, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([0, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ // Second seat
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([1, 0, 1]), CubeFace::Right, FaceVertex::TopRight), // NOTE: This face is hidden.
+ 2,
+ ),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Third seat
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopRight), // NOTE: This face is hidden.
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([2, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Right seat
+ ((PointN([3, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([3, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ ((PointN([3, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([3, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([3, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ // Left back
+ (
+ (PointN([0, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([0, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ // Second back
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Third back
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Right back
+ (
+ (PointN([3, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ *offset.x_mut() = 1;
+ offset += row_step;
+ }
+ shift += 1;
+
+ // Looks like a 2-seater sofa with high back and large seat and we want to check that the faces of the seat and backrests are NOT merged
+ // as the ambient occlusion values on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ let positions = [
+ // Left armrest
+ PointN([0, 1, 1]),
+ // Seat
+ PointN([1, 0, 1]),
+ PointN([2, 0, 1]),
+ PointN([1, 0, 2]),
+ PointN([2, 0, 2]),
+ PointN([1, 0, 3]),
+ PointN([2, 0, 3]),
+ // Back
+ PointN([1, 1, 0]),
+ PointN([2, 1, 0]),
+ PointN([1, 2, 0]),
+ PointN([2, 2, 0]),
+ PointN([1, 3, 0]),
+ PointN([2, 3, 0]),
+ // Right armrest
+ PointN([3, 1, 1]),
+ ];
+ let vertex_aos: HashMap<(Point3i, CubeFace, FaceVertex), i32> = [
+ // Left armrest
+ ((PointN([0, 1, 1]), CubeFace::Top, FaceVertex::TopRight), 2),
+ (
+ (PointN([0, 1, 1]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 1,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 1,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([0, 1, 1]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([0, 1, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ (
+ (PointN([0, 1, 1]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Left seat
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 0),
+ ((PointN([1, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 1),
+ (
+ (PointN([1, 0, 1]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 0, 1]), CubeFace::Front, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 1]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 1]), CubeFace::Right, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 1]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 1),
+ ((PointN([1, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([1, 0, 1]), CubeFace::Left, FaceVertex::TopRight), 2),
+ // Right seat
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopLeft), 1),
+ ((PointN([2, 0, 1]), CubeFace::Top, FaceVertex::TopRight), 0),
+ (
+ (PointN([2, 0, 1]), CubeFace::Top, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 0, 1]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 0, 1]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([2, 0, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ ((PointN([2, 0, 1]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ ((PointN([2, 0, 1]), CubeFace::Left, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([2, 0, 1]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Left seat first extension forward
+ ((PointN([1, 0, 2]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 0, 2]), CubeFace::Front, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 2]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 2]), CubeFace::Right, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 2]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 2]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 2]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 2]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([1, 0, 2]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 0, 2]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 0, 2]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ // Right seat first extension forward
+ ((PointN([2, 0, 2]), CubeFace::Top, FaceVertex::TopRight), 2),
+ ((PointN([2, 0, 2]), CubeFace::Front, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([2, 0, 2]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 0, 2]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([2, 0, 2]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ ((PointN([2, 0, 2]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([2, 0, 2]), CubeFace::Back, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([2, 0, 2]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ ((PointN([2, 0, 2]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([2, 0, 2]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([2, 0, 2]), CubeFace::Left, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Left seat second extension forward
+ (
+ (PointN([1, 0, 3]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 0, 3]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 0, 3]), CubeFace::Back, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 0, 3]), CubeFace::Back, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Right seat second extension forward
+ ((PointN([2, 0, 3]), CubeFace::Back, FaceVertex::TopRight), 2),
+ (
+ (PointN([2, 0, 3]), CubeFace::Back, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([2, 0, 3]), CubeFace::Left, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 0, 3]), CubeFace::Left, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Left back
+ ((PointN([1, 1, 0]), CubeFace::Top, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Top, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ ((PointN([1, 1, 0]), CubeFace::Front, FaceVertex::TopLeft), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 0,
+ ),
+ ((PointN([1, 1, 0]), CubeFace::Right, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([1, 1, 0]), CubeFace::Left, FaceVertex::TopRight), 2),
+ (
+ (PointN([1, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ // Right back
+ ((PointN([2, 1, 0]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 1, 0]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 0,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 1,
+ ),
+ ((PointN([2, 1, 0]), CubeFace::Right, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 1, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([2, 1, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([2, 1, 0]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ ((PointN([2, 1, 0]), CubeFace::Left, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([2, 1, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Left back first extension upward
+ ((PointN([1, 2, 0]), CubeFace::Top, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Top, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([1, 2, 0]), CubeFace::Right, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Right, FaceVertex::TopRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 2, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ),
+ // Right back first extension upward
+ ((PointN([2, 2, 0]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ (
+ (PointN([2, 2, 0]), CubeFace::Top, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 2, 0]), CubeFace::Front, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([2, 2, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([2, 2, 0]), CubeFace::Bottom, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([2, 2, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ (
+ (PointN([2, 2, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ ((PointN([2, 2, 0]), CubeFace::Left, FaceVertex::TopLeft), 2), // NOTE: This face is hidden.
+ ((PointN([2, 2, 0]), CubeFace::Left, FaceVertex::TopRight), 2), // NOTE: This face is hidden.
+ (
+ (PointN([2, 2, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 2, 0]), CubeFace::Left, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ // Left back second extension upward
+ (
+ (PointN([1, 3, 0]), CubeFace::Right, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 3, 0]), CubeFace::Right, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([1, 3, 0]), CubeFace::Bottom, FaceVertex::TopLeft),
+ 2,
+ ),
+ (
+ (PointN([1, 3, 0]), CubeFace::Bottom, FaceVertex::BottomLeft),
+ 2,
+ ),
+ // Right back second extension upward
+ (
+ (PointN([2, 3, 0]), CubeFace::Left, FaceVertex::BottomRight),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 3, 0]), CubeFace::Left, FaceVertex::BottomLeft),
+ 2,
+ ), // NOTE: This face is hidden.
+ (
+ (PointN([2, 3, 0]), CubeFace::Bottom, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([2, 3, 0]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 2,
+ ),
+ // Right arm rest
+ ((PointN([3, 1, 1]), CubeFace::Top, FaceVertex::TopLeft), 2),
+ (
+ (PointN([3, 1, 1]), CubeFace::Front, FaceVertex::BottomLeft),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 1]), CubeFace::Bottom, FaceVertex::TopRight),
+ 2,
+ ),
+ (
+ (PointN([3, 1, 1]), CubeFace::Bottom, FaceVertex::BottomRight),
+ 1,
+ ),
+ ((PointN([3, 1, 1]), CubeFace::Back, FaceVertex::TopRight), 1),
+ (
+ (PointN([3, 1, 1]), CubeFace::Back, FaceVertex::BottomRight),
+ 2,
+ ),
+ ((PointN([3, 1, 1]), CubeFace::Left, FaceVertex::TopLeft), 1),
+ (
+ (PointN([3, 1, 1]), CubeFace::Left, FaceVertex::BottomRight),
+ 1,
+ ),
+ (
+ (PointN([3, 1, 1]), CubeFace::Left, FaceVertex::BottomLeft),
+ 0,
+ ),
+ ]
+ .iter()
+ .cloned()
+ .collect();
+ populate_and_verify(&mut voxels, &positions, offset, &vertex_aos, verify);
+ offset += PointN([6, 0, 0]);
+ }
+ shift += 1;
+
+ // Looks like a 4-seater sofa and we want to check that the faces of the seat and backrests are
+ // merged for the center two seats but not the left/right seats as the ambient occlusion values
+ // on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ // Left armrest
+ *voxels.get_mut(offset + PointN([0, 1, 1])) = Voxel(true);
+ // Seat
+ *voxels.get_mut(offset + PointN([1, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 3])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 0, 3])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 0, 3])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 0, 3])) = Voxel(true);
+ // Back
+ *voxels.get_mut(offset + PointN([1, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 3, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 3, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 3, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([4, 3, 0])) = Voxel(true);
+ // Right armrest
+ *voxels.get_mut(offset + PointN([5, 1, 1])) = Voxel(true);
+ offset += PointN([8, 0, 0]);
+ }
+ shift += 1;
+
+ // Looks like a 2-seater sofa and we want to check that the faces of the seat and backrests are NOT merged
+ // as the ambient occlusion values on the edges are different than in the middle
+ if (tests >> shift) & 1 == 1 {
+ // Seat
+ *voxels.get_mut(offset + PointN([0, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([0, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 2])) = Voxel(true);
+ // Back
+ *voxels.get_mut(offset + PointN([0, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([0, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 2, 0])) = Voxel(true);
+ offset += PointN([4, 0, 0]);
+
+ // Looks like a 4-seater sofa and we want to check that the faces of the seat and backrests are
+ // merged for the center two seats but not the left/right seats as the ambient occlusion values
+ // on the edges are different than in the middle
+ // Seat
+ *voxels.get_mut(offset + PointN([0, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 0, 1])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([0, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 0, 2])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 0, 2])) = Voxel(true);
+ // Back
+ *voxels.get_mut(offset + PointN([0, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 1, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([0, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([1, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([2, 2, 0])) = Voxel(true);
+ *voxels.get_mut(offset + PointN([3, 2, 0])) = Voxel(true);
+ *offset.x_mut() = 1;
+ offset += row_step;
+ }
+ shift += 1;
+
+ println!("<<< set_up_voxels");
+ voxels
+}
+
+fn toggle_fly_camera(keyboard_input: Res>, mut fly_camera: Query<&mut FlyCamera>) {
+ if keyboard_input.just_pressed(KeyCode::C) {
+ for mut fc in fly_camera.iter_mut() {
+ fc.enabled = !fc.enabled;
+ }
+ }
+}