diff --git a/benches/math.rs b/benches/math.rs index e56e548..608f509 100644 --- a/benches/math.rs +++ b/benches/math.rs @@ -32,7 +32,7 @@ fn criterion_benchmark(c: &mut Criterion) { height: 10.0, falloff: 1.0, } - .eval_simd(Vec4::splat(9.5), Vec4::splat(0.0)) + .eval_simd(Vec4::splat(9.5), Vec4::splat(0.0), &[]) })) }); } diff --git a/examples/basic_setup.rs b/examples/basic_setup.rs index fd0fde5..9276074 100644 --- a/examples/basic_setup.rs +++ b/examples/basic_setup.rs @@ -26,7 +26,7 @@ use bevy_world_seed::{ ModifierStrengthLimitProperty, ModifierTileAabb, ShapeModifier, ShapeModifierBundle, TerrainSplineBundle, TerrainSplineCached, TerrainSplineProperties, TerrainSplineShape, }, - noise::{LayerNoiseSettings, LayerOperation, NoiseGroup, NoiseLayer, NoiseScaling, TerrainNoiseSettings}, + noise::{LayerNoiseSettings, LayerOperation, NoiseGroup, NoiseLayer, NoiseScaling, StrengthCombinator, TerrainNoiseSettings}, snap_to_terrain::SnapToTerrain, terrain::Terrain, TerrainPlugin, TerrainSettings, @@ -82,20 +82,22 @@ fn insert_texturing_rules( asset_server: Res, ) { texturing_rules.rules.push(TexturingRule { - evaluator: TexturingRuleEvaluator::AngleGreaterThan { + evaluators: vec![TexturingRuleEvaluator::AngleGreaterThan { angle_radians: 30.0_f32.to_radians(), falloff_radians: 2.5_f32.to_radians(), - }, + }], + evaulator_combinator: StrengthCombinator::Min, texture: asset_server.load("textures/cracked_concrete_diff_1k.dds"), normal_texture: Some(asset_server.load("textures/cracked_concrete_nor_gl_1k.dds")), units_per_texture: 4.0, }); texturing_rules.rules.push(TexturingRule { - evaluator: TexturingRuleEvaluator::AngleLessThan { + evaluators: vec![TexturingRuleEvaluator::AngleLessThan { angle_radians: 30.0_f32.to_radians(), falloff_radians: 2.5_f32.to_radians(), - }, + }], + evaulator_combinator: StrengthCombinator::Min, texture: asset_server.load("textures/brown_mud_leaves.dds"), normal_texture: Some(asset_server.load("textures/brown_mud_leaves_01_nor_gl_2k.dds")), units_per_texture: 4.0, diff --git a/examples/many_tiles.rs b/examples/many_tiles.rs index b1a2e96..e6fef01 100644 --- a/examples/many_tiles.rs +++ b/examples/many_tiles.rs @@ -34,7 +34,7 @@ use bevy_world_seed::{ }, meshing::TerrainMeshRebuildQueue, noise::{ - calc_filter_strength, DomainWarping, FilterCombinator, FilterComparingTo, LayerNoiseSettings, LayerOperation, NoiseCache, NoiseFilter, NoiseFilterCondition, NoiseGroup, NoiseIndexCache, NoiseLayer, NoiseScaling, TerrainNoiseSettings, TerrainNoiseSplineLayer + calc_filter_strength, BiomeSettings, DomainWarping, StrengthCombinator, FilterComparingTo, LayerNoiseSettings, LayerOperation, NoiseCache, NoiseFilter, NoiseFilterCondition, NoiseGroup, NoiseIndexCache, NoiseLayer, NoiseScaling, TerrainNoiseSettings, TerrainNoiseSplineLayer }, terrain::{Terrain, TileToTerrain}, RebuildTile, TerrainHeightRebuildQueue, TerrainPlugin, TerrainSettings, @@ -77,7 +77,7 @@ fn main() { falloff_easing_function: EasingFunction::SmoothStep, compare_to: FilterComparingTo::Spline { index: 0 }, }], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -90,7 +90,7 @@ fn main() { }, }, filters: vec![], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -103,7 +103,7 @@ fn main() { } }, filters: vec![], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -116,7 +116,7 @@ fn main() { } }, filters: vec![], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -129,7 +129,7 @@ fn main() { }, }, filters: vec![], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -147,7 +147,7 @@ fn main() { falloff_easing_function: EasingFunction::SmoothStep, compare_to: FilterComparingTo::Spline { index: 0 }, }], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, NoiseLayer { operation: LayerOperation::Noise { @@ -160,7 +160,7 @@ fn main() { }, }, filters: vec![], - filter_combinator: FilterCombinator::Max + filter_combinator: StrengthCombinator::Max }, ], filters: vec![NoiseFilter { @@ -169,7 +169,7 @@ fn main() { falloff_easing_function: EasingFunction::SmoothStep, compare_to: FilterComparingTo::Spline { index: 0 }, }], - filter_combinator: FilterCombinator::Min + filter_combinator: StrengthCombinator::Min } ], data: vec![ @@ -195,6 +195,29 @@ fn main() { scaling: NoiseScaling::Unitized }, ], + biome: vec![ + BiomeSettings { + filters: vec![NoiseFilter { + condition: NoiseFilterCondition::Above(0.5), + falloff: 0.25, + falloff_easing_function: EasingFunction::SmoothStep, + compare_to: FilterComparingTo::Data { index: 0 } + }, + NoiseFilter { + condition: NoiseFilterCondition::Above(0.5), + falloff: 0.25, + falloff_easing_function: EasingFunction::SmoothStep, + compare_to: FilterComparingTo::Data { index: 1 } + }, + NoiseFilter { + condition: NoiseFilterCondition::Above(0.5), + falloff: 0.25, + falloff_easing_function: EasingFunction::SmoothStep, + compare_to: FilterComparingTo::Data { index: 2 } + }], + filter_combinator: StrengthCombinator::Min + } + ], ..default() }), terrain_settings: TerrainSettings { @@ -238,7 +261,7 @@ fn insert_rules( seed: 5, domain_warp: vec![], filters: vec![], - filter_combinator: FilterCombinator::Min + filter_combinator: StrengthCombinator::Min }, TerrainNoiseSplineLayer { amplitude_curve: peaks_and_valleys.clone(), @@ -265,7 +288,7 @@ fn insert_rules( compare_to: FilterComparingTo::Data { index: 0 } } ], - filter_combinator: FilterCombinator::Min + filter_combinator: StrengthCombinator::Min }, ]); @@ -290,19 +313,21 @@ fn insert_rules( texturing_rules.rules.extend([ TexturingRule { - evaluator: TexturingRuleEvaluator::AngleGreaterThan { + evaluators: vec![TexturingRuleEvaluator::AngleGreaterThan { angle_radians: 40.0_f32.to_radians(), falloff_radians: 2.5_f32.to_radians(), - }, + }], + evaulator_combinator: StrengthCombinator::Min, texture: asset_server.load("textures/cracked_concrete_diff_1k.dds"), normal_texture: Some(asset_server.load("textures/cracked_concrete_nor_gl_1k.dds")), units_per_texture: 4.0, }, TexturingRule { - evaluator: TexturingRuleEvaluator::AngleLessThan { + evaluators: vec![TexturingRuleEvaluator::AngleLessThan { angle_radians: 40.0_f32.to_radians(), falloff_radians: 2.5_f32.to_radians(), - }, + }], + evaulator_combinator: StrengthCombinator::Min, texture: asset_server.load("textures/brown_mud_leaves.dds"), normal_texture: Some(asset_server.load("textures/brown_mud_leaves_01_nor_gl_2k.dds")), units_per_texture: 4.0, @@ -478,7 +503,8 @@ impl EditorWindow for NoiseDebugWindow { noise_index_cache, translation.xz(), lookup_curves, - &data + &data, + &[] ); ui.heading(format!("Height: {height}")); @@ -504,12 +530,13 @@ impl EditorWindow for NoiseDebugWindow { noise_settings, noise_cache, &data, + &[], &noise_index_cache.spline_index_cache, cached_noise, lookup_curves, ); - let strength = calc_filter_strength(translation.xz(), &spline.filters, spline.filter_combinator, noise_settings, noise_cache, &data, &noise_index_cache.spline_index_cache); + let strength = calc_filter_strength(translation.xz(), &spline.filters, spline.filter_combinator, noise_settings, noise_cache, &data, &[], &noise_index_cache.spline_index_cache); if let Some(lookup_curve) = lookup_curves.get(&spline.amplitude_curve) { ui.label(format!( @@ -528,17 +555,18 @@ impl EditorWindow for NoiseDebugWindow { noise_settings, noise_cache, &data, + &[], &noise_index_cache.spline_index_cache, group_noises, translation.xz() ); - let strength = calc_filter_strength(translation.xz(), &group.filters, group.filter_combinator, noise_settings, noise_cache, &data, &noise_index_cache.spline_index_cache); + let strength = calc_filter_strength(translation.xz(), &group.filters, group.filter_combinator, noise_settings, noise_cache, &data, &[], &noise_index_cache.spline_index_cache); egui::CollapsingHeader::new(format!("GROUP {i}: {noise:.3} ({strength:.3})")).id_source(format!("group_{i}")).show(ui, |ui| { unsafe { for (i,layer) in group.layers.iter().enumerate() { - let strength = calc_filter_strength(translation.xz(), &layer.filters, layer.filter_combinator, noise_settings, noise_cache, &data, &noise_index_cache.spline_index_cache); + let strength = calc_filter_strength(translation.xz(), &layer.filters, layer.filter_combinator, noise_settings, noise_cache, &data, &[], &noise_index_cache.spline_index_cache); match &layer.operation { LayerOperation::Noise { noise } => { diff --git a/src/lib.rs b/src/lib.rs index e772297..a711fb5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ use modifiers::{ ShapeModifier, TerrainSplineCached, TerrainSplineProperties, TerrainSplineShape, TileToModifierMapping, }; -use noise::{apply_noise_simd, LayerNoiseSettings, NoiseCache, NoiseIndexCache, TerrainNoiseSettings}; +use noise::{apply_noise_simd, LayerNoiseSettings, NoiseCache, NoiseIndexCache, TerrainNoiseSettings, TileBiomes}; use snap_to_terrain::TerrainSnapToTerrainPlugin; use terrain::{insert_components, update_tiling, Holes, Terrain, TileToTerrain}; use utils::{distance_squared_to_line_segment, index_to_x_z}; @@ -127,7 +127,9 @@ impl Plugin for TerrainPlugin { app.init_resource::() .init_resource::() .init_resource::() - .init_resource::() + .init_resource::(); + + app .register_type::() .register_type::() .register_type::() @@ -147,7 +149,8 @@ impl Plugin for TerrainPlugin { { app.register_type::() - .register_type::(); + .register_type::() + .register_type::(); } app.init_resource::(); @@ -234,7 +237,7 @@ fn update_terrain_heights( Option<&ModifierFalloffProperty>, Option<&ModifierStrengthLimitProperty>, )>, - mut heights: Query<(&mut Heights, &mut Holes)>, + mut terrain_tile_query: Query<(&mut Heights, &mut Holes, &mut TileBiomes)>, terrain_settings: Res, tile_to_modifier: Res, tile_to_terrain: Res, @@ -290,8 +293,8 @@ fn update_terrain_heights( let shape_modifiers = tile_to_modifier.shape.get(&tile); let splines = tile_to_modifier.splines.get(&tile); - let mut iter = heights.iter_many_mut(tiles.iter()); - while let Some((mut heights, mut holes)) = iter.fetch_next() { + let mut iter = terrain_tile_query.iter_many_mut(tiles.iter()); + while let Some((mut heights, mut holes, mut biomes)) = iter.fetch_next() { // Clear heights. heights.0.fill(0.0); holes.0.clear(); @@ -299,7 +302,7 @@ fn update_terrain_heights( // First, set by noise. if let Some(terrain_noise_layers) = terrain_noise_settings.as_ref() { let _span = info_span!("Apply noise").entered(); - apply_noise_simd( + *biomes = apply_noise_simd( &mut heights.0, &terrain_settings, terrain_translation, diff --git a/src/material.rs b/src/material.rs index 8137568..1b67e50 100644 --- a/src/material.rs +++ b/src/material.rs @@ -5,7 +5,7 @@ use bevy_asset::{load_internal_asset, Asset, AssetApp, Assets, Handle}; use bevy_log::{info, info_span}; use bevy_math::{IVec2, UVec4, Vec2, Vec3, Vec3Swizzles, Vec4}; use bevy_pbr::{ExtendedMaterial, MaterialExtension, MaterialPlugin, StandardMaterial}; -use bevy_ecs::prelude::{Commands, Component, Entity, EventReader, Query, Res, ResMut, Resource, With, Without, IntoSystemConfigs, ReflectComponent, ReflectResource}; +use bevy_ecs::prelude::{Commands, Component, Entity, EventReader, Query, Res, ResMut, Resource, With, Without, IntoSystemConfigs, ReflectComponent, ReflectResource, Local, DetectChanges}; use bevy_render::{ primitives::Aabb, render_asset::RenderAssetUsages, @@ -17,16 +17,10 @@ use bevy_transform::prelude::GlobalTransform; use bevy_reflect::{Reflect, prelude::ReflectDefault}; use crate::{ - distance_squared_to_line_segment, - easing::EasingFunction, - meshing::TerrainMeshRebuilt, - modifiers::{ + distance_squared_to_line_segment, easing::EasingFunction, meshing::TerrainMeshRebuilt, modifiers::{ ModifierFalloffProperty, ShapeModifier, TerrainSplineCached, TerrainSplineProperties, TileToModifierMapping, - }, - terrain::{Terrain, TileToTerrain}, - utils::{get_height_at_position_in_quad, get_normal_at_position_in_quad, index_to_x_z, index_to_x_z_simd}, - Heights, TerrainSets, TerrainSettings, + }, noise::{NoiseCache, NoiseIndexCache, StrengthCombinator, TerrainNoiseSettings, TileBiomes}, terrain::{Terrain, TileToTerrain}, utils::{get_height_at_position_in_quad, get_normal_at_position_in_quad, index_to_x_z, index_to_x_z_simd}, Heights, TerrainSets, TerrainSettings }; pub const TERRAIN_SHADER_HANDLE: Handle = @@ -171,9 +165,27 @@ pub enum TexturingRuleEvaluator { /// Texture strength will linearly reduce for this many radians. falloff_radians: f32, }, + /// Applies a texture when in the biome. + /// + /// Using this rule will cause noise sampling when generating. This may slow down updating textures. + InBiome { + biome: u32 + } } impl TexturingRuleEvaluator { - pub fn can_apply_to_tile(&self, min: f32, max: f32) -> bool { + pub fn needs_noise(&self) -> bool { + match self { + TexturingRuleEvaluator::Above { .. } + | TexturingRuleEvaluator::Below { .. } + | TexturingRuleEvaluator::Between { .. } + | TexturingRuleEvaluator::AngleGreaterThan { .. } + | TexturingRuleEvaluator::AngleLessThan { .. } + | TexturingRuleEvaluator::AngleBetween { .. } => false, + TexturingRuleEvaluator::InBiome { .. } => true, + } + } + + pub fn can_apply_to_tile(&self, min: f32, max: f32, tile_biomes: &TileBiomes) -> bool { match self { TexturingRuleEvaluator::Above { height, falloff } => { max >= (*height - *falloff) @@ -188,10 +200,14 @@ impl TexturingRuleEvaluator { TexturingRuleEvaluator::AngleGreaterThan { .. } => true, TexturingRuleEvaluator::AngleLessThan { .. } => true, TexturingRuleEvaluator::AngleBetween { .. } => true, + TexturingRuleEvaluator::InBiome { biome } => { + // Anything below 1 / 255 means it's rounded to 0. + tile_biomes.0.get(*biome as usize).is_some_and(|val| *val >= (1.0 / 255.0)) + }, } } - pub fn eval_simd(&self, height_at_position: Vec4, angle_at_position: Vec4) -> Vec4 { + pub fn eval_simd(&self, height_at_position: Vec4, angle_at_position: Vec4, biomes: &[Vec4]) -> Vec4 { match self { TexturingRuleEvaluator::Above { height, falloff } => { Vec4::ONE - ((Vec4::splat(*height) - height_at_position).max(Vec4::ZERO) / falloff.max(f32::EPSILON)) @@ -244,13 +260,18 @@ impl TexturingRuleEvaluator { strength_below.min(strength_above) } + TexturingRuleEvaluator::InBiome { biome } => { + biomes.get(*biome as usize).cloned().unwrap_or(Vec4::ZERO) + }, } } } #[derive(Reflect)] pub struct TexturingRule { - pub evaluator: TexturingRuleEvaluator, + pub evaluators: Vec, + pub evaulator_combinator: StrengthCombinator, + /// The texture to apply with this rule. pub texture: Handle, pub normal_texture: Option>, @@ -267,7 +288,6 @@ pub struct GlobalTexturingRules { pub rules: Vec, } -// This struct defines the data that will be passed to your shader #[derive(Asset, AsBindGroup, Default, Debug, Clone, Reflect)] #[reflect(Default, Debug)] pub(super) struct TerrainMaterial { @@ -473,8 +493,12 @@ fn update_terrain_texture_maps( &Handle, &Terrain, &Aabb, + &TileBiomes )>, - texture_settings: Res, + texturing_settings: ( + Res, + Res, + ), terrain_settings: Res, tile_to_modifier: Res, tile_to_terrain: Res, @@ -483,8 +507,19 @@ fn update_terrain_texture_maps( mut materials: ResMut>, mut images: ResMut>, meshes: Res>, - texturing_rules: Res, + noise_resources: ( + Res, + Res, + Res + ), + mut needs_noise: Local, + mut data_samples: Local>, + mut biome_samples: Local> ) { + if texturing_settings.1.is_changed() { + *needs_noise = texturing_settings.1.rules.iter().any(|rule| rule.evaluators.iter().any(|evaulator| evaulator.needs_noise())); + } + for TerrainMeshRebuilt(tile) in event_reader.read() { if !tile_generate_queue.0.contains(tile) { tile_generate_queue.0.push(*tile); @@ -496,14 +531,14 @@ fn update_terrain_texture_maps( } let tile_size = terrain_settings.tile_size(); - let resolution = texture_settings.resolution(); + let resolution = texturing_settings.0.resolution(); let scale = tile_size / resolution as f32; let vertex_scale = (terrain_settings.edge_points - 1) as f32 / resolution as f32; let inv_tile_size_scale = scale * (7.0 / tile_size); let tiles_to_generate = tile_generate_queue .count() - .min(texture_settings.max_tile_updates_per_frame.get() as usize); + .min(texturing_settings.0.max_tile_updates_per_frame.get() as usize); tiles_query .iter_many( @@ -513,7 +548,7 @@ fn update_terrain_texture_maps( .filter_map(|tile| tile_to_terrain.0.get(&tile)) .flatten(), ) - .for_each(|(heights, material, mesh, terrain_coordinate, aabb)| { + .for_each(|(heights, material, mesh, terrain_coordinate, aabb, tile_biomes)| { let Some(material) = materials.get_mut(material) else { return; }; @@ -530,7 +565,7 @@ fn update_terrain_texture_maps( let terrain_translation = (terrain_coordinate.0 << terrain_settings.tile_size_power.get()).as_vec2(); - if !texturing_rules.rules.is_empty() { + if !texturing_settings.1.rules.is_empty() { let _span = info_span!("Apply global texturing rules.").entered(); let normals = mesh @@ -539,11 +574,15 @@ fn update_terrain_texture_maps( .as_float3() .unwrap(); - let min = aabb.center.y - aabb.half_extents.y; - let max = aabb.center.y + aabb.half_extents.y; + let min = aabb.center.y - aabb.half_extents.y; + let max = aabb.center.y + aabb.half_extents.y; - for rule in texturing_rules.rules.iter() { - if !rule.evaluator.can_apply_to_tile(min, max) { + for rule in texturing_settings.1.rules.iter() { + + if ( + matches!(rule.evaulator_combinator, StrengthCombinator::Min | StrengthCombinator::Multiply) && !rule.evaluators.iter().all(|evaluator| evaluator.can_apply_to_tile(min, max, tile_biomes)) + || !rule.evaluators.iter().any(|evaluator| evaluator.can_apply_to_tile(min, max, tile_biomes)) + ) { continue; } @@ -554,8 +593,11 @@ fn update_terrain_texture_maps( tile_size, ) else { info!("Hit max texture channels."); - return; + continue; }; + + let needs_noise = *needs_noise && rule.evaluators.iter().any(|evaluator| evaluator.needs_noise()); + for (i, val) in texture.data.chunks_exact_mut(16).enumerate() { let true_i = (i * 4) as u32; let (x, z) = index_to_x_z_simd(UVec4::new(true_i, true_i + 1, true_i + 2, true_i + 3), resolution); @@ -574,7 +616,16 @@ fn update_terrain_texture_maps( let local_x = x_f.fract(); let local_z = z_f.fract(); - // TODO: We are doing this redundantly for each rule, where a single rule can only use one of these. + if needs_noise { + let world_x = x_f + Vec4::splat(terrain_translation.x); + let world_z = z_f + Vec4::splat(terrain_translation.y); + + data_samples.clear(); + noise_resources.0.sample_data_simd(&noise_resources.1, &noise_resources.2, world_x, world_z, &mut data_samples); + + biome_samples.clear(); + noise_resources.0.sample_biomes_simd(&noise_resources.1, &noise_resources.2, world_x, world_z, &data_samples, &mut biome_samples); + } // Skip the bounds checks. let height_at_position = unsafe { @@ -652,7 +703,17 @@ fn update_terrain_texture_maps( ) }; - let strength = rule.evaluator.eval_simd(height_at_position, normal_angle); + let strength = if let Some(evaluator) = rule.evaluators.first() { + let initial_strength = evaluator.eval_simd(height_at_position, normal_angle, &biome_samples); + + rule.evaluators.iter().skip(1).fold(initial_strength, |acc, filter| { + let eval_sample = filter.eval_simd(height_at_position, normal_angle, &biome_samples); + + rule.evaulator_combinator.combine_simd(acc, eval_sample) + }) + } else { + Vec4::ONE + }; // Apply texture. apply_texture(&mut val[0..4], texture_channel, strength.x); diff --git a/src/noise.rs b/src/noise.rs index 309ae15..deab4c0 100644 --- a/src/noise.rs +++ b/src/noise.rs @@ -4,7 +4,7 @@ use bevy_log::info; use ::noise::{NoiseFn, Simplex}; use bevy_asset::{Assets, Handle}; use bevy_math::{UVec4, Vec2, Vec4}; -use bevy_ecs::prelude::{ReflectResource, Resource}; +use bevy_ecs::prelude::{ReflectResource, Resource, Component, ReflectComponent}; use bevy_reflect::{prelude::ReflectDefault, Reflect}; use bevy_lookup_curve::LookupCurve; @@ -121,7 +121,7 @@ pub struct TerrainNoiseSplineLayer { pub domain_warp: Vec, pub filters: Vec, - pub filter_combinator: FilterCombinator + pub filter_combinator: StrengthCombinator } impl TerrainNoiseSplineLayer { /// Sample noise at the x & z coordinates WITHOUT amplitude curve. @@ -152,12 +152,13 @@ impl TerrainNoiseSplineLayer { noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[f32], + biome_values: &[f32], spline_noise_cache: &[u32], noise: &Simplex, lookup_curves: &Assets, ) -> f32 { if let Some(curve) = lookup_curves.get(&self.amplitude_curve) { - let strength = calc_filter_strength(Vec2::new(x, z), &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let strength = calc_filter_strength(Vec2::new(x, z), &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); curve.lookup(self.sample_raw(x, z, noise)) * strength } else { @@ -195,13 +196,14 @@ impl TerrainNoiseSplineLayer { noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[Vec4], + biome_values: &[Vec4], spline_noise_cache: &[u32], noise: &Simplex, lookup_curves: &Assets, ) -> Vec4 { // Fetch the lookup curve and apply it to all 4 noise values if let Some(curve) = lookup_curves.get(&self.amplitude_curve) { - let strength = calc_filter_strength_simd(x, z, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let strength = calc_filter_strength_simd(x, z, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); let normalized_noise = self.sample_simd_raw(x, z, noise); @@ -406,16 +408,21 @@ pub struct NoiseLayer { pub operation: LayerOperation, pub filters: Vec, - pub filter_combinator: FilterCombinator + pub filter_combinator: StrengthCombinator } +#[derive(Component, Reflect, Default, Clone, PartialEq)] +#[reflect(Component)] +pub struct TileBiomes(pub Vec); + pub fn calc_filter_strength( pos: Vec2, filters: &[NoiseFilter], - combinator: FilterCombinator, + combinator: StrengthCombinator, noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[f32], + biome_values: &[f32], spline_noise_cache: &[u32] ) -> f32 { if let Some(initial_filter) = filters.first() { @@ -435,25 +442,23 @@ pub fn calc_filter_strength( noise_cache.get_by_index(spline_noise_cache[*index as usize] as usize), ) }), - FilterComparingTo::Biome { index } => noise_settings - .biome - .get(*index as usize) - .map_or(0.0, |biome| { - calc_filter_strength(pos, &biome.filters, biome.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache) - }), + FilterComparingTo::Biome { index } => { + biome_values.get(*index as usize).cloned().unwrap_or_else(|| + noise_settings + .biome + .get(*index as usize) + .map_or(0.0, |biome| { + calc_filter_strength(pos, &biome.filters, biome.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache) + }) + ) + } }; let initial_filter_strength = initial_filter.get_filter(sample_filter(initial_filter)); let calculated_filter_strength = filters.iter().skip(1).fold(initial_filter_strength, |acc, filter| { let filter_sample = filter.get_filter(sample_filter(filter)); - match combinator { - FilterCombinator::Max => acc.max(filter_sample), - FilterCombinator::Min => acc.min(filter_sample), - FilterCombinator::Multiply => acc * filter_sample, - FilterCombinator::Sum => (acc + filter_sample).min(1.0), - FilterCombinator::SumUncapped => acc + filter_sample, - } + combinator.combine(acc, filter_sample) }); calculated_filter_strength @@ -467,10 +472,11 @@ fn calc_filter_strength_simd( x: Vec4, z: Vec4, filters: &[NoiseFilter], - combinator: FilterCombinator, + combinator: StrengthCombinator, noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[Vec4], + biome_values: &[Vec4], spline_noise_cache: &[u32] ) -> Vec4 { if let Some(initial_filter) = filters.first() { @@ -490,25 +496,21 @@ fn calc_filter_strength_simd( noise_cache.get_by_index(spline_noise_cache[*index as usize] as usize), ) }), - FilterComparingTo::Biome { index } => noise_settings - .biome - .get(*index as usize) - .map_or(Vec4::ZERO, |biome| { - calc_filter_strength_simd(x, z, &biome.filters, biome.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache) - }), + FilterComparingTo::Biome { index } => biome_values.get(*index as usize).cloned().unwrap_or_else(|| + noise_settings + .biome + .get(*index as usize) + .map_or(Vec4::ZERO, |biome| { + calc_filter_strength_simd(x, z, &biome.filters, biome.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache) + }) + ) }; let initial_filter_strength = initial_filter.get_filter_simd(sample_filter(initial_filter)); let calculated_filter_strength = filters.iter().skip(1).fold(initial_filter_strength, |acc, filter| { let filter_sample = filter.get_filter_simd(sample_filter(filter)); - match combinator { - FilterCombinator::Max => acc.max(filter_sample), - FilterCombinator::Min => acc.min(filter_sample), - FilterCombinator::Multiply => acc * filter_sample, - FilterCombinator::Sum => (acc + filter_sample).min(Vec4::ZERO), - FilterCombinator::SumUncapped => acc + filter_sample, - } + combinator.combine_simd(acc, filter_sample) }); calculated_filter_strength @@ -531,7 +533,7 @@ pub struct NoiseGroup { /// Filter this detail layer to only apply during certain conditions. pub filters: Vec, - pub filter_combinator: FilterCombinator + pub filter_combinator: StrengthCombinator } impl NoiseGroup { pub fn sample( @@ -539,18 +541,19 @@ impl NoiseGroup { noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[f32], + biome_values: &[f32], spline_noise_cache: &[u32], layer_noise_cache: &[u32], pos: Vec2, ) -> f32 { - let group_strength = calc_filter_strength(pos, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let group_strength = calc_filter_strength(pos, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); if group_strength <= f32::EPSILON { return 0.0; } unsafe { let group_height = self.layers.iter().enumerate().fold(0.0, |acc, (i, layer)| { - let layer_strength = calc_filter_strength(pos, &layer.filters, layer.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let layer_strength = calc_filter_strength(pos, &layer.filters, layer.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); if layer_strength > f32::EPSILON { let layer_value = match &layer.operation { @@ -581,19 +584,20 @@ impl NoiseGroup { noise_settings: &TerrainNoiseSettings, noise_cache: &NoiseCache, data_noise_values: &[Vec4], + biome_values: &[Vec4], spline_noise_cache: &[u32], layer_noise_cache: &[u32], x: Vec4, z: Vec4 ) -> Vec4 { - let group_strength = calc_filter_strength_simd(x, z, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let group_strength = calc_filter_strength_simd(x, z, &self.filters, self.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); if group_strength.cmple(Vec4::splat(f32::EPSILON)).all() { return Vec4::ZERO; } unsafe { let group_height = self.layers.iter().enumerate().fold(Vec4::ZERO, |acc, (i, layer)| { - let layer_strength = calc_filter_strength_simd(x, z, &layer.filters, layer.filter_combinator, noise_settings, noise_cache, data_noise_values, spline_noise_cache); + let layer_strength = calc_filter_strength_simd(x, z, &layer.filters, layer.filter_combinator, noise_settings, noise_cache, data_noise_values, biome_values, spline_noise_cache); if layer_strength.cmpge(Vec4::splat(f32::EPSILON)).any() { let layer_value = match &layer.operation { @@ -716,7 +720,7 @@ impl NoiseFilter { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Reflect, Default, Clone, Copy, PartialEq, Debug)] #[reflect(Default)] -pub enum FilterCombinator { +pub enum StrengthCombinator { #[default] Max, Min, @@ -724,6 +728,29 @@ pub enum FilterCombinator { Sum, SumUncapped } +impl StrengthCombinator { + #[inline] + pub fn combine(&self, a: f32, b: f32) -> f32 { + match self { + StrengthCombinator::Max => a.max(b), + StrengthCombinator::Min => a.min(b), + StrengthCombinator::Multiply => a * b, + StrengthCombinator::Sum => (a + b).min(1.0), + StrengthCombinator::SumUncapped => a + b, + } + } + + #[inline] + pub fn combine_simd(&self, a: Vec4, b: Vec4) -> Vec4 { + match self { + StrengthCombinator::Max => a.max(b), + StrengthCombinator::Min => a.min(b), + StrengthCombinator::Multiply => a * b, + StrengthCombinator::Sum => (a + b).min(Vec4::ONE), + StrengthCombinator::SumUncapped => a + b, + } + } +} #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Reflect, Clone, PartialEq, Debug)] @@ -748,7 +775,7 @@ impl Default for NoiseFilterCondition { #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct BiomeSettings { pub filters: Vec, - pub filter_combinator: FilterCombinator + pub filter_combinator: StrengthCombinator // Biome flags? } @@ -810,7 +837,21 @@ impl TerrainNoiseSettings { ) { biomes.extend( self.biome.iter() - .map(|biome| calc_filter_strength(pos, &biome.filters, biome.filter_combinator, self, noise_cache, data_noise_values, &noise_index_cache.data_index_cache)) + .map(|biome| calc_filter_strength(pos, &biome.filters, biome.filter_combinator, self, noise_cache, data_noise_values, &[], &noise_index_cache.data_index_cache)) + ); + } + pub fn sample_biomes_simd( + &self, + noise_cache: &NoiseCache, + noise_index_cache: &NoiseIndexCache, + x: Vec4, + z: Vec4, + data_noise_values: &[Vec4], + biomes: &mut Vec + ) { + biomes.extend( + self.biome.iter() + .map(|biome| calc_filter_strength_simd(x, z, &biome.filters, biome.filter_combinator, self, noise_cache, data_noise_values, &[], &noise_index_cache.data_index_cache)) ); } @@ -823,7 +864,8 @@ impl TerrainNoiseSettings { noise_index_cache: &NoiseIndexCache, pos: Vec2, lookup_curves: &Assets, - data_noise_values: &[f32] + data_noise_values: &[f32], + biome_values: &[f32] ) -> f32 { unsafe { let spline_height = self.splines.iter().enumerate().fold(0.0, |acc, (i, layer)| { @@ -833,6 +875,7 @@ impl TerrainNoiseSettings { self, noise_cache, data_noise_values, + biome_values, &noise_index_cache.spline_index_cache, noise_cache.get_by_index(noise_index_cache.spline_index_cache[i] as usize), lookup_curves, @@ -840,7 +883,7 @@ impl TerrainNoiseSettings { }); let layer_height = self.noise_groups.iter().enumerate().fold(0.0, |acc, (i, group)| { - acc + group.sample(self, noise_cache, data_noise_values, &noise_index_cache.spline_index_cache, &noise_index_cache.group_index_cache[noise_index_cache.group_offset_cache[i] as usize..], pos) + acc + group.sample(self, noise_cache, data_noise_values, biome_values, &noise_index_cache.spline_index_cache, &noise_index_cache.group_index_cache[noise_index_cache.group_offset_cache[i] as usize..], pos) }); spline_height + layer_height @@ -854,7 +897,8 @@ impl TerrainNoiseSettings { lookup_curves: &Assets, x: Vec4, z: Vec4, - data_cached: &[Vec4] + data_cached: &[Vec4], + biome_values: &[Vec4], ) -> Vec4 { unsafe { let spline_height = self.splines.iter().enumerate().fold(Vec4::ZERO, |acc, (i, layer)| { @@ -864,6 +908,7 @@ impl TerrainNoiseSettings { self, noise_cache, data_cached, + biome_values, &noise_index_cache.spline_index_cache, noise_cache.get_by_index(noise_index_cache.spline_index_cache[i] as usize), lookup_curves, @@ -871,7 +916,7 @@ impl TerrainNoiseSettings { }); let layer_height = self.noise_groups.iter().enumerate().fold(Vec4::ZERO, |acc, (i, group)| { - acc + group.sample_simd(self, noise_cache, data_cached, &noise_index_cache.spline_index_cache, &noise_index_cache.group_index_cache[noise_index_cache.group_offset_cache[i] as usize..], x, z) + acc + group.sample_simd(self, noise_cache, data_cached, biome_values, &noise_index_cache.spline_index_cache, &noise_index_cache.group_index_cache[noise_index_cache.group_offset_cache[i] as usize..], x, z) }); spline_height + layer_height @@ -893,7 +938,7 @@ pub(super) fn apply_noise_simd( noise_index_cache: &NoiseIndexCache, lookup_curves: &Assets, terrain_noise_layers: &TerrainNoiseSettings, -) { +) -> TileBiomes { let edge_points = terrain_settings.edge_points as usize; let length = heights.len(); @@ -902,7 +947,10 @@ pub(super) fn apply_noise_simd( #[cfg(feature = "count_samples")] SAMPLES.set(0); + let mut tile_biomes = TileBiomes(vec![0.0; terrain_noise_layers.biome.len()]); + let mut data_simd = Vec::with_capacity(terrain_noise_layers.data.len()); + let mut biomes_simd = Vec::with_capacity(terrain_noise_layers.biome.len()); // Process in chunks of 4 for i in (0..simd_len).step_by(4) { @@ -923,7 +971,16 @@ pub(super) fn apply_noise_simd( data_simd.clear(); terrain_noise_layers.sample_data_simd(noise_cache, noise_index_cache, x_translated, z_translated, &mut data_simd); - let final_heights = terrain_noise_layers.sample_position_simd(noise_cache, noise_index_cache, lookup_curves, x_translated, z_translated, &data_simd); + biomes_simd.clear(); + terrain_noise_layers.sample_biomes_simd(noise_cache, noise_index_cache, x_translated, z_translated, &data_simd, &mut biomes_simd); + for (i, biome) in biomes_simd.iter().enumerate() { + let max_biome_strength = &mut tile_biomes.0[i]; + + *max_biome_strength = max_biome_strength.max(biome.max_element()); + } + + + let final_heights = terrain_noise_layers.sample_position_simd(noise_cache, noise_index_cache, lookup_curves, x_translated, z_translated, &data_simd, &biomes_simd); // Store the results back into the heights array heights[i] = final_heights.x; @@ -934,6 +991,7 @@ pub(super) fn apply_noise_simd( let mut data = Vec::with_capacity(terrain_noise_layers.data.len()); + let mut biomes = Vec::with_capacity(terrain_noise_layers.biome.len()); // Process any remaining heights that aren't divisible by 4 for (i, height) in heights.iter_mut().enumerate().skip(simd_len) { @@ -943,9 +1001,19 @@ pub(super) fn apply_noise_simd( data.clear(); terrain_noise_layers.sample_data(noise_cache, noise_index_cache, vertex_position, &mut data); - *height = terrain_noise_layers.sample_position(noise_cache, noise_index_cache, vertex_position, lookup_curves, &data); + biomes.clear(); + terrain_noise_layers.sample_biomes(noise_cache, noise_index_cache, vertex_position, &data, &mut biomes); + for (i, biome) in biomes.iter().enumerate() { + let max_biome_strength = &mut tile_biomes.0[i]; + + *max_biome_strength = max_biome_strength.max(*biome); + } + + *height = terrain_noise_layers.sample_position(noise_cache, noise_index_cache, vertex_position, lookup_curves, &data, &biomes); } #[cfg(feature = "count_samples")] info!("Average samples: {}", SAMPLES.get() / length); + + tile_biomes } diff --git a/src/terrain.rs b/src/terrain.rs index c98c8ad..765061d 100644 --- a/src/terrain.rs +++ b/src/terrain.rs @@ -10,7 +10,7 @@ use bevy_utils::HashMap; use fixedbitset::FixedBitSet; use crate::{ - feature_placement::SpawnedFeatures, utils::index_to_x_z, Heights, RebuildTile, TerrainSettings, + feature_placement::SpawnedFeatures, noise::TileBiomes, utils::index_to_x_z, Heights, RebuildTile, TerrainSettings }; /// Bitset marking which points are holes. @@ -117,6 +117,7 @@ pub(super) fn insert_components( Heights(vec![0.0; heights].into_boxed_slice()), Holes(FixedBitSet::with_capacity(heights)), SpawnedFeatures::default(), + TileBiomes::default() )); }); }