MaximilianPs

BiomeSpawner

Aug 18th, 2025 (edited)
303
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 33.19 KB | Source Code | 0 0
  1. using System.Collections.Generic;
  2. using System.Linq;
  3. using Unity.Burst;
  4. using Unity.Collections;
  5. using Unity.Jobs;
  6. using Unity.Mathematics;
  7. using UnityEngine;
  8. using static GranDucato.LandGenerator.BiomeConfig;
  9. using static GranDucato.LandGenerator.POIPlanner;
  10.  
  11. namespace GranDucato.LandGenerator
  12. {
  13.     /// <summary>
  14.     /// Data container for all maps and parameters used during biome selection and terrain painting.
  15.     /// Manages both managed and native arrays for performance with the C# Job System.
  16.     /// </summary>
  17.     public class BiomeSelectionData
  18.     {
  19.         public float2 WorldSize { get; private set; }
  20.         public float MaxAltitude { get; private set; }
  21.         public int AlphaMapResolution { get; private set; }
  22.         public int AlphaMapLayerCount { get; private set; }
  23.         public Terrain[,] TerrainGrid { get; private set; }
  24.  
  25.         // Managed data for easy access from main thread
  26.         public float[,] HeightMap { get; private set; }
  27.         public int[,] BiomeMap { get; private set; }
  28.         public float[,] SlopeMap { get; private set; }
  29.         public float[,] TemperatureMap { get; set; }
  30.         public float[,] HumidityMap { get; set; }
  31.         public float[,] AlphaMap { get; set; }
  32.  
  33.         // Native data for jobs
  34.         private NativeArray<int> biomeMapNative;
  35.         private NativeArray<float> heightMapNative;
  36.         private NativeArray<float> slopeMapNative;
  37.         private NativeArray<float> alphaMapNative;
  38.  
  39.         /// <summary>
  40.         /// Creates a data container and prepares native arrays from final, managed map data.
  41.         /// </summary>
  42.         public BiomeSelectionData(Terrain[,] grid, int[,] bMap, float[,] hMap, float[,] sMap, NativeArray<float> aMapNative, float maxAlt, float2 worldSize, int alphaMapRes, int alphaMapLayers)
  43.         {
  44.             this.TerrainGrid = grid;
  45.             this.WorldSize = worldSize;
  46.             this.MaxAltitude = maxAlt;
  47.             this.AlphaMapResolution = alphaMapRes;
  48.             this.AlphaMapLayerCount = alphaMapLayers;
  49.             this.BiomeMap = bMap;
  50.             this.HeightMap = hMap;
  51.             this.SlopeMap = sMap;
  52.  
  53.             // Converti gli altri array
  54.             biomeMapNative = new NativeArray<int>(To1DArray(bMap), Allocator.Persistent);
  55.             heightMapNative = new NativeArray<float>(To1DArray(hMap), Allocator.Persistent);
  56.             slopeMapNative = new NativeArray<float>(To1DArray(sMap), Allocator.Persistent);
  57.  
  58.             // Assegna direttamente la NativeArray dell'alphamap
  59.             this.alphaMapNative = aMapNative;
  60.         }
  61.  
  62.         public NativeArray<int>.ReadOnly GetBiomeMapNative_ReadOnly() => biomeMapNative.AsReadOnly();
  63.         public NativeArray<float>.ReadOnly GetHeightMapNative_ReadOnly() => heightMapNative.AsReadOnly();
  64.         public NativeArray<float>.ReadOnly GetSlopeMapNative_ReadOnly() => slopeMapNative.AsReadOnly();
  65.         public NativeArray<float>.ReadOnly GetAlphaMapNative_ReadOnly() => alphaMapNative.AsReadOnly();
  66.  
  67.         /// <summary>
  68.         /// Updates the managed HeightMap and rebuilds the underlying NativeArray for job system consistency.
  69.         /// </summary>
  70.         public void UpdateHeightMap(float[,] newHeightMap)
  71.         {
  72.             HeightMap = newHeightMap;
  73.  
  74.             // Re-create the native array to match the new managed data
  75.             if (heightMapNative.IsCreated) heightMapNative.Dispose();
  76.             heightMapNative = new NativeArray<float>(To1DArray(newHeightMap), Allocator.Persistent);
  77.         }
  78.  
  79.         /// <summary>
  80.         /// Updates the managed SlopeMap and rebuilds the underlying NativeArray for job system consistency.
  81.         /// </summary>
  82.         public void UpdateSlopeMap(float[,] newSlopeMap)
  83.         {
  84.             this.SlopeMap = newSlopeMap;
  85.  
  86.             // Re-create the native array to match the new managed data
  87.             if (slopeMapNative.IsCreated) slopeMapNative.Dispose();
  88.             slopeMapNative = new NativeArray<float>(To1DArray(newSlopeMap), Allocator.Persistent);
  89.         }
  90.  
  91.         public void UpdateAlphaMap(float[,,] newAlphaMap)
  92.         {
  93.             // Non c'è una variabile gestita per la master alphamap, ma dobbiamo aggiornare quella nativa
  94.             if (alphaMapNative.IsCreated) alphaMapNative.Dispose();
  95.             alphaMapNative = new NativeArray<float>(To1DArray(newAlphaMap), Allocator.Persistent);
  96.  
  97.             // Aggiorna anche le dimensioni se necessario
  98.             this.AlphaMapResolution = newAlphaMap.GetLength(0);
  99.             this.AlphaMapLayerCount = newAlphaMap.GetLength(2);
  100.         }
  101.  
  102.         /// <summary>
  103.         /// Disposes all underlying NativeArray collections to prevent memory leaks.
  104.         /// </summary>
  105.         public void DisposeNativeArrays()
  106.         {
  107.             if (biomeMapNative.IsCreated) biomeMapNative.Dispose();
  108.             if (heightMapNative.IsCreated) heightMapNative.Dispose();
  109.             if (slopeMapNative.IsCreated) slopeMapNative.Dispose();
  110.             if (alphaMapNative.IsCreated) alphaMapNative.Dispose();
  111.         }
  112.  
  113.         private T[] To1DArray<T>(T[,] input) where T : struct
  114.         {
  115.             int height = input.GetLength(0);
  116.             int width = input.GetLength(1);
  117.             T[] result = new T[width * height];
  118.             for (int z = 0; z < height; z++)
  119.             {
  120.                 for (int x = 0; x < width; x++)
  121.                 {
  122.                     result[z * width + x] = input[z, x];
  123.                 }
  124.             }
  125.             return result;
  126.         }
  127.  
  128.         private T[] To1DArray<T>(T[,,] input) where T : struct
  129.         {
  130.             int d0 = input.GetLength(0);
  131.             int d1 = input.GetLength(1);
  132.             int d2 = input.GetLength(2);
  133.             T[] result = new T[d0 * d1 * d2];
  134.             for (int i = 0; i < d0; i++)
  135.             {
  136.                 for (int j = 0; j < d1; j++)
  137.                 {
  138.                     for (int k = 0; k < d2; k++)
  139.                     {
  140.                         result[(i * d1 + j) * d2 + k] = input[i, j, k];
  141.                     }
  142.                 }
  143.             }
  144.             return result;
  145.         }
  146.     }
  147.  
  148.     /// <summary>
  149.     /// Static class responsible for creating climate maps and selecting biomes based on terrain properties.
  150.     /// </summary>
  151.  
  152.     public static class BiomePlanner
  153.     {
  154.         private const int CLIMATE_POSTERIZE_LEVELS = 8;
  155.         private const float NOISE_THRESHOLD = 0.5f;
  156.  
  157.         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)
  158.         {
  159.             float[,] masterHeightMap = ReconstructMasterMap(heightmaps, masterResolution);
  160.  
  161.             int layerCount = biomeConfigs.SelectMany(b => b.proceduralTerrainLayers).Select(l => l.terrainLayer).Distinct().Count();
  162.             if (layerCount == 0) layerCount = 1;
  163.  
  164.             NativeArray<float> masterAlphaMapNative = TerrainUtils.ReconstructMasterAlphaMap_Optimized(alphaMaps, masterResolution, layerCount, Allocator.Persistent);
  165.  
  166.             (int[,] distanceMap, int maxDist) = CalculateDistanceMap(masterHeightMap, masterResolution, normalizedSeaLevel);
  167.             float[,] temperatureMap = CalculateTemperatureMap(masterHeightMap, distanceMap, maxDist, masterResolution, tempAltitudeInfluence, tempDistanceInfluence, normalizedSeaLevel);
  168.             float[,] humidityMap = CalculateHumidityMap(masterHeightMap, distanceMap, maxDist, masterResolution, humidityFalloff, normalizedSeaLevel);
  169.             PosterizeMap(temperatureMap, CLIMATE_POSTERIZE_LEVELS);
  170.             PosterizeMap(humidityMap, CLIMATE_POSTERIZE_LEVELS);
  171.  
  172.             float pixelToMeters = TerrainUtils.PixelToMeters(worldSize, masterResolution);
  173.             float maxAltitude = FindMaxAltitude(masterHeightMap);
  174.             float[,] slopeMap = TerrainUtils.CreateSlopeMapFromNH(masterHeightMap, masterResolution, pixelToMeters, maxAltitude);
  175.  
  176.             // Generate noise maps for all biomes that might need them
  177.             Dictionary<int, float[,]> biomeNoiseMaps = GenerateBiomeNoiseMaps(masterResolution, biomeConfigs, biomeControlNoise);
  178.  
  179.             // Identify conflict groups based on overlapping parameters
  180.             Dictionary<int, List<int>> conflictGroups = IdentifyConflictGroups(biomeConfigs);
  181.  
  182.             int[,] biomeMap = SelectBiomesWithConflicts(masterHeightMap, temperatureMap, humidityMap, slopeMap, biomeNoiseMaps, masterResolution, biomeConfigs, conflictGroups);
  183.  
  184.             float2 worldDimensions = new float2(worldSize, worldSize);
  185.             BiomeSelectionData selectionData = new BiomeSelectionData(
  186.                 terrainGrid,
  187.                 biomeMap,
  188.                 masterHeightMap,
  189.                 slopeMap,
  190.                 masterAlphaMapNative,
  191.                 maxAltitude,
  192.                 worldDimensions,
  193.                 masterResolution,
  194.                 layerCount
  195.             );
  196.             selectionData.TemperatureMap = temperatureMap;
  197.             selectionData.HumidityMap = humidityMap;
  198.  
  199.  
  200.             return selectionData;
  201.         }
  202.  
  203.         #region Map Reconstruction
  204.  
  205.         private static float[,] ReconstructMasterMap(Dictionary<Vector2Int, float[,]> heightmaps, int masterResolution)
  206.         {
  207.             if (heightmaps.Count == 0) return new float[masterResolution, masterResolution];
  208.  
  209.             int tileRes = heightmaps[Vector2Int.zero].GetLength(0);
  210.             int tileStep = tileRes - 1;
  211.             float[,] masterMap = new float[masterResolution, masterResolution];
  212.  
  213.             foreach (KeyValuePair<Vector2Int, float[,]> tile in heightmaps)
  214.             {
  215.                 int startX = tile.Key.x * tileStep;
  216.                 int startY = tile.Key.y * tileStep;
  217.                 float[,] tileHeights = tile.Value;
  218.  
  219.                 for (int y = 0; y < tileRes; y++)
  220.                 {
  221.                     for (int x = 0; x < tileRes; x++)
  222.                     {
  223.                         if (startY + y < masterResolution && startX + x < masterResolution)
  224.                         {
  225.                             masterMap[startY + y, startX + x] = tileHeights[y, x];
  226.                         }
  227.                     }
  228.                 }
  229.             }
  230.             return masterMap;
  231.         }
  232.  
  233.         private static float[,,] ReconstructMasterAlphaMap(Dictionary<Vector2Int, float[,,]> alphaMaps, int masterResolution, List<BiomeConfig> biomes)
  234.         {
  235.             int layerCount = biomes.Count > 0 ? biomes[0].proceduralTerrainLayers.Count : 1;
  236.             if (alphaMaps.Count == 0) return new float[masterResolution, masterResolution, layerCount];
  237.  
  238.             int tileRes = alphaMaps[Vector2Int.zero].GetLength(0);
  239.             layerCount = alphaMaps[Vector2Int.zero].GetLength(2);
  240.             int tileStep = tileRes;
  241.             float[,,] masterMap = new float[masterResolution, masterResolution, layerCount];
  242.  
  243.             foreach (KeyValuePair<Vector2Int, float[,,]> tile in alphaMaps)
  244.             {
  245.                 int startX = tile.Key.x * tileStep;
  246.                 int startY = tile.Key.y * tileStep;
  247.                 float[,,] tileAlpha = tile.Value;
  248.  
  249.                 for (int y = 0; y < tileRes; y++)
  250.                 {
  251.                     for (int x = 0; x < tileRes; x++)
  252.                     {
  253.                         if (startY + y < masterResolution && startX + x < masterResolution)
  254.                         {
  255.                             for (int l = 0; l < layerCount; l++)
  256.                             {
  257.                                 masterMap[startY + y, startX + x, l] = tileAlpha[y, x, l];
  258.                             }
  259.                         }
  260.                     }
  261.                 }
  262.             }
  263.             return masterMap;
  264.         }
  265.  
  266.         #endregion
  267.  
  268.         #region Climate & Physical Map Calculation
  269.  
  270.         /// <summary>
  271.         /// Calculates a map where each pixel's value is its integer distance from the nearest water source.
  272.         /// </summary>
  273.         /// <returns>A tuple containing the distance map and the maximum distance found.</returns>
  274.         private static (int[,] distanceMap, int maxDistance) CalculateDistanceMap(float[,] heightMap, int resolution, float normalizedSeaLevel)
  275.         {
  276.             int[,] distanceMap = new int[resolution, resolution];
  277.             Queue<Vector2Int> queue = new Queue<Vector2Int>(resolution * 4);
  278.             int maxDistance = 1;
  279.  
  280.             for (int y = 0; y < resolution; y++)
  281.             {
  282.                 for (int x = 0; x < resolution; x++)
  283.                 {
  284.                     if (heightMap[y, x] <= normalizedSeaLevel)
  285.                     {
  286.                         distanceMap[y, x] = 0;
  287.                         queue.Enqueue(new Vector2Int(x, y));
  288.                     }
  289.                     else
  290.                     {
  291.                         distanceMap[y, x] = int.MaxValue;
  292.                     }
  293.                 }
  294.             }
  295.  
  296.             int[] dx = { 0, 0, 1, -1 };
  297.             int[] dy = { 1, -1, 0, 0 };
  298.  
  299.             while (queue.Count > 0)
  300.             {
  301.                 Vector2Int current = queue.Dequeue();
  302.                 for (int i = 0; i < 4; i++)
  303.                 {
  304.                     int nx = current.x + dx[i];
  305.                     int ny = current.y + dy[i];
  306.  
  307.                     if (nx >= 0 && nx < resolution && ny >= 0 && ny < resolution && distanceMap[ny, nx] == int.MaxValue)
  308.                     {
  309.                         int newDist = distanceMap[current.y, current.x] + 1;
  310.                         distanceMap[ny, nx] = newDist;
  311.                         queue.Enqueue(new Vector2Int(nx, ny));
  312.                         if (newDist > maxDistance) maxDistance = newDist;
  313.                     }
  314.                 }
  315.             }
  316.             return (distanceMap, maxDistance);
  317.         }
  318.  
  319.         /// <summary>
  320.         /// Calculates temperature based on distance from sea (base temp) and altitude (cooling factor).
  321.         /// </summary>
  322.         private static float[,] CalculateTemperatureMap(float[,] heightMap, int[,] distanceMap, int maxDistance, int resolution, float altitudeInfluence, float distanceInfluence, float normalizedSeaLevel)
  323.         {
  324.             float[,] temperatureMap = new float[resolution, resolution];
  325.             float invMaxDist = 1.0f / maxDistance;
  326.  
  327.             for (int y = 0; y < resolution; y++)
  328.             {
  329.                 for (int x = 0; x < resolution; x++)
  330.                 {
  331.                     if (heightMap[y, x] <= normalizedSeaLevel)
  332.                     {
  333.                         temperatureMap[y, x] = 0.5f; // Sea has a constant, moderate temperature
  334.                         continue;
  335.                     }
  336.  
  337.                     // Start with a base temperature of 1.0 (max heat)
  338.                     float temperature = 1.0f;
  339.  
  340.                     // 1. Maritime Cooling Effect: areas near the sea are cooler.
  341.                     // The effect diminishes as we move inland.
  342.                     float normalizedDistance = distanceMap[y, x] * invMaxDist;
  343.                     float seaCooling = (1.0f - normalizedDistance) * distanceInfluence;
  344.                     temperature -= seaCooling;
  345.  
  346.                     // 2. Altitude Cooling Effect (Lapse Rate): higher means colder.
  347.                     float altitudeCooling = heightMap[y, x] * altitudeInfluence;
  348.                     temperature -= altitudeCooling;
  349.  
  350.                     temperatureMap[y, x] = Mathf.Clamp01(temperature);
  351.                 }
  352.             }
  353.             return temperatureMap;
  354.         }
  355.  
  356.         /// <summary>
  357.         /// Calculates humidity based on distance from the sea and modified by altitude.
  358.         /// </summary>
  359.         private static float[,] CalculateHumidityMap(float[,] heightMap, int[,] distanceMap, int maxDistance, int resolution, AnimationCurve humidityFalloff, float normalizedSeaLevel)
  360.         {
  361.             float[,] humidityMap = new float[resolution, resolution];
  362.             float invMaxDist = 1.0f / maxDistance;
  363.  
  364.             for (int y = 0; y < resolution; y++)
  365.             {
  366.                 for (int x = 0; x < resolution; x++)
  367.                 {
  368.                     // 1. Water has maximum humidity
  369.                     if (heightMap[y, x] <= normalizedSeaLevel)
  370.                     {
  371.                         humidityMap[y, x] = 1.0f;
  372.                         continue;
  373.                     }
  374.  
  375.                     // 2. Base humidity from distance, controlled by the curve
  376.                     float normalizedDistance = distanceMap[y, x] * invMaxDist;
  377.                     float baseHumidity = humidityFalloff.Evaluate(normalizedDistance);
  378.  
  379.                     // 3. Lower areas are more humid, higher areas are drier
  380.                     float altitudeModifier = 1.0f - heightMap[y, x];
  381.  
  382.                     // 4. Final humidity is a product of the two factors
  383.                     float finalHumidity = baseHumidity * altitudeModifier;
  384.  
  385.                     humidityMap[y, x] = Mathf.Clamp01(finalHumidity);
  386.                 }
  387.             }
  388.             return humidityMap;
  389.         }
  390.  
  391.         /// <summary>
  392.         /// Generates a 2D noise map used to break ties between valid biomes.
  393.         /// </summary>
  394.         public static float[,] GenerateControlMap(int resolution, NoiseParameters settings, int posterizeLevels)
  395.         {
  396.             NativeArray<float> controlMapNative = new NativeArray<float>(resolution * resolution, Allocator.TempJob);
  397.  
  398.             GenerateControlMapJob job = new GenerateControlMapJob
  399.             {
  400.                 ControlMap = controlMapNative,
  401.                 Settings = settings,
  402.                 Resolution = resolution,
  403.                 PosterizeLevels = posterizeLevels
  404.             };
  405.  
  406.             JobHandle handle = job.Schedule(resolution * resolution, 64);
  407.             handle.Complete();
  408.  
  409.             float[,] managedMap = new float[resolution, resolution];
  410.             for (int i = 0; i < controlMapNative.Length; i++)
  411.             {
  412.                 int y = i / resolution;
  413.                 int x = i % resolution;
  414.                 managedMap[y, x] = controlMapNative[i];
  415.             }
  416.  
  417.             controlMapNative.Dispose();
  418.             return managedMap;
  419.         }
  420.  
  421.         #endregion
  422.  
  423.         #region Conflict Identification
  424.  
  425.         private static Dictionary<int, List<int>> IdentifyConflictGroups(List<BiomeConfig> biomeConfigs)
  426.         {
  427.             var conflictGroups = new Dictionary<int, List<int>>();
  428.  
  429.             for (int i = 0; i < biomeConfigs.Count; i++)
  430.             {
  431.                 for (int j = i + 1; j < biomeConfigs.Count; j++)
  432.                 {
  433.                     if (RangesOverlap(biomeConfigs[i], biomeConfigs[j]))
  434.                     {
  435.                         // Found an overlap. Find the group they belong to or create a new one.
  436.                         int groupKeyA = FindGroupKey(i, conflictGroups);
  437.                         int groupKeyB = FindGroupKey(j, conflictGroups);
  438.  
  439.                         if (groupKeyA != -1 && groupKeyB != -1 && groupKeyA != groupKeyB)
  440.                         {
  441.                             // Merge two existing groups
  442.                             MergeGroups(groupKeyA, groupKeyB, conflictGroups);
  443.                         }
  444.                         else if (groupKeyA != -1)
  445.                         {
  446.                             // Add j to group A
  447.                             conflictGroups[groupKeyA].Add(j);
  448.                         }
  449.                         else if (groupKeyB != -1)
  450.                         {
  451.                             // Add i to group B
  452.                             conflictGroups[groupKeyB].Add(i);
  453.                         }
  454.                         else
  455.                         {
  456.                             // Create a new group with the biome of the lower index as the key
  457.                             int key = Mathf.Min(i, j);
  458.                             conflictGroups[key] = new List<int> { i, j };
  459.                         }
  460.                     }
  461.                 }
  462.             }
  463.             // Ensure all lists in the dictionary are unique and sorted
  464.             foreach (var key in conflictGroups.Keys.ToList())
  465.             {
  466.                 conflictGroups[key] = conflictGroups[key].Distinct().OrderBy(id => id).ToList();
  467.             }
  468.             return conflictGroups;
  469.         }
  470.  
  471.         private static bool RangesOverlap(BiomeConfig a, BiomeConfig b)
  472.         {
  473.             if (a == null || b == null) return false;
  474.             return RangeOverlap1D(a.altitudeRange, b.altitudeRange) &&
  475.                    RangeOverlap1D(a.humidityRange, b.humidityRange) &&
  476.                    RangeOverlap1D(a.temperatureRange, b.temperatureRange) &&
  477.                    RangeOverlap1D(a.slopeRange, b.slopeRange);
  478.         }
  479.  
  480.         private static bool RangeOverlap1D(Vector2 rangeA, Vector2 rangeB)
  481.         {
  482.             return rangeA.x <= rangeB.y && rangeA.y >= rangeB.x;
  483.         }
  484.  
  485.         private static int FindGroupKey(int biomeIndex, Dictionary<int, List<int>> groups)
  486.         {
  487.             foreach (var kvp in groups)
  488.             {
  489.                 if (kvp.Value.Contains(biomeIndex)) return kvp.Key;
  490.             }
  491.             return -1;
  492.         }
  493.  
  494.         private static void MergeGroups(int keyA, int keyB, Dictionary<int, List<int>> groups)
  495.         {
  496.             int primaryKey = Mathf.Min(keyA, keyB);
  497.             int secondaryKey = Mathf.Max(keyA, keyB);
  498.  
  499.             groups[primaryKey].AddRange(groups[secondaryKey]);
  500.             groups.Remove(secondaryKey);
  501.         }
  502.  
  503.         #endregion
  504.  
  505.         #region Biome Selection
  506.         /********************************************************************************************************
  507.          * Ora il Sistema Funziona Così ✅
  508.          *
  509.          *       Biomi specifici (1+) competono tra loro normalmente
  510.          *       Bioma 0 viene usato solo quando NESSUN bioma specifico è valido
  511.          *       Le regole di competizione (2 biomi = blend, 3+ = noise) funzionano sui biomi veri
  512.          *       Niente più aree viola indesiderate!
  513.          *      
  514.          ********************************************************************************************************/
  515.  
  516.         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)
  517.         {
  518.             int[,] biomeMap = new int[resolution, resolution];
  519.  
  520.             for (int y = 0; y < resolution; y++)
  521.             {
  522.                 for (int x = 0; x < resolution; x++)
  523.                 {
  524.                     List<int> validBiomes = GetValidBiomesAt(x, y, heightMap, tempMap, humidityMap, slopeMap, biomeConfigs);
  525.  
  526.                     // IMPORTANTE: Escludiamo il bioma 0 (fallback universale) dalla competizione
  527.                     List<int> specificBiomes = validBiomes.Where(b => b > 0).ToList();
  528.  
  529.                     if (specificBiomes.Count == 0)
  530.                     {
  531.                         // Nessun bioma specifico è valido, usa il fallback universale (bioma 0)
  532.                         biomeMap[y, x] = 0;
  533.                     }
  534.                     else if (specificBiomes.Count == 1)
  535.                     {
  536.                         // Un solo bioma specifico è valido
  537.                         biomeMap[y, x] = specificBiomes[0];
  538.                     }
  539.                     else
  540.                     {
  541.                         // Più biomi specifici sono validi, risolvi la competizione
  542.                         biomeMap[y, x] = ResolveCompetition(specificBiomes, x, y, biomeNoiseMaps, conflictGroups);
  543.                     }
  544.                 }
  545.             }
  546.             return biomeMap;
  547.         }
  548.  
  549.         private static List<int> GetValidBiomesAt(int x, int y, float[,] heightMap, float[,] tempMap, float[,] humidityMap, float[,] slopeMap, List<BiomeConfig> biomeConfigs)
  550.         {
  551.             List<int> validIndices = new List<int>();
  552.             for (int i = 0; i < biomeConfigs.Count; i++)
  553.             {
  554.                 if (IsBiomeValidAt(biomeConfigs[i], heightMap[y, x], tempMap[y, x], humidityMap[y, x], slopeMap[y, x]))
  555.                 {
  556.                     validIndices.Add(i);
  557.                 }
  558.             }
  559.             return validIndices;
  560.         }
  561.  
  562.         private static int ResolveCompetition(List<int> competingIndices, int x, int y, Dictionary<int, float[,]> noiseMaps, Dictionary<int, List<int>> conflictGroups)
  563.         {
  564.             // SAFETY CHECK: Non dovremmo mai arrivare qui con una lista vuota (ma per sicurezza)
  565.             if (competingIndices == null || competingIndices.Count == 0)
  566.             {
  567.                 Debug.LogError($"ERRORE: ResolveCompetition chiamato con lista vuota a ({x}, {y})!");
  568.                 return 1; // Fallback al bioma 1 invece che 0
  569.             }
  570.  
  571.             if (competingIndices.Count == 1)
  572.             {
  573.                 return competingIndices[0];
  574.             }
  575.  
  576.             // Verifica se almeno un bioma appartiene a un gruppo di conflitto
  577.             int groupKey = -1;
  578.             List<int> groupMembers = null;
  579.  
  580.             foreach (int biomeIndex in competingIndices)
  581.             {
  582.                 groupKey = FindGroupKey(biomeIndex, conflictGroups);
  583.                 if (groupKey != -1)
  584.                 {
  585.                     groupMembers = conflictGroups[groupKey];
  586.                     break;
  587.                 }
  588.             }
  589.  
  590.             // CASO 1: Nessun bioma appartiene a gruppi di conflitto
  591.             // Regola semplice: vince quello con l'indice più alto
  592.             if (groupKey == -1)
  593.             {
  594.                 return competingIndices.Max();
  595.             }
  596.  
  597.             // CASO 2: Almeno un bioma appartiene a un gruppo di conflitto
  598.             // Separiamo i biomi in due categorie
  599.             List<int> biomesInGroup = competingIndices.Where(b => groupMembers.Contains(b)).ToList();
  600.             List<int> biomesOutsideGroup = competingIndices.Where(b => !groupMembers.Contains(b)).ToList();
  601.  
  602.             // Se ci sono biomi fuori dal gruppo, hanno priorità automatica (vince il più alto)
  603.             if (biomesOutsideGroup.Count > 0)
  604.             {
  605.                 return biomesOutsideGroup.Max();
  606.             }
  607.  
  608.             // Tutti i biomi appartengono al gruppo di conflitto
  609.             biomesInGroup.Sort();
  610.  
  611.             // REGOLA: Se solo 2 biomi nel gruppo, vince quello con indice più alto (blend)
  612.             if (biomesInGroup.Count == 2)
  613.             {
  614.                 return biomesInGroup.Max();
  615.             }
  616.  
  617.             // REGOLA: 3+ biomi nel gruppo, usa il noise per scegliere
  618.             // Il bioma con l'indice più basso nel gruppo è il fallback per questo gruppo
  619.             int fallbackBiomeIndex = biomesInGroup[0];
  620.             int winningBiomeIndex = fallbackBiomeIndex;
  621.             float highestNoiseValue = -1f;
  622.  
  623.             // Controlla tutti i competitor per trovare quello con noise più alto
  624.             foreach (int competitorIndex in biomesInGroup)
  625.             {
  626.                 float noiseValue = noiseMaps[competitorIndex][y, x];
  627.  
  628.                 if (noiseValue > NOISE_THRESHOLD && noiseValue > highestNoiseValue)
  629.                 {
  630.                     highestNoiseValue = noiseValue;
  631.                     winningBiomeIndex = competitorIndex;
  632.                 }
  633.             }
  634.  
  635.             return winningBiomeIndex;
  636.         }
  637.  
  638.         #endregion
  639.  
  640.         #region Helpers
  641.  
  642.         private static bool IsBiomeValidAt(BiomeConfig biome, float height, float temp, float humidity, float slope)
  643.         {
  644.             if (biome == null) return false;
  645.             return temp >= biome.temperatureRange.x && temp <= biome.temperatureRange.y &&
  646.                    humidity >= biome.humidityRange.x && humidity <= biome.humidityRange.y &&
  647.                    height >= biome.altitudeRange.x && height <= biome.altitudeRange.y &&
  648.                    slope >= biome.slopeRange.x && slope <= biome.slopeRange.y;
  649.         }
  650.  
  651.         private static Dictionary<int, float[,]> GenerateBiomeNoiseMaps(int resolution, List<BiomeConfig> biomeConfigs, NoiseParameters baseNoise)
  652.         {
  653.             Dictionary<int, float[,]> noiseMaps = new Dictionary<int, float[,]>();
  654.             for (int i = 0; i < biomeConfigs.Count; i++)
  655.             {
  656.                 NoiseParameters biomeNoise = baseNoise;
  657.                 biomeNoise.seed += i;
  658.                 noiseMaps[i] = GenerateSingleNoiseMap(resolution, biomeNoise);
  659.             }
  660.             return noiseMaps;
  661.         }
  662.  
  663.         private static float[,] GenerateSingleNoiseMap(int resolution, NoiseParameters settings)
  664.         {
  665.             NativeArray<float> noiseMapNative = new NativeArray<float>(resolution * resolution, Allocator.TempJob);
  666.             GenerateControlMapJob job = new GenerateControlMapJob { ControlMap = noiseMapNative, Settings = settings, Resolution = resolution, PosterizeLevels = 0 };
  667.             JobHandle handle = job.Schedule(resolution * resolution, 64);
  668.             handle.Complete();
  669.             float[,] managedMap = new float[resolution, resolution];
  670.             for (int i = 0; i < noiseMapNative.Length; i++)
  671.             {
  672.                 int y = i / resolution;
  673.                 int x = i % resolution;
  674.                 managedMap[y, x] = noiseMapNative[i];
  675.             }
  676.             noiseMapNative.Dispose();
  677.             return managedMap;
  678.         }
  679.  
  680.         private static float FindMaxAltitude(float[,] heightMap)
  681.         {
  682.             float maxAlt = float.MinValue;
  683.             int resolution = heightMap.GetLength(0);
  684.             for (int z = 0; z < resolution; z++)
  685.             {
  686.                 for (int x = 0; x < resolution; x++)
  687.                 {
  688.                     if (heightMap[z, x] > maxAlt)
  689.                     {
  690.                         maxAlt = heightMap[z, x];
  691.                     }
  692.                 }
  693.             }
  694.             return maxAlt;
  695.         }
  696.  
  697.         /// <summary>
  698.         /// Applies posterization to all values in a 2D map.
  699.         /// </summary>
  700.         private static void PosterizeMap(float[,] map, int levels)
  701.         {
  702.             if (levels <= 1) return;
  703.             int resolution = map.GetLength(0);
  704.             for (int y = 0; y < resolution; y++)
  705.             {
  706.                 for (int x = 0; x < resolution; x++)
  707.                 {
  708.                     map[y, x] = Posterize(map[y, x], levels);
  709.                 }
  710.             }
  711.         }
  712.  
  713.         private static float Posterize(float value, int levels)
  714.         {
  715.             if (levels <= 1) return value;
  716.             // Multiply by (levels - 1), round, and divide by (levels - 1)
  717.             // to map the value to one of the discrete steps (e.g., 0, 0.25, 0.5, 0.75, 1 for 5 levels)
  718.             float scaledValue = value * (levels - 1);
  719.             float roundedValue = Mathf.Round(scaledValue);
  720.             return roundedValue / (float)(levels - 1);
  721.         }
  722.  
  723.         #endregion
  724.  
  725.         [BurstCompile]
  726.         private struct GenerateControlMapJob : IJobParallelFor
  727.         {
  728.             public NativeArray<float> ControlMap;
  729.             public int PosterizeLevels;
  730.  
  731.             [ReadOnly] public NoiseParameters Settings;
  732.             public int Resolution;
  733.  
  734.             public void Execute(int index)
  735.             {
  736.                 int y = index / Resolution;
  737.                 int x = index % Resolution;
  738.  
  739.                 float maxPossibleValue = 0;
  740.                 float currentAmplitude = Settings.amplitude;
  741.                 for (int i = 0; i < Settings.octaves; i++)
  742.                 {
  743.                     maxPossibleValue += currentAmplitude;
  744.                     currentAmplitude *= Settings.persistence;
  745.                 }
  746.                 float inverseMaxRange = 1f / math.max(0.0001f, maxPossibleValue * 2f);
  747.  
  748.                 float amplitude = Settings.amplitude;
  749.                 float frequency = Settings.frequency;
  750.                 float noiseHeight = 0;
  751.                 float2 seedOffset = Settings.GetSeedOffset();
  752.                 float halfRes = Resolution / 2f;
  753.  
  754.                 for (int i = 0; i < Settings.octaves; i++)
  755.                 {
  756.                     float sampleX = (x - halfRes) / (float)Resolution * frequency + seedOffset.x + Settings.offset.x;
  757.                     float sampleY = (y - halfRes) / (float)Resolution * frequency + seedOffset.y + Settings.offset.y;
  758.  
  759.                     float perlinValue = noise.snoise(new float2(sampleX, sampleY));
  760.                     noiseHeight += perlinValue * amplitude;
  761.  
  762.                     amplitude *= Settings.persistence;
  763.                     frequency *= Settings.lacunarity;
  764.                 }
  765.  
  766.                 float normalizedValue = (noiseHeight + maxPossibleValue) * inverseMaxRange;
  767.                 normalizedValue = math.saturate(normalizedValue);
  768.  
  769.                 if (PosterizeLevels > 1)
  770.                 {
  771.                     float scaledValue = normalizedValue * (PosterizeLevels - 1);
  772.                     float roundedValue = math.round(scaledValue);
  773.                     normalizedValue = roundedValue / (float)(PosterizeLevels - 1);
  774.                 }
  775.  
  776.                 ControlMap[index] = math.saturate(normalizedValue);
  777.             }
  778.         }
  779.     }
  780. }
Advertisement
Add Comment
Please, Sign In to add comment