From 9623bbd3e4b2f33e958f1c685331ae574ea3303c Mon Sep 17 00:00:00 2001 From: yukirij Date: Sat, 15 Jun 2024 12:24:06 -0700 Subject: [PATCH] Correct global/local coordinate handling; add basic mesh stitching. --- src/anchor.rs | 23 +++-- src/bin/world.rs | 264 +++++++++++++++++++++++++++++++---------------- src/generator.rs | 2 +- src/icosphere.rs | 22 ++-- src/terrain.rs | 54 ++++------ 5 files changed, 222 insertions(+), 143 deletions(-) diff --git a/src/anchor.rs b/src/anchor.rs index 938688e..c44b83e 100644 --- a/src/anchor.rs +++ b/src/anchor.rs @@ -1,4 +1,8 @@ use glam::{DMat3, DVec3}; +use crate::{ + terrain::Terrain, + generator::Generator, +}; #[derive(Clone, Copy)] pub struct Neighbor { @@ -19,25 +23,25 @@ impl Neighbor { #[derive(Clone, Copy)] pub struct Octave { - pub scale:f64, - pub weight:f64, + pub frequency:f64, + pub amplitude:f64, } impl Octave { pub fn new() -> Self { Self { - scale:1., - weight:1., + frequency:1., + amplitude:1., } } } #[derive(Clone, Copy)] -pub struct Terrain { +pub struct Geography { pub elevation:f64, pub octaves:[Octave; 4], } -impl Terrain { +impl Geography { pub fn new() -> Self { Self { @@ -47,7 +51,7 @@ impl Terrain { } } -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct Anchor { pub position:DVec3, pub centroid:DVec3, @@ -56,6 +60,8 @@ pub struct Anchor { pub neighbors:[Neighbor; 3], pub terrain:Terrain, + pub geography:Geography, + } impl Anchor { pub fn new() -> Self @@ -67,7 +73,8 @@ impl Anchor { ibasis:DMat3::IDENTITY, neighbors:[Neighbor::new(); 3], - terrain:Terrain::new(), + terrain:Terrain::new(Generator::new()), + geography:Geography::new(), } } } diff --git a/src/bin/world.rs b/src/bin/world.rs index 010222d..0624476 100644 --- a/src/bin/world.rs +++ b/src/bin/world.rs @@ -1,4 +1,5 @@ use glam::{DMat3, DVec3, Vec3}; +use noise::NoiseFn; use rand::prelude::*; use sparse::Sparse; use terrain::{ @@ -6,6 +7,10 @@ use terrain::{ }; fn get_subdivisions(radius_km:f64) -> usize +// Determines the number of subdivisions required for an icosphere, +// given a world radius, such that the distance between triangle +// centers is approximately 1-2 kilometers (assuming units of meters). +// { let edge_length = 4. * radius_km / (10. + (2. * f64::sqrt(5.))).sqrt(); let cell_radius = edge_length / f64::sqrt(3.); @@ -16,7 +21,10 @@ fn get_subdivisions(radius_km:f64) -> usize count } -fn get_neighbors(ico:&Icosphere, origin:usize, distance:usize) -> Vec<(usize, usize)> +fn get_neighbors(ico:&Icosphere, origin:usize, distance:usize) -> Vec<(usize, usize)> +// (Id, Distance) +// Gets a unique list of icosphere cells up to a specified distance from a origin cell. +// { let mut search = Vec::<(usize, usize)>::new(); let mut found = Sparse::<()>::new(); @@ -44,21 +52,33 @@ fn get_neighbors(ico:&Icosphere, origin:usize, distance:usize) -> Vec<(u } fn main() { - let radius_km = 1.; - let terrain_radius = 1; + let radius_km = 100.; + let terrain_radius = 3; - let mut ico = Icosphere::::new(); - for _ in 0..get_subdivisions(radius_km) { ico.subdivide(); } + let mut ico = Icosphere::new(); + let mut anchors = Sparse::::new(); + let subdivisions = get_subdivisions(radius_km); + for _ in 0..subdivisions { ico.subdivide(); } + println!("Subdivisions: {}", subdivisions); let world_scale = radius_km * 1000.; let mut rng = rand::thread_rng(); + let perlin = noise::Perlin::new(12); + // Generate an anchor for each cell in the isosphere. + // + let cells_list = ico.cells.list(); + let mut anchor_counter = 0; + let anchor_counter_total = cells_list.len(); + for id in cells_list { + if anchor_counter % 10000 == 0 { + println!("Generating anchor block #{}/{}", anchor_counter, anchor_counter_total); + } anchor_counter += 1; - // Set anchor properties. - for id in ico.cells.list() { - let mut cell = ico.cells.get_mut(id).unwrap().clone(); + let cell = ico.cells.get_mut(id).unwrap().clone(); + // Calculate cell space and transformations. let vertices = [ *ico.vertices.get(cell.vertices[0]).unwrap(), *ico.vertices.get(cell.vertices[1]).unwrap(), @@ -74,27 +94,30 @@ fn main() { let basis_inverse = DMat3::from_cols(basis_i, basis_j, basis_k); let basis = basis_inverse.inverse(); - cell.data = Some({ + // Define anchor properties. + anchors.set(id as isize, { let mut anchor = Anchor::new(); - anchor.terrain.elevation = (rng.gen::() * 50.) - 10.; - - let mut scale = (rng.gen::() / 32.) + (1. / 256.); - let mut weight = (rng.gen::() * 2.) + 0.25; - let octaves = (rng.gen::() * anchor.terrain.octaves.len() as f64) as usize + 1; - for oct in 0..octaves { - anchor.terrain.octaves[oct].scale = scale; - anchor.terrain.octaves[oct].weight = weight; - - scale *= (rng.gen::() / 2.) + (1. / 32.); - weight *= (rng.gen::() * 16.) + 1.; - } - - anchor.position = centroid.normalize() * world_scale; - anchor.centroid = centroid * world_scale; + anchor.centroid = centroid; + anchor.position = centroid.normalize(); anchor.basis = basis; anchor.ibasis = basis_inverse; + let mut elevation_frequency = 10000000. / 1.;// / world_scale; + for _ in 0..4 { + anchor.geography.elevation += (100. * rng.gen::() / elevation_frequency) * perlin.get([elevation_frequency * anchor.position.x, elevation_frequency * anchor.position.y, elevation_frequency * anchor.position.z]); + elevation_frequency *= 25.; + } + + let mut frequency = 32.; + let _octaves = (rng.gen::() * anchor.geography.octaves.len() as f64) as usize + 1; + for oct in 0..1 { + anchor.geography.octaves[oct].frequency = frequency; + anchor.geography.octaves[oct].amplitude = 100.; //rng.gen::(); + + frequency *= 1. / 4.; + } + anchor }); @@ -104,97 +127,164 @@ fn main() { // Set anchor neighbor properties. for id in ico.cells.list() { - let mut cell = ico.cells.get(id).unwrap().clone(); + let cell = ico.cells.get(id).unwrap(); + let mut anchor = anchors.get(id as isize).unwrap().clone(); - match &mut cell.data { - Some(anchor) => { - for n in 0..cell.neighbors.len() { - let neighbor = ico.cells.get(id).unwrap().clone(); + for n in 0..cell.neighbors.len() { + let neighbor_id = cell.neighbors[n]; + let neighbor_anchor = anchors.get(neighbor_id as isize).unwrap(); - match &neighbor.data { - Some(neighbor_anchor) => { - anchor.neighbors[n].distance = anchor.position.angle_between(neighbor_anchor.position) * world_scale; - anchor.neighbors[n].position = anchor.basis * (neighbor_anchor.position - anchor.position); - anchor.neighbors[n].transform = anchor.ibasis * neighbor_anchor.basis; - } - None => { } - } - } - } - None => { } + anchor.neighbors[n].distance = anchor.position.angle_between(neighbor_anchor.position) * world_scale; + anchor.neighbors[n].position = (anchor.basis * (neighbor_anchor.position - anchor.position)) * world_scale; + anchor.neighbors[n].transform = anchor.ibasis * neighbor_anchor.basis; } - ico.cells.update(id, cell).ok(); + anchors.set(id as isize, anchor); } // Generate terrain. - let mut gen = Generator::new(); - let mut terrain_mesh: Mesh = Mesh::new(); let mut mesh_count = 0; - if let Some(origin_cell) = ico.cells.get(1) { - let origin_anchor = &origin_cell.data.unwrap(); - let world_origin = -(origin_anchor.basis * origin_anchor.position); + let origin_id = 1; - // Generate zone meshes. - let tiles = get_neighbors(&ico, 1, terrain_radius); - let total_tiles = tiles.len(); - for (tile_id, depth) in tiles { - mesh_count += 1; - println!("Generating {} [{} / {}]", tile_id, mesh_count, total_tiles); + let origin_anchor = anchors.get(origin_id as isize).unwrap().clone(); + let world_origin = DVec3::NEG_Y * world_scale; - if let Some(zone) = ico.cells.get(tile_id) { + // Generate zone meshes. + let tiles = get_neighbors(&ico, 1, terrain_radius); + let total_tiles = tiles.len(); + for (tile_id, depth) in &tiles { - let mut zone_vertices = [DVec3::ZERO; 3]; + let mut gen = Generator::new(); - // Prepare generator with parameters of neighboring tiles. - let nearby = get_neighbors(&ico, tile_id, 3); - for vindex in 0..zone.vertices.len() { - let mut influence_count = 0; + mesh_count += 1; + println!("Generating {} [{} / {}]", tile_id, mesh_count, total_tiles); - let extent = zone.vertices[vindex]; - gen.sources[vindex] = terrain::generator::Source::new(); - zone_vertices[vindex] = (*ico.vertices.get(extent).unwrap()) * world_scale; - gen.sources[vindex].position = zone_vertices[vindex]; + let terrain = if let Some(zone) = ico.cells.get(*tile_id) { + // Prepare generator with parameters of neighboring tiles. + let nearby = get_neighbors(&ico, *tile_id, 3); + for vindex in 0..zone.vertices.len() { + let mut influence_count = 0; - for n in 0..nearby.len() { - let (nearby_id, _depth) = nearby[n]; + let extent = zone.vertices[vindex]; + gen.sources[vindex] = terrain::generator::Source::new(); + gen.sources[vindex].position = *ico.vertices.get(extent).unwrap(); - if let Some(tile) = ico.cells.get(nearby_id) { - if let Some(tile_anchor) = &tile.data { - for nvid in tile.vertices { - if nvid == extent { - gen.sources[vindex].elevation += tile_anchor.terrain.elevation; - gen.sources[vindex].octaves.append(&mut tile_anchor.terrain.octaves.to_vec()); - influence_count += 1; - break; - } - } + // Average geography of nearby anchors for each tile vertex. + for n in 0..nearby.len() { + let (nearby_id, _depth) = nearby[n]; + + if let Some(tile) = ico.cells.get(nearby_id) { + let tile_anchor = anchors.get(nearby_id as isize).unwrap(); + for nvid in tile.vertices { + if nvid == extent { + gen.sources[vindex].elevation += tile_anchor.geography.elevation; + gen.sources[vindex].octaves.append(&mut tile_anchor.geography.octaves.to_vec()); + influence_count += 1; + break; } } } - - gen.sources[vindex].elevation /= influence_count as f64; } - let mut terrain = Terrain::triangle((zone_vertices[0], zone_vertices[1], zone_vertices[2]), gen.clone()); - for _ in 0..(4 - depth.min(3)) { terrain.subdivide(); } - terrain.tessellate(); + gen.sources[vindex].elevation /= influence_count as f64; + } - for vid in terrain.vertices.list() { - if let Some(vertex) = terrain.vertices.get_mut(vid) { - let sv = vertex.position.normalize() * (world_scale + vertex.height); - vertex.position = (origin_anchor.basis * sv) + world_origin; - vertex.height = 0.; + let mut terrain = Terrain::triangle((gen.sources[0].position, gen.sources[1].position, gen.sources[2].position), gen); + for _ in 0..(4 - (*depth).min(3)) { terrain.subdivide(); } + terrain.tessellate(); + + terrain + } else { break; }; + + anchors.get_mut(*tile_id as isize).unwrap().terrain = terrain; + } + + // Modify anchor terrain to match neighbor edge subdivisons. + let mut apply_count = 1; + while apply_count > 0 { + apply_count = 0; + + let mut tile_count = 0; + for (tile_id, _depth) in &tiles { + let cell = ico.cells.get(*tile_id).unwrap().clone(); + let anchor = anchors.get(*tile_id as isize).unwrap().clone(); + + tile_count += 1; + println!("Stitching {} [{} / {}]", *tile_id, tile_count, tiles.len()); + + for side_id in 0..cell.neighbors.len() { + let neighbor_id = cell.neighbors[side_id]; + let neighbor_cell = ico.cells.get(neighbor_id).unwrap(); + let neighbor_anchor = anchors.get_mut(neighbor_id as isize).unwrap(); + + // Determine rotation of neighboring tile. + let side_vertices = [(side_id + 1) % 3, (side_id + 2) % 3]; + let mut neighbor_vertex_side = [0, 0]; + for s in 0..side_vertices.len() { + let vid = cell.vertices[side_vertices[s]]; + + for ns in 0..neighbor_cell.vertices.len() { + if neighbor_cell.vertices[ns] == vid { + neighbor_vertex_side[s] = ns; + break; + } + } + } + let neighbor_side = 3 - (neighbor_vertex_side[0] + neighbor_vertex_side[1]); + + // Get maximum depth on edge of current cell. + let mut max_depth = 0; + for cell_id in anchor.terrain.cells.list() { + let tcell = anchor.terrain.cells.get(cell_id).unwrap(); + + let side_counts = (tcell.neighbors[0] == 0) as usize + + (tcell.neighbors[1] == 0) as usize + + (tcell.neighbors[2] == 0) as usize; + + if side_counts == 1 && tcell.neighbors[side_id] == 0 { + max_depth = max_depth.max(tcell.depth); + } + } + + // Subdivide neighbor edges. + let mut subdivide_count = 1; + while subdivide_count > 0 { + subdivide_count = 0; + + let cell_list = neighbor_anchor.terrain.cells.list(); + for cell_id in cell_list { + let tcell = neighbor_anchor.terrain.cells.get(cell_id).unwrap().clone(); + + if tcell.neighbors[neighbor_side] == 0 { + if tcell.depth < max_depth { + neighbor_anchor.terrain.subdivide_cell(cell_id); + subdivide_count += 1; + } + } } } - terrain_mesh.merge(&terrain.to_mesh(), Vec3::ZERO); } } } - terrain::obj::save("icosphere.obj", &ico.to_mesh(), 50.).ok(); - terrain::obj::save("test.obj", &terrain_mesh, 0.1).ok(); + // Write anchors to mesh. + for (tile_id, _depth) in &tiles { + let anchor = anchors.get_mut(*tile_id as isize).unwrap(); + + for vid in anchor.terrain.vertices.list() { + if let Some(vertex) = anchor.terrain.vertices.get_mut(vid) { + let norm = vertex.position.normalize() * (world_scale + vertex.height); + vertex.position = (origin_anchor.basis * norm) + world_origin; + vertex.height = 0.; + } + } + + terrain_mesh.merge(&anchor.terrain.to_mesh(), Vec3::ZERO); + } + + //terrain::obj::save("icosphere.obj", &ico.to_mesh(), 20.).ok(); + terrain::obj::save("test.obj", &terrain_mesh, 0.01).ok(); } diff --git a/src/generator.rs b/src/generator.rs index dd2a2c0..2c61b2b 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -42,7 +42,7 @@ impl Generator { heights[src] = self.sources[src].elevation; for octave in &self.sources[src].octaves { - heights[src] += octave.weight * self.generator.get([octave.scale * position.x, octave.scale * position.y, octave.scale * position.z]); + heights[src] += 10. * octave.amplitude * self.generator.get([octave.frequency * position.x, octave.frequency * position.y, octave.frequency * position.z]); } heights[src] /= self.sources[src].octaves.len().max(1) as f64; } diff --git a/src/icosphere.rs b/src/icosphere.rs index 5a60877..ce83a84 100644 --- a/src/icosphere.rs +++ b/src/icosphere.rs @@ -12,17 +12,17 @@ const FLIPPED :u32 = 1 << 31; const NFLIPPED :u32 = !FLIPPED; #[derive(Clone, Copy)] -pub struct Cell where T: Clone { - pub data:Option, +pub struct Cell { + pub data:usize, depth:u32, pub neighbors:[usize;3], pub vertices:[usize;3], } -impl Cell { +impl Cell { pub fn new() -> Self { Self { - data:None, + data:0, depth:0, neighbors:[0; 3], vertices:[0; 3], @@ -30,16 +30,16 @@ impl Cell { } } -pub struct Icosphere where T: Clone { +pub struct Icosphere { pub vertices:Pool, - pub cells:Pool>, + pub cells:Pool, } -impl Icosphere { +impl Icosphere { pub fn new() -> Self { Self { vertices:Pool::::new(), - cells:Pool::>::new(), + cells:Pool::::new(), }.init() } @@ -72,7 +72,7 @@ impl Icosphere { // Top row cells for i in 0..5 { self.cells.add({ - let mut cell = Cell::::new(); + let mut cell = Cell::new(); cell.vertices = [ 2 + ((i + 1) % 5), @@ -297,7 +297,7 @@ impl Icosphere { for i in 0..children.len() { self.print_cell("SUBDIV", child_ids[i], &children[i]); - let mut child = Cell::::new(); + let mut child = Cell::new(); std::mem::swap(&mut children[i], &mut child); self.cells.update(child_ids[i], child).ok(); } @@ -308,7 +308,7 @@ impl Icosphere { self.vertices.add(vertex.normalize()) } - fn print_cell(&self, text:&str, id:usize, cell:&Cell) + fn print_cell(&self, text:&str, id:usize, cell:&Cell) { if DEBUG_PRINTS { println!("{:8} {:3} ( {}, {:2} ) [{:3} {:3} {:3}] [{:3} {:3} {:3}]", diff --git a/src/terrain.rs b/src/terrain.rs index f124b3f..7ff407d 100644 --- a/src/terrain.rs +++ b/src/terrain.rs @@ -11,11 +11,10 @@ const DEBUG_PRINTS :bool = false; const DEGREES :f64 = PI / 180.; -const DELTA_THRESHOLD :f64 = (1. / 24.) * DEGREES; -const MAX_SUBDIVISIONS :u16 = 14; +const DELTA_THRESHOLD :f64 = (1. / 48.) * DEGREES; +const MAX_SUBDIVISIONS :u16 = 4; const MAX_DEPTH :u16 = 2 * MAX_SUBDIVISIONS; -const RADIAL_FALLOFF :f64 = 0.7; -const RADIAL_FALLOFF_INV :f64 = 1. / RADIAL_FALLOFF; +const RADIAL_FALLOFF :f64 = 2.; #[derive(Clone, Copy)] enum Action { @@ -68,23 +67,22 @@ impl Cell { } } +#[derive(Clone)] pub struct Terrain { pub vertices:Pool, pub cells:Pool, pub extent:[DVec3; 3], pub normal:DVec3, - pub scale:f64, generator:Generator, } impl Terrain { - fn new(generator:Generator) -> Self + pub fn new(generator:Generator) -> Self { Self { vertices:Pool::::new(), cells:Pool::::new(), extent:[DVec3::ZERO; 3], normal:DVec3::Y, - scale:1., generator:generator, } } @@ -96,38 +94,22 @@ impl Terrain { let cross = (c - a).cross(b - a); grid.normal = cross.normalize(); - grid.scale = ((b - a).length() + (c - a).length()) / (1000. * f64::sqrt(3.)); grid.extent = [a, b, c]; - let vertices = [ - grid.add_vertex(a), - grid.add_vertex(b), - grid.add_vertex(c), - ]; + grid.add_vertex(a); + grid.add_vertex(b); + grid.add_vertex(c); grid.cells.add({ let mut cell = Cell::new(); - - cell.vertices = vertices; - + cell.vertices = [1, 2, 3]; cell }); grid } - fn init_hexagon(&mut self, _radius:usize) - { - let triangle_edge = 0.5; - let triangle_vertex = 2. * triangle_edge; - let triangle_halfside = f64::sqrt(3.) * triangle_edge; - let _triangle_side = 2. * triangle_halfside; - let _triangle_cross = triangle_edge + triangle_vertex; - - // Implementation Here - } - pub fn subdivide(&mut self) { for id in self.cells.list() { @@ -135,6 +117,11 @@ impl Terrain { } } + pub fn subdivide_cell(&mut self, id:usize) + { + self.node_update(&vec![Action::Subdivide(id, 0)]); + } + pub fn tessellate(&mut self) { let mut data = self.cells.list(); @@ -157,7 +144,6 @@ impl Terrain { data_len = new_data.len(); } - if passes % 10 == 0 { println!("Pass {}", passes); } @@ -216,11 +202,11 @@ impl Terrain { } } neighbor_normal /= count; - - let min_dist = vp[0].length().min(vp[1].length()).min(vp[2].length()).max(1.); + let centroid = (vp[0] + vp[1] + vp[2]) / 3.; + let min_dist = centroid.length_squared().max(1.); let delta = normal.angle_between(neighbor_normal); - let max_depth = MAX_DEPTH - (f64::log2(min_dist) * RADIAL_FALLOFF_INV).min((MAX_DEPTH - 2) as f64) as u16; + let max_depth = MAX_DEPTH - 2 * (f64::log2(min_dist) / f64::log2(RADIAL_FALLOFF)).min((MAX_SUBDIVISIONS - 1) as f64) as u16; let threshold = DELTA_THRESHOLD * f64::log2(cell.depth as f64 + 16.) * 4.; if delta > threshold && cell.depth < max_depth { self.node_update(&vec![Action::Subdivide(id, 0)]); @@ -339,10 +325,6 @@ impl Terrain { } twin = Some(self.cells.get(twin_id).unwrap().clone()); - //match &twin { - // Some(twin) => { self.print_cell("TWIN", twin_id, twin); } - // None => { } - //} } // Get outer neighbors and vertices. @@ -647,7 +629,7 @@ impl Terrain { fn add_vertex(&mut self, vertex:DVec3) -> usize { - self.vertices.add(Vertex::with(vertex, self.scale * self.generator.generate(vertex))) + self.vertices.add(Vertex::with(vertex, self.generator.generate(vertex))) } fn print_cell(&self, text:&str, id:usize, cell:&Cell)