mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
Goodbye beaches
Replacement biomes are no longer supported. Ocean now go a bit higher, which makes beaches appear naturally. Also removed the old terrain generator which was no longer compatible anyways.
This commit is contained in:
parent
555838e369
commit
3f73c81de3
@ -1,16 +0,0 @@
|
||||
{
|
||||
"chance" : 0,
|
||||
"properties" : [
|
||||
"hot",
|
||||
"ocean"
|
||||
],
|
||||
"minHeight" : -4,
|
||||
"maxHeight" : 6,
|
||||
|
||||
"music" : "Sincerely",
|
||||
|
||||
"ground_structure" : [
|
||||
"3 to 4 cubyz:sand",
|
||||
"1 to 2 cubyz:sandstone"
|
||||
]
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
{
|
||||
"properties" : [
|
||||
"ocean"
|
||||
],
|
||||
"chance" : 0,
|
||||
"minHeight" : -4,
|
||||
"maxHeight" : 6,
|
||||
|
||||
"music" : "GymnopedieNo1",
|
||||
|
||||
"ground_structure" : [
|
||||
"2 to 3 cubyz:gravel"
|
||||
],
|
||||
"structures" : [
|
||||
{
|
||||
"id" : "cubyz:ground_patch",
|
||||
"block" : "cubyz:mossy_cobblestone",
|
||||
"chance" : 0.001,
|
||||
"width" : 6,
|
||||
"variation" : 2,
|
||||
"depth" : 2,
|
||||
"smoothness" : 0.5
|
||||
}
|
||||
]
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
{
|
||||
"properties" : [
|
||||
"cold",
|
||||
"ocean",
|
||||
],
|
||||
"chance" : 0,
|
||||
"minHeight" : -4,
|
||||
"maxHeight" : 6,
|
||||
|
||||
"music" : "GymnopedieNo1",
|
||||
|
||||
"ground_structure" : [
|
||||
"2 to 3 cubyz:cobblestone"
|
||||
],
|
||||
"structures" : [
|
||||
{
|
||||
"id" : "cubyz:ground_patch",
|
||||
"block" : "cubyz:mossy_cobblestone",
|
||||
"chance" : 0.004,
|
||||
"width" : 6,
|
||||
"variation" : 2,
|
||||
"depth" : 2,
|
||||
"smoothness" : 0.2
|
||||
},
|
||||
{
|
||||
"id" : "cubyz:ground_patch",
|
||||
"block" : "cubyz:gravel",
|
||||
"chance" : 0.002,
|
||||
"width" : 6,
|
||||
"variation" : 2,
|
||||
"depth" : 2,
|
||||
"smoothness" : 0.2
|
||||
}
|
||||
]
|
||||
}
|
@ -20,7 +20,7 @@ const Vec2f = vec.Vec2f;
|
||||
|
||||
// Generates the climate map using a fluidynamics simulation, with a circular heat distribution.
|
||||
|
||||
pub const id = "cubyz:polar_circles"; // TODO
|
||||
pub const id = "cubyz:noise_based_voronoi";
|
||||
|
||||
pub fn init(parameters: JsonElement) void {
|
||||
_ = parameters;
|
@ -1,240 +0,0 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const main = @import("root");
|
||||
const Array2D = main.utils.Array2D;
|
||||
const random = main.random;
|
||||
const JsonElement = main.JsonElement;
|
||||
const terrain = main.server.terrain;
|
||||
const ClimateMapFragment = terrain.ClimateMap.ClimateMapFragment;
|
||||
const noise = terrain.noise;
|
||||
const FractalNoise = noise.FractalNoise;
|
||||
const RandomlyWeightedFractalNoise = noise.RandomlyWeightedFractalNoise;
|
||||
const PerlinNoise = noise.PerlinNoise;
|
||||
const Biome = terrain.biomes.Biome;
|
||||
|
||||
// Generates the climate map using a fluidynamics simulation, with a circular heat distribution.
|
||||
|
||||
pub const id = "cubyz:polar_circles";
|
||||
|
||||
pub fn init(parameters: JsonElement) void {
|
||||
_ = parameters;
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
|
||||
}
|
||||
|
||||
// Constants that define how the climate map is generated. TODO: Change these depending on the world.
|
||||
const oceanThreshold: f32 = 0.5;
|
||||
const mountainRatio: f32 = 0.8;
|
||||
const mountainPower: f32 = 3;
|
||||
const icePoint: f32 = -0.5;
|
||||
const frostPoint: f32 = -0.35;
|
||||
const hotPoint: f32 = 0.5;
|
||||
const dryPoint: f32 = 0.35;
|
||||
const wetPoint: f32 = 0.65;
|
||||
|
||||
const ringSize: f32 = 64;
|
||||
const windSpeed: f32 = 1;
|
||||
const windInfluence: f32 = 0.1;
|
||||
|
||||
pub fn generateMapFragment(map: *ClimateMapFragment, worldSeed: u64) Allocator.Error!void {
|
||||
const mapSize = ClimateMapFragment.mapSize;
|
||||
const biomeSize = terrain.SurfaceMap.MapFragment.biomeSize;
|
||||
// Create the surrounding height and wind maps needed for wind propagation:
|
||||
const heightMap = try Array2D(f32).init(main.threadAllocator, 3*mapSize/biomeSize, 3*mapSize/biomeSize);
|
||||
defer heightMap.deinit(main.threadAllocator);
|
||||
try FractalNoise.generateSparseFractalTerrain(map.pos.wx -% mapSize, map.pos.wz -% mapSize, mapSize/16, worldSeed ^ 92786504683290654, heightMap, biomeSize);
|
||||
|
||||
const windXMap = try Array2D(f32).init(main.threadAllocator, 3*mapSize/biomeSize, 3*mapSize/biomeSize);
|
||||
defer windXMap.deinit(main.threadAllocator);
|
||||
try FractalNoise.generateSparseFractalTerrain(map.pos.wx -% mapSize, map.pos.wz -% mapSize, mapSize/8, worldSeed ^ 4382905640235972, windXMap, biomeSize);
|
||||
|
||||
const windZMap = try Array2D(f32).init(main.threadAllocator, 3*mapSize/biomeSize, 3*mapSize/biomeSize);
|
||||
defer windZMap.deinit(main.threadAllocator);
|
||||
try FractalNoise.generateSparseFractalTerrain(map.pos.wx -% mapSize, map.pos.wz -% mapSize, mapSize/8, worldSeed ^ 532985472894530, windZMap, biomeSize);
|
||||
|
||||
// Make non-ocean regions more flat:
|
||||
for(heightMap.mem) |*height| {
|
||||
if(height.* >= oceanThreshold) {
|
||||
height.* = oceanThreshold + (1 - oceanThreshold)*std.math.pow(f32, (height.* - oceanThreshold)/(1 - oceanThreshold), mountainPower);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the temperature and humidty for each point on the map. This is done by backtracing along the wind.
|
||||
// On mountains the water will often rain down, so wind that goes through a mountain will carry less.
|
||||
// Oceans carry water, so if the wind went through an ocean it picks up water.
|
||||
|
||||
// Alongside that there is also an initial temperature and humidity distribution that mimics the earth.
|
||||
// How is that possible? Isn't Cubyz flat?
|
||||
// On the earth there are just two arctic poles. Cubyz takes the north pole and places it at (0, 0).
|
||||
// Then there are infinite poles with ring shapes and each ring will have an equal distance to the previous one.
|
||||
// That's not perfectly realistic, but it's ok in the sense that following a compass will lead to one arctic
|
||||
// and away from another.
|
||||
var biomeMap: [mapSize/biomeSize + 2][mapSize/biomeSize + 2]*const Biome = undefined;
|
||||
|
||||
var x: i32 = -1;
|
||||
while(x < mapSize/biomeSize + 1) : (x += 1) {
|
||||
var z: i32 = -1;
|
||||
while(z < mapSize/biomeSize + 1) : (z += 1) {
|
||||
var seed: u64 = @as(u64, @intCast(@as(u32, @bitCast(x +% map.pos.wx))))*%65784967549 +% @as(u64, @intCast(@as(u32, @bitCast(z +% map.pos.wz))))*%6758934659 +% worldSeed;
|
||||
random.scrambleSeed(&seed);
|
||||
const xOffset = random.nextFloat(&seed) - 0.5;
|
||||
const zOffset = random.nextFloat(&seed) - 0.5;
|
||||
var humid = getInitialHumidity(map, @floatFromInt(x), @floatFromInt(z), heightMap.get(@intCast(x + mapSize/biomeSize), @intCast(z + mapSize/biomeSize)));
|
||||
var temp = getInitialTemperature(map, @floatFromInt(x), @floatFromInt(z), heightMap.get(@intCast(x + mapSize/biomeSize), @intCast(z + mapSize/biomeSize)));
|
||||
var humidInfluence = windInfluence;
|
||||
var tempInfluence = windInfluence;
|
||||
var nextX = @as(f32, @floatFromInt(x)) + xOffset;
|
||||
var nextZ = @as(f32, @floatFromInt(z)) + zOffset;
|
||||
for(0..50) |_| {
|
||||
const windX = windXMap.get(@intCast(@as(i32, @intFromFloat(nextX)) + mapSize/biomeSize), @intCast(@as(i32, @intFromFloat(nextZ)) + mapSize/biomeSize));
|
||||
const windZ = windZMap.get(@intCast(@as(i32, @intFromFloat(nextX)) + mapSize/biomeSize), @intCast(@as(i32, @intFromFloat(nextZ)) + mapSize/biomeSize));
|
||||
nextX += windX*windSpeed;
|
||||
nextZ += windZ*windSpeed;
|
||||
// Make sure the bounds are ok:
|
||||
if(nextX < -@as(f32, @floatFromInt(mapSize/biomeSize)) or nextX > 2*@as(f32, @floatFromInt(mapSize/biomeSize)) - 1) break;
|
||||
if(nextZ < -@as(f32, @floatFromInt(mapSize/biomeSize)) or nextZ > 2*@as(f32, @floatFromInt(mapSize/biomeSize)) - 1) break;
|
||||
// Find the local temperature and humidity:
|
||||
const localHeight = heightMap.get(@intCast(@as(i32, @intFromFloat(nextX)) + mapSize/biomeSize), @intCast(@as(i32, @intFromFloat(nextZ)) + mapSize/biomeSize));
|
||||
const localTemp = getInitialTemperature(map, nextX, nextZ, localHeight);
|
||||
const localHumid = getInitialHumidity(map, nextX, nextZ, localHeight);
|
||||
humid = (1 - humidInfluence)*humid + humidInfluence*localHumid;
|
||||
temp = (1 - tempInfluence)*temp + tempInfluence*localTemp;
|
||||
tempInfluence *= 0.9; // Distance reduction
|
||||
humidInfluence *= 0.9; // Distance reduction
|
||||
// Reduction from mountains:
|
||||
humidInfluence *= std.math.pow(f32, 1 - localHeight, 0.05);
|
||||
}
|
||||
// Insert the biome type:
|
||||
const typ = findClimate(heightMap.get(@intCast(x + mapSize/biomeSize), @intCast(z + mapSize/biomeSize)), humid, temp);
|
||||
biomeMap[@intCast(x + 1)][@intCast(z + 1)] = terrain.biomes.getRandomly(typ, &seed);
|
||||
}
|
||||
}
|
||||
x = 0;
|
||||
while(x < mapSize/biomeSize) : (x += 1) {
|
||||
var z: i32 = 0;
|
||||
while(z < mapSize/biomeSize) : (z += 1) {
|
||||
const biome = (&biomeMap[@intCast(x + 1)])[@intCast(z + 1)]; // TODO: #15685
|
||||
var maxMinHeight: i32 = std.math.minInt(i32);
|
||||
var minMaxHeight: i32 = std.math.maxInt(i32);
|
||||
var dx: i32 = -1;
|
||||
while(dx <= 1) : (dx += 1) {
|
||||
var dz: i32 = -1;
|
||||
while(dz <= 1) : (dz += 1) {
|
||||
maxMinHeight = @max(maxMinHeight, (&biomeMap[@intCast(x + dx + 1)])[@intCast(z + dz + 1)].minHeight); // TODO: #15685
|
||||
minMaxHeight = @min(minMaxHeight, (&biomeMap[@intCast(x + dx + 1)])[@intCast(z + dz + 1)].maxHeight); // TODO: #15685
|
||||
}
|
||||
}
|
||||
var seed: u64 = @as(u64, @bitCast(x +% map.pos.wx))*%675893674893 +% @as(u64, @bitCast(z +% map.pos.wz))*%2895478591 +% worldSeed;
|
||||
const xOffset = random.nextFloat(&seed) - 0.5;
|
||||
const zOffset = random.nextFloat(&seed) - 0.5;
|
||||
var height = random.nextFloat(&seed);
|
||||
if(maxMinHeight > biome.maxHeight - @divTrunc(biome.maxHeight - biome.minHeight, 4)) {
|
||||
height = height*0.25 + 0.75;
|
||||
}
|
||||
if(minMaxHeight < biome.minHeight + @divTrunc(biome.maxHeight - biome.minHeight, 4)) {
|
||||
height = height*0.25;
|
||||
}
|
||||
height = height*@as(f32, @floatFromInt(biome.maxHeight - biome.minHeight)) + @as(f32, @floatFromInt(biome.minHeight));
|
||||
const wx = x*terrain.SurfaceMap.MapFragment.biomeSize +% map.pos.wx;
|
||||
const wz = z*terrain.SurfaceMap.MapFragment.biomeSize +% map.pos.wz;
|
||||
map.map[@intCast(x)][@intCast(z)] = .{
|
||||
.biome = biome,
|
||||
.x = wx +% @as(i32, @intFromFloat(xOffset*terrain.SurfaceMap.MapFragment.biomeSize)),
|
||||
.z = wz +% @as(i32, @intFromFloat(zOffset*terrain.SurfaceMap.MapFragment.biomeSize)),
|
||||
.height = height,
|
||||
.seed = random.nextInt(u64, &seed),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn getInitialHumidity(map: *ClimateMapFragment, x: f32, z: f32, height: f32) f32 {
|
||||
if(height < oceanThreshold) return 1;
|
||||
const wx = x + @as(f32, @floatFromInt(map.pos.wx >> terrain.SurfaceMap.MapFragment.biomeShift));
|
||||
const wz = z + @as(f32, @floatFromInt(map.pos.wz >> terrain.SurfaceMap.MapFragment.biomeShift));
|
||||
var distance = @sqrt(wx*wx + wz*wz);
|
||||
distance = @rem(distance, 1);
|
||||
// On earth there is high humidity at the equator and the poles and low humidty at around 30°.
|
||||
// Interpolating through this data resulted in this function:
|
||||
// 1 - 2916/125*x² - 16038/125*x⁴ + 268272/125*x⁶ - 629856/125*x⁸
|
||||
if(distance >= 0.5) distance -= 1;
|
||||
const @"x²" = distance*distance;
|
||||
const @"x⁴" = @"x²"*@"x²";
|
||||
const @"x⁶" = @"x⁴"*@"x²";
|
||||
const @"x⁸" = @"x⁴"*@"x⁴";
|
||||
const result = 1 - 2916.0/125.0*@"x²" - 16038.0/125.0*@"x⁴" + 268272.0/125.0*@"x⁶" - 629856.0/125.0*@"x⁸";
|
||||
return result*0.5 + 0.5;
|
||||
}
|
||||
|
||||
fn getInitialTemperature(map: *ClimateMapFragment, x: f32, z: f32, height: f32) f32 {
|
||||
const wx = x + @as(f32, @floatFromInt(map.pos.wx >> terrain.SurfaceMap.MapFragment.biomeShift));
|
||||
const wz = z + @as(f32, @floatFromInt(map.pos.wz >> terrain.SurfaceMap.MapFragment.biomeShift));
|
||||
var temp = @rem(@sqrt(wx*wx + wz*wz)/ringSize, 1);
|
||||
// Uses a simple triangle function:
|
||||
if(temp > 0.5) temp = 1 - temp;
|
||||
temp = 4*temp - 1;
|
||||
return heightDependantTemperature(temp, height);
|
||||
}
|
||||
|
||||
fn heightDependantTemperature(temperature: f32, height: f32) f32 {
|
||||
// On earth temperature changes by `6.5K/km`.
|
||||
// On a cubyz world the highest mountain will be `(1 - oceanThreshold)` units high.
|
||||
// If the highest possible height of a mountain is assumed to be 10km, the temperature change gets: `65K/(1 - oceanThreshold)*(height - oceanThreshold)`
|
||||
// Annual average temperature on earth range between -50°C and +30°C giving a difference of 80K
|
||||
// On cubyz average temperature range from -1 to 1 giving a difference of 2.
|
||||
// Therefor the total equation gets: `65K*2/80K/(1 - oceanThreshold)*(height - oceanThreshold)` = `1.625/(1 - oceanThreshold)*(height - oceanThreshold)`
|
||||
|
||||
// Furthermore I assume that the average temperature is at 1km of height.
|
||||
return temperature - 1.625*(@max(0, height - oceanThreshold)/(1 - oceanThreshold) - 0.1);
|
||||
}
|
||||
|
||||
fn findClimate(height: f32, humid: f32, temp: f32) Biome.Type {
|
||||
if (height < oceanThreshold) {
|
||||
if (temp <= frostPoint) {
|
||||
return .arctic_ocean;
|
||||
} else if (temp < hotPoint) {
|
||||
return .ocean;
|
||||
} else {
|
||||
return .warm_ocean;
|
||||
}
|
||||
} else if (height < (1.0 - oceanThreshold)*(1 - mountainRatio) + oceanThreshold) {
|
||||
if (temp <= frostPoint) {
|
||||
if (temp <= icePoint) {
|
||||
return .glacier;
|
||||
} else if (humid < wetPoint) {
|
||||
return .taiga;
|
||||
} else {
|
||||
return .tundra;
|
||||
}
|
||||
} else if (temp < hotPoint) {
|
||||
if (humid <= dryPoint) {
|
||||
return .grassland;
|
||||
} else if (humid < wetPoint) {
|
||||
return .forest;
|
||||
} else {
|
||||
return .swamp;
|
||||
}
|
||||
} else {
|
||||
if (humid <= dryPoint) {
|
||||
return .desert;
|
||||
} else if (humid < wetPoint) {
|
||||
return .shrubland;
|
||||
} else {
|
||||
return .rainforest;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (temp <= frostPoint) {
|
||||
return .peak;
|
||||
} else {
|
||||
if (humid <= wetPoint) {
|
||||
return .mountain_grassland;
|
||||
} else {
|
||||
return .mountain_forest;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
|
||||
|
||||
pub const RecursiveAttempt = @import("RecursiveAttempt.zig");
|
||||
pub const NoiseBasedVoronoi = @import("NoiseBasedVoronoi.zig");
|
Loading…
x
Reference in New Issue
Block a user