-- NOTE the API of this file should probably change? local lume = require "lib.lume" local simplex_map = require "lib.simplex_map" local biomes = { -- biomes are checked in order, with the first candidate always being selected [800] = { name = "sea ice", altitude = {min = -math.huge, max = 0}, temperature = {min = -math.huge, max = -2}, rainfall = {min = -math.huge, max = math.huge}, color = {0.6, 0.6, 1, 1}, }, [900] = { name = "deep ocean", altitude = {min = -math.huge, max = -5000}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {0, 0, 0.25, 1}, }, [1000] = { name = "ocean (definition, 0 meters)", altitude = {min = -math.huge, max = 0}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {0, 0, 0.5, 1}, }, [1500] = { name = "frozen (definition, 0 C)", altitude = {min = -math.huge, max = math.huge}, temperature = {min = -math.huge, max = 0}, rainfall = {min = -math.huge, max = math.huge}, color = {1, 1, 1, 1}, }, [2000] = { name = "snowline (middle estimate)", altitude = {min = 4000, max = math.huge}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {1, 1, 1, 1}, }, [2100] = { name = "alpine zone", altitude = {min = 3500, max = math.huge}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {0.8, 0.8, 0.8, 1}, }, [2200] = { name = "subalpine zone", altitude = {min = 3000, max = math.huge}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {0.67, 0.67, 0.67, 1}, }, [2300] = { name = "montane zone", altitude = {min = 2500, max = math.huge}, temperature = {min = -math.huge, max = math.huge}, rainfall = {min = -math.huge, max = math.huge}, color = {0.67, 0.67, 0.5, 1}, }, } local generate = function() local map = {} map.size = 500 map.tile_size = 1 -- TODO tile_size really shouldn't be the responsibility of the map generator - it's a rendering detail, not a map feature map.altitude = simplex_map.generate{ -- will be converted to meters size = map.size, scale = 0.001, min = 0, max = 1, octaves = 7, lacunarity = 2, gain = 0.5, } map.temperature = simplex_map.generate{ -- in celsius -- -20 to 45 chosen by looking at the temperature range shown on https://openclimatemap.org/ size = map.size, scale = 0.005, min = 20, max = 65, -- approximate target extremes of -89.2 to 56.7 octaves = 2, lacunarity = 2, gain = 0.5, } map.rainfall = simplex_map.generate{ -- in millimeters of rainfall size = map.size, scale = 0.005, -- min = 18, max = 3240, octaves = 1, lacunarity = 2, gain = 0.5, } local function earthlike_altitude_adjustment(altitude) -- TODO replace with a simpler function? return 6099 - 114909 * altitude + 830891 * altitude^2 - 2.72e6 * altitude^3 + 4.19e6 * altitude^4 - 3.02e6 * altitude^5 + 815866 * altitude^6 end for x = 1, map.size do for y = 1, map.size do map.altitude[x][y] = earthlike_altitude_adjustment(map.altitude[x][y]) end end map.altitude.min = simplex_map.min(map.altitude) map.altitude.max = simplex_map.max(map.altitude) local biome_order = {} for order in pairs(biomes) do biome_order[#biome_order + 1] = order end table.sort(biome_order) local radius_squared = (map.size / 2)^2 map.biome = {} for x = 1, map.size do map.biome[x] = {} for y = 1, map.size do if lume.distance(x, y, map.size / 2, map.size / 2, true) < radius_squared then local altitude = map.altitude[x][y] local temperature = map.temperature[x][y] local rainfall = map.rainfall[x][y] -- modify temperature based on altitude if altitude > 0 then temperature = temperature - 0.0065 * altitude -- -6.5 C / km else temperature = temperature + altitude / 1000 -- -1 C / km (deep water adjustment) end -- modify temperature by latitude local polar_distance = math.abs(y - (map.size / 2))^1.3 -- experimenting with slight non-linearity local max_temperature_delta = 10 temperature = temperature - (polar_distance / map.size * 2 * max_temperature_delta) -- 'save' temperature changes map.temperature[x][y] = temperature -- choose biome for _, order in ipairs(biome_order) do local biome = biomes[order] if altitude >= biome.altitude.min and altitude <= biome.altitude.max then if temperature >= biome.temperature.min and temperature <= biome.temperature.max then if rainfall >= biome.rainfall.min and rainfall <= biome.rainfall.max then map.biome[x][y] = biome.color break end end end end else -- clear non-planet zones map.altitude[x][y] = nil map.temperature[x][y] = nil map.rainfall[x][y] = nil end end end return map end return { generate = generate, }