mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-07 19:21:14 -04:00
Store biome and height maps, interpolate height map with surrounding terrain on load, store heightmaps for old worlds that didn't have them on load.
fixes #350
This commit is contained in:
parent
43f37763ce
commit
3de8271671
@ -270,6 +270,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, biomePal
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Biomes:
|
// Biomes:
|
||||||
|
var i: u32 = 0;
|
||||||
for(biomePalette.palette.items) |id| {
|
for(biomePalette.palette.items) |id| {
|
||||||
const nullValue = biomes.get(id);
|
const nullValue = biomes.get(id);
|
||||||
var json: JsonElement = undefined;
|
var json: JsonElement = undefined;
|
||||||
@ -279,13 +280,15 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, biomePal
|
|||||||
std.log.err("Missing biomes: {s}. Replacing it with default biomes.", .{id});
|
std.log.err("Missing biomes: {s}. Replacing it with default biomes.", .{id});
|
||||||
json = .{.JsonNull={}};
|
json = .{.JsonNull={}};
|
||||||
}
|
}
|
||||||
biomes_zig.register(id, json);
|
biomes_zig.register(id, i, json);
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
iterator = biomes.iterator();
|
iterator = biomes.iterator();
|
||||||
while(iterator.next()) |entry| {
|
while(iterator.next()) |entry| {
|
||||||
if(biomes_zig.hasRegistered(entry.key_ptr.*)) continue;
|
if(biomes_zig.hasRegistered(entry.key_ptr.*)) continue;
|
||||||
biomes_zig.register(entry.key_ptr.*, entry.value_ptr.*);
|
biomes_zig.register(entry.key_ptr.*, i, entry.value_ptr.*);
|
||||||
biomePalette.add(entry.key_ptr.*);
|
biomePalette.add(entry.key_ptr.*);
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
biomes_zig.finishLoading();
|
biomes_zig.finishLoading();
|
||||||
|
|
||||||
|
@ -458,6 +458,24 @@ pub const ServerChunk = struct {
|
|||||||
pub fn save(self: *ServerChunk, world: *main.server.ServerWorld) void {
|
pub fn save(self: *ServerChunk, world: *main.server.ServerWorld) void {
|
||||||
self.mutex.lock();
|
self.mutex.lock();
|
||||||
defer self.mutex.unlock();
|
defer self.mutex.unlock();
|
||||||
|
if(!self.wasStored and self.super.pos.voxelSize == 1) {
|
||||||
|
// Store the surrounding map pieces as well:
|
||||||
|
self.mutex.unlock();
|
||||||
|
defer self.mutex.lock();
|
||||||
|
const mapStartX = self.super.pos.wx -% main.server.terrain.SurfaceMap.MapFragment.mapSize/2 & ~@as(i32, main.server.terrain.SurfaceMap.MapFragment.mapMask);
|
||||||
|
const mapStartY = self.super.pos.wy -% main.server.terrain.SurfaceMap.MapFragment.mapSize/2 & ~@as(i32, main.server.terrain.SurfaceMap.MapFragment.mapMask);
|
||||||
|
for(0..2) |dx| {
|
||||||
|
for(0..2) |dy| {
|
||||||
|
const mapX = mapStartX +% main.server.terrain.SurfaceMap.MapFragment.mapSize*@as(i32, @intCast(dx));
|
||||||
|
const mapY = mapStartY +% main.server.terrain.SurfaceMap.MapFragment.mapSize*@as(i32, @intCast(dy));
|
||||||
|
const map = main.server.terrain.SurfaceMap.getOrGenerateFragmentAndIncreaseRefCount(mapX, mapY, self.super.pos.voxelSize);
|
||||||
|
defer map.decreaseRefCount();
|
||||||
|
if(!map.wasStored.swap(true, .monotonic)) {
|
||||||
|
map.save(null, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.wasStored = true;
|
self.wasStored = true;
|
||||||
if(self.wasChanged) {
|
if(self.wasChanged) {
|
||||||
const pos = self.super.pos;
|
const pos = self.super.pos;
|
||||||
|
@ -69,6 +69,8 @@ pub const MapFragment = struct {
|
|||||||
minHeight: f32 = std.math.floatMax(f32),
|
minHeight: f32 = std.math.floatMax(f32),
|
||||||
maxHeight: f32 = 0,
|
maxHeight: f32 = 0,
|
||||||
pos: MapFragmentPosition,
|
pos: MapFragmentPosition,
|
||||||
|
|
||||||
|
wasStored: Atomic(bool) = .{.raw = false},
|
||||||
|
|
||||||
refCount: Atomic(u16) = Atomic(u16).init(0),
|
refCount: Atomic(u16) = Atomic(u16).init(0),
|
||||||
|
|
||||||
@ -102,6 +104,97 @@ pub const MapFragment = struct {
|
|||||||
const yIndex = wy>>self.pos.voxelSizeShift & mapMask;
|
const yIndex = wy>>self.pos.voxelSizeShift & mapMask;
|
||||||
return self.heightMap[@intCast(xIndex)][@intCast(yIndex)];
|
return self.heightMap[@intCast(xIndex)][@intCast(yIndex)];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const StorageHeader = struct {
|
||||||
|
const activeVersion: u8 = 0;
|
||||||
|
version: u8 = activeVersion,
|
||||||
|
neighborInfo: NeighborInfo,
|
||||||
|
};
|
||||||
|
const NeighborInfo = packed struct(u8) {
|
||||||
|
@"-o": bool = false,
|
||||||
|
@"+o": bool = false,
|
||||||
|
@"o-": bool = false,
|
||||||
|
@"o+": bool = false,
|
||||||
|
@"--": bool = false,
|
||||||
|
@"-+": bool = false,
|
||||||
|
@"+-": bool = false,
|
||||||
|
@"++": bool = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn load(self: *MapFragment, biomePalette: *main.assets.Palette, originalHeightMap: ?*[mapSize][mapSize]f32) !NeighborInfo {
|
||||||
|
const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(saveFolder);
|
||||||
|
|
||||||
|
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(path);
|
||||||
|
|
||||||
|
const fullData = try main.files.read(main.stackAllocator, path);
|
||||||
|
defer main.stackAllocator.free(fullData);
|
||||||
|
|
||||||
|
const header: StorageHeader = .{
|
||||||
|
.version = fullData[0],
|
||||||
|
.neighborInfo = @bitCast(fullData[1]),
|
||||||
|
};
|
||||||
|
if(header.version != StorageHeader.activeVersion) return error.OutdatedFileVersion;
|
||||||
|
const compressedData = fullData[@sizeOf(StorageHeader)..];
|
||||||
|
const rawData: []u8 = main.stackAllocator.alloc(u8, mapSize*mapSize*(@sizeOf(u32) + 2*@sizeOf(f32)));
|
||||||
|
defer main.stackAllocator.free(rawData);
|
||||||
|
if(try main.utils.Compression.inflateTo(rawData, compressedData) != rawData.len) return error.CorruptedFile;
|
||||||
|
const biomeData = rawData[0..mapSize*mapSize*@sizeOf(u32)];
|
||||||
|
const heightData = rawData[mapSize*mapSize*@sizeOf(u32)..][0..mapSize*mapSize*@sizeOf(f32)];
|
||||||
|
const originalHeightData = rawData[mapSize*mapSize*(@sizeOf(u32) + @sizeOf(f32))..][0..mapSize*mapSize*@sizeOf(f32)];
|
||||||
|
for(0..mapSize) |x| {
|
||||||
|
for(0..mapSize) |y| {
|
||||||
|
self.biomeMap[x][y] = main.server.terrain.biomes.getById(biomePalette.palette.items[std.mem.readInt(u32, biomeData[4*(x*mapSize + y)..][0..4], .big)]);
|
||||||
|
self.heightMap[x][y] = @bitCast(std.mem.readInt(u32, heightData[4*(x*mapSize + y)..][0..4], .big));
|
||||||
|
if(originalHeightMap) |map| map[x][y] = @bitCast(std.mem.readInt(u32, originalHeightData[4*(x*mapSize + y)..][0..4], .big));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.wasStored.store(true, .monotonic);
|
||||||
|
return header.neighborInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save(self: *MapFragment, originalData: ?*[mapSize][mapSize]f32, neighborInfo: NeighborInfo) void {
|
||||||
|
const rawData: []u8 = main.stackAllocator.alloc(u8, mapSize*mapSize*(@sizeOf(u32) + 2*@sizeOf(f32)));
|
||||||
|
defer main.stackAllocator.free(rawData);
|
||||||
|
const biomeData = rawData[0..mapSize*mapSize*@sizeOf(u32)];
|
||||||
|
const heightData = rawData[mapSize*mapSize*@sizeOf(u32)..][0..mapSize*mapSize*@sizeOf(f32)];
|
||||||
|
const originalHeightData = rawData[mapSize*mapSize*(@sizeOf(u32) + @sizeOf(f32))..][0..mapSize*mapSize*@sizeOf(f32)];
|
||||||
|
for(0..mapSize) |x| {
|
||||||
|
for(0..mapSize) |y| {
|
||||||
|
std.mem.writeInt(u32, biomeData[4*(x*mapSize + y)..][0..4], self.biomeMap[x][y].paletteId, .big);
|
||||||
|
std.mem.writeInt(u32, heightData[4*(x*mapSize + y)..][0..4], @bitCast(self.heightMap[x][y]), .big);
|
||||||
|
std.mem.writeInt(u32, originalHeightData[4*(x*mapSize + y)..][0..4], @bitCast((if(originalData) |map| map else &self.heightMap)[x][y]), .big);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const compressedData = main.utils.Compression.deflate(main.stackAllocator, rawData);
|
||||||
|
defer main.stackAllocator.free(compressedData);
|
||||||
|
|
||||||
|
const fullData = main.stackAllocator.alloc(u8, compressedData.len + @sizeOf(StorageHeader));
|
||||||
|
defer main.stackAllocator.free(fullData);
|
||||||
|
const header: StorageHeader = .{
|
||||||
|
.neighborInfo = neighborInfo,
|
||||||
|
};
|
||||||
|
fullData[0] = header.version;
|
||||||
|
fullData[1] = @bitCast(header.neighborInfo);
|
||||||
|
@memcpy(fullData[@sizeOf(StorageHeader)..], compressedData);
|
||||||
|
|
||||||
|
const saveFolder: []const u8 = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{main.server.world.?.name}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(saveFolder);
|
||||||
|
|
||||||
|
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}/{}.surface", .{saveFolder, self.pos.voxelSize, self.pos.wx, self.pos.wy}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(path);
|
||||||
|
const folder = std.fmt.allocPrint(main.stackAllocator.allocator, "{s}/{}/{}", .{saveFolder, self.pos.voxelSize, self.pos.wx}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(folder);
|
||||||
|
|
||||||
|
main.files.makeDir(folder) catch |err| {
|
||||||
|
std.log.err("Error while writing to file {s}: {s}", .{path, @errorName(err)});
|
||||||
|
};
|
||||||
|
|
||||||
|
main.files.write(path, fullData) catch |err| {
|
||||||
|
std.log.err("Error while writing to file {s}: {s}", .{path, @errorName(err)});
|
||||||
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Generates the detailed(block-level precision) height and biome maps from the climate map.
|
/// Generates the detailed(block-level precision) height and biome maps from the climate map.
|
||||||
@ -150,11 +243,292 @@ pub fn deinitGenerators() void {
|
|||||||
fn cacheInit(pos: MapFragmentPosition) *MapFragment {
|
fn cacheInit(pos: MapFragmentPosition) *MapFragment {
|
||||||
const mapFragment = main.globalAllocator.create(MapFragment);
|
const mapFragment = main.globalAllocator.create(MapFragment);
|
||||||
mapFragment.init(pos.wx, pos.wy, pos.voxelSize);
|
mapFragment.init(pos.wx, pos.wy, pos.voxelSize);
|
||||||
profile.mapFragmentGenerator.generateMapFragment(mapFragment, profile.seed);
|
_ = mapFragment.load(main.server.world.?.biomePalette, null) catch {
|
||||||
|
profile.mapFragmentGenerator.generateMapFragment(mapFragment, profile.seed);
|
||||||
|
};
|
||||||
_ = @atomicRmw(u16, &mapFragment.refCount.raw, .Add, 1, .monotonic);
|
_ = @atomicRmw(u16, &mapFragment.refCount.raw, .Add, 1, .monotonic);
|
||||||
return mapFragment;
|
return mapFragment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn regenerateLOD(worldName: []const u8) !void {
|
||||||
|
std.log.info("Regenerating map LODs...", .{});
|
||||||
|
// Delete old LODs:
|
||||||
|
for(1..main.settings.highestLOD+1) |i| {
|
||||||
|
const lod = @as(u32, 1) << @intCast(i);
|
||||||
|
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{worldName}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(path);
|
||||||
|
const dir = std.fmt.allocPrint(main.stackAllocator.allocator, "{}", .{lod}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(dir);
|
||||||
|
main.files.deleteDir(path, dir) catch |err| {
|
||||||
|
std.log.err("Error while deleting directory {s}/{s}: {s}", .{path, dir, @errorName(err)});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Find all the stored maps:
|
||||||
|
var mapPositions = main.List(MapFragmentPosition).init(main.stackAllocator);
|
||||||
|
defer mapPositions.deinit();
|
||||||
|
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps/1", .{worldName}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(path);
|
||||||
|
{
|
||||||
|
var dirX = try std.fs.cwd().openDir(path, .{.iterate = true});
|
||||||
|
defer dirX.close();
|
||||||
|
var iterX = dirX.iterate();
|
||||||
|
while(try iterX.next()) |entryX| {
|
||||||
|
if(entryX.kind != .directory) continue;
|
||||||
|
const wx = std.fmt.parseInt(i32, entryX.name, 0) catch continue;
|
||||||
|
var dirY = try dirX.openDir(entryX.name, .{.iterate = true});
|
||||||
|
defer dirY.close();
|
||||||
|
var iterY = dirY.iterate();
|
||||||
|
while(try iterY.next()) |entryY| {
|
||||||
|
if(entryY.kind != .file) continue;
|
||||||
|
const nameY = entryY.name[0..std.mem.indexOfScalar(u8, entryY.name, '.') orelse entryY.name.len];
|
||||||
|
const wy = std.fmt.parseInt(i32, nameY, 0) catch continue;
|
||||||
|
mapPositions.append(.{.wx = wx, .wy = wy, .voxelSize = 1, .voxelSizeShift = 0});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Load all the stored maps and update their next LODs.
|
||||||
|
const interpolationDistance = 64;
|
||||||
|
for(mapPositions.items) |pos| {
|
||||||
|
var neighborInfo: MapFragment.NeighborInfo = undefined;
|
||||||
|
inline for(comptime std.meta.fieldNames(MapFragment.NeighborInfo)) |name| {
|
||||||
|
var neighborPos = pos;
|
||||||
|
if(name[0] == '+') neighborPos.wx +%= MapFragment.mapSize;
|
||||||
|
if(name[0] == '-') neighborPos.wx -%= MapFragment.mapSize;
|
||||||
|
if(name[1] == '+') neighborPos.wy +%= MapFragment.mapSize;
|
||||||
|
if(name[1] == '-') neighborPos.wy -%= MapFragment.mapSize;
|
||||||
|
var isPresent: bool = false;
|
||||||
|
for(mapPositions.items) |otherPos| {
|
||||||
|
if(neighborPos.wx == otherPos.wx and neighborPos.wy == otherPos.wy) {
|
||||||
|
isPresent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@field(neighborInfo, name) = isPresent;
|
||||||
|
}
|
||||||
|
const mapFragment = main.globalAllocator.create(MapFragment);
|
||||||
|
defer main.stackAllocator.destroy(mapFragment);
|
||||||
|
mapFragment.init(pos.wx, pos.wy, pos.voxelSize);
|
||||||
|
var originalHeightMap: [MapFragment.mapSize][MapFragment.mapSize]f32 = undefined;
|
||||||
|
const oldNeighborInfo = mapFragment.load(main.server.world.?.biomePalette, &originalHeightMap) catch |err| {
|
||||||
|
std.log.err("Error loading map at position {}: {s}", .{pos, @errorName(err)});
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if(@as(u8, @bitCast(neighborInfo)) != @as(u8, @bitCast(oldNeighborInfo))) {
|
||||||
|
// Now we do the fun stuff
|
||||||
|
// Basically we want to only keep the interpolated map in the direction of the changes.
|
||||||
|
// Edges:
|
||||||
|
if(neighborInfo.@"+o" != oldNeighborInfo.@"+o" or neighborInfo.@"-o" != oldNeighborInfo.@"-o" or neighborInfo.@"o+" != oldNeighborInfo.@"o+" or neighborInfo.@"o-" != oldNeighborInfo.@"o-") {
|
||||||
|
for(0..interpolationDistance) |a| { // edges
|
||||||
|
for(interpolationDistance..MapFragment.mapSize - interpolationDistance) |b| {
|
||||||
|
if(neighborInfo.@"+o" and !oldNeighborInfo.@"+o") {
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y];
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"-o" and !oldNeighborInfo.@"-o") {
|
||||||
|
const x = a;
|
||||||
|
const y = b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y];
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"o+" and !oldNeighborInfo.@"o+") {
|
||||||
|
const x = b;
|
||||||
|
const y = MapFragment.mapSize - 1 - a;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y];
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"o-" and !oldNeighborInfo.@"o-") {
|
||||||
|
const x = b;
|
||||||
|
const y = a;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Corners:
|
||||||
|
{
|
||||||
|
for(0..interpolationDistance) |a| { // corners:
|
||||||
|
for(0..interpolationDistance) |b| {
|
||||||
|
const weirdSquareInterpolation = struct{fn interp(x: f32, y: f32) f32 {
|
||||||
|
// Basically we want to interpolate the values such that two sides of the square have value zero, while the opposing two sides have value 1.
|
||||||
|
// Chenge coordinate system:
|
||||||
|
if(x == y) return 0.5;
|
||||||
|
const sqrt2 = @sqrt(0.5);
|
||||||
|
const k = sqrt2*x + sqrt2*y - sqrt2;
|
||||||
|
const l = -sqrt2*x + sqrt2*y;
|
||||||
|
const maxMagnitude = sqrt2 - @abs(k);
|
||||||
|
return l/maxMagnitude*0.5 + 0.5;
|
||||||
|
// if x = y:
|
||||||
|
}}.interp;
|
||||||
|
var factorA = @as(f32, @floatFromInt(a))/interpolationDistance;
|
||||||
|
factorA = (3 - 2*factorA)*factorA*factorA;
|
||||||
|
var factorB = @as(f32, @floatFromInt(b))/interpolationDistance;
|
||||||
|
factorB = (3 - 2*factorB)*factorB*factorB;
|
||||||
|
if(neighborInfo.@"+o" or neighborInfo.@"o+") {
|
||||||
|
var factor: f32 = 1;
|
||||||
|
if(neighborInfo.@"+o" and neighborInfo.@"o+" == oldNeighborInfo.@"o+" and !oldNeighborInfo.@"+o") factor = weirdSquareInterpolation(1 - factorB, 1 - factorA);
|
||||||
|
if(neighborInfo.@"o+" and neighborInfo.@"+o" == oldNeighborInfo.@"+o" and !oldNeighborInfo.@"o+") factor = weirdSquareInterpolation(1 - factorA, 1 - factorB);
|
||||||
|
if(neighborInfo.@"+o" == oldNeighborInfo.@"+o" and neighborInfo.@"o+" == oldNeighborInfo.@"o+") factor = 0;
|
||||||
|
if(neighborInfo.@"+o" and neighborInfo.@"o+" and neighborInfo.@"++") factor = 1;
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = MapFragment.mapSize - 1 - b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y]*factor + originalHeightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"+o" or neighborInfo.@"o-") {
|
||||||
|
var factor: f32 = 1;
|
||||||
|
if(neighborInfo.@"+o" and neighborInfo.@"o-" == oldNeighborInfo.@"o-" and !oldNeighborInfo.@"+o") factor = weirdSquareInterpolation(1 - factorB, 1 - factorA);
|
||||||
|
if(neighborInfo.@"o-" and neighborInfo.@"+o" == oldNeighborInfo.@"+o" and !oldNeighborInfo.@"o-") factor = weirdSquareInterpolation(1 - factorA, 1 - factorB);
|
||||||
|
if(neighborInfo.@"+o" == oldNeighborInfo.@"+o" and neighborInfo.@"o-" == oldNeighborInfo.@"o-") factor = 0;
|
||||||
|
if(neighborInfo.@"+o" and neighborInfo.@"o-" and neighborInfo.@"+-") factor = 1;
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y]*factor + originalHeightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"-o" or neighborInfo.@"o+") {
|
||||||
|
var factor: f32 = 1;
|
||||||
|
if(neighborInfo.@"-o" and neighborInfo.@"o+" == oldNeighborInfo.@"o+" and !oldNeighborInfo.@"-o") factor = weirdSquareInterpolation(1 - factorB, 1 - factorA);
|
||||||
|
if(neighborInfo.@"o+" and neighborInfo.@"-o" == oldNeighborInfo.@"-o" and !oldNeighborInfo.@"o+") factor = weirdSquareInterpolation(1 - factorA, 1 - factorB);
|
||||||
|
if(neighborInfo.@"-o" == oldNeighborInfo.@"-o" and neighborInfo.@"o+" == oldNeighborInfo.@"o+") factor = 0;
|
||||||
|
if(neighborInfo.@"-o" and neighborInfo.@"o+" and neighborInfo.@"-+") factor = 1;
|
||||||
|
const x = a;
|
||||||
|
const y = MapFragment.mapSize - 1 - b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y]*factor + originalHeightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(neighborInfo.@"-o" or neighborInfo.@"o-") {
|
||||||
|
var factor: f32 = 1;
|
||||||
|
if(neighborInfo.@"-o" and neighborInfo.@"o-" == oldNeighborInfo.@"o-" and !oldNeighborInfo.@"-o") factor = weirdSquareInterpolation(1 - factorB, 1 - factorA);
|
||||||
|
if(neighborInfo.@"o-" and neighborInfo.@"-o" == oldNeighborInfo.@"-o" and !oldNeighborInfo.@"o-") factor = weirdSquareInterpolation(1 - factorA, 1 - factorB);
|
||||||
|
if(neighborInfo.@"-o" == oldNeighborInfo.@"-o" and neighborInfo.@"o-" == oldNeighborInfo.@"o-") factor = 0;
|
||||||
|
if(neighborInfo.@"-o" and neighborInfo.@"o-" and neighborInfo.@"--") factor = 1;
|
||||||
|
const x = a;
|
||||||
|
const y = b;
|
||||||
|
originalHeightMap[x][y] = mapFragment.heightMap[x][y]*factor + originalHeightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{ // Interpolate the terraing height:
|
||||||
|
const generatedMap = main.globalAllocator.create(MapFragment);
|
||||||
|
defer main.stackAllocator.destroy(generatedMap);
|
||||||
|
generatedMap.init(pos.wx, pos.wy, pos.voxelSize);
|
||||||
|
profile.mapFragmentGenerator.generateMapFragment(generatedMap, profile.seed);
|
||||||
|
|
||||||
|
@memcpy(&mapFragment.heightMap, &originalHeightMap);
|
||||||
|
for(0..interpolationDistance) |a| { // edges
|
||||||
|
for(0..MapFragment.mapSize) |b| {
|
||||||
|
var factor = @as(f32, @floatFromInt(a))/interpolationDistance;
|
||||||
|
factor = (3 - 2*factor)*factor*factor;
|
||||||
|
if(!neighborInfo.@"+o") {
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"-o") {
|
||||||
|
const x = a;
|
||||||
|
const y = b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"o+") {
|
||||||
|
const x = b;
|
||||||
|
const y = MapFragment.mapSize - 1 - a;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"o-") {
|
||||||
|
const x = b;
|
||||||
|
const y = a;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for(0..interpolationDistance) |a| { // corners:
|
||||||
|
for(0..interpolationDistance) |b| {
|
||||||
|
var factorA = @as(f32, @floatFromInt(a))/interpolationDistance;
|
||||||
|
factorA = (3 - 2*factorA)*factorA*factorA;
|
||||||
|
var factorB = @as(f32, @floatFromInt(b))/interpolationDistance;
|
||||||
|
factorB = (3 - 2*factorB)*factorB*factorB;
|
||||||
|
const factor = 1 - (1 - factorA)*(1 - factorB);
|
||||||
|
if(!neighborInfo.@"++" and neighborInfo.@"+o" and neighborInfo.@"o+") {
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = MapFragment.mapSize - 1 - b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"+-" and neighborInfo.@"+o" and neighborInfo.@"o-") {
|
||||||
|
const x = MapFragment.mapSize - 1 - a;
|
||||||
|
const y = b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"-+" and neighborInfo.@"-o" and neighborInfo.@"o+") {
|
||||||
|
const x = a;
|
||||||
|
const y = MapFragment.mapSize - 1 - b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
if(!neighborInfo.@"--" and neighborInfo.@"-o" and neighborInfo.@"o-") {
|
||||||
|
const x = a;
|
||||||
|
const y = b;
|
||||||
|
mapFragment.heightMap[x][y] = mapFragment.heightMap[x][y]*factor + generatedMap.heightMap[x][y]*(1 - factor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapFragment.save(&originalHeightMap, neighborInfo); // Store the interpolated map
|
||||||
|
// Generate LODs
|
||||||
|
var cur = mapFragment;
|
||||||
|
defer if(cur.pos.voxelSize != 1) cur.decreaseRefCount();
|
||||||
|
while(cur.pos.voxelSizeShift < main.settings.highestLOD) {
|
||||||
|
var nextPos = cur.pos;
|
||||||
|
nextPos.voxelSize *= 2;
|
||||||
|
nextPos.voxelSizeShift += 1;
|
||||||
|
const nextMask = ~@as(i32, nextPos.voxelSize*MapFragment.mapSize - 1);
|
||||||
|
nextPos.wx &= nextMask;
|
||||||
|
nextPos.wy &= nextMask;
|
||||||
|
const next = getOrGenerateFragmentAndIncreaseRefCount(nextPos.wx, nextPos.wy, nextPos.voxelSize);
|
||||||
|
const offSetX: usize = @intCast((cur.pos.wx -% nextPos.wx) >> nextPos.voxelSizeShift);
|
||||||
|
const offSetY: usize = @intCast((cur.pos.wy -% nextPos.wy) >> nextPos.voxelSizeShift);
|
||||||
|
for(0..MapFragment.mapSize/2) |x| {
|
||||||
|
for(0..MapFragment.mapSize/2) |y| {
|
||||||
|
var biomes: [4]?*const Biome = .{null} ** 4;
|
||||||
|
var biomeCounts: [4]u8 = .{0} ** 4;
|
||||||
|
var height: f32 = 0;
|
||||||
|
for(0..2) |dx| {
|
||||||
|
for(0..2) |dy| {
|
||||||
|
const curX = x*2 + dx;
|
||||||
|
const curY = y*2 + dy;
|
||||||
|
height += cur.heightMap[curX][curY]/4;
|
||||||
|
const biome = cur.biomeMap[curX][curY];
|
||||||
|
for(0..4) |i| {
|
||||||
|
if(biomes[i] == biome) {
|
||||||
|
biomeCounts[i] += 1;
|
||||||
|
break;
|
||||||
|
} else if(biomes[i] == null) {
|
||||||
|
biomes[i] = biome;
|
||||||
|
biomeCounts[i] += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var bestBiome: *const Biome = biomes[0].?;
|
||||||
|
var bestBiomeCount: u8 = biomeCounts[0];
|
||||||
|
for(1..4) |i| {
|
||||||
|
if(biomeCounts[i] > bestBiomeCount) {
|
||||||
|
bestBiomeCount = biomeCounts[i];
|
||||||
|
bestBiome = biomes[i].?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const nextX = offSetX + x;
|
||||||
|
const nextY = offSetY + y;
|
||||||
|
next.heightMap[nextX][nextY] = height;
|
||||||
|
next.biomeMap[nextX][nextY] = bestBiome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.save(null, .{});
|
||||||
|
next.wasStored.store(true, .monotonic);
|
||||||
|
if(cur.pos.voxelSize != 1) cur.decreaseRefCount();
|
||||||
|
cur = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std.log.info("Finished regenerating map LODs...", .{});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init(_profile: TerrainGenerationProfile) void {
|
pub fn init(_profile: TerrainGenerationProfile) void {
|
||||||
profile = _profile;
|
profile = _profile;
|
||||||
}
|
}
|
||||||
|
@ -237,6 +237,7 @@ pub const Biome = struct {
|
|||||||
fogDensity: f32,
|
fogDensity: f32,
|
||||||
fogColor: Vec3f,
|
fogColor: Vec3f,
|
||||||
id: []const u8,
|
id: []const u8,
|
||||||
|
paletteId: u32,
|
||||||
structure: BlockStructure = undefined,
|
structure: BlockStructure = undefined,
|
||||||
/// Whether the starting point of a river can be in this biome. If false rivers will be able to flow through this biome anyways.
|
/// Whether the starting point of a river can be in this biome. If false rivers will be able to flow through this biome anyways.
|
||||||
supportsRivers: bool, // TODO: Reimplement rivers.
|
supportsRivers: bool, // TODO: Reimplement rivers.
|
||||||
@ -250,9 +251,10 @@ pub const Biome = struct {
|
|||||||
isValidPlayerSpawn: bool,
|
isValidPlayerSpawn: bool,
|
||||||
chance: f32,
|
chance: f32,
|
||||||
|
|
||||||
pub fn init(self: *Biome, id: []const u8, json: JsonElement) void {
|
pub fn init(self: *Biome, id: []const u8, paletteId: u32, json: JsonElement) void {
|
||||||
self.* = Biome {
|
self.* = Biome {
|
||||||
.id = main.globalAllocator.dupe(u8, id),
|
.id = main.globalAllocator.dupe(u8, id),
|
||||||
|
.paletteId = paletteId,
|
||||||
.properties = GenerationProperties.fromJson(json.getChild("properties")),
|
.properties = GenerationProperties.fromJson(json.getChild("properties")),
|
||||||
.isCave = json.get(bool, "isCave", false),
|
.isCave = json.get(bool, "isCave", false),
|
||||||
.radius = json.get(f32, "radius", 256),
|
.radius = json.get(f32, "radius", 256),
|
||||||
@ -556,11 +558,11 @@ pub fn deinit() void {
|
|||||||
StructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator);
|
StructureModel.modelRegistry.clearAndFree(main.globalAllocator.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(id: []const u8, json: JsonElement) void {
|
pub fn register(id: []const u8, paletteId: u32, json: JsonElement) void {
|
||||||
std.log.debug("Registered biome: {s}", .{id});
|
std.log.debug("Registered biome: {s}", .{id});
|
||||||
std.debug.assert(!finishedLoading);
|
std.debug.assert(!finishedLoading);
|
||||||
var biome: Biome = undefined;
|
var biome: Biome = undefined;
|
||||||
biome.init(id, json);
|
biome.init(id, paletteId, json);
|
||||||
if(biome.isCave) {
|
if(biome.isCave) {
|
||||||
caveBiomes.append(biome);
|
caveBiomes.append(biome);
|
||||||
} else {
|
} else {
|
||||||
|
@ -425,6 +425,7 @@ pub const ServerWorld = struct {
|
|||||||
|
|
||||||
const RegenerateLODTask = struct {
|
const RegenerateLODTask = struct {
|
||||||
pos: ChunkPosition,
|
pos: ChunkPosition,
|
||||||
|
storeMaps: bool,
|
||||||
|
|
||||||
const vtable = utils.ThreadPool.VTable{
|
const vtable = utils.ThreadPool.VTable{
|
||||||
.getPriority = @ptrCast(&getPriority),
|
.getPriority = @ptrCast(&getPriority),
|
||||||
@ -433,10 +434,11 @@ pub const ServerWorld = struct {
|
|||||||
.clean = @ptrCast(&clean),
|
.clean = @ptrCast(&clean),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn schedule(pos: ChunkPosition) void {
|
pub fn schedule(pos: ChunkPosition, storeMaps: bool) void {
|
||||||
const task = main.globalAllocator.create(RegenerateLODTask);
|
const task = main.globalAllocator.create(RegenerateLODTask);
|
||||||
task.* = .{
|
task.* = .{
|
||||||
.pos = pos,
|
.pos = pos,
|
||||||
|
.storeMaps = storeMaps,
|
||||||
};
|
};
|
||||||
main.threadPool.addTask(task, &vtable);
|
main.threadPool.addTask(task, &vtable);
|
||||||
}
|
}
|
||||||
@ -469,6 +471,22 @@ pub const ServerWorld = struct {
|
|||||||
};
|
};
|
||||||
const ch = ChunkManager.getOrGenerateChunkAndIncreaseRefCount(pos);
|
const ch = ChunkManager.getOrGenerateChunkAndIncreaseRefCount(pos);
|
||||||
defer ch.decreaseRefCount();
|
defer ch.decreaseRefCount();
|
||||||
|
if(self.storeMaps and ch.super.pos.voxelSize == 1) { // TODO: Remove after first release
|
||||||
|
// Store the surrounding map pieces as well:
|
||||||
|
const mapStartX = ch.super.pos.wx -% main.server.terrain.SurfaceMap.MapFragment.mapSize/2 & ~@as(i32, main.server.terrain.SurfaceMap.MapFragment.mapMask);
|
||||||
|
const mapStartY = ch.super.pos.wy -% main.server.terrain.SurfaceMap.MapFragment.mapSize/2 & ~@as(i32, main.server.terrain.SurfaceMap.MapFragment.mapMask);
|
||||||
|
for(0..2) |dx| {
|
||||||
|
for(0..2) |dy| {
|
||||||
|
const mapX = mapStartX +% main.server.terrain.SurfaceMap.MapFragment.mapSize*@as(i32, @intCast(dx));
|
||||||
|
const mapY = mapStartY +% main.server.terrain.SurfaceMap.MapFragment.mapSize*@as(i32, @intCast(dy));
|
||||||
|
const map = main.server.terrain.SurfaceMap.getOrGenerateFragmentAndIncreaseRefCount(mapX, mapY, ch.super.pos.voxelSize);
|
||||||
|
defer map.decreaseRefCount();
|
||||||
|
if(!map.wasStored.swap(true, .monotonic)) {
|
||||||
|
map.save(null, .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
var nextPos = pos;
|
var nextPos = pos;
|
||||||
nextPos.wx &= ~@as(i32, self.pos.voxelSize*chunk.chunkSize);
|
nextPos.wx &= ~@as(i32, self.pos.voxelSize*chunk.chunkSize);
|
||||||
nextPos.wy &= ~@as(i32, self.pos.voxelSize*chunk.chunkSize);
|
nextPos.wy &= ~@as(i32, self.pos.voxelSize*chunk.chunkSize);
|
||||||
@ -492,6 +510,16 @@ pub const ServerWorld = struct {
|
|||||||
|
|
||||||
fn regenerateLOD(self: *ServerWorld, newBiomeCheckSum: i64) !void {
|
fn regenerateLOD(self: *ServerWorld, newBiomeCheckSum: i64) !void {
|
||||||
std.log.info("Biomes have changed. Regenerating LODs... (this might take some time)", .{});
|
std.log.info("Biomes have changed. Regenerating LODs... (this might take some time)", .{});
|
||||||
|
const hasSurfaceMaps = blk: {
|
||||||
|
const path = std.fmt.allocPrint(main.stackAllocator.allocator, "saves/{s}/maps", .{self.name}) catch unreachable;
|
||||||
|
defer main.stackAllocator.free(path);
|
||||||
|
var dir = std.fs.cwd().openDir(path, .{}) catch break :blk false;
|
||||||
|
defer dir.close();
|
||||||
|
break :blk true;
|
||||||
|
};
|
||||||
|
if(hasSurfaceMaps) {
|
||||||
|
try terrain.SurfaceMap.regenerateLOD(self.name);
|
||||||
|
}
|
||||||
// Delete old LODs:
|
// Delete old LODs:
|
||||||
for(1..main.settings.highestLOD+1) |i| {
|
for(1..main.settings.highestLOD+1) |i| {
|
||||||
const lod = @as(u32, 1) << @intCast(i);
|
const lod = @as(u32, 1) << @intCast(i);
|
||||||
@ -535,7 +563,7 @@ pub const ServerWorld = struct {
|
|||||||
}
|
}
|
||||||
// Load all the stored chunks and update their next LODs.
|
// Load all the stored chunks and update their next LODs.
|
||||||
for(chunkPositions.items) |pos| {
|
for(chunkPositions.items) |pos| {
|
||||||
RegenerateLODTask.schedule(pos);
|
RegenerateLODTask.schedule(pos, !hasSurfaceMaps);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.mutex.lock();
|
self.mutex.lock();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user