use crate::duals::DualGrid;
use crate::octree::*;
use crate::tables;
use crate::util::*;
use cgmath::{Point3, Vector3};
use rayon::prelude::*;
use std::mem::MaybeUninit;
pub fn mesh_from_octree<T: McNode>(octree: &HashedOctree<T>, scale: f32) -> Mesh {
let dual = DualGrid::from_octree(octree);
let vertices = dual
.volumes
.into_par_iter()
.map(|volume| {
let VolumeData { nodes, cube_index } = fetch_volume_data(volume, &octree);
(volume, nodes, cube_index)
})
.filter(|(_, _, cube_index)| is_cell_visible(*cube_index))
.map(|(volume, nodes, cube_index)| {
let cell_data = calculate_mc_vertex_data(volume, nodes, cube_index, scale);
(cell_data, cube_index)
})
.flat_map_iter(|(cell_data, cube_index)| obtain_mc_vertices(cell_data, cube_index))
.collect::<Vec<_>>();
let indices = (0..vertices.len() as u32).collect();
Mesh { indices, vertices }
}
#[derive(Clone, Debug, PartialEq)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub indices: Vec<u32>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Vertex {
pub position: cgmath::Point3<f32>,
pub normal: cgmath::Vector3<f32>,
}
pub trait McNode: Copy + Send + Sync {
fn density(&self) -> f32;
fn normal(&self) -> Vector3<f32>;
}
struct CellVertexData {
pub positions: [Point3<f32>; 12],
pub normals: [Vector3<f32>; 12],
}
fn obtain_mc_vertices(
cell_data: CellVertexData,
cube_index: u8,
) -> impl std::iter::Iterator<Item = Vertex> {
tables::TRI_TABLE[cube_index as usize]
.chunks(3)
.take_while(|chunk| chunk[0] != -1)
.flat_map(move |chunk| {
let (vtx1, vtx2, vtx3) = (
cell_data.positions[chunk[0] as usize],
cell_data.positions[chunk[1] as usize],
cell_data.positions[chunk[2] as usize],
);
let (n1, n2, n3) = (
cell_data.normals[chunk[0] as usize],
cell_data.normals[chunk[1] as usize],
cell_data.normals[chunk[2] as usize],
);
std::array::IntoIter::new([(vtx1, n1), (vtx2, n2), (vtx3, n3)]).map(
move |(vtx, norm)| Vertex {
position: vtx.into(),
normal: norm.into(),
},
)
})
}
struct VolumeData {
pub nodes: [NodeData; 8],
pub cube_index: u8,
}
struct NodeData {
pub density: f32,
pub normal: Vector3<f32>,
}
fn calculate_mc_vertex_data(
volume: [MortonKey; 8],
nodes: [NodeData; 8],
cube_index: u8,
vertex_position_scale: f32,
) -> CellVertexData {
const MC_CUBE_EDGES: [(usize, usize); 12] = [
(4, 5),
(1, 5),
(0, 1),
(0, 4),
(6, 7),
(3, 7),
(2, 3),
(2, 6),
(4, 6),
(5, 7),
(1, 3),
(0, 2),
];
let mut positions: [Point3<f32>; 12] = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
let mut normals: [Vector3<f32>; 12] = unsafe { std::mem::MaybeUninit::uninit().assume_init() };
let edges = tables::EDGE_TABLE[cube_index as usize];
(0..12)
.into_iter()
.filter(|i| (edges & (1 << i)) > 0)
.for_each(|i| {
let (v1_i, v2_i) = MC_CUBE_EDGES[i];
let factor = interpolation_factor(nodes[v1_i].density, nodes[v2_i].density);
positions[i] = (volume[v1_i]
.position()
.interpolate(volume[v2_i].position(), factor)
* vertex_position_scale)
.into();
normals[i] = nodes[v1_i]
.normal
.interpolate(nodes[v2_i].normal, factor)
.into();
});
CellVertexData { positions, normals }
}
fn is_cell_visible(cube_index: u8) -> bool {
cube_index != 0 && cube_index != 0xFF
}
fn fetch_volume_data<T: McNode>(volume: [MortonKey; 8], octree: &HashedOctree<T>) -> VolumeData {
const VOLUME_TO_MC_VERTEX: [usize; 8] = [3, 2, 7, 6, 0, 1, 4, 5];
let mut nodes: [MaybeUninit<NodeData>; 8] = unsafe { MaybeUninit::uninit().assume_init() };
let mut cube_index = 0;
volume.iter().enumerate().for_each(|(volume_i, &key)| {
let mc_i = VOLUME_TO_MC_VERTEX[volume_i];
if key == MortonKey::none() {
return;
}
match octree.value(key) {
Some(node) => {
let (density, normal) = (node.density(), node.normal());
if density > 0. {
cube_index |= 1 << mc_i;
}
nodes[volume_i] = MaybeUninit::new(NodeData { density, normal });
}
None => unreachable!(),
}
});
let nodes: [NodeData; 8] = unsafe { std::mem::transmute(nodes) };
VolumeData { nodes, cube_index }
}
fn interpolation_factor(p1: f32, p2: f32) -> f32 {
if p1.abs() < 0.00001 {
p1
} else if p2.abs() < 0.00001 {
p2
} else if (p1 - p2).abs() < 0.00001 {
p1
} else {
(0. - p1) / (p2 - p1)
}
}