-
Notifications
You must be signed in to change notification settings - Fork 20
Gradient propgation #479
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Gradient propgation #479
Changes from all commits
5db5eec
9c4e20b
d811fb7
c81ec1f
b5ec294
60e0403
0b5a89a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -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) = { | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like we're expanding a small amount of information into a much larger amount before passing it all through a function call. Could we push all this down into |
||||
| 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 | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this comment mean? Elaborate. |
||||
|
|
||||
| #[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<f32>, | ||||
| } | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style nit: include an empty line between top-level blocks |
||||
| impl EnviroGradient { | ||||
| fn new_from_direction(magnitude: f32, direction: MDirection<f32>) -> 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<f32>, | ||||
| 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 | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not use a rotation here and avoid the hazard? |
||||
| // if you need a stronger effect. | ||||
| assert!(ratio > 1.0); | ||||
| assert!(perturbance_direction.w == na::zero()); | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather than panicking, could we pick an arbitrary direction here? |
||||
|
|
||||
| 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 | ||||
| } | ||||
|
Comment on lines
+614
to
+621
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could this be simplified using |
||||
| }, | ||||
| 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<f32>, | ||||
| up_direction: MDirection<f32>, | ||||
| 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::<f32>::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 | ||||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @patowen can you comment on the significance of this pattern? I'm completely drawing a blank on why we don't e.g. just average everything together here, and on what exactly "ab" is and what this method for combining vectors looks like visually.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll have to take a close look at this. This seems similar to the previous "continue_from" logic, which was needed to handle the "one consistent offset per dividing plane" explained at hypermine/docs/world_generation.md Line 20 in ca2903c
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ralith imagine we have a grid of 4 nodes laid out as follows: We know a scalar value at a, b, and ab. We can calculate that crossing the boundary from ab->a increases the value by (a.value - ab.value), and crossing the boundary from ab->b increases the value by (b.value - ab.value). The way hypermine random-step noise functions work, we declare that the effect on the value of moving from ab->a is the same as that of b->c, in other words a plane is one big boundary that acts the same everywhere on it. From that we calculate the value of c as (ab.value + (a.value - ab.value) + (b.value - ab.value)), which simplifies to the classic expression (a.value + (b.value - ab.value)) |
||||
| // 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())) | ||||
|
Comment on lines
+693
to
+706
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: this is kind of hard to visually parse and has serious rightward drift. Lift out some intermediate variables? |
||||
| .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, | ||||
| ), | ||||
| } | ||||
| } | ||||
| } | ||||
|
|
||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated change; please omit.