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; + } + } +}