diff --git a/common/src/math.rs b/common/src/math.rs index a4a0bb68..b6fa4966 100644 --- a/common/src/math.rs +++ b/common/src/math.rs @@ -507,6 +507,13 @@ impl MDirection { pub fn cast>(self) -> MDirection { MDirection(self.0.cast()) } + + /// Project to be orthogonal to the origin [0 0 0 1] + #[inline] + pub fn project_to_origin(&self) -> Self { + let p = MPoint::origin(); + (self.as_ref() + p.as_ref() * self.mip(&p)).normalized_direction() + } } impl From> for na::Vector4 { diff --git a/common/src/worldgen/horosphere.rs b/common/src/worldgen/horosphere.rs index e9433927..2cce01ef 100644 --- a/common/src/worldgen/horosphere.rs +++ b/common/src/worldgen/horosphere.rs @@ -17,7 +17,7 @@ use crate::{ /// Whether an assortment of random horospheres should be added to world generation. This is a temporary /// option until large structures that fit with the theme of the world are introduced. /// For code simplicity, this is made into a constant instead of a configuration option. -const HOROSPHERES_ENABLED: bool = true; +const HOROSPHERES_ENABLED: bool = false; /// Value to mix into the node's spice for generating horospheres. Chosen randomly. const HOROSPHERE_SEED: u64 = 6046133366614030452; diff --git a/common/src/worldgen/mod.rs b/common/src/worldgen/mod.rs index 17ef4111..a4283bdc 100644 --- a/common/src/worldgen/mod.rs +++ b/common/src/worldgen/mod.rs @@ -1,14 +1,15 @@ use horosphere::{HorosphereChunk, HorosphereNode}; +use na::Vector4; use plane::Plane; use rand::{Rng, SeedableRng, distr::Uniform}; -use rand_distr::Normal; +use rand_distr::{Normal, UnitSphere}; use terraingen::VoronoiInfo; use crate::{ dodeca::{Side, Vertex}, graph::{Graph, NodeId}, margins, - math::{self, MVector}, + math::{self, MDirection, MPoint, MVector}, node::{ChunkId, VoxelData}, world::Material, }; @@ -112,10 +113,26 @@ impl NodeState { temperature: 0.0, rainfall: 0.0, blockiness: 0.0, + elevation_gradient: EnviroGradient::new_from_direction(1.5_f32, *Side::B.normal()), }, (Some(parent), None) => { let spice = graph.hash_of(node) as u64; - EnviroFactors::varied_from(parent.node_state.enviro, spice) + let (traversal_direction, up_direction) = { + let p = MPoint::origin(); + let t = parent.side.normal(); + let u = parent.node_state.up_direction(); + ( + (t.as_ref() + p.as_ref() * t.mip(&p)).normalized_direction(), + (u.as_ref() + p.as_ref() * u.mip(&p)).normalized_direction(), + ) + }; + EnviroFactors::varied_from( + parent.node_state.enviro, + parent.side, + traversal_direction, + up_direction, + spice, + ) } (Some(parent_a), Some(parent_b)) => { let ab_node = graph.neighbor(parent_a.node_id, parent_b.side).unwrap(); @@ -124,6 +141,8 @@ impl NodeState { parent_a.node_state.enviro, parent_b.node_state.enviro, ab_state.enviro, + parent_a.side, + parent_b.side, ) } _ => unreachable!(), @@ -530,32 +549,173 @@ struct NeighborData { material: Material, } +const ELEVATION_GRADIENT_MAGNITUDE_FLOOR: f32 = 0.0; +const ELEVATION_GRADIENT_MAGNITUDE_CEILING: f32 = 3.0; // maximum 27.0 + +#[derive(Copy, Clone)] +/// A gradient representing evironmental factors. +/// Consists of a magnitude and direction, which are for the most part operated on indpendently. +/// The magnitude can be scaled arbitrarily, for example the elevation gradient is on a cubic scale. +struct EnviroGradient { + magnitude: f32, + direction: MDirection, +} +impl EnviroGradient { + fn new_from_direction(magnitude: f32, direction: MDirection) -> Self { + Self { + magnitude, + direction: direction.project_to_origin(), + } + } + + fn reflect_from_side(&self, side: Side) -> Self { + // reflect, then drop the w-component and renormalize + Self { + magnitude: self.magnitude, + direction: (side.reflection() * self.direction).project_to_origin(), + } + } + + /// Linear "average" between two gradients + fn average(&self, other: &EnviroGradient) -> Self { + let v1 = Vector4::from(self.direction); + let v2 = Vector4::from(other.direction); + Self { + magnitude: 0.5 * (self.magnitude + other.magnitude), + direction: MVector::from(v1 + v2).normalized_direction(), + } + } + + /// Linearally mix the gradient's direction with another direction. + /// The mix is biased toward the origonal direction. + /// The intent is to use this with a randomly-generated direction. + /// ratio is the ratio of origonal direction's weight to perturbance direction's weight. + /// Also linearly and independently adds a delta to the gradient magnitude, + /// which could certainly be a seperate function + fn perturb( + &self, + perturbance_direction: MDirection, + ratio: f32, + magnitude_delta: f32, + ) -> Self { + // Allowing the perturbance to equal the origonal opens the door for division by zero + // during normalization. Consider using rotations or making use of smaller-scale noise + // if you need a stronger effect. + assert!(ratio > 1.0); + assert!(perturbance_direction.w == na::zero()); + + let v1 = Vector4::from(self.direction) * ratio; + let v2 = Vector4::from(perturbance_direction); + + let m = self.magnitude + magnitude_delta; + + Self { + magnitude: { + if m < ELEVATION_GRADIENT_MAGNITUDE_FLOOR { + ELEVATION_GRADIENT_MAGNITUDE_FLOOR - (m - ELEVATION_GRADIENT_MAGNITUDE_FLOOR) + } else if m > ELEVATION_GRADIENT_MAGNITUDE_CEILING { + ELEVATION_GRADIENT_MAGNITUDE_CEILING + - (m - ELEVATION_GRADIENT_MAGNITUDE_CEILING) + } else { + m + } + }, + direction: MVector::from(v1 + v2).normalized_direction(), + } + } + + /// Gets the gradient magnitude used for elevation delta calculations. + /// That magnitude is the cube of the stored/propogated magnitude for more noticeable + /// terrain differences. + #[inline] + fn get_gradient_magnitude(&self) -> f32 { + self.magnitude.powi(3) + } +} + #[derive(Copy, Clone)] struct EnviroFactors { max_elevation: f32, temperature: f32, rainfall: f32, blockiness: f32, + elevation_gradient: EnviroGradient, } impl EnviroFactors { - fn varied_from(parent: Self, spice: u64) -> Self { + fn varied_from( + parent: Self, + side: Side, + traversal_direction: MDirection, + up_direction: MDirection, + spice: u64, + ) -> Self { let mut rng = rand_pcg::Pcg64Mcg::seed_from_u64(spice); let unif = Uniform::new_inclusive(-1.0, 1.0).unwrap(); - let max_elevation = parent.max_elevation + rng.sample(Normal::new(0.0, 4.0).unwrap()); + + let elevation_gradient = parent.elevation_gradient.reflect_from_side(side).perturb( + { + let v: [f32; 3] = rng.sample(UnitSphere); + MDirection::::new_unchecked(v[0], v[1], v[2], 0.0) + }, + 10.0, + rng.sample(unif), + ); + let average_gradient = parent + .elevation_gradient + .average(&elevation_gradient.reflect_from_side(side)); + + // Increase elevation according to how perpendicular the gradient is to traversal direction + let parallel_component = average_gradient.direction.mip(&traversal_direction); + // Don't increase elevation when moving vertically + let sky_islands_suppression_factor = + (1.0 - (traversal_direction.mip(&up_direction)).powi(2)).sqrt(); + + let max_elevation = parent.max_elevation + + average_gradient.get_gradient_magnitude() + * sky_islands_suppression_factor + * parallel_component + + rng.sample(Normal::new(0.0, 0.1).unwrap()); Self { max_elevation, temperature: parent.temperature + rng.sample(unif), rainfall: parent.rainfall + rng.sample(unif), blockiness: parent.blockiness + rng.sample(unif), + elevation_gradient, } } - fn continue_from(a: Self, b: Self, ab: Self) -> Self { + fn continue_from(a: Self, b: Self, ab: Self, side_a: Side, side_b: Side) -> Self { + // project the gradients of a, b, and ab into the new node's frame of reference, + // then the new gradient's direction is calculated as directions of (a + (b - ab). + // This process doesn't really have a physical meaning, but it's an isotropic way for + // the child node's gradient direction to be inherited, and it's intuitive for it to operate + // in a similar fashion to the scalar environmental factors. + let gradient_direction = (a + .elevation_gradient + .reflect_from_side(side_a) + .direction + .as_ref() + + (b.elevation_gradient + .reflect_from_side(side_b) + .direction + .as_ref() + - ab.elevation_gradient + .reflect_from_side(side_a) + .reflect_from_side(side_b) + .direction + .as_ref())) + .normalized_direction(); + let gradient_magnitude = a.elevation_gradient.magnitude + + (b.elevation_gradient.magnitude - ab.elevation_gradient.magnitude); Self { max_elevation: a.max_elevation + (b.max_elevation - ab.max_elevation), temperature: a.temperature + (b.temperature - ab.temperature), rainfall: a.rainfall + (b.rainfall - ab.rainfall), blockiness: a.blockiness + (b.blockiness - ab.blockiness), + elevation_gradient: EnviroGradient::new_from_direction( + gradient_magnitude, + gradient_direction, + ), } } }