1
0
Fork 0

Terrain mesh optimization

This commit is contained in:
Adrian Hedqvist 2021-04-05 14:48:39 +02:00
parent df694989c9
commit 89a35db491

View file

@ -1,14 +1,14 @@
use bracket_noise::prelude::*;
use cgmath::{prelude::*, Vector3};
use cgmath::{prelude::*, AbsDiffEq, Vector3};
use rayon::prelude::*;
use crate::model::Vertex;
mod table;
const CHUNK_SIZE: usize = 16;
const CHUNK_SIZE: usize = 32;
const CHUNK_ARRAY_SIZE: usize = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE;
const THRESHOLD: f32 = 0.0;
const THRESHOLD: f32 = 0.5;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
@ -55,45 +55,61 @@ const fn pos_to_index(x: usize, y: usize, z: usize) -> usize {
x + y * CHUNK_SIZE + z * CHUNK_SIZE.pow(2)
}
pub fn generate_terrain_marching_cube(seed: u64, chunk_coords: Vector3<i32>) -> Vec<TerrainVertex> {
const CUBE_SIZE: f32 = 1.0;
fn generate_terrain_voxels(
seed: u64,
chunk_coords: Vector3<i32>,
) -> [[[f32; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE] {
let mut noise = FastNoise::new();
let chunk_coords = chunk_coords * CHUNK_SIZE as i32;
noise.set_seed(seed);
noise.set_frequency(0.1);
(0..CHUNK_ARRAY_SIZE)
let mut chunk_values = [[[0.0; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE];
chunk_values.par_iter_mut().enumerate().for_each(|(x, v)| {
v.par_iter_mut().enumerate().for_each(|(y, v)| {
v.par_iter_mut().enumerate().for_each(|(z, v)| {
*v = noise.get_noise3d(
chunk_coords.x as f32 + x as f32,
chunk_coords.y as f32 + y as f32,
chunk_coords.z as f32 + z as f32,
)
})
})
});
chunk_values
}
fn marching_cubes(chunk_data: [[[f32; CHUNK_SIZE]; CHUNK_SIZE]; CHUNK_SIZE]) -> Vec<TerrainVertex> {
(0..CHUNK_SIZE - 1)
.into_par_iter()
.map(|i| {
let pos = index_to_pos(i);
let npos = Vector3::new(
pos.x + chunk_coords.x as f32,
pos.y + chunk_coords.y as f32,
pos.z + chunk_coords.z as f32,
);
.flat_map(|x| (0..CHUNK_SIZE - 1).into_par_iter().map(move |y| (x, y)))
.flat_map(|(x, y)| (0..CHUNK_SIZE - 1).into_par_iter().map(move |z| (x, y, z)))
.map(|(x, y, z)| {
let corners = [
Vector3::new(pos.x, pos.y, pos.z),
Vector3::new(pos.x + CUBE_SIZE, pos.y, pos.z),
Vector3::new(pos.x + CUBE_SIZE, pos.y, pos.z + CUBE_SIZE),
Vector3::new(pos.x, pos.y, pos.z + CUBE_SIZE),
Vector3::new(pos.x, pos.y + CUBE_SIZE, pos.z),
Vector3::new(pos.x + CUBE_SIZE, pos.y + CUBE_SIZE, pos.z),
Vector3::new(pos.x + CUBE_SIZE, pos.y + CUBE_SIZE, pos.z + CUBE_SIZE),
Vector3::new(pos.x, pos.y + CUBE_SIZE, pos.z + CUBE_SIZE),
Vector3::new(x as f32, y as f32, z as f32),
Vector3::new(x as f32 + 1.0, y as f32, z as f32),
Vector3::new(x as f32 + 1.0, y as f32, z as f32 + 1.0),
Vector3::new(x as f32, y as f32, z as f32 + 1.0),
Vector3::new(x as f32, y as f32 + 1.0, z as f32),
Vector3::new(x as f32 + 1.0, y as f32 + 1.0, z as f32),
Vector3::new(x as f32 + 1.0, y as f32 + 1.0, z as f32 + 1.0),
Vector3::new(x as f32, y as f32 + 1.0, z as f32 + 1.0),
];
let values = [
noise.get_noise3d(npos.x, npos.y, npos.z),
noise.get_noise3d(npos.x + CUBE_SIZE, npos.y, npos.z),
noise.get_noise3d(npos.x + CUBE_SIZE, npos.y, npos.z + CUBE_SIZE),
noise.get_noise3d(npos.x, npos.y, npos.z + CUBE_SIZE),
noise.get_noise3d(npos.x, npos.y + CUBE_SIZE, npos.z),
noise.get_noise3d(npos.x + CUBE_SIZE, npos.y + CUBE_SIZE, npos.z),
noise.get_noise3d(npos.x + CUBE_SIZE, npos.y + CUBE_SIZE, npos.z + CUBE_SIZE),
noise.get_noise3d(npos.x, npos.y + CUBE_SIZE, npos.z + CUBE_SIZE),
let cube_values: [f32; 8] = [
chunk_data[x][y][z],
chunk_data[x + 1][y][z],
chunk_data[x + 1][y][z + 1],
chunk_data[x][y][z + 1],
chunk_data[x][y + 1][z],
chunk_data[x + 1][y + 1][z],
chunk_data[x + 1][y + 1][z + 1],
chunk_data[x][y + 1][z + 1],
];
let mut cube_index: usize = 0;
for (i, val) in values.iter().enumerate() {
for (i, val) in cube_values.iter().enumerate() {
if *val >= THRESHOLD {
cube_index |= 1 << i;
}
@ -101,7 +117,8 @@ pub fn generate_terrain_marching_cube(seed: u64, chunk_coords: Vector3<i32>) ->
let edge = table::EDGES[cube_index];
let points = calculate_points(edge, corners, values);
let points = calculate_points(edge, corners, cube_values);
table::TRIANGLES[cube_index]
.into_par_iter()
.chunks(3)
@ -161,37 +178,37 @@ fn calculate_points(edge: i32, corners: [Vector3<f32>; 8], values: [f32; 8]) ->
p1 + mu * (p2 - p1)
}
if edge & 0b1 != 0 {
if edge & 0b000000000001 != 0 {
points[0] = lerp(corners[0], corners[1], values[0], values[1]);
}
if edge & 0b10 != 0 {
if edge & 0b000000000010 != 0 {
points[1] = lerp(corners[1], corners[2], values[1], values[2]);
}
if edge & 0b100 != 0 {
if edge & 0b000000000100 != 0 {
points[2] = lerp(corners[2], corners[3], values[2], values[3]);
}
if edge & 0b1000 != 0 {
if edge & 0b000000001000 != 0 {
points[3] = lerp(corners[3], corners[0], values[3], values[0]);
}
if edge & 0b10000 != 0 {
if edge & 0b000000010000 != 0 {
points[4] = lerp(corners[4], corners[5], values[4], values[5]);
}
if edge & 0b100000 != 0 {
if edge & 0b000000100000 != 0 {
points[5] = lerp(corners[5], corners[6], values[5], values[6]);
}
if edge & 0b1000000 != 0 {
if edge & 0b000001000000 != 0 {
points[6] = lerp(corners[6], corners[7], values[6], values[7]);
}
if edge & 0b10000000 != 0 {
if edge & 0b000010000000 != 0 {
points[7] = lerp(corners[7], corners[4], values[7], values[4]);
}
if edge & 0b100000000 != 0 {
if edge & 0b000100000000 != 0 {
points[8] = lerp(corners[0], corners[4], values[0], values[4]);
}
if edge & 0b1000000000 != 0 {
if edge & 0b001000000000 != 0 {
points[9] = lerp(corners[1], corners[5], values[1], values[5]);
}
if edge & 0b10000000000 != 0 {
if edge & 0b010000000000 != 0 {
points[10] = lerp(corners[2], corners[6], values[2], values[6]);
}
if edge & 0b100000000000 != 0 {
@ -201,23 +218,63 @@ fn calculate_points(edge: i32, corners: [Vector3<f32>; 8], values: [f32; 8]) ->
points
}
fn optimize_mesh(mesh: Vec<TerrainVertex>) -> (Vec<TerrainVertex>, Vec<usize>) {
fn equal(a: &[f32; 3], b: &[f32; 3]) -> bool {
a[0].abs_diff_eq(&b[0], f32::EPSILON)
&& a[1].abs_diff_eq(&b[1], f32::EPSILON)
&& a[2].abs_diff_eq(&b[2], f32::EPSILON)
}
let mut indices: Vec<usize> = vec![];
let mut vertices: Vec<TerrainVertex> = vec![];
// This scales incredibly poorly, larger meshes ends up taking multiple seconds
for vert in mesh {
if let Some((i, _v)) = vertices
.iter()
.enumerate()
.find(|(_i, v)| equal(&vert.position, &v.position))
{
indices.push(i);
} else {
indices.push(vertices.len());
vertices.push(vert);
}
}
(vertices, indices)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gen() {
let vert = generate_terrain_marching_cube(0, Vector3 { x: 0, y: 0, z: 0 });
let count = vert.len();
let start = std::time::Instant::now();
let data = generate_terrain_voxels(0, Vector3 { x: 0, y: 0, z: 0 });
let duration = std::time::Instant::now() - start;
eprintln!("Voxel generation took {}s", duration.as_secs_f64());
let start = std::time::Instant::now();
let vert = marching_cubes(data);
let duration = std::time::Instant::now() - start;
eprintln!("Mesh generation took {}s", duration.as_secs_f64());
let start = std::time::Instant::now();
let (vert, idx) = optimize_mesh(vert);
let duration = std::time::Instant::now() - start;
eprintln!("Mesh optimization took {}s", duration.as_secs_f64());
for v in &vert {
let [x, y, z] = v.position;
println!("v {} {} {}", x, y, z);
}
for v in &vert {
let [x, y, z] = v.normal;
println!("vn {} {} {}", x, y, z);
}
for i in (1..=count).step_by(3) {
println!("f {} {} {}", i, i + 1, i + 2);
// for v in &vert {
// let [x, y, z] = v.normal;
// println!("vn {} {} {}", x, y, z);
// }
for i in idx.chunks(3) {
println!("f {} {} {}", i[0] + 1, i[1] + 1, i[2] + 1);
}
}
}