private float FindBlendParameters(Vector3 pos, out ClimateParameters src, out ClimateParameters dst) { if (climateLookup == null) { climateLookup = new ClimateParameters[4] { Arid, Temperate, Tundra, Arctic }; } if (TerrainMeta.BiomeMap == null) { src = Temperate; dst = Temperate; return(0.5f); } int biomeMaxType = TerrainMeta.BiomeMap.GetBiomeMaxType(pos); int biomeMaxType2 = TerrainMeta.BiomeMap.GetBiomeMaxType(pos, ~biomeMaxType); src = climateLookup[TerrainBiome.TypeToIndex(biomeMaxType)]; dst = climateLookup[TerrainBiome.TypeToIndex(biomeMaxType2)]; return(TerrainMeta.BiomeMap.GetBiome(pos, biomeMaxType2)); }
public void Step( ClimateParameters parameters ) { HexAdjacencyGraph adjacencyGraph = _hexMap.AdjacencyGraph; // Initialize the next climate step. ClimateData[] nextClimates = new ClimateData[ClimateArray.Length]; // Populate the next climate step. for (int i = 0; i < ClimateArray.Length; i++) { nextClimates[i] = new ClimateData(); } // For each hex cell... for (int i = 0; i < _hexMap.SizeSquared; i++) { Hex source = _hexMap.GetHex(i); List <HexEdge> adjacentEdges = adjacencyGraph.GetOutEdgesList(source); // Get the next climate step for that cell. StepClimate( ClimateArray, ref nextClimates, source, adjacentEdges, parameters ); source.clouds = nextClimates[source.Index].clouds; source.moisture = nextClimates[source.Index].moisture; source.temperature = nextClimates[source.Index].clouds; } ClimateArray = nextClimates; }
public void RefreshTerrainTypes( ClimateParameters parameters, RiverDigraph riverDigraph ) { int temperatureJitterChannel = Random.Range(0, 4); int rockDesertElevation = parameters.elevationMax - (parameters.elevationMax - parameters.waterLevel) / 2; Holdridge holdridge = new Holdridge(); foreach (Hex hex in _hexMap.Hexes) { float temperature = ClimateArray[hex.Index].temperature; float moisture = ClimateArray[hex.Index].moisture; if (!hex.IsUnderwater) { hex.HoldrigeZone = holdridge.GetHoldridgeZone( temperature, moisture ); continue; int temperatureBand = 0; for ( ; temperatureBand < temperatureBands.Length; temperatureBand++ ) { if (temperature < temperatureBands[temperatureBand]) { break; } } int moistureBand = 0; for (; moistureBand < moistureBands.Length; moistureBand++) { if (moisture < moistureBands[moistureBand]) { break; } } Biome hexBiome = biomes[temperatureBand * 4 + moistureBand]; if (hexBiome.terrain == Terrains.Desert) { if (hex.elevation >= rockDesertElevation) { hexBiome.terrain = Terrains.Stone; } } else if (hex.elevation == parameters.elevationMax) { hexBiome.terrain = Terrains.Snow; } if (hexBiome.terrain == Terrains.Snow) { hexBiome.plant = 0; } if (hexBiome.plant < 3 && riverDigraph.HasRiver(hex)) { hexBiome.plant += 1; } //hex.Biome = hexBiome; //hex.ClimateData = ClimateArray[hex.Index]; } else { LZone lZone; if (hex.elevation == parameters.waterLevel - 1) { int cliffs = 0; int slopes = 0; List <Hex> neighbors; if (_hexMap.TryGetNeighbors(hex, out neighbors)) { foreach (Hex neighbor in neighbors) { int delta = neighbor.elevation - hex.WaterLevel; if (delta == 0) { slopes += 1; } else if (delta > 0) { cliffs += 1; } } } // More than half neighbors at same level. Inlet or // lake, therefore terrain is grass. if (cliffs + slopes > 3) { lZone = LZone.Steppe; } // More than half cliffs, terrain is stone. else if (cliffs > 0) { lZone = LZone.DesertScb; } // More than half slopes, terrain is beach. else if (slopes > 0) { lZone = LZone.DesertH; } // Shallow non-coast, terrain is grass. else { lZone = LZone.Steppe; } } else if (hex.elevation >= parameters.waterLevel) { lZone = LZone.Steppe; } else if (hex.elevation < 0) { lZone = LZone.DesertH; } else { lZone = LZone.WTundra; } // Coldest temperature band produces mud instead of grass. if ( lZone == LZone.Steppe && temperature < temperatureBands[0] ) { lZone = LZone.WTundra; } //hex.Biome = new Biome(terrain, 0); } } }
private void StepClimate( ClimateData[] currentClimates, ref ClimateData[] nextClimates, Hex source, List <HexEdge> adjacentEdges, ClimateParameters parameters ) { ClimateData currentClimate = currentClimates[source.Index]; // If the tile is water, add clouds equivalent to the evaporation // factor. if (source.IsUnderwater) { currentClimate.moisture = 1f; currentClimate.clouds += parameters.evaporationFactor; } // If the tile is not water, scale evaporation with the moisture // of the tile and create clouds from that. else { float evaporation = currentClimate.moisture * parameters.evaporationFactor; currentClimate.moisture -= evaporation; currentClimate.clouds += evaporation; } // Precipitation is scaled with the number of clouds. float precipitation = currentClimate.clouds * parameters.precipitationFactor; currentClimate.clouds -= precipitation; currentClimate.moisture += precipitation; // As the elevation of the hex approaches the maximum elevation, // the maximum number of clouds decreases. float cloudMaximum = 1f - source.ViewElevation / (parameters.elevationMax + 1f); // If the number of clouds exceeds the cloud maximum, convert // excess clouds to moisture. if (currentClimate.clouds > cloudMaximum) { currentClimate.moisture += currentClimate.clouds - cloudMaximum; currentClimate.clouds = cloudMaximum; } // Get the main cloud dispersal direction. HexDirections mainCloudDispersalDirection = parameters.windDirection.Opposite(); // Get the cloud dispersal magnitude. float cloudDispersalMagnitude = currentClimate.clouds * (1f / (5f + parameters.windStrength)); // Calculate the amount of runoff. float runoff = currentClimate.moisture * parameters.runoffFactor * (1f / 6f); // Calculate the amount of seepage. float seepage = currentClimate.moisture * parameters.seepageFactor * (1f / 6f); // Disperse clouds, runoff, and seepage to adjacent climates. foreach (HexEdge edge in adjacentEdges) { ClimateData neighborClimate = nextClimates[edge.Target.Index]; if (edge.Direction == mainCloudDispersalDirection) { neighborClimate.clouds += cloudDispersalMagnitude * parameters.windStrength; } else { neighborClimate.clouds += cloudDispersalMagnitude; } int elevationDelta = edge.Target.ViewElevation - edge.Source.ViewElevation; if (elevationDelta < 0) { currentClimate.moisture -= runoff; neighborClimate.moisture += runoff; } else if (elevationDelta == 0) { currentClimate.moisture -= seepage; neighborClimate.moisture += seepage; } neighborClimate.moisture = Mathf.Clamp( neighborClimate.moisture, 0f, 1f ); nextClimates[edge.Target.Index] = neighborClimate; } ClimateData nextHexClimate = nextClimates[source.Index]; nextHexClimate.moisture += currentClimate.moisture; // Ensure that no hex can have more moisture than a hex that is // underwater. nextHexClimate.moisture = Mathf.Clamp( nextHexClimate.moisture, 0f, 1f ); nextHexClimate.temperature = GenerateTemperature( source, parameters.hemisphere, parameters.waterLevel, parameters.elevationMax, _temperatureJitterChannel, parameters.temperatureJitter, parameters.lowTemperature, parameters.highTemperature, parameters.hexOuterRadius ); //Store the data for the next climate. nextClimates[source.Index] = nextHexClimate; }
/// <summary> /// Generate a HexMap using the standard RootGen algorithm. /// </summary> /// <param name="config"> /// The configuration data for the map to be generated. /// </param> /// <returns> ///A randomly generated HexMap object. /// </returns> public HexMap GenerateMap( RootGenConfig config, bool editMode = true ) { string diagnostics = "Generate Map Performance Diagnostics\n\n"; HexMap result = HexMap.CreateHexMapGameObject(); int seed; if (config.useFixedSeed) { seed = config.seed; } else { config.seed = Random.Range(0, int.MaxValue); config.seed ^= (int)System.DateTime.Now.Ticks; config.seed ^= (int)Time.time; config.seed &= int.MaxValue; seed = config.seed; } // Snapshot the initial random state before consuming the random // sequence. Random.State snapshot = RandomState.Snapshot(seed); result.Initialize( new Rect(0, 0, config.width, config.height), seed, config.hexSize, config.wrapping, editMode ); foreach (Hex hex in result.Hexes) { hex.WaterLevel = config.waterLevel; } Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); HexMapTectonics hexMapTectonics = new HexMapTectonics( result, config.regionBorder, config.mapBorderX, config.mapBorderZ, config.numRegions ); int landBudget = Mathf.RoundToInt( result.SizeSquared * config.landPercentage * 0.01f ); TectonicParameters tectonicParameters = new TectonicParameters( config.hexSize, config.highRiseProbability, config.jitterProbability, config.sinkProbability, config.elevationMax, config.elevationMin, config.waterLevel, landBudget, config.maximumRegionDensity, config.minimumRegionDensity ); string logString = "Tectonic Statistics\n"; for (int i = 0; i < MAX_TECTONIC_STEPS; i++) { tectonicParameters.LandBudget = hexMapTectonics.Step( tectonicParameters ); //result.Draw(config.hexSize); logString += "Step " + i + ", Land Hexes: " + result.LandHexes.Count + " / Land Budget: " + tectonicParameters.LandBudget + " Total: " + (result.LandHexes.Count + tectonicParameters.LandBudget) + "\n"; if (tectonicParameters.LandBudget == 0) { break; } } RootLog.Log(logString, Severity.Information, "Diagnostics"); // If land budget is greater than 0, all land hexes specified to // be allocated were not allocated successfully. Log a warning, // decrement the remaining land budget from the result, and return // the result as the number of land hexes allocated. if (tectonicParameters.LandBudget > 0) { RootLog.Log( "Failed to use up " + tectonicParameters.LandBudget + " land budget.", Severity.Warning, "MapGenerator" ); } stopwatch.Stop(); diagnostics += "Generate Tectonics: " + stopwatch.Elapsed + "\n"; stopwatch.Start(); HexMapErosion erosion = new HexMapErosion(result); int erodibleHexes = erosion.ErodibleHexes.Count; // Calculate the target number of uneroded hexes. int targetUnerodedHexes = (int)( erodibleHexes * (100 - config.erosionPercentage) * 0.01f ); while (erosion.ErodibleHexes.Count > targetUnerodedHexes) { erosion.Step( config.hexSize ); } stopwatch.Stop(); diagnostics += "Generate Erosion: " + stopwatch.Elapsed + "\n"; stopwatch.Start(); HexMapClimate hexMapClimate = new HexMapClimate( result, config.startingMoisture ); ClimateParameters climateParameters = new ClimateParameters( config.hemisphere, config.windDirection, config.evaporationFactor, config.highTemperature, config.lowTemperature, config.precipitationFactor, config.runoffFactor, config.seepageFactor, config.temperatureJitter, config.windStrength, config.hexSize, config.elevationMax, config.waterLevel ); for (int i = 0; i < config.initialClimateSteps; i++) { hexMapClimate.Step(climateParameters); } List <ClimateData> climates = hexMapClimate.List; stopwatch.Stop(); diagnostics += "Generate Climate: " + stopwatch.Elapsed + "\n"; stopwatch.Start(); HexMapRivers hexMapRivers = new HexMapRivers( result, climates, config.waterLevel, config.elevationMax ); for (int i = 0; i < config.numInitialRivers; i++) { hexMapRivers.StartRiver(); } for (int i = 0; i < config.numInitialRiverSteps; i++) { hexMapRivers.Step( config.waterLevel, config.elevationMax, config.extraLakeProbability, config.hexSize, climates ); } result.RiverDigraph = hexMapRivers.RiverDigraph; stopwatch.Stop(); diagnostics += "Generate Rivers: " + stopwatch.Elapsed + "\n"; stopwatch.Start(); hexMapClimate.RefreshTerrainTypes( climateParameters, result.RiverDigraph ); stopwatch.Stop(); diagnostics += "Assign Terrain Types: " + stopwatch.Elapsed + "\n"; RootLog.Log( diagnostics, Severity.Information, "Diagonstics" ); // Restore the snapshot of the random state taken before consuming // the random sequence. Random.state = snapshot; return(result); }