diff --git a/src/assets.zig b/src/assets.zig index dbda598d..4bc3f6d9 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -270,6 +270,7 @@ pub fn loadWorldAssets(assetFolder: []const u8, blockPalette: *Palette, biomePal } // Biomes: + var i: u32 = 0; for(biomePalette.palette.items) |id| { const nullValue = biomes.get(id); 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}); json = .{.JsonNull={}}; } - biomes_zig.register(id, json); + biomes_zig.register(id, i, json); + i += 1; } iterator = biomes.iterator(); while(iterator.next()) |entry| { 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.*); + i += 1; } biomes_zig.finishLoading(); diff --git a/src/chunk.zig b/src/chunk.zig index ff557ada..413f835d 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -458,6 +458,24 @@ pub const ServerChunk = struct { pub fn save(self: *ServerChunk, world: *main.server.ServerWorld) void { self.mutex.lock(); 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; if(self.wasChanged) { const pos = self.super.pos; diff --git a/src/server/terrain/SurfaceMap.zig b/src/server/terrain/SurfaceMap.zig index 2b04cc13..289544c4 100644 --- a/src/server/terrain/SurfaceMap.zig +++ b/src/server/terrain/SurfaceMap.zig @@ -69,6 +69,8 @@ pub const MapFragment = struct { minHeight: f32 = std.math.floatMax(f32), maxHeight: f32 = 0, pos: MapFragmentPosition, + + wasStored: Atomic(bool) = .{.raw = false}, refCount: Atomic(u16) = Atomic(u16).init(0), @@ -102,6 +104,97 @@ pub const MapFragment = struct { const yIndex = wy>>self.pos.voxelSizeShift & mapMask; 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. @@ -150,11 +243,292 @@ pub fn deinitGenerators() void { fn cacheInit(pos: MapFragmentPosition) *MapFragment { const mapFragment = main.globalAllocator.create(MapFragment); 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); 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 { profile = _profile; } diff --git a/src/server/terrain/biomes.zig b/src/server/terrain/biomes.zig index cea1b865..29f0b1a1 100644 --- a/src/server/terrain/biomes.zig +++ b/src/server/terrain/biomes.zig @@ -237,6 +237,7 @@ pub const Biome = struct { fogDensity: f32, fogColor: Vec3f, id: []const u8, + paletteId: u32, 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. supportsRivers: bool, // TODO: Reimplement rivers. @@ -250,9 +251,10 @@ pub const Biome = struct { isValidPlayerSpawn: bool, 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 { .id = main.globalAllocator.dupe(u8, id), + .paletteId = paletteId, .properties = GenerationProperties.fromJson(json.getChild("properties")), .isCave = json.get(bool, "isCave", false), .radius = json.get(f32, "radius", 256), @@ -556,11 +558,11 @@ pub fn deinit() void { 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.debug.assert(!finishedLoading); var biome: Biome = undefined; - biome.init(id, json); + biome.init(id, paletteId, json); if(biome.isCave) { caveBiomes.append(biome); } else { diff --git a/src/server/world.zig b/src/server/world.zig index 8e1b88ed..7397497b 100644 --- a/src/server/world.zig +++ b/src/server/world.zig @@ -425,6 +425,7 @@ pub const ServerWorld = struct { const RegenerateLODTask = struct { pos: ChunkPosition, + storeMaps: bool, const vtable = utils.ThreadPool.VTable{ .getPriority = @ptrCast(&getPriority), @@ -433,10 +434,11 @@ pub const ServerWorld = struct { .clean = @ptrCast(&clean), }; - pub fn schedule(pos: ChunkPosition) void { + pub fn schedule(pos: ChunkPosition, storeMaps: bool) void { const task = main.globalAllocator.create(RegenerateLODTask); task.* = .{ .pos = pos, + .storeMaps = storeMaps, }; main.threadPool.addTask(task, &vtable); } @@ -469,6 +471,22 @@ pub const ServerWorld = struct { }; const ch = ChunkManager.getOrGenerateChunkAndIncreaseRefCount(pos); 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; nextPos.wx &= ~@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 { 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: for(1..main.settings.highestLOD+1) |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. for(chunkPositions.items) |pos| { - RegenerateLODTask.schedule(pos); + RegenerateLODTask.schedule(pos, !hasSurfaceMaps); } self.mutex.lock();