Correct global/local coordinate handling; add basic mesh stitching.

This commit is contained in:
yukirij 2024-06-15 12:24:06 -07:00
parent f2bea2ebbe
commit 9623bbd3e4
5 changed files with 222 additions and 143 deletions

View File

@ -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(),
}
}
}

View File

@ -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<Anchor>, 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<Anchor>, 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::<Anchor>::new();
for _ in 0..get_subdivisions(radius_km) { ico.subdivide(); }
let mut ico = Icosphere::new();
let mut anchors = Sparse::<Anchor>::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::<f64>() * 50.) - 10.;
let mut scale = (rng.gen::<f64>() / 32.) + (1. / 256.);
let mut weight = (rng.gen::<f64>() * 2.) + 0.25;
let octaves = (rng.gen::<f64>() * 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::<f64>() / 2.) + (1. / 32.);
weight *= (rng.gen::<f64>() * 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::<f64>() / 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::<f64>() * 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::<f64>();
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();
}

View File

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

View File

@ -12,17 +12,17 @@ const FLIPPED :u32 = 1 << 31;
const NFLIPPED :u32 = !FLIPPED;
#[derive(Clone, Copy)]
pub struct Cell<T> where T: Clone {
pub data:Option<T>,
pub struct Cell {
pub data:usize,
depth:u32,
pub neighbors:[usize;3],
pub vertices:[usize;3],
}
impl<T: Clone> Cell<T> {
impl Cell {
pub fn new() -> Self
{
Self {
data:None,
data:0,
depth:0,
neighbors:[0; 3],
vertices:[0; 3],
@ -30,16 +30,16 @@ impl<T: Clone> Cell<T> {
}
}
pub struct Icosphere<T> where T: Clone {
pub struct Icosphere {
pub vertices:Pool<DVec3>,
pub cells:Pool<Cell<T>>,
pub cells:Pool<Cell>,
}
impl<T: Clone> Icosphere<T> {
impl Icosphere {
pub fn new() -> Self
{
Self {
vertices:Pool::<DVec3>::new(),
cells:Pool::<Cell<T>>::new(),
cells:Pool::<Cell>::new(),
}.init()
}
@ -72,7 +72,7 @@ impl<T: Clone> Icosphere<T> {
// Top row cells
for i in 0..5 {
self.cells.add({
let mut cell = Cell::<T>::new();
let mut cell = Cell::new();
cell.vertices = [
2 + ((i + 1) % 5),
@ -297,7 +297,7 @@ impl<T: Clone> Icosphere<T> {
for i in 0..children.len() {
self.print_cell("SUBDIV", child_ids[i], &children[i]);
let mut child = Cell::<T>::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<T: Clone> Icosphere<T> {
self.vertices.add(vertex.normalize())
}
fn print_cell(&self, text:&str, id:usize, cell:&Cell<T>)
fn print_cell(&self, text:&str, id:usize, cell:&Cell)
{
if DEBUG_PRINTS {
println!("{:8} {:3} ( {}, {:2} ) [{:3} {:3} {:3}] [{:3} {:3} {:3}]",

View File

@ -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<Vertex>,
pub cells:Pool<Cell>,
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::<Vertex>::new(),
cells:Pool::<Cell>::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)