Reorganize terrain; add orbital system.

This commit is contained in:
yukirij 2024-06-18 17:21:59 -07:00
parent 9623bbd3e4
commit 5273e55c0b
18 changed files with 780 additions and 144 deletions

View File

@ -7,6 +7,7 @@ edition = "2021"
glam = "0.27.0"
rand = "0.8.5"
noise = "0.9.0"
gltf = "1.4.1"
pool = { git = "https://git.tsukiyo.org/Utility/pool" }
sparse = { path = "../../Utility/sparse" }

6
LICENSE.txt Normal file
View File

@ -0,0 +1,6 @@
Project: Kirisame Project
Organization: Atelier Kirisame
Licensor: Yukiri Corporation
License available at:
https://ykr.info/license/

66
docs/szun/planet.szun Normal file
View File

@ -0,0 +1,66 @@
Quaternion { x :decimal, y :decimal, z :decimal, w :decimal }
StarSystem {
Orbit {
Orientation :Quaternion
Periapsis :decimal
Eccentricity :decimal
}
Entity {
Data :record
Parent :natural
Children :list<{ Orbit :Orbit, Entity :natural }>
}
Entities :list<Entity>
}
Octave {
Frequency :decimal
Amplitude :decimal
}
Planet {
Standard {
Radius :decimal # km
Gravity :decimal # m/s2
}
Terrain :option<{
Anchors :list<{
Geography :{
Elevation :decimal
Octaves :Octave
}
Biome :Biome
}>
}>
Ocean :option<{
Anchors :list<{
}>
}>
Atmosphere :option<{
Anchors :list<{
}>
}>
Climate :option<{
Anchors :list<{
}>
}>
}

View File

@ -1,7 +1,7 @@
use glam::{DMat3, DVec3};
use crate::{
terrain::Terrain,
generator::Generator,
generator::{Generator, Octave},
};
#[derive(Clone, Copy)]
@ -21,21 +21,6 @@ impl Neighbor {
}
}
#[derive(Clone, Copy)]
pub struct Octave {
pub frequency:f64,
pub amplitude:f64,
}
impl Octave {
pub fn new() -> Self
{
Self {
frequency:1.,
amplitude:1.,
}
}
}
#[derive(Clone, Copy)]
pub struct Geography {
pub elevation:f64,

44
src/bin/system.rs Normal file
View File

@ -0,0 +1,44 @@
use glam::{DVec3, DQuat};
use terrain::{
icosphere::Icosphere,
system::{StarSystem, Orbit},
export::export_obj,
};
fn main()
{
let ico = Icosphere::new();
let systems = [
5.,
1.,
0.5,
1.5,
];
let sys = StarSystem::singular(0, 10.)
.append(
StarSystem::singular(1, 1.)
.append(
StarSystem::singular(2, 0.5),
Orbit::with(DQuat::IDENTITY, 2., 0.1, 5., 0.),
),
Orbit::with(DQuat::IDENTITY, 20., 0.5, 10., 0.),
)
.append(
StarSystem::singular(3, 1.),
Orbit::with(DQuat::from_axis_angle(-DVec3::Z, f64::to_radians(30.)), 50., 0.1, 15., 0.),
);
let mut mesh = ico.to_mesh().scale(systems[0]);
for frame in 0..20 {
let time = frame as f64 * 0.5;
for (id, position) in sys.resolve(time) {
if id != 0 {
mesh = mesh.merge(&ico.to_mesh().scale(systems[id]), position.as_vec3());
}
}
}
export_obj("system.obj", &mesh, 10.).ok();
}

View File

@ -3,7 +3,12 @@ use noise::NoiseFn;
use rand::prelude::*;
use sparse::Sparse;
use terrain::{
anchor::Anchor, generator::Generator, icosphere::Icosphere, mesh::Mesh, terrain::Terrain
anchor::Anchor,
generator::Generator,
icosphere::Icosphere,
mesh::Mesh,
terrain::Terrain,
utility::rgb,
};
fn get_subdivisions(radius_km:f64) -> usize
@ -52,8 +57,9 @@ fn get_neighbors(ico:&Icosphere, origin:usize, distance:usize) -> Vec<(usize, us
}
fn main() {
let radius_km = 100.;
let terrain_radius = 3;
let radius_km = 10.;
let terrain_radius = 6;
let max_terrain_detail = 14;
let mut ico = Icosphere::new();
let mut anchors = Sparse::<Anchor>::new();
@ -61,7 +67,10 @@ fn main() {
for _ in 0..subdivisions { ico.subdivide(); }
println!("Subdivisions: {}", subdivisions);
let world_scale = radius_km * 1000.;
let world_radius = radius_km * 1000.;
let world_relief = world_radius / 150.; // typically 100:1 - 300:1
println!("radius: {} / relief: {}", world_radius, world_relief);
let mut rng = rand::thread_rng();
let perlin = noise::Perlin::new(12);
@ -95,6 +104,7 @@ fn main() {
let basis = basis_inverse.inverse();
// Define anchor properties.
//
anchors.set(id as isize, {
let mut anchor = Anchor::new();
@ -103,19 +113,25 @@ fn main() {
anchor.basis = basis;
anchor.ibasis = basis_inverse;
let mut elevation_frequency = 10000000. / 1.;// / world_scale;
let mut elevation_frequency = 1. / 4.;
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 frequency = world_radius * elevation_frequency;
anchor.geography.elevation += (world_relief * elevation_frequency) * perlin.get([frequency * anchor.position.x, frequency * anchor.position.y, frequency * anchor.position.z]);
elevation_frequency /= 10.;
}
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.;
let octaves = (rng.gen::<f64>() * anchor.geography.octaves.len() as f64) as usize + 1;
let mut octave_modifier = 500.;
for oct in 0..octaves {
let wavelength = (octave_modifier * world_relief * rng.gen::<f64>()) + 10.;
anchor.geography.octaves[oct].frequency = world_radius / wavelength;
anchor.geography.octaves[oct].amplitude = f64::sqrt(0.25 * rng.gen::<f64>() * wavelength);
anchor.geography.octaves[oct].offset = world_radius * DVec3::new(
(rng.gen::<f64>() * 2.) - 1.,
(rng.gen::<f64>() * 2.) - 1.,
(rng.gen::<f64>() * 2.) - 1.,
);
octave_modifier /= (rng.gen::<f64>() * 100.) + 2.;
}
anchor
@ -134,8 +150,8 @@ fn main() {
let neighbor_id = cell.neighbors[n];
let neighbor_anchor = anchors.get(neighbor_id as isize).unwrap();
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].distance = anchor.position.angle_between(neighbor_anchor.position) * world_radius;
anchor.neighbors[n].position = (anchor.basis * (neighbor_anchor.position - anchor.position)) * world_radius;
anchor.neighbors[n].transform = anchor.ibasis * neighbor_anchor.basis;
}
@ -150,7 +166,7 @@ fn main() {
let origin_id = 1;
let origin_anchor = anchors.get(origin_id as isize).unwrap().clone();
let world_origin = DVec3::NEG_Y * world_scale;
let world_origin = DVec3::NEG_Y * world_radius;
// Generate zone meshes.
let tiles = get_neighbors(&ico, 1, terrain_radius);
@ -193,9 +209,11 @@ fn main() {
}
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.config.origin = origin_anchor.position;
terrain.config.scale = world_radius;
for _ in 0..(4 - (*depth).min(3)) { terrain.subdivide(); }
terrain.tessellate(max_terrain_detail);
terrain
} else { break; };
@ -209,64 +227,224 @@ fn main() {
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();
let tile = ico.cells.get(*tile_id).unwrap().clone();
let mut cterrain = anchors.get(*tile_id as isize).unwrap().terrain.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();
// Apply stitching to each side of the triangle mesh.
for tile_side_index in 0..tile.neighbors.len() {
let neighbor_id = tile.neighbors[tile_side_index];
let neighbor_tile = ico.cells.get(neighbor_id).unwrap();
let nterrain = &mut anchors.get_mut(neighbor_id as isize).unwrap().terrain;
// 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]];
if nterrain.extent == [0; 3] {
// Ignore uninitialized (out of scope) terrain.
continue;
}
for ns in 0..neighbor_cell.vertices.len() {
if neighbor_cell.vertices[ns] == vid {
neighbor_vertex_side[s] = ns;
// Find indices of shared vertices.
let mut shared_vertex_indices = [0, 0];
let mut nshared_vertex_indices = [0, 0];
let mut shared_vertices = [0, 0];
let mut nshared_vertices = [0, 0];
let mut shared_index = 0;
for vertex_index in 0..tile.vertices.len() {
for nvertex_index in 0..neighbor_tile.vertices.len() {
if shared_index < 2 && tile.vertices[vertex_index] == neighbor_tile.vertices[nvertex_index] {
shared_vertex_indices[shared_index] = vertex_index;
nshared_vertex_indices[shared_index] = nvertex_index;
shared_vertices[shared_index] = tile.vertices[vertex_index];
nshared_vertices[shared_index] = neighbor_tile.vertices[nvertex_index];
shared_index += 1;
}
}
}
let neighbor_direction = 3 - (nshared_vertex_indices[0] + nshared_vertex_indices[1]);
/*println!("shared c({} : {}={}/{}, {}={}/{}) n({} : {}={}/{}, {}={}/{})",
tile_side_index,
shared_vertex_indices[0], shared_vertices[0], cterrain.extent[shared_vertex_indices[0]],
shared_vertex_indices[1], shared_vertices[1], cterrain.extent[shared_vertex_indices[1]],
neighbor_direction,
nshared_vertex_indices[0], nshared_vertices[0], nterrain.extent[nshared_vertex_indices[0]],
nshared_vertex_indices[1], nshared_vertices[1], nterrain.extent[nshared_vertex_indices[1]],
);*/
// Get ids of corner cells.
let mut current_face_id = {
let mut find = 0;
for face_id in cterrain.cells.list() {
let face = cterrain.cells.get(face_id).unwrap();
let search = cterrain.extent[shared_vertex_indices[0]];
if face.vertices[0] == search || face.vertices[1] == search || face.vertices[2] == search {
find = face_id;
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;
}
find
};
let mut neighbor_face_id = {
let mut find = 0;
for face_id in nterrain.cells.list() {
let face = nterrain.cells.get(face_id).unwrap();
let search = nterrain.extent[nshared_vertex_indices[0]];
if face.vertices[0] == search || face.vertices[1] == search || face.vertices[2] == search {
find = face_id;
break;
}
}
find
};
if current_face_id == 0 || neighbor_face_id == 0 {
//println!("! Failed to find faces: {}, {}", current_face_id, neighbor_face_id);
continue;
}
let current_direction_down = shared_vertex_indices[0];
let current_direction_edge = tile_side_index;
let current_direction_away = 3 - (current_direction_down + current_direction_edge);
let neighbor_direction_down = nshared_vertex_indices[0];
let neighbor_direction_edge = neighbor_direction;
let neighbor_direction_away = 3 - (neighbor_direction_down + neighbor_direction_edge);
/*println!(" DIR D0 {} E0 {} A0 {} D1 {} E1 {} A1 {}",
current_direction_down,
current_direction_edge,
current_direction_away,
neighbor_direction_down,
neighbor_direction_edge,
neighbor_direction_away,
);*/
let mut end_current = false;
let mut end_neighbor = false;
// Check if each edge face requires subdivision.
//
loop {
let tile_cell = cterrain.cells.get(current_face_id).unwrap().clone();
let neighbor_cell = nterrain.cells.get(neighbor_face_id).unwrap().clone();
/*println!("instep t {} (d {} r {}) n {} (d {} r {})",
current_face_id, tile_cell.depth, tile_cell.direction,
neighbor_face_id, neighbor_cell.depth, neighbor_cell.direction,
);*/
// Work is done once both cells have no neighbor in donward directions.
if end_current && end_neighbor {
//println!("done");
break;
}
//println!("step t({}) = {} n({}) = {}", current_face_id, tile_cell.depth, neighbor_face_id, neighbor_cell.depth);
if tile_cell.depth & !1 > neighbor_cell.depth & !1 {
// Subdivide neighbor tile's terrain and traverse backward.
nterrain.subdivide_cell(neighbor_face_id);
let new_face = nterrain.cells.get(neighbor_face_id).unwrap();
if new_face.sibling != 3 {
neighbor_face_id = new_face.neighbors[new_face.sibling as usize];
}
neighbor_face_id = nterrain.cells.get(neighbor_face_id).unwrap().neighbors[neighbor_direction_down];
apply_count += 1;
} else if tile_cell.depth & !1 < neighbor_cell.depth & !1 {
// Subdivide current tile's terrain and traverse backward.
cterrain.subdivide_cell(current_face_id);
let new_face = cterrain.cells.get(current_face_id).unwrap();
if new_face.sibling != 3 {
current_face_id = new_face.neighbors[new_face.sibling as usize];
}
current_face_id = cterrain.cells.get(current_face_id).unwrap().neighbors[current_direction_down];
apply_count += 1;
} else {
// Traverse to next tile on edge.
let mut move_last = current_face_id;
let mut move_direction = current_direction_down;
let mut move_count = 0;
while {
let face = cterrain.cells.get(current_face_id).unwrap();
let neighbor_id = face.neighbors[move_direction];
/*println!("mv ({}) current {} / {} (d {} s {} r {}) -> {}",
move_direction,
move_last, neighbor_face_id,
face.depth, face.sibling, face.direction,
neighbor_id
);*/
if neighbor_id != 0 {
move_count += 1;
if move_last != neighbor_id {
move_last = current_face_id;
current_face_id = neighbor_id;
} else {
if move_direction != current_direction_away {
move_direction = current_direction_away;
} else {
move_direction = current_direction_edge;
}
}
true
} else {
if move_count == 0 {
end_current = true;
}
false
}
} { }
// Traverse neighbor tile to next edge face.
move_last = neighbor_face_id;
move_direction = neighbor_direction_down;
move_count = 0;
while {
let face = nterrain.cells.get(neighbor_face_id).unwrap();
let neighbor_id = face.neighbors[move_direction];
/*println!("mv ({}) neighbor {} / {} (d {} s {} r {}) -> {}",
move_direction,
move_last, neighbor_face_id,
face.depth, face.sibling, face.direction,
neighbor_id
);*/
if neighbor_id != 0 {
move_count += 1;
if move_last != neighbor_id {
move_last = neighbor_face_id;
neighbor_face_id = neighbor_id;
} else {
if move_direction != neighbor_direction_away {
move_direction = neighbor_direction_away;
} else {
move_direction = neighbor_direction_edge;
}
}
true
} else {
if move_count == 0 {
end_neighbor = true;
}
false
}
} { }
}
}
}
anchors.get_mut(*tile_id as isize).unwrap().terrain = cterrain;
}
}
@ -276,15 +454,20 @@ fn main() {
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);
let norm = vertex.position.normalize() * (world_radius + vertex.height);
vertex.position = (origin_anchor.basis * norm) + world_origin;
if vertex.height > 0. {
vertex.color = rgb(192, 128, 32);
} else {
vertex.color = rgb(32, 96, 192);
}
vertex.height = 0.;
}
}
terrain_mesh.merge(&anchor.terrain.to_mesh(), Vec3::ZERO);
terrain_mesh = 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();
terrain::export::export_obj("test.obj", &terrain_mesh, 0.1).ok();
}

View File

@ -2,19 +2,22 @@ use std::io::Write;
use glam::Vec3;
use crate::mesh::Mesh;
pub fn save(path:&str, mesh:&Mesh, scale:f32) -> Result<(),()>
pub fn export_obj(path:&str, mesh:&Mesh, scale:f32) -> Result<(),()>
{
match std::fs::File::create(path) {
Ok(mut file) => {
let mut min = Vec3::ZERO;
let mut max = Vec3::ZERO;
for v in &mesh.vertices {
let v = *v * scale;
for vertex in &mesh.vertices {
let v = vertex.position * scale;
if v.x < min.x { min.x = v.x } else if v.x > max.x { max.x = v.x };
if v.z < min.z { min.z = v.z } else if v.z > max.z { max.z = v.z };
file.write_all(format!("v {:.6} {:.6} {:.6}\n", v.x, v.y, v.z).as_bytes()).ok();
file.write_all(format!("v {:.6} {:.6} {:.6} {:3} {:3} {:3}\n",
v.x, v.y, v.z,
vertex.color.x, vertex.color.y, vertex.color.z,
).as_bytes()).ok();
}
file.write_all("\n".as_bytes()).ok();
@ -22,8 +25,8 @@ pub fn save(path:&str, mesh:&Mesh, scale:f32) -> Result<(),()>
let rx = max.x - min.x;
let rz = max.z - min.z;
for v in &mesh.vertices {
let v = *v * scale;
for vertex in &mesh.vertices {
let v = vertex.position * scale;
let ux = ((v.x / rx) + 0.5).clamp(0., 1.);
let uz = ((v.z / rz) + 0.5).clamp(0., 1.);

View File

@ -1,7 +1,23 @@
use glam::DVec3;
use noise::NoiseFn;
use crate::anchor::Octave;
use crate::utility::barycentric;
use crate::utility::barycentric_value;
#[derive(Clone, Copy)]
pub struct Octave {
pub frequency:f64,
pub amplitude:f64,
pub offset:DVec3,
}
impl Octave {
pub fn new() -> Self
{
Self {
frequency:1.,
amplitude:1.,
offset:DVec3::ZERO,
}
}
}
#[derive(Clone)]
pub struct Source {
@ -42,12 +58,16 @@ impl Generator {
heights[src] = self.sources[src].elevation;
for octave in &self.sources[src].octaves {
heights[src] += 10. * octave.amplitude * self.generator.get([octave.frequency * position.x, octave.frequency * position.y, octave.frequency * position.z]);
heights[src] += octave.amplitude * self.generator.get([
octave.frequency * (position.x + octave.offset.x),
octave.frequency * (position.y + octave.offset.y),
octave.frequency * (position.z + octave.offset.z),
]);
}
heights[src] /= self.sources[src].octaves.len().max(1) as f64;
//heights[src] /= self.sources[src].octaves.len().max(1) as f64;
}
barycentric(
barycentric_value(
(self.sources[0].position, self.sources[1].position, self.sources[2].position),
(heights[0], heights[1], heights[2]),
position

View File

@ -1,7 +1,7 @@
#![allow(dead_code)]
use std::f64::consts::PI;
use glam::{IVec3, DVec3};
use glam::{IVec3, DVec3, Vec3};
use pool::Pool;
use sparse::Sparse;
@ -335,7 +335,10 @@ impl Icosphere {
for index in self.vertices.list() {
if let Some(vertex) = self.vertices.get(index) {
conversion.set(index as isize, mesh.vertices.len());
mesh.vertices.push(vertex.as_vec3() * MESH_SCALE);
mesh.vertices.push(crate::mesh::Vertex::new(
vertex.as_vec3() * MESH_SCALE,
Vec3::new(1., 1., 1.),
));
}
}

View File

@ -2,8 +2,10 @@
pub mod utility;
pub mod mesh;
pub mod obj;
pub mod export;
pub mod anchor;
pub mod generator;
pub mod terrain;
pub mod icosphere;
pub mod planet;
pub mod system;

View File

@ -1,23 +1,37 @@
use glam::{Vec3, IVec3};
pub struct Vertex {
pub position:Vec3,
pub color:Vec3,
}
impl Vertex{
pub fn new(position:Vec3, color:Vec3) -> Self
{
Self {
position:position,
color:color,
}
}
}
pub struct Mesh {
pub vertices:Vec<Vec3>,
pub vertices:Vec<Vertex>,
pub faces:Vec<IVec3>,
}
impl Mesh {
pub fn new() -> Self
{
Self {
vertices:Vec::<Vec3>::new(),
vertices:Vec::<Vertex>::new(),
faces:Vec::<IVec3>::new(),
}
}
pub fn merge(&mut self, mesh:&Mesh, translation:Vec3)
pub fn merge(mut self, mesh:&Mesh, translation:Vec3) -> Self
{
let start = self.vertices.len() as i32;
for vertex in &mesh.vertices {
self.vertices.push(*vertex + translation);
self.vertices.push(Vertex::new(vertex.position + translation, vertex.color));
}
for face in &mesh.faces {
@ -27,5 +41,14 @@ impl Mesh {
face.z + start,
));
}
self
}
pub fn scale(mut self, scale:f32) -> Self
{
for vertex in &mut self.vertices {
vertex.position *= scale;
}
self
}
}

13
src/planet/builder.rs Normal file
View File

@ -0,0 +1,13 @@
use super::Planet;
pub struct Builder {
data:Planet,
}
impl Builder {
pub fn new() -> Self
{
Builder {
data:Planet::new(),
}
}
}

2
src/planet/mod.rs Normal file
View File

@ -0,0 +1,2 @@
mod planet; pub use planet::Planet;
mod builder; pub use builder::Builder;

20
src/planet/planet.rs Normal file
View File

@ -0,0 +1,20 @@
pub struct Planet {
// Icosphere
// Anchors
// Geography
// Ocean
// Atmosphere
// Weather
// Climate
}
impl Planet {
pub fn new() -> Self
{
Self { }
}
pub fn builder() -> super::Builder
{
super::Builder::new()
}
}

127
src/system/mod.rs Normal file
View File

@ -0,0 +1,127 @@
use glam::DVec3;
mod orbit; pub use orbit::Orbit;
#[derive(Clone, Copy)]
struct Edge {
pub id:usize,
pub orbit:Orbit,
}
impl Edge {
pub fn new(id:usize, orbit:Orbit) -> Self
{
Self {
id:id,
orbit:orbit,
}
}
}
#[derive(Clone, Copy)]
enum NodeData {
Single(usize),
Binary(Edge, Edge),
}
#[derive(Clone)]
struct Node {
pub data:NodeData,
pub mass:f64,
pub children:Vec<Edge>,
}
#[derive(Clone)]
pub struct StarSystem {
nodes:Vec<Node>,
}
impl StarSystem {
pub fn singular(id:usize, mass:f64) -> Self
{
Self {
nodes:vec![Node {
data:NodeData::Single(id),
mass:mass,
children:vec![],
}],
}
}
pub fn binary(a:Self, b:Self, distance:f64, orbit:Orbit) -> Self
{
let offset_a = 1;
let offset_b = a.nodes.len() + 1;
let mass_a = a.nodes[0].mass;
let mass_b = b.nodes[0].mass;
let total_mass = mass_a + mass_b;
let mut orbit_a = orbit;
orbit_a.periapsis = mass_b * distance / total_mass;
let mut orbit_b = orbit;
orbit_b.periapsis = mass_a * distance / total_mass;
Self {
nodes:[
vec![
Node {
data:NodeData::Binary(
Edge::new(offset_a, orbit_a),
Edge::new(offset_b, orbit_b),
),
mass:total_mass,
children:vec![],
}
],
a.nodes,
b.nodes,
].concat(),
}
}
pub fn append(mut self, mut system:Self, orbit:Orbit) -> Self
{
let id = self.nodes.len();
println!("append at {}", id);
for node in &mut system.nodes {
for child in &mut node.children {
child.id += id;
}
}
self.nodes[0].children.push(Edge::new(id, orbit));
self.nodes.append(&mut system.nodes.clone());
self
}
pub fn resolve(&self, time:f64) -> Vec<(usize, DVec3)>
// Returns the absolute-space positions of all bodies.
//
{
let mut result = Vec::<(usize, DVec3)>::new();
let mut stack = Vec::<(usize, DVec3)>::new();
stack.push((0, DVec3::ZERO));
while let Some(params) = stack.pop() {
let (id, position) = params;
println!("resolving {} at {}", id, position);
match &self.nodes[id].data {
NodeData::Single(eid) => {
result.push((*eid, position));
}
NodeData::Binary(edge1, edge2) => {
stack.push((edge1.id, position + edge1.orbit.position(time)));
stack.push((edge2.id, position - edge2.orbit.position(time)));
}
}
for edge in &self.nodes[id].children {
stack.push((edge.id, position + edge.orbit.position(time)));
}
}
result
}
}

92
src/system/orbit.rs Normal file
View File

@ -0,0 +1,92 @@
use std::f64::consts::PI;
use glam::{DVec3, DQuat};
#[derive(Clone, Copy)]
pub struct Orbit {
pub orientation:DQuat, // orientation of the orbital system
pub periapsis:f64, // focal distance at periapsis
pub eccentricity:f64, // eccentricity of orbit
pub period:f64, // period of orbit
pub basis:f64, // starting position in multiples of period
}
impl Orbit {
pub fn new() -> Self
{
Self {
orientation:DQuat::IDENTITY,
periapsis:1.,
eccentricity:0.,
period:1.,
basis:0.,
}
}
pub fn with(orientation:DQuat, periapsis:f64, eccentricity:f64, period:f64, basis:f64) -> Self
{
Self {
orientation:orientation,
periapsis:periapsis,
eccentricity:eccentricity,
period:period,
basis:basis,
}
}
pub fn orientation(mut self, orientation:DQuat) -> Self
{
self.orientation = orientation;
self
}
pub fn periapsis(mut self, periapsis:f64) -> Self
{
self.periapsis = periapsis;
self
}
pub fn eccentricity(mut self, eccentricity:f64) -> Self
{
self.eccentricity = eccentricity;
self
}
pub fn period(mut self, period:f64) -> Self
{
self.period = period;
self
}
pub fn basis(mut self, basis:f64) -> Self
{
self.basis = basis;
self
}
pub fn position(&self, time:f64) -> DVec3
{
let semimajor_axis = self.periapsis / (1. - self.eccentricity);
let time = time + (self.basis * self.period);
let n = 2. * PI / self.period;
let mean_anomaly = n * time;
let mut eccentric_anomaly = mean_anomaly + (self.eccentricity * ((mean_anomaly < PI) as i32 as f64 - 0.5));
{
let threshold = 1e-6;
let mut delta: f64 = 1.;
while delta.abs() > threshold {
delta = (eccentric_anomaly - (self.eccentricity * eccentric_anomaly.sin()) - mean_anomaly) / (1. - self.eccentricity * eccentric_anomaly.cos());
eccentric_anomaly = eccentric_anomaly - delta;
}
}
let theta = 2. * ((1. + self.eccentricity).sqrt() * (eccentric_anomaly / 2.).tan()).atan2((1. - self.eccentricity).sqrt());
let radius = semimajor_axis * (1. - (self.eccentricity * eccentric_anomaly.cos()));
self.orientation * DVec3::new(
radius * theta.cos(),
0.,
-radius * theta.sin(),
)
}
}

View File

@ -1,7 +1,7 @@
#![allow(dead_code)]
use std::f64::consts::PI;
use glam::{IVec3, DVec3};
use glam::{IVec3, DVec3, Vec3};
use pool::Pool;
use sparse::Sparse;
@ -11,10 +11,8 @@ const DEBUG_PRINTS :bool = false;
const DEGREES :f64 = PI / 180.;
const DELTA_THRESHOLD :f64 = (1. / 48.) * DEGREES;
const MAX_SUBDIVISIONS :u16 = 4;
const MAX_DEPTH :u16 = 2 * MAX_SUBDIVISIONS;
const RADIAL_FALLOFF :f64 = 2.;
const DELTA_THRESHOLD :f64 = (1. / 16.) * DEGREES;
const RADIAL_FALLOFF :f64 = 1. / 6.;
#[derive(Clone, Copy)]
enum Action {
@ -23,10 +21,28 @@ enum Action {
Split(/*Id*/usize, /*Caller*/usize),
}
#[derive(Clone, Copy)]
pub struct TessellateConfig {
pub origin:DVec3,
pub scale:f64,
pub max_depth:u16,
}
impl TessellateConfig {
pub fn new() -> Self
{
Self {
origin:DVec3::ZERO,
scale:1.,
max_depth:1,
}
}
}
#[derive(Clone, Copy)]
pub struct Vertex {
pub position:DVec3,
pub height:f64,
pub color:Vec3,
}
impl Vertex {
pub fn new() -> Self
@ -34,6 +50,7 @@ impl Vertex {
Self {
position:DVec3::ZERO,
height:0.,
color:Vec3::ONE,
}
}
@ -42,6 +59,7 @@ impl Vertex {
Self {
position:position,
height:height,
color:Vec3::ONE,
}
}
}
@ -71,9 +89,11 @@ impl Cell {
pub struct Terrain {
pub vertices:Pool<Vertex>,
pub cells:Pool<Cell>,
pub extent:[DVec3; 3],
pub extent:[usize; 3],
pub normal:DVec3,
generator:Generator,
pub config:TessellateConfig,
}
impl Terrain {
pub fn new(generator:Generator) -> Self
@ -81,9 +101,11 @@ impl Terrain {
Self {
vertices:Pool::<Vertex>::new(),
cells:Pool::<Cell>::new(),
extent:[DVec3::ZERO; 3],
extent:[0; 3],
normal:DVec3::Y,
generator:generator,
config:TessellateConfig::new(),
}
}
@ -95,15 +117,15 @@ impl Terrain {
let cross = (c - a).cross(b - a);
grid.normal = cross.normalize();
grid.extent = [a, b, c];
grid.add_vertex(a);
grid.add_vertex(b);
grid.add_vertex(c);
grid.extent = [
grid.add_vertex(a),
grid.add_vertex(b),
grid.add_vertex(c),
];
grid.cells.add({
let mut cell = Cell::new();
cell.vertices = [1, 2, 3];
cell.vertices = grid.extent;
cell
});
@ -122,18 +144,21 @@ impl Terrain {
self.node_update(&vec![Action::Subdivide(id, 0)]);
}
pub fn tessellate(&mut self)
pub fn tessellate(&mut self, max_depth:u16)
{
let mut data = self.cells.list();
let mut data_len = data.len();
let mut config = self.config.clone();
config.max_depth = max_depth;
let mut passes = 1;
while data.len() > 0 {
let pass = data.clone();
data.clear();
for id in pass {
if self.node_prepare(id) {
if self.node_prepare(id, &config) {
data.push(id);
}
}
@ -152,7 +177,7 @@ impl Terrain {
}
}
fn node_prepare(&mut self, id:usize) -> bool
fn node_prepare(&mut self, id:usize, config:&TessellateConfig) -> bool
// Test the height change of the node to determine if it should be subdivided.
//
{
@ -178,9 +203,7 @@ impl Terrain {
];
let normal = (v[1] - v[0]).cross(v[2] - v[0]).normalize();
let mut neighbor_normal = DVec3::ZERO;
let mut count = 0.;
let mut delta: f64 = 0.;
for n in 0..3 {
let neighbor_id = cell.neighbors[n];
if neighbor_id != 0 {
@ -196,29 +219,33 @@ impl Terrain {
vt[2].position + (self.normal * vt[2].height),
];
neighbor_normal += (v[1] - v[0]).cross(v[2] - v[0]).normalize();
count += 1.;
delta = delta.max(normal.angle_between((v[1] - v[0]).cross(v[2] - v[0]).normalize()));
}
}
}
neighbor_normal /= count;
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 - 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 {
let cvp = [
vp[0] - config.origin,
vp[1] - config.origin,
vp[2] - config.origin,
];
let centroid = (cvp[0] + cvp[1] + cvp[2]) / 3.;
let min_dist = centroid.length_squared().min(cvp[0].length_squared()).min(cvp[1].length_squared()).min(cvp[2].length_squared()).sqrt() * config.scale;
let depth_threshold = 0.125 * cell.depth as f64;
let distance_threshold = min_dist.log2();
//println!("dt {}", distance_threshold);
let threshold_modifier = (depth_threshold + distance_threshold) * RADIAL_FALLOFF;
let threshold: f64 = DELTA_THRESHOLD * threshold_modifier * threshold_modifier;
if delta > threshold && delta > threshold_modifier && cell.depth < (config.max_depth * 2) {
self.node_update(&vec![Action::Subdivide(id, 0)]);
let new_cell = if let Some(data) = self.cells.get(id) {
*data
} else { return false; };
new_cell.depth != cell.depth
} else {
false
}
if let Some(data) = self.cells.get(id) {
data.depth != cell.depth
} else { false }
} else { false }
}
fn node_update(&mut self, actions:&Vec<Action>)
@ -298,7 +325,6 @@ impl Terrain {
let mut cell = if let Some(data) = self.cells.get(id) {
*data
} else { return vec![]; };
if cell.depth >= MAX_DEPTH { return vec![]; }
let mut actions = Vec::<Action>::new();
@ -658,7 +684,10 @@ impl Terrain {
for index in self.vertices.list() {
if let Some(vertex) = self.vertices.get(index) {
conversion.set(index as isize, mesh.vertices.len());
mesh.vertices.push((vertex.position + (self.normal * vertex.height)).as_vec3());
mesh.vertices.push(crate::mesh::Vertex::new(
(vertex.position + (self.normal * vertex.height)).as_vec3(),
vertex.color,
));
}
}

View File

@ -1,14 +1,13 @@
use glam::DVec3;
use glam::{Vec3, DVec3};
pub fn f64_sign(value:f64) -> f64
{
((value > 0.) as i32 - (value < 0.) as i32) as f64
}
pub fn barycentric(positions:(DVec3, DVec3, DVec3), values:(f64, f64, f64), point:DVec3) -> f64
pub fn barycentric_coordinates(positions:(DVec3, DVec3, DVec3), point:DVec3) -> DVec3
{
let (a, b, c) = positions;
let (va, vb, vc) = values;
let v0 = b - a;
let v1 = c - a;
@ -24,5 +23,23 @@ pub fn barycentric(positions:(DVec3, DVec3, DVec3), values:(f64, f64, f64), poin
let b1 = ((d00 * d21) - (d01 * d20)) / denom;
let b2 = 1. - (b0 + b1);
(va * b2) + (vb * b0) + (vc * b1)
DVec3::new(b2, b0, b1)
}
pub fn barycentric_inside(positions:(DVec3, DVec3, DVec3), point:DVec3) -> bool
{
let coords = barycentric_coordinates(positions, point);
coords.x >= 0. && coords.x <= 1. && coords.y >= 0. && coords.y <= 1. && coords.z >= 0. && coords.z <= 1.
}
pub fn barycentric_value(positions:(DVec3, DVec3, DVec3), values:(f64, f64, f64), point:DVec3) -> f64
{
let coords = barycentric_coordinates(positions, point);
let (va, vb, vc) = values;
(va * coords.x) + (vb * coords.y) + (vc * coords.z)
}
pub fn rgb(r:u8, g:u8, b:u8) -> Vec3
{
Vec3::new(r as f32 / 255., g as f32 / 255., b as f32 / 255.)
}