/// <summary> /// Generate rain shadow for given latitude line /// </summary> protected void GenerateShadowLine(int y) { var currentRain = this.InitialRainAmount; var sourceRange = new Range(this.Elevation.SeaThreshold, this.Elevation.LandThreshold); var destRange = new Range(this.MinRainLoss, this.MaxRainLoss); for (var ix = 0; ix < this.Dimensions.Width; ++ix) { // Retrieve elevation var elevation = this.Elevation[ix, y]; // Are we over water? if (elevation < this.Elevation.SeaThreshold) { // Accumulate more rain by evaporation of ocean water currentRain *= this.RainGain; currentRain = Math.Clamp(currentRain, this.MinRainAmount, this.MaxRainAmount); } else if (elevation <= this.Elevation.LandThreshold) // Are we over land? { // Percentage of how much water is lost from the cloud and rained down var factor = MathUtil.Map(elevation, sourceRange, destRange); var loss = currentRain * factor; currentRain *= 1.0f - factor; this.RainShadow[ix, y] = loss; // This isnt realistic, but to make maps less weird looking we add a fixed amount of water to the cloud // each tile it travels. currentRain += this.FlatRainGain; if (currentRain < this.MinRainAmount) { currentRain = this.MinRainAmount; } } else { // Loose rain at max rate var loss = currentRain * this.MountainRainLoss; currentRain *= 1.0f - this.MountainRainLoss; this.RainShadow[ix, y] = loss; // DEBUG remove this, because it skews the threshold calculations if (currentRain < this.MinRainAmount) { currentRain = this.MinRainAmount; } } } }
/// <summary> /// Map all biomes. /// </summary> protected void MapBiomes() { var rng = new Random(this.Seed + 13185); // Chance ranges for broad leaf forest generation, pulled out of the loops to avoid repeated heap allocations //var sourceRange = new Range(this.Temperature.ColderThreshold, this.Temperature.ColdThreshold); // Alternative: Source range starting at 0.0f. Causes forests to almost never be 100% coniferous var coniferousSrcRange = new Range(this.Temperature.ColdestThreshold, this.Temperature.ColdThreshold); var destRange = new Range(0.0f, 1.0f); var difference = this.Temperature.WarmThreshold - this.Temperature.ColdThreshold; var jungleSrcRange = new Range(this.Temperature.ColdThreshold + (difference / 2), this.Temperature.WarmThreshold); var climateZoneSrcRange = jungleSrcRange; for (var ix = 0; ix < this.Dimensions.Width; ++ix) { for (var iy = 0; iy < this.Dimensions.Height; ++iy) { var elevation = this.Elevation.HeightLevels[ix, iy]; var drainage = this.Drainage[ix, iy]; var temperature = this.Temperature.TemperatureLevels[ix, iy]; var rawTemperature = this.Temperature[ix, iy]; var rainfall = this.Rainfall[ix, iy]; // Determine rough climate zone. var climateZone = ClimateZone.Temperate; // Warmer and warmest temperatures are always tropical if (temperature == TemperatureLevel.Warmer || temperature == TemperatureLevel.Warmest) { climateZone = ClimateZone.Tropical; } else if (temperature == TemperatureLevel.Warm) // Smoothly transition in warm temperature zone { var tropicalChance = MathUtil.Map(rawTemperature, climateZoneSrcRange, destRange); if (rng.NextDouble() <= tropicalChance) { climateZone = ClimateZone.Tropical; } } var type = TerrainType.Unknown; if (elevation == HeightLevel.Sea) { if (temperature == TemperatureLevel.Coldest) { type = TerrainType.Glacier; } else if (temperature == TemperatureLevel.Colder) { type = TerrainType.SeaIce; } else { type = TerrainType.Ocean; } } else if (elevation == HeightLevel.Land) { if (temperature == TemperatureLevel.Coldest) { type = TerrainType.Glacier; } else if (temperature == TemperatureLevel.Colder) { type = TerrainType.Tundra; } else { // Very dry region if (rainfall < 0.1f) { if (climateZone == ClimateZone.Tropical) { // Tropical zones have little bad lands, but lots of deserts and rocky // wastelands if (drainage < 0.85f) { type = TerrainType.SandDesert; } else { type = TerrainType.RockyWasteland; } } else { // Temperate regions have no deserts if (drainage < 0.65f) { type = TerrainType.RockyWasteland; } else { type = TerrainType.BadLands; } } } else if (rainfall < 0.66f) { if (climateZone == ClimateZone.Tropical) { // Since evaporation is high, even moderately high rainfall levels will still result // in arid biomes if (rainfall < 0.30f) { type = TerrainType.SandDesert; } else if (rainfall < 0.45f) { if (drainage < 0.5) { type = TerrainType.Steppe; } else { type = TerrainType.HillySteppe; } } else if (rainfall < 0.55f) { if (drainage < 0.5) { type = TerrainType.SavannaDry; } else { type = TerrainType.HillsDry; } } else { if (drainage < 0.5) { type = TerrainType.Savanna; } else { type = TerrainType.HillySavanna; } } } else // Temperate climate { // In a temperate climate, evaporation is low. Thus, only areas with very little // rainfall will develop arid biomes if (rainfall < 0.20f) { if (drainage < 0.5) { type = TerrainType.ShrublandDry; } else { type = TerrainType.HillsDry; } } else if (rainfall < 0.40f) { if (drainage < 0.5) { type = TerrainType.Shrubland; } else { type = TerrainType.Hills; } } else if (rainfall > 0.55 && drainage < 0.15f) { type = TerrainType.Marsh; } else { if (drainage < 0.5) { type = TerrainType.Grassland; } else { type = TerrainType.HillyGrassland; } } } } // Grasslands region /*else if (rainfall < 0.2f) * { * if (drainage < 0.5f) * { * type = TerrainType.Grassland; * } * else * { * type = TerrainType.Hills; * } * } * // Savanna region * else if (rainfall < 0.33f) * { * if (drainage < 0.5f) * { * type = TerrainType.Savanna; * } * else * { * type = TerrainType.HillsDry; * } * } * // Marsh, shrubland region * else if (rainfall < 0.66f) * { * if (drainage < 0.12f) * type = TerrainType.Marsh; * else if (drainage < 0.5f) * { * if (temperature == TemperatureLevel.Warmer || * temperature == TemperatureLevel.Warmest) * type = TerrainType.ShrublandDry; * else * type = TerrainType.Shrubland; * } * else * { * if (temperature == TemperatureLevel.Warmer || * temperature == TemperatureLevel.Warmest) * type = TerrainType.HillsDry; * else * type = TerrainType.Hills; * } * }*/ // Forest/Swamp region else { if (drainage < 0.12f /*0.10f*/) { type = TerrainType.Swamp; } else { if (temperature == TemperatureLevel.Warmer || temperature == TemperatureLevel.Warmest) { type = TerrainType.TropicalBroadleafForest; } else if (temperature == TemperatureLevel.Warm) { var jungleChance = MathUtil.Map(rawTemperature, jungleSrcRange, destRange); if (rng.NextDouble() <= jungleChance) { type = TerrainType.TropicalBroadleafForest; } else { type = TerrainType.TemperateBroadleafForest; } } else { // In colder climates, we use a scaling chance of generation broad leaf forests // to smoothly transition from temperate zones with broad leaf forests to // more colder zones with coniferous forests var broadleafChance = MathUtil.Map(rawTemperature, coniferousSrcRange, destRange); // broadleafChance *= broadleafChance; if (rng.NextDouble() <= broadleafChance) { type = TerrainType.TemperateBroadleafForest; } else { type = TerrainType.ConiferousForest; } } } } } } else if (elevation == HeightLevel.LowMountain) { type = TerrainType.MountainsLow; } else if (elevation == HeightLevel.MediumMountain) { type = TerrainType.MountainsMed; } else if (elevation == HeightLevel.HighMountain) { type = TerrainType.MountainsHigh; } else if (elevation == HeightLevel.MountainPeak) { type = TerrainType.MountainPeak; } this.TerrainTypes[ix, iy] = type; } } }