Terrain mesh optimization
This commit is contained in:
parent
df694989c9
commit
89a35db491
1 changed files with 108 additions and 51 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue