Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using System.Collections.Generic;
- using System.Linq;
- using Unity.Burst;
- using Unity.Collections;
- using Unity.Jobs;
- using Unity.Mathematics;
- using UnityEngine;
- using static GranDucato.LandGenerator.BiomeConfig;
- using static GranDucato.LandGenerator.POIPlanner;
- namespace GranDucato.LandGenerator
- {
- /// <summary>
- /// Data container for all maps and parameters used during biome selection and terrain painting.
- /// Manages both managed and native arrays for performance with the C# Job System.
- /// </summary>
- public class BiomeSelectionData
- {
- public float2 WorldSize { get; private set; }
- public float MaxAltitude { get; private set; }
- public int AlphaMapResolution { get; private set; }
- public int AlphaMapLayerCount { get; private set; }
- public Terrain[,] TerrainGrid { get; private set; }
- // Managed data for easy access from main thread
- public float[,] HeightMap { get; private set; }
- public int[,] BiomeMap { get; private set; }
- public float[,] SlopeMap { get; private set; }
- public float[,] TemperatureMap { get; set; }
- public float[,] HumidityMap { get; set; }
- public float[,] AlphaMap { get; set; }
- // Native data for jobs
- private NativeArray<int> biomeMapNative;
- private NativeArray<float> heightMapNative;
- private NativeArray<float> slopeMapNative;
- private NativeArray<float> alphaMapNative;
- /// <summary>
- /// Creates a data container and prepares native arrays from final, managed map data.
- /// </summary>
- public BiomeSelectionData(Terrain[,] grid, int[,] bMap, float[,] hMap, float[,] sMap, NativeArray<float> aMapNative, float maxAlt, float2 worldSize, int alphaMapRes, int alphaMapLayers)
- {
- this.TerrainGrid = grid;
- this.WorldSize = worldSize;
- this.MaxAltitude = maxAlt;
- this.AlphaMapResolution = alphaMapRes;
- this.AlphaMapLayerCount = alphaMapLayers;
- this.BiomeMap = bMap;
- this.HeightMap = hMap;
- this.SlopeMap = sMap;
- // Converti gli altri array
- biomeMapNative = new NativeArray<int>(To1DArray(bMap), Allocator.Persistent);
- heightMapNative = new NativeArray<float>(To1DArray(hMap), Allocator.Persistent);
- slopeMapNative = new NativeArray<float>(To1DArray(sMap), Allocator.Persistent);
- // Assegna direttamente la NativeArray dell'alphamap
- this.alphaMapNative = aMapNative;
- }
- public NativeArray<int>.ReadOnly GetBiomeMapNative_ReadOnly() => biomeMapNative.AsReadOnly();
- public NativeArray<float>.ReadOnly GetHeightMapNative_ReadOnly() => heightMapNative.AsReadOnly();
- public NativeArray<float>.ReadOnly GetSlopeMapNative_ReadOnly() => slopeMapNative.AsReadOnly();
- public NativeArray<float>.ReadOnly GetAlphaMapNative_ReadOnly() => alphaMapNative.AsReadOnly();
- /// <summary>
- /// Updates the managed HeightMap and rebuilds the underlying NativeArray for job system consistency.
- /// </summary>
- public void UpdateHeightMap(float[,] newHeightMap)
- {
- HeightMap = newHeightMap;
- // Re-create the native array to match the new managed data
- if (heightMapNative.IsCreated) heightMapNative.Dispose();
- heightMapNative = new NativeArray<float>(To1DArray(newHeightMap), Allocator.Persistent);
- }
- /// <summary>
- /// Updates the managed SlopeMap and rebuilds the underlying NativeArray for job system consistency.
- /// </summary>
- public void UpdateSlopeMap(float[,] newSlopeMap)
- {
- this.SlopeMap = newSlopeMap;
- // Re-create the native array to match the new managed data
- if (slopeMapNative.IsCreated) slopeMapNative.Dispose();
- slopeMapNative = new NativeArray<float>(To1DArray(newSlopeMap), Allocator.Persistent);
- }
- public void UpdateAlphaMap(float[,,] newAlphaMap)
- {
- // Non c'è una variabile gestita per la master alphamap, ma dobbiamo aggiornare quella nativa
- if (alphaMapNative.IsCreated) alphaMapNative.Dispose();
- alphaMapNative = new NativeArray<float>(To1DArray(newAlphaMap), Allocator.Persistent);
- // Aggiorna anche le dimensioni se necessario
- this.AlphaMapResolution = newAlphaMap.GetLength(0);
- this.AlphaMapLayerCount = newAlphaMap.GetLength(2);
- }
- /// <summary>
- /// Disposes all underlying NativeArray collections to prevent memory leaks.
- /// </summary>
- public void DisposeNativeArrays()
- {
- if (biomeMapNative.IsCreated) biomeMapNative.Dispose();
- if (heightMapNative.IsCreated) heightMapNative.Dispose();
- if (slopeMapNative.IsCreated) slopeMapNative.Dispose();
- if (alphaMapNative.IsCreated) alphaMapNative.Dispose();
- }
- private T[] To1DArray<T>(T[,] input) where T : struct
- {
- int height = input.GetLength(0);
- int width = input.GetLength(1);
- T[] result = new T[width * height];
- for (int z = 0; z < height; z++)
- {
- for (int x = 0; x < width; x++)
- {
- result[z * width + x] = input[z, x];
- }
- }
- return result;
- }
- private T[] To1DArray<T>(T[,,] input) where T : struct
- {
- int d0 = input.GetLength(0);
- int d1 = input.GetLength(1);
- int d2 = input.GetLength(2);
- T[] result = new T[d0 * d1 * d2];
- for (int i = 0; i < d0; i++)
- {
- for (int j = 0; j < d1; j++)
- {
- for (int k = 0; k < d2; k++)
- {
- result[(i * d1 + j) * d2 + k] = input[i, j, k];
- }
- }
- }
- return result;
- }
- }
- /// <summary>
- /// Static class responsible for creating climate maps and selecting biomes based on terrain properties.
- /// </summary>
- public static class BiomePlanner
- {
- private const int CLIMATE_POSTERIZE_LEVELS = 8;
- private const float NOISE_THRESHOLD = 0.5f;
- public static BiomeSelectionData PlanBiomes(Terrain[,] terrainGrid, Dictionary<Vector2Int, float[,]> heightmaps, Dictionary<Vector2Int, float[,,]> alphaMaps, int masterResolution, float normalizedSeaLevel, List<BiomeConfig> biomeConfigs, float worldSize, NoiseParameters biomeControlNoise, int posterizeLevels, float tempAltitudeInfluence, float tempDistanceInfluence, AnimationCurve humidityFalloff)
- {
- float[,] masterHeightMap = ReconstructMasterMap(heightmaps, masterResolution);
- int layerCount = biomeConfigs.SelectMany(b => b.proceduralTerrainLayers).Select(l => l.terrainLayer).Distinct().Count();
- if (layerCount == 0) layerCount = 1;
- NativeArray<float> masterAlphaMapNative = TerrainUtils.ReconstructMasterAlphaMap_Optimized(alphaMaps, masterResolution, layerCount, Allocator.Persistent);
- (int[,] distanceMap, int maxDist) = CalculateDistanceMap(masterHeightMap, masterResolution, normalizedSeaLevel);
- float[,] temperatureMap = CalculateTemperatureMap(masterHeightMap, distanceMap, maxDist, masterResolution, tempAltitudeInfluence, tempDistanceInfluence, normalizedSeaLevel);
- float[,] humidityMap = CalculateHumidityMap(masterHeightMap, distanceMap, maxDist, masterResolution, humidityFalloff, normalizedSeaLevel);
- PosterizeMap(temperatureMap, CLIMATE_POSTERIZE_LEVELS);
- PosterizeMap(humidityMap, CLIMATE_POSTERIZE_LEVELS);
- float pixelToMeters = TerrainUtils.PixelToMeters(worldSize, masterResolution);
- float maxAltitude = FindMaxAltitude(masterHeightMap);
- float[,] slopeMap = TerrainUtils.CreateSlopeMapFromNH(masterHeightMap, masterResolution, pixelToMeters, maxAltitude);
- // Generate noise maps for all biomes that might need them
- Dictionary<int, float[,]> biomeNoiseMaps = GenerateBiomeNoiseMaps(masterResolution, biomeConfigs, biomeControlNoise);
- // Identify conflict groups based on overlapping parameters
- Dictionary<int, List<int>> conflictGroups = IdentifyConflictGroups(biomeConfigs);
- int[,] biomeMap = SelectBiomesWithConflicts(masterHeightMap, temperatureMap, humidityMap, slopeMap, biomeNoiseMaps, masterResolution, biomeConfigs, conflictGroups);
- float2 worldDimensions = new float2(worldSize, worldSize);
- BiomeSelectionData selectionData = new BiomeSelectionData(
- terrainGrid,
- biomeMap,
- masterHeightMap,
- slopeMap,
- masterAlphaMapNative,
- maxAltitude,
- worldDimensions,
- masterResolution,
- layerCount
- );
- selectionData.TemperatureMap = temperatureMap;
- selectionData.HumidityMap = humidityMap;
- return selectionData;
- }
- #region Map Reconstruction
- private static float[,] ReconstructMasterMap(Dictionary<Vector2Int, float[,]> heightmaps, int masterResolution)
- {
- if (heightmaps.Count == 0) return new float[masterResolution, masterResolution];
- int tileRes = heightmaps[Vector2Int.zero].GetLength(0);
- int tileStep = tileRes - 1;
- float[,] masterMap = new float[masterResolution, masterResolution];
- foreach (KeyValuePair<Vector2Int, float[,]> tile in heightmaps)
- {
- int startX = tile.Key.x * tileStep;
- int startY = tile.Key.y * tileStep;
- float[,] tileHeights = tile.Value;
- for (int y = 0; y < tileRes; y++)
- {
- for (int x = 0; x < tileRes; x++)
- {
- if (startY + y < masterResolution && startX + x < masterResolution)
- {
- masterMap[startY + y, startX + x] = tileHeights[y, x];
- }
- }
- }
- }
- return masterMap;
- }
- private static float[,,] ReconstructMasterAlphaMap(Dictionary<Vector2Int, float[,,]> alphaMaps, int masterResolution, List<BiomeConfig> biomes)
- {
- int layerCount = biomes.Count > 0 ? biomes[0].proceduralTerrainLayers.Count : 1;
- if (alphaMaps.Count == 0) return new float[masterResolution, masterResolution, layerCount];
- int tileRes = alphaMaps[Vector2Int.zero].GetLength(0);
- layerCount = alphaMaps[Vector2Int.zero].GetLength(2);
- int tileStep = tileRes;
- float[,,] masterMap = new float[masterResolution, masterResolution, layerCount];
- foreach (KeyValuePair<Vector2Int, float[,,]> tile in alphaMaps)
- {
- int startX = tile.Key.x * tileStep;
- int startY = tile.Key.y * tileStep;
- float[,,] tileAlpha = tile.Value;
- for (int y = 0; y < tileRes; y++)
- {
- for (int x = 0; x < tileRes; x++)
- {
- if (startY + y < masterResolution && startX + x < masterResolution)
- {
- for (int l = 0; l < layerCount; l++)
- {
- masterMap[startY + y, startX + x, l] = tileAlpha[y, x, l];
- }
- }
- }
- }
- }
- return masterMap;
- }
- #endregion
- #region Climate & Physical Map Calculation
- /// <summary>
- /// Calculates a map where each pixel's value is its integer distance from the nearest water source.
- /// </summary>
- /// <returns>A tuple containing the distance map and the maximum distance found.</returns>
- private static (int[,] distanceMap, int maxDistance) CalculateDistanceMap(float[,] heightMap, int resolution, float normalizedSeaLevel)
- {
- int[,] distanceMap = new int[resolution, resolution];
- Queue<Vector2Int> queue = new Queue<Vector2Int>(resolution * 4);
- int maxDistance = 1;
- for (int y = 0; y < resolution; y++)
- {
- for (int x = 0; x < resolution; x++)
- {
- if (heightMap[y, x] <= normalizedSeaLevel)
- {
- distanceMap[y, x] = 0;
- queue.Enqueue(new Vector2Int(x, y));
- }
- else
- {
- distanceMap[y, x] = int.MaxValue;
- }
- }
- }
- int[] dx = { 0, 0, 1, -1 };
- int[] dy = { 1, -1, 0, 0 };
- while (queue.Count > 0)
- {
- Vector2Int current = queue.Dequeue();
- for (int i = 0; i < 4; i++)
- {
- int nx = current.x + dx[i];
- int ny = current.y + dy[i];
- if (nx >= 0 && nx < resolution && ny >= 0 && ny < resolution && distanceMap[ny, nx] == int.MaxValue)
- {
- int newDist = distanceMap[current.y, current.x] + 1;
- distanceMap[ny, nx] = newDist;
- queue.Enqueue(new Vector2Int(nx, ny));
- if (newDist > maxDistance) maxDistance = newDist;
- }
- }
- }
- return (distanceMap, maxDistance);
- }
- /// <summary>
- /// Calculates temperature based on distance from sea (base temp) and altitude (cooling factor).
- /// </summary>
- private static float[,] CalculateTemperatureMap(float[,] heightMap, int[,] distanceMap, int maxDistance, int resolution, float altitudeInfluence, float distanceInfluence, float normalizedSeaLevel)
- {
- float[,] temperatureMap = new float[resolution, resolution];
- float invMaxDist = 1.0f / maxDistance;
- for (int y = 0; y < resolution; y++)
- {
- for (int x = 0; x < resolution; x++)
- {
- if (heightMap[y, x] <= normalizedSeaLevel)
- {
- temperatureMap[y, x] = 0.5f; // Sea has a constant, moderate temperature
- continue;
- }
- // Start with a base temperature of 1.0 (max heat)
- float temperature = 1.0f;
- // 1. Maritime Cooling Effect: areas near the sea are cooler.
- // The effect diminishes as we move inland.
- float normalizedDistance = distanceMap[y, x] * invMaxDist;
- float seaCooling = (1.0f - normalizedDistance) * distanceInfluence;
- temperature -= seaCooling;
- // 2. Altitude Cooling Effect (Lapse Rate): higher means colder.
- float altitudeCooling = heightMap[y, x] * altitudeInfluence;
- temperature -= altitudeCooling;
- temperatureMap[y, x] = Mathf.Clamp01(temperature);
- }
- }
- return temperatureMap;
- }
- /// <summary>
- /// Calculates humidity based on distance from the sea and modified by altitude.
- /// </summary>
- private static float[,] CalculateHumidityMap(float[,] heightMap, int[,] distanceMap, int maxDistance, int resolution, AnimationCurve humidityFalloff, float normalizedSeaLevel)
- {
- float[,] humidityMap = new float[resolution, resolution];
- float invMaxDist = 1.0f / maxDistance;
- for (int y = 0; y < resolution; y++)
- {
- for (int x = 0; x < resolution; x++)
- {
- // 1. Water has maximum humidity
- if (heightMap[y, x] <= normalizedSeaLevel)
- {
- humidityMap[y, x] = 1.0f;
- continue;
- }
- // 2. Base humidity from distance, controlled by the curve
- float normalizedDistance = distanceMap[y, x] * invMaxDist;
- float baseHumidity = humidityFalloff.Evaluate(normalizedDistance);
- // 3. Lower areas are more humid, higher areas are drier
- float altitudeModifier = 1.0f - heightMap[y, x];
- // 4. Final humidity is a product of the two factors
- float finalHumidity = baseHumidity * altitudeModifier;
- humidityMap[y, x] = Mathf.Clamp01(finalHumidity);
- }
- }
- return humidityMap;
- }
- /// <summary>
- /// Generates a 2D noise map used to break ties between valid biomes.
- /// </summary>
- public static float[,] GenerateControlMap(int resolution, NoiseParameters settings, int posterizeLevels)
- {
- NativeArray<float> controlMapNative = new NativeArray<float>(resolution * resolution, Allocator.TempJob);
- GenerateControlMapJob job = new GenerateControlMapJob
- {
- ControlMap = controlMapNative,
- Settings = settings,
- Resolution = resolution,
- PosterizeLevels = posterizeLevels
- };
- JobHandle handle = job.Schedule(resolution * resolution, 64);
- handle.Complete();
- float[,] managedMap = new float[resolution, resolution];
- for (int i = 0; i < controlMapNative.Length; i++)
- {
- int y = i / resolution;
- int x = i % resolution;
- managedMap[y, x] = controlMapNative[i];
- }
- controlMapNative.Dispose();
- return managedMap;
- }
- #endregion
- #region Conflict Identification
- private static Dictionary<int, List<int>> IdentifyConflictGroups(List<BiomeConfig> biomeConfigs)
- {
- var conflictGroups = new Dictionary<int, List<int>>();
- for (int i = 0; i < biomeConfigs.Count; i++)
- {
- for (int j = i + 1; j < biomeConfigs.Count; j++)
- {
- if (RangesOverlap(biomeConfigs[i], biomeConfigs[j]))
- {
- // Found an overlap. Find the group they belong to or create a new one.
- int groupKeyA = FindGroupKey(i, conflictGroups);
- int groupKeyB = FindGroupKey(j, conflictGroups);
- if (groupKeyA != -1 && groupKeyB != -1 && groupKeyA != groupKeyB)
- {
- // Merge two existing groups
- MergeGroups(groupKeyA, groupKeyB, conflictGroups);
- }
- else if (groupKeyA != -1)
- {
- // Add j to group A
- conflictGroups[groupKeyA].Add(j);
- }
- else if (groupKeyB != -1)
- {
- // Add i to group B
- conflictGroups[groupKeyB].Add(i);
- }
- else
- {
- // Create a new group with the biome of the lower index as the key
- int key = Mathf.Min(i, j);
- conflictGroups[key] = new List<int> { i, j };
- }
- }
- }
- }
- // Ensure all lists in the dictionary are unique and sorted
- foreach (var key in conflictGroups.Keys.ToList())
- {
- conflictGroups[key] = conflictGroups[key].Distinct().OrderBy(id => id).ToList();
- }
- return conflictGroups;
- }
- private static bool RangesOverlap(BiomeConfig a, BiomeConfig b)
- {
- if (a == null || b == null) return false;
- return RangeOverlap1D(a.altitudeRange, b.altitudeRange) &&
- RangeOverlap1D(a.humidityRange, b.humidityRange) &&
- RangeOverlap1D(a.temperatureRange, b.temperatureRange) &&
- RangeOverlap1D(a.slopeRange, b.slopeRange);
- }
- private static bool RangeOverlap1D(Vector2 rangeA, Vector2 rangeB)
- {
- return rangeA.x <= rangeB.y && rangeA.y >= rangeB.x;
- }
- private static int FindGroupKey(int biomeIndex, Dictionary<int, List<int>> groups)
- {
- foreach (var kvp in groups)
- {
- if (kvp.Value.Contains(biomeIndex)) return kvp.Key;
- }
- return -1;
- }
- private static void MergeGroups(int keyA, int keyB, Dictionary<int, List<int>> groups)
- {
- int primaryKey = Mathf.Min(keyA, keyB);
- int secondaryKey = Mathf.Max(keyA, keyB);
- groups[primaryKey].AddRange(groups[secondaryKey]);
- groups.Remove(secondaryKey);
- }
- #endregion
- #region Biome Selection
- /********************************************************************************************************
- * Ora il Sistema Funziona Così ✅
- *
- * Biomi specifici (1+) competono tra loro normalmente
- * Bioma 0 viene usato solo quando NESSUN bioma specifico è valido
- * Le regole di competizione (2 biomi = blend, 3+ = noise) funzionano sui biomi veri
- * Niente più aree viola indesiderate!
- *
- ********************************************************************************************************/
- private static int[,] SelectBiomesWithConflicts(float[,] heightMap, float[,] tempMap, float[,] humidityMap, float[,] slopeMap, Dictionary<int, float[,]> biomeNoiseMaps, int resolution, List<BiomeConfig> biomeConfigs, Dictionary<int, List<int>> conflictGroups)
- {
- int[,] biomeMap = new int[resolution, resolution];
- for (int y = 0; y < resolution; y++)
- {
- for (int x = 0; x < resolution; x++)
- {
- List<int> validBiomes = GetValidBiomesAt(x, y, heightMap, tempMap, humidityMap, slopeMap, biomeConfigs);
- // IMPORTANTE: Escludiamo il bioma 0 (fallback universale) dalla competizione
- List<int> specificBiomes = validBiomes.Where(b => b > 0).ToList();
- if (specificBiomes.Count == 0)
- {
- // Nessun bioma specifico è valido, usa il fallback universale (bioma 0)
- biomeMap[y, x] = 0;
- }
- else if (specificBiomes.Count == 1)
- {
- // Un solo bioma specifico è valido
- biomeMap[y, x] = specificBiomes[0];
- }
- else
- {
- // Più biomi specifici sono validi, risolvi la competizione
- biomeMap[y, x] = ResolveCompetition(specificBiomes, x, y, biomeNoiseMaps, conflictGroups);
- }
- }
- }
- return biomeMap;
- }
- private static List<int> GetValidBiomesAt(int x, int y, float[,] heightMap, float[,] tempMap, float[,] humidityMap, float[,] slopeMap, List<BiomeConfig> biomeConfigs)
- {
- List<int> validIndices = new List<int>();
- for (int i = 0; i < biomeConfigs.Count; i++)
- {
- if (IsBiomeValidAt(biomeConfigs[i], heightMap[y, x], tempMap[y, x], humidityMap[y, x], slopeMap[y, x]))
- {
- validIndices.Add(i);
- }
- }
- return validIndices;
- }
- private static int ResolveCompetition(List<int> competingIndices, int x, int y, Dictionary<int, float[,]> noiseMaps, Dictionary<int, List<int>> conflictGroups)
- {
- // SAFETY CHECK: Non dovremmo mai arrivare qui con una lista vuota (ma per sicurezza)
- if (competingIndices == null || competingIndices.Count == 0)
- {
- Debug.LogError($"ERRORE: ResolveCompetition chiamato con lista vuota a ({x}, {y})!");
- return 1; // Fallback al bioma 1 invece che 0
- }
- if (competingIndices.Count == 1)
- {
- return competingIndices[0];
- }
- // Verifica se almeno un bioma appartiene a un gruppo di conflitto
- int groupKey = -1;
- List<int> groupMembers = null;
- foreach (int biomeIndex in competingIndices)
- {
- groupKey = FindGroupKey(biomeIndex, conflictGroups);
- if (groupKey != -1)
- {
- groupMembers = conflictGroups[groupKey];
- break;
- }
- }
- // CASO 1: Nessun bioma appartiene a gruppi di conflitto
- // Regola semplice: vince quello con l'indice più alto
- if (groupKey == -1)
- {
- return competingIndices.Max();
- }
- // CASO 2: Almeno un bioma appartiene a un gruppo di conflitto
- // Separiamo i biomi in due categorie
- List<int> biomesInGroup = competingIndices.Where(b => groupMembers.Contains(b)).ToList();
- List<int> biomesOutsideGroup = competingIndices.Where(b => !groupMembers.Contains(b)).ToList();
- // Se ci sono biomi fuori dal gruppo, hanno priorità automatica (vince il più alto)
- if (biomesOutsideGroup.Count > 0)
- {
- return biomesOutsideGroup.Max();
- }
- // Tutti i biomi appartengono al gruppo di conflitto
- biomesInGroup.Sort();
- // REGOLA: Se solo 2 biomi nel gruppo, vince quello con indice più alto (blend)
- if (biomesInGroup.Count == 2)
- {
- return biomesInGroup.Max();
- }
- // REGOLA: 3+ biomi nel gruppo, usa il noise per scegliere
- // Il bioma con l'indice più basso nel gruppo è il fallback per questo gruppo
- int fallbackBiomeIndex = biomesInGroup[0];
- int winningBiomeIndex = fallbackBiomeIndex;
- float highestNoiseValue = -1f;
- // Controlla tutti i competitor per trovare quello con noise più alto
- foreach (int competitorIndex in biomesInGroup)
- {
- float noiseValue = noiseMaps[competitorIndex][y, x];
- if (noiseValue > NOISE_THRESHOLD && noiseValue > highestNoiseValue)
- {
- highestNoiseValue = noiseValue;
- winningBiomeIndex = competitorIndex;
- }
- }
- return winningBiomeIndex;
- }
- #endregion
- #region Helpers
- private static bool IsBiomeValidAt(BiomeConfig biome, float height, float temp, float humidity, float slope)
- {
- if (biome == null) return false;
- return temp >= biome.temperatureRange.x && temp <= biome.temperatureRange.y &&
- humidity >= biome.humidityRange.x && humidity <= biome.humidityRange.y &&
- height >= biome.altitudeRange.x && height <= biome.altitudeRange.y &&
- slope >= biome.slopeRange.x && slope <= biome.slopeRange.y;
- }
- private static Dictionary<int, float[,]> GenerateBiomeNoiseMaps(int resolution, List<BiomeConfig> biomeConfigs, NoiseParameters baseNoise)
- {
- Dictionary<int, float[,]> noiseMaps = new Dictionary<int, float[,]>();
- for (int i = 0; i < biomeConfigs.Count; i++)
- {
- NoiseParameters biomeNoise = baseNoise;
- biomeNoise.seed += i;
- noiseMaps[i] = GenerateSingleNoiseMap(resolution, biomeNoise);
- }
- return noiseMaps;
- }
- private static float[,] GenerateSingleNoiseMap(int resolution, NoiseParameters settings)
- {
- NativeArray<float> noiseMapNative = new NativeArray<float>(resolution * resolution, Allocator.TempJob);
- GenerateControlMapJob job = new GenerateControlMapJob { ControlMap = noiseMapNative, Settings = settings, Resolution = resolution, PosterizeLevels = 0 };
- JobHandle handle = job.Schedule(resolution * resolution, 64);
- handle.Complete();
- float[,] managedMap = new float[resolution, resolution];
- for (int i = 0; i < noiseMapNative.Length; i++)
- {
- int y = i / resolution;
- int x = i % resolution;
- managedMap[y, x] = noiseMapNative[i];
- }
- noiseMapNative.Dispose();
- return managedMap;
- }
- private static float FindMaxAltitude(float[,] heightMap)
- {
- float maxAlt = float.MinValue;
- int resolution = heightMap.GetLength(0);
- for (int z = 0; z < resolution; z++)
- {
- for (int x = 0; x < resolution; x++)
- {
- if (heightMap[z, x] > maxAlt)
- {
- maxAlt = heightMap[z, x];
- }
- }
- }
- return maxAlt;
- }
- /// <summary>
- /// Applies posterization to all values in a 2D map.
- /// </summary>
- private static void PosterizeMap(float[,] map, int levels)
- {
- if (levels <= 1) return;
- int resolution = map.GetLength(0);
- for (int y = 0; y < resolution; y++)
- {
- for (int x = 0; x < resolution; x++)
- {
- map[y, x] = Posterize(map[y, x], levels);
- }
- }
- }
- private static float Posterize(float value, int levels)
- {
- if (levels <= 1) return value;
- // Multiply by (levels - 1), round, and divide by (levels - 1)
- // to map the value to one of the discrete steps (e.g., 0, 0.25, 0.5, 0.75, 1 for 5 levels)
- float scaledValue = value * (levels - 1);
- float roundedValue = Mathf.Round(scaledValue);
- return roundedValue / (float)(levels - 1);
- }
- #endregion
- [BurstCompile]
- private struct GenerateControlMapJob : IJobParallelFor
- {
- public NativeArray<float> ControlMap;
- public int PosterizeLevels;
- [ReadOnly] public NoiseParameters Settings;
- public int Resolution;
- public void Execute(int index)
- {
- int y = index / Resolution;
- int x = index % Resolution;
- float maxPossibleValue = 0;
- float currentAmplitude = Settings.amplitude;
- for (int i = 0; i < Settings.octaves; i++)
- {
- maxPossibleValue += currentAmplitude;
- currentAmplitude *= Settings.persistence;
- }
- float inverseMaxRange = 1f / math.max(0.0001f, maxPossibleValue * 2f);
- float amplitude = Settings.amplitude;
- float frequency = Settings.frequency;
- float noiseHeight = 0;
- float2 seedOffset = Settings.GetSeedOffset();
- float halfRes = Resolution / 2f;
- for (int i = 0; i < Settings.octaves; i++)
- {
- float sampleX = (x - halfRes) / (float)Resolution * frequency + seedOffset.x + Settings.offset.x;
- float sampleY = (y - halfRes) / (float)Resolution * frequency + seedOffset.y + Settings.offset.y;
- float perlinValue = noise.snoise(new float2(sampleX, sampleY));
- noiseHeight += perlinValue * amplitude;
- amplitude *= Settings.persistence;
- frequency *= Settings.lacunarity;
- }
- float normalizedValue = (noiseHeight + maxPossibleValue) * inverseMaxRange;
- normalizedValue = math.saturate(normalizedValue);
- if (PosterizeLevels > 1)
- {
- float scaledValue = normalizedValue * (PosterizeLevels - 1);
- float roundedValue = math.round(scaledValue);
- normalizedValue = roundedValue / (float)(PosterizeLevels - 1);
- }
- ControlMap[index] = math.saturate(normalizedValue);
- }
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment