Skip to content

Commit

Permalink
Add NoiseScaling, allowing selecting how the noise is applied.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheGrimsey committed Nov 2, 2024
1 parent ee28543 commit 648a7d7
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 38 deletions.
11 changes: 7 additions & 4 deletions examples/basic_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use bevy_world_seed::{
ModifierStrengthLimitProperty, ModifierTileAabb, ShapeModifier, ShapeModifierBundle,
TerrainSplineBundle, TerrainSplineCached, TerrainSplineProperties, TerrainSplineShape,
},
noise::{FilterCombinator, FilteredTerrainNoiseDetailLayer, TerrainNoiseDetailLayer, TerrainNoiseSettings},
noise::{FilterCombinator, FilteredTerrainNoiseDetailLayer, NoiseScaling, TerrainNoiseDetailLayer, TerrainNoiseSettings},
snap_to_terrain::SnapToTerrain,
terrain::Terrain,
TerrainPlugin, TerrainSettings,
Expand All @@ -46,7 +46,8 @@ fn main() {
amplitude: 6.0,
frequency: 1.0 / 30.0,
seed: 1,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
filter: vec![],
filter_combinator: FilterCombinator::Max
Expand Down Expand Up @@ -181,7 +182,8 @@ fn spawn_terrain(
amplitude: 2.0,
frequency: 1.0,
seed: 5,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
},
ModifierHeightOperation::Set,
Expand Down Expand Up @@ -236,7 +238,8 @@ fn spawn_terrain(
amplitude: 2.0,
frequency: 0.1,
seed: 5,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
},
TextureModifierOperation {
Expand Down
18 changes: 11 additions & 7 deletions examples/many_tiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use bevy_world_seed::{
},
meshing::TerrainMeshRebuildQueue,
noise::{
DomainWarping, FilterCombinator, FilterComparingTo, FilteredTerrainNoiseDetailLayer, NoiseCache, NoiseFilter, NoiseFilterCondition, TerrainNoiseDetailLayer, TerrainNoiseSettings, TerrainNoiseSplineLayer
DomainWarping, FilterCombinator, FilterComparingTo, FilteredTerrainNoiseDetailLayer, NoiseCache, NoiseFilter, NoiseFilterCondition, NoiseScaling, TerrainNoiseDetailLayer, TerrainNoiseSettings, TerrainNoiseSplineLayer
},
terrain::{Terrain, TileToTerrain},
RebuildTile, TerrainHeightRebuildQueue, TerrainPlugin, TerrainSettings,
Expand Down Expand Up @@ -76,7 +76,8 @@ fn main() {
amplitude: 60.0,
frequency: 0.004,
z_offset: 10.0
}]
}],
scaling: NoiseScaling::Normalized
},
filter: vec![NoiseFilter {
condition: NoiseFilterCondition::Above(0.4),
Expand All @@ -91,7 +92,8 @@ fn main() {
amplitude: 8.0,
frequency: 0.01,
seed: 1,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
filter: vec![],
filter_combinator: FilterCombinator::Max
Expand All @@ -101,7 +103,8 @@ fn main() {
amplitude: 4.0,
frequency: 0.02,
seed: 2,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
filter: vec![],
filter_combinator: FilterCombinator::Max
Expand All @@ -111,7 +114,8 @@ fn main() {
amplitude: 2.0,
frequency: 0.04,
seed: 3,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
filter: vec![],
filter_combinator: FilterCombinator::Max
Expand Down Expand Up @@ -399,7 +403,7 @@ impl EditorWindow for NoiseDebugWindow {
ui.heading("Detail Noise");

for (i, detail_layer) in noise_settings.layers.iter().enumerate() {
let noise_raw = detail_layer.layer.sample_raw(
let noise_raw = detail_layer.layer.sample_scaled_raw(
translation.x,
translation.z,
noise_cache.get(detail_layer.layer.seed),
Expand All @@ -415,7 +419,7 @@ impl EditorWindow for NoiseDebugWindow {

ui.heading("Data");
for (i, data_layer) in noise_settings.data.iter().enumerate() {
let noise = data_layer.sample_raw(translation.x, translation.z, noise_cache.get(data_layer.seed));
let noise = data_layer.sample_scaled_raw(translation.x, translation.z, noise_cache.get(data_layer.seed));

ui.label(format!("- {i}: {noise:.3}"));
}
Expand Down
5 changes: 3 additions & 2 deletions examples/terrain_collider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use bevy_world_seed::{
ModifierHeightProperties, ModifierHoleOperation, ModifierPriority, ModifierTileAabb,
ShapeModifier, ShapeModifierBundle,
},
noise::{FilterCombinator, FilteredTerrainNoiseDetailLayer, TerrainNoiseDetailLayer, TerrainNoiseSettings},
noise::{FilterCombinator, FilteredTerrainNoiseDetailLayer, NoiseScaling, TerrainNoiseDetailLayer, TerrainNoiseSettings},
terrain::{Holes, Terrain, TileToTerrain},
utils::index_to_x_z,
Heights, TerrainPlugin, TerrainSettings, TileHeightsRebuilt,
Expand Down Expand Up @@ -56,7 +56,8 @@ fn main() {
amplitude: 4.0,
frequency: 1.0 / 30.0,
seed: 2,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
},
filter: vec![],
filter_combinator: FilterCombinator::Max
Expand Down
85 changes: 60 additions & 25 deletions src/noise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,27 @@ impl DomainWarping {
}
}

#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Reflect, Clone, PartialEq, Default)]
#[reflect(Default)]
pub enum NoiseScaling {
/// Noise is scaled to range -1.0..=1.0
#[default]
Normalized,
/// Noise is scaled to range 0.0..=1.0.
///
/// This makes the noise only additive.
Unitized,
/// Noise is normalized and the absolute value is returned.
///
/// Useful to make wavy hills.
Billow,
/// Noise is normalized and the complement to the absolute value is returned.
///
/// Useful to make shapr alpine like ridges.
Ridged
}

#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Reflect, Clone, PartialEq)]
#[reflect(Default)]
Expand All @@ -196,20 +217,29 @@ pub struct TerrainNoiseDetailLayer {
/// Seed for the noise function.
pub seed: u32,
/// Applies domain warping to the noise layer.
pub domain_warp: Vec<DomainWarping>
pub domain_warp: Vec<DomainWarping>,
/// Scaling of the noise.
pub scaling: NoiseScaling
}
impl TerrainNoiseDetailLayer {
/// Sample noise at the x & z coordinates WITHOUT amplitude.
///
/// `noise` is expected to be a Simplex noise initialized with this `TerrainNoiseLayer`'s `seed`.
/// It is not contained within the noise layer to keep the size of a layer smaller.
///
/// The result is normalized.
/// The result is scaled according to `scaling`.
#[inline]
pub fn sample_raw(&self, x: f32, z: f32, noise: &Simplex) -> f32 {
pub fn sample_scaled_raw(&self, x: f32, z: f32, noise: &Simplex) -> f32 {
let (x, z) = self.domain_warp.iter().fold((x, z), |(x, z), warp| warp.warp(x, z, noise));

(noise.get([(x * self.frequency) as f64, (z * self.frequency) as f64]) / 2.0 + 0.5) as f32
let noise = noise.get([(x * self.frequency) as f64, (z * self.frequency) as f64]) as f32;

match &self.scaling {
NoiseScaling::Normalized => noise,
NoiseScaling::Unitized => noise / 2.0 + 0.5,
NoiseScaling::Billow => noise.abs(),
NoiseScaling::Ridged => 1.0 - noise.abs(),
}
}

/// Sample noise at the x & z coordinates.
Expand All @@ -218,30 +248,36 @@ impl TerrainNoiseDetailLayer {
/// It is not contained within the noise layer to keep the size of a layer smaller.
#[inline]
pub fn sample(&self, x: f32, z: f32, noise: &Simplex) -> f32 {
self.sample_raw(x, z, noise) * self.amplitude
self.sample_scaled_raw(x, z, noise) * self.amplitude
}

/// The result is normalized.
#[inline]
fn sample_simd_raw(&self, x: Vec4, z: Vec4, noise: &Simplex) -> Vec4 {
fn sample_simd_scaled_raw(&self, x: Vec4, z: Vec4, noise: &Simplex) -> Vec4 {
let (x, z) = self.domain_warp.iter().fold((x, z), |(x, z), warp| warp.warp_simd(x, z, noise));

let x = x * self.frequency;
let z = z * self.frequency;

Vec4::new(
let noise =Vec4::new(
noise.get([x.x as f64, z.x as f64]) as f32,
noise.get([x.y as f64, z.y as f64]) as f32,
noise.get([x.z as f64, z.z as f64]) as f32,
noise.get([x.w as f64, z.w as f64]) as f32,
) / 2.0
+ Vec4::splat(0.5)
);

match &self.scaling {
NoiseScaling::Normalized => noise,
NoiseScaling::Unitized => noise / 2.0 + Vec4::splat(0.5),
NoiseScaling::Billow => noise.abs(),
NoiseScaling::Ridged => Vec4::ONE - noise.abs(),
}
}

#[inline]
pub fn sample_simd(&self, x: Vec4, z: Vec4, noise: &Simplex) -> Vec4 {
// Step 1: Get the noise values for all 4 positions (x, z)
let noise_values = self.sample_simd_raw(x, z, noise);
let noise_values = self.sample_simd_scaled_raw(x, z, noise);

// Step 2: Multiply by the amplitude
noise_values * Vec4::splat(self.amplitude)
Expand All @@ -253,7 +289,8 @@ impl Default for TerrainNoiseDetailLayer {
amplitude: 1.0,
frequency: 1.0,
seed: 1,
domain_warp: vec![]
domain_warp: vec![],
scaling: NoiseScaling::Normalized
}
}
}
Expand Down Expand Up @@ -303,19 +340,17 @@ impl NoiseFilter {
) -> f32 {
let strength = match &self.condition {
NoiseFilterCondition::Above(threshold) => {
1.0 - ((threshold - noise_value).max(0.0) / self.falloff.max(f32::EPSILON))
.clamp(0.0, 1.0)
1.0 - (threshold - noise_value / self.falloff.max(f32::EPSILON)).clamp(0.0, 1.0)
}
NoiseFilterCondition::Below(threshold) => {
1.0 - ((noise_value - threshold).max(0.0) / self.falloff.max(f32::EPSILON))
.clamp(0.0, 1.0)
1.0 - (noise_value - threshold / self.falloff.max(f32::EPSILON)).clamp(0.0, 1.0)
}
NoiseFilterCondition::Between { min, max } => {
let strength_below = 1.0
- ((min - noise_value).max(0.0) / self.falloff.max(f32::EPSILON))
- ((min - noise_value) / self.falloff.max(f32::EPSILON))
.clamp(0.0, 1.0);
let strength_above = 1.0
- ((noise_value - max).max(0.0) / self.falloff.max(f32::EPSILON))
- ((noise_value - max) / self.falloff.max(f32::EPSILON))
.clamp(0.0, 1.0);

strength_below.min(strength_above)
Expand All @@ -331,23 +366,23 @@ impl NoiseFilter {
let strength = match &self.condition {
NoiseFilterCondition::Above(threshold) => {
Vec4::ONE
- ((Vec4::splat(*threshold) - noise_value).max(Vec4::ZERO)
- ((Vec4::splat(*threshold) - noise_value)
/ self.falloff.max(f32::EPSILON))
.clamp(Vec4::ZERO, Vec4::ONE)
}
NoiseFilterCondition::Below(threshold) => {
Vec4::ONE
- ((noise_value - Vec4::splat(*threshold)).max(Vec4::ZERO)
- ((noise_value - Vec4::splat(*threshold))
/ self.falloff.max(f32::EPSILON))
.clamp(Vec4::ZERO, Vec4::ONE)
}
NoiseFilterCondition::Between { min, max } => {
let strength_below = 1.0
- ((Vec4::splat(*min) - noise_value).max(Vec4::ZERO)
- ((Vec4::splat(*min) - noise_value)
/ self.falloff.max(f32::EPSILON))
.clamp(Vec4::ZERO, Vec4::ONE);
let strength_above = 1.0
- ((noise_value - Vec4::splat(*max)).max(Vec4::ZERO)
- ((noise_value - Vec4::splat(*max))
/ self.falloff.max(f32::EPSILON))
.clamp(Vec4::ZERO, Vec4::ONE);

Expand Down Expand Up @@ -480,7 +515,7 @@ pub(super) fn apply_noise_simd(
.data
.get(*index as usize)
.map(|layer| {
layer.sample_simd_raw(
layer.sample_simd_scaled_raw(
x_translated,
z_translated,
noise_cache.get_by_index(
Expand All @@ -506,7 +541,7 @@ pub(super) fn apply_noise_simd(
.layers
.get(*index as usize)
.map(|layer| {
layer.layer.sample_simd_raw(
layer.layer.sample_simd_scaled_raw(
x_translated,
z_translated,
noise_cache.get_by_index(
Expand Down Expand Up @@ -577,7 +612,7 @@ pub(super) fn apply_noise_simd(
.data
.get(*index as usize)
.map(|layer| {
layer.sample_raw(
layer.sample_scaled_raw(
vertex_position.x,
vertex_position.y,
noise_cache.get_by_index(
Expand All @@ -603,7 +638,7 @@ pub(super) fn apply_noise_simd(
.layers
.get(*index as usize)
.map(|layer| {
layer.layer.sample_raw(
layer.layer.sample_scaled_raw(
vertex_position.x,
vertex_position.y,
noise_cache.get_by_index(
Expand Down

0 comments on commit 648a7d7

Please sign in to comment.