mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
1082 lines
38 KiB
Zig
1082 lines
38 KiB
Zig
const std = @import("std");
|
|
const Atomic = std.atomic.Value;
|
|
|
|
const main = @import("main");
|
|
const blocks = main.blocks;
|
|
const chunk = main.chunk;
|
|
const game = main.game;
|
|
const network = main.network;
|
|
const settings = main.settings;
|
|
const utils = main.utils;
|
|
const LightMap = main.server.terrain.LightMap;
|
|
const vec = main.vec;
|
|
const Vec2f = vec.Vec2f;
|
|
const Vec3i = vec.Vec3i;
|
|
const Vec3f = vec.Vec3f;
|
|
const Vec3d = vec.Vec3d;
|
|
const Vec4f = vec.Vec4f;
|
|
const Mat4f = vec.Mat4f;
|
|
const EventStatus = main.entity_data.EventStatus;
|
|
|
|
const chunk_meshing = @import("chunk_meshing.zig");
|
|
const ChunkMesh = chunk_meshing.ChunkMesh;
|
|
|
|
const ChunkMeshNode = struct {
|
|
mesh: ?*chunk_meshing.ChunkMesh = null,
|
|
active: bool = false,
|
|
rendered: bool = false,
|
|
finishedMeshing: bool = false, // Must be synced with mesh.finishedMeshing
|
|
finishedMeshingHigherResolution: u8 = 0, // Must be synced with finishedMeshing of the 8 higher resolution chunks.
|
|
pos: chunk.ChunkPosition = undefined,
|
|
isNeighborLod: [6]bool = @splat(false), // Must be synced with mesh.isNeighborLod
|
|
mutex: std.Thread.Mutex = .{},
|
|
};
|
|
const storageSize = 64;
|
|
const storageMask = storageSize - 1;
|
|
var storageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize*storageSize]ChunkMeshNode = undefined;
|
|
var mapStorageLists: [settings.highestSupportedLod + 1]*[storageSize*storageSize]?*LightMap.LightMapFragment = undefined;
|
|
var meshList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
|
|
var priorityMeshUpdateList: main.utils.ConcurrentQueue(*chunk_meshing.ChunkMesh) = undefined;
|
|
pub var updatableList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
|
|
var mapUpdatableList: main.utils.ConcurrentQueue(*LightMap.LightMapFragment) = undefined;
|
|
var clearList = main.List(*chunk_meshing.ChunkMesh).init(main.globalAllocator);
|
|
var lastPx: i32 = 0;
|
|
var lastPy: i32 = 0;
|
|
var lastPz: i32 = 0;
|
|
var lastRD: u16 = 0;
|
|
var mutex: std.Thread.Mutex = .{};
|
|
|
|
pub const BlockUpdate = struct {
|
|
x: i32,
|
|
y: i32,
|
|
z: i32,
|
|
newBlock: blocks.Block,
|
|
|
|
pub fn init(pos: Vec3i, block: blocks.Block) BlockUpdate {
|
|
return .{.x = pos[0], .y = pos[1], .z = pos[2], .newBlock = block};
|
|
}
|
|
};
|
|
|
|
var blockUpdateList: main.utils.ConcurrentQueue(BlockUpdate) = undefined;
|
|
|
|
var meshMemoryPool: main.heap.MemoryPool(chunk_meshing.ChunkMesh) = undefined;
|
|
|
|
pub fn init() void { // MARK: init()
|
|
lastRD = 0;
|
|
blockUpdateList = .init(main.globalAllocator, 16);
|
|
meshMemoryPool = .init(main.globalAllocator);
|
|
for(&storageLists) |*storageList| {
|
|
storageList.* = main.globalAllocator.create([storageSize*storageSize*storageSize]ChunkMeshNode);
|
|
for(storageList.*) |*val| {
|
|
val.* = .{};
|
|
}
|
|
}
|
|
for(&mapStorageLists) |*mapStorageList| {
|
|
mapStorageList.* = main.globalAllocator.create([storageSize*storageSize]?*LightMap.LightMapFragment);
|
|
@memset(mapStorageList.*, null);
|
|
}
|
|
priorityMeshUpdateList = .init(main.globalAllocator, 16);
|
|
mapUpdatableList = .init(main.globalAllocator, 16);
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
const olderPx = lastPx;
|
|
const olderPy = lastPy;
|
|
const olderPz = lastPz;
|
|
const olderRD = lastRD;
|
|
lastPx = 0;
|
|
lastPy = 0;
|
|
lastPz = 0;
|
|
lastRD = 0;
|
|
freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
|
|
for(storageLists) |storageList| {
|
|
main.globalAllocator.destroy(storageList);
|
|
}
|
|
for(mapStorageLists) |mapStorageList| {
|
|
main.globalAllocator.destroy(mapStorageList);
|
|
}
|
|
|
|
for(updatableList.items) |mesh| {
|
|
mesh.decreaseRefCount();
|
|
}
|
|
updatableList.clearAndFree();
|
|
while(mapUpdatableList.dequeue()) |map| {
|
|
map.decreaseRefCount();
|
|
}
|
|
mapUpdatableList.deinit();
|
|
while(priorityMeshUpdateList.dequeue()) |mesh| {
|
|
mesh.decreaseRefCount();
|
|
}
|
|
priorityMeshUpdateList.deinit();
|
|
blockUpdateList.deinit();
|
|
meshList.clearAndFree();
|
|
for(clearList.items) |mesh| {
|
|
mesh.deinit();
|
|
meshMemoryPool.destroy(mesh);
|
|
}
|
|
clearList.clearAndFree();
|
|
meshMemoryPool.deinit();
|
|
}
|
|
|
|
// MARK: getters
|
|
|
|
fn getNodePointer(pos: chunk.ChunkPosition) *ChunkMeshNode {
|
|
const lod = std.math.log2_int(u31, pos.voxelSize);
|
|
var xIndex = pos.wx >> lod + chunk.chunkShift;
|
|
var yIndex = pos.wy >> lod + chunk.chunkShift;
|
|
var zIndex = pos.wz >> lod + chunk.chunkShift;
|
|
xIndex &= storageMask;
|
|
yIndex &= storageMask;
|
|
zIndex &= storageMask;
|
|
const index = (xIndex*storageSize + yIndex)*storageSize + zIndex;
|
|
return &storageLists[lod][@intCast(index)];
|
|
}
|
|
|
|
fn finishedMeshingMask(x: bool, y: bool, z: bool) u8 {
|
|
return @as(u8, 1) << (@as(u3, @intFromBool(x))*4 + @as(u3, @intFromBool(y))*2 + @as(u3, @intFromBool(z)));
|
|
}
|
|
|
|
fn updateHigherLodNodeFinishedMeshing(pos_: chunk.ChunkPosition, finishedMeshing: bool) void {
|
|
const lod = std.math.log2_int(u31, pos_.voxelSize);
|
|
if(lod == settings.highestLod) return;
|
|
var pos = pos_;
|
|
pos.wx &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
|
|
pos.wy &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
|
|
pos.wz &= ~@as(i32, pos.voxelSize*chunk.chunkSize);
|
|
pos.voxelSize *= 2;
|
|
const mask = finishedMeshingMask(pos.wx != pos_.wx, pos.wy != pos_.wy, pos.wz != pos_.wz);
|
|
const node = getNodePointer(pos);
|
|
if(finishedMeshing) {
|
|
node.finishedMeshingHigherResolution |= mask;
|
|
} else {
|
|
node.finishedMeshingHigherResolution &= ~mask;
|
|
}
|
|
}
|
|
|
|
fn getMapPiecePointer(x: i32, y: i32, voxelSize: u31) *?*LightMap.LightMapFragment {
|
|
const lod = std.math.log2_int(u31, voxelSize);
|
|
var xIndex = x >> lod + LightMap.LightMapFragment.mapShift;
|
|
var yIndex = y >> lod + LightMap.LightMapFragment.mapShift;
|
|
xIndex &= storageMask;
|
|
yIndex &= storageMask;
|
|
const index = xIndex*storageSize + yIndex;
|
|
return &(&mapStorageLists)[lod][@intCast(index)];
|
|
}
|
|
|
|
pub fn getLightMapPieceAndIncreaseRefCount(x: i32, y: i32, voxelSize: u31) ?*LightMap.LightMapFragment {
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
const result: *LightMap.LightMapFragment = getMapPiecePointer(x, y, voxelSize).* orelse {
|
|
return null;
|
|
};
|
|
result.increaseRefCount();
|
|
return result;
|
|
}
|
|
|
|
pub fn getBlock(x: i32, y: i32, z: i32) ?blocks.Block {
|
|
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
|
|
node.mutex.lock();
|
|
defer node.mutex.unlock();
|
|
const mesh = node.mesh orelse return null;
|
|
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
|
|
return block;
|
|
}
|
|
|
|
pub fn triggerOnInteractBlock(x: i32, y: i32, z: i32) EventStatus {
|
|
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = 1});
|
|
node.mutex.lock();
|
|
defer node.mutex.unlock();
|
|
const mesh = node.mesh orelse return .ignored;
|
|
const block = mesh.chunk.getBlock(x & chunk.chunkMask, y & chunk.chunkMask, z & chunk.chunkMask);
|
|
if(block.entityDataClass()) |class| {
|
|
return class.onInteract(.{x, y, z}, mesh.chunk);
|
|
}
|
|
// Event was not handled.
|
|
return .ignored;
|
|
}
|
|
|
|
pub fn getLight(wx: i32, wy: i32, wz: i32) ?[6]u8 {
|
|
const node = getNodePointer(.{.wx = wx, .wy = wy, .wz = wz, .voxelSize = 1});
|
|
node.mutex.lock();
|
|
defer node.mutex.unlock();
|
|
const mesh = node.mesh orelse return null;
|
|
const x = (wx >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
|
|
const y = (wy >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
|
|
const z = (wz >> mesh.chunk.voxelSizeShift) & chunk.chunkMask;
|
|
mesh.lightingData[0].lock.lockRead();
|
|
defer mesh.lightingData[0].lock.unlockRead();
|
|
mesh.lightingData[1].lock.lockRead();
|
|
defer mesh.lightingData[1].lock.unlockRead();
|
|
return mesh.lightingData[1].getValue(x, y, z) ++ mesh.lightingData[0].getValue(x, y, z);
|
|
}
|
|
|
|
pub fn getBlockFromAnyLod(x: i32, y: i32, z: i32) blocks.Block {
|
|
var lod: u5 = 0;
|
|
while(lod < settings.highestLod) : (lod += 1) {
|
|
const node = getNodePointer(.{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod});
|
|
node.mutex.lock();
|
|
defer node.mutex.unlock();
|
|
const mesh = node.mesh orelse continue;
|
|
const block = mesh.chunk.getBlock(x & chunk.chunkMask << lod, y & chunk.chunkMask << lod, z & chunk.chunkMask << lod);
|
|
return block;
|
|
}
|
|
return blocks.Block{.typ = 0, .data = 0};
|
|
}
|
|
|
|
pub fn getMeshAndIncreaseRefCount(pos: chunk.ChunkPosition) ?*chunk_meshing.ChunkMesh {
|
|
const lod = std.math.log2_int(u31, pos.voxelSize);
|
|
const mask = ~((@as(i32, 1) << lod + chunk.chunkShift) - 1);
|
|
const node = getNodePointer(pos);
|
|
node.mutex.lock();
|
|
const mesh = node.mesh orelse {
|
|
node.mutex.unlock();
|
|
return null;
|
|
};
|
|
mesh.increaseRefCount();
|
|
node.mutex.unlock();
|
|
if(pos.wx & mask != mesh.pos.wx or pos.wy & mask != mesh.pos.wy or pos.wz & mask != mesh.pos.wz) {
|
|
mesh.decreaseRefCount();
|
|
return null;
|
|
}
|
|
return mesh;
|
|
}
|
|
|
|
pub fn getMeshFromAnyLodAndIncreaseRefCount(wx: i32, wy: i32, wz: i32, voxelSize: u31) ?*chunk_meshing.ChunkMesh {
|
|
var lod: u5 = @ctz(voxelSize);
|
|
while(lod < settings.highestLod) : (lod += 1) {
|
|
const mesh = getMeshAndIncreaseRefCount(.{.wx = wx & ~chunk.chunkMask << lod, .wy = wy & ~chunk.chunkMask << lod, .wz = wz & ~chunk.chunkMask << lod, .voxelSize = @as(u31, 1) << lod});
|
|
return mesh orelse continue;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn getNeighborAndIncreaseRefCount(_pos: chunk.ChunkPosition, resolution: u31, neighbor: chunk.Neighbor) ?*chunk_meshing.ChunkMesh {
|
|
var pos = _pos;
|
|
pos.wx +%= pos.voxelSize*chunk.chunkSize*neighbor.relX();
|
|
pos.wy +%= pos.voxelSize*chunk.chunkSize*neighbor.relY();
|
|
pos.wz +%= pos.voxelSize*chunk.chunkSize*neighbor.relZ();
|
|
pos.voxelSize = resolution;
|
|
return getMeshAndIncreaseRefCount(pos);
|
|
}
|
|
|
|
fn reduceRenderDistance(fullRenderDistance: i64, reduction: i64) i32 {
|
|
const reducedRenderDistanceSquare: f64 = @floatFromInt(fullRenderDistance*fullRenderDistance - reduction*reduction);
|
|
const reducedRenderDistance: i32 = @intFromFloat(@ceil(@sqrt(@max(0, reducedRenderDistanceSquare))));
|
|
return reducedRenderDistance;
|
|
}
|
|
|
|
fn isInRenderDistance(pos: chunk.ChunkPosition) bool { // MARK: isInRenderDistance()
|
|
const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize;
|
|
const size: u31 = chunk.chunkSize*pos.voxelSize;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
const minX = lastPx -% maxRenderDistance & invMask;
|
|
const maxX = lastPx +% maxRenderDistance +% size & invMask;
|
|
if(pos.wx -% minX < 0) return false;
|
|
if(pos.wx -% maxX >= 0) return false;
|
|
var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx);
|
|
deltaX = @max(0, deltaX - size/2);
|
|
|
|
const maxYRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX);
|
|
const minY = lastPy -% maxYRenderDistance & invMask;
|
|
const maxY = lastPy +% maxYRenderDistance +% size & invMask;
|
|
if(pos.wy -% minY < 0) return false;
|
|
if(pos.wy -% maxY >= 0) return false;
|
|
var deltaY: i64 = @abs(pos.wy +% size/2 -% lastPy);
|
|
deltaY = @max(0, deltaY - size/2);
|
|
|
|
const maxZRenderDistance: i32 = reduceRenderDistance(maxYRenderDistance, deltaY);
|
|
if(maxZRenderDistance == 0) return false;
|
|
const minZ = lastPz -% maxZRenderDistance & invMask;
|
|
const maxZ = lastPz +% maxZRenderDistance +% size & invMask;
|
|
if(pos.wz -% minZ < 0) return false;
|
|
if(pos.wz -% maxZ >= 0) return false;
|
|
return true;
|
|
}
|
|
|
|
fn isMapInRenderDistance(pos: LightMap.MapFragmentPosition) bool {
|
|
const maxRenderDistance = lastRD*chunk.chunkSize*pos.voxelSize;
|
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize)*pos.voxelSize;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
const minX = lastPx -% maxRenderDistance & invMask;
|
|
const maxX = lastPx +% maxRenderDistance +% size & invMask;
|
|
if(pos.wx -% minX < 0) return false;
|
|
if(pos.wx -% maxX >= 0) return false;
|
|
var deltaX: i64 = @abs(pos.wx +% size/2 -% lastPx);
|
|
deltaX = @max(0, deltaX - size/2);
|
|
|
|
const maxYRenderDistance: i32 = reduceRenderDistance(maxRenderDistance, deltaX);
|
|
if(maxYRenderDistance == 0) return false;
|
|
const minY = lastPy -% maxYRenderDistance & invMask;
|
|
const maxY = lastPy +% maxYRenderDistance +% size & invMask;
|
|
if(pos.wy -% minY < 0) return false;
|
|
if(pos.wy -% maxY >= 0) return false;
|
|
return true;
|
|
}
|
|
|
|
fn freeOldMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16) void { // MARK: freeOldMeshes()
|
|
for(0..settings.highestLod + 1) |_lod| {
|
|
const lod: u5 = @intCast(_lod);
|
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
|
const size: u31 = chunk.chunkSize << lod;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size - 1, size) + 2 <= storageSize);
|
|
|
|
const minX = olderPx -% maxRenderDistanceOld & invMask;
|
|
const maxX = olderPx +% maxRenderDistanceOld +% size & invMask;
|
|
var x = minX;
|
|
while(x != maxX) : (x +%= size) {
|
|
const xIndex = @divExact(x, size) & storageMask;
|
|
var deltaXNew: i64 = @abs(x +% size/2 -% lastPx);
|
|
deltaXNew = @max(0, deltaXNew - size/2);
|
|
var deltaXOld: i64 = @abs(x +% size/2 -% olderPx);
|
|
deltaXOld = @max(0, deltaXOld - size/2);
|
|
const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
|
const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
|
|
|
const minY = olderPy -% maxYRenderDistanceOld & invMask;
|
|
const maxY = olderPy +% maxYRenderDistanceOld +% size & invMask;
|
|
var y = minY;
|
|
while(y != maxY) : (y +%= size) {
|
|
const yIndex = @divExact(y, size) & storageMask;
|
|
var deltaYOld: i64 = @abs(y +% size/2 -% olderPy);
|
|
deltaYOld = @max(0, deltaYOld - size/2);
|
|
var deltaYNew: i64 = @abs(y +% size/2 -% lastPy);
|
|
deltaYNew = @max(0, deltaYNew - size/2);
|
|
var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld);
|
|
if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2;
|
|
var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew);
|
|
if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2;
|
|
|
|
const minZOld = olderPz -% maxZRenderDistanceOld & invMask;
|
|
const maxZOld = olderPz +% maxZRenderDistanceOld +% size & invMask;
|
|
const minZNew = lastPz -% maxZRenderDistanceNew & invMask;
|
|
const maxZNew = lastPz +% maxZRenderDistanceNew +% size & invMask;
|
|
|
|
var zValues: [storageSize]i32 = undefined;
|
|
var zValuesLen: usize = 0;
|
|
if(minZNew -% minZOld > 0) {
|
|
var z = minZOld;
|
|
while(z != minZNew and z != maxZOld) : (z +%= size) {
|
|
zValues[zValuesLen] = z;
|
|
zValuesLen += 1;
|
|
}
|
|
}
|
|
if(maxZOld -% maxZNew > 0) {
|
|
var z = minZOld +% @max(0, maxZNew -% minZOld);
|
|
while(z != maxZOld) : (z +%= size) {
|
|
zValues[zValuesLen] = z;
|
|
zValuesLen += 1;
|
|
}
|
|
}
|
|
|
|
for(zValues[0..zValuesLen]) |z| {
|
|
const zIndex = @divExact(z, size) & storageMask;
|
|
const index = (xIndex*storageSize + yIndex)*storageSize + zIndex;
|
|
|
|
const node = &storageLists[_lod][@intCast(index)];
|
|
node.mutex.lock();
|
|
const oldMesh = node.mesh;
|
|
node.mesh = null;
|
|
node.mutex.unlock();
|
|
node.pos = undefined;
|
|
if(oldMesh) |mesh| {
|
|
node.finishedMeshing = false;
|
|
updateHigherLodNodeFinishedMeshing(mesh.pos, false);
|
|
mesh.decreaseRefCount();
|
|
}
|
|
node.isNeighborLod = @splat(false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(0..settings.highestLod + 1) |_lod| {
|
|
const lod: u5 = @intCast(_lod);
|
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size - 1, size) + 2 <= storageSize);
|
|
|
|
const minX = olderPx -% maxRenderDistanceOld & invMask;
|
|
const maxX = olderPx +% maxRenderDistanceOld +% size & invMask;
|
|
var x = minX;
|
|
while(x != maxX) : (x +%= size) {
|
|
const xIndex = @divExact(x, size) & storageMask;
|
|
var deltaXNew: i64 = @abs(x +% size/2 -% lastPx);
|
|
deltaXNew = @max(0, deltaXNew - size/2);
|
|
var deltaXOld: i64 = @abs(x +% size/2 -% olderPx);
|
|
deltaXOld = @max(0, deltaXOld - size/2);
|
|
var maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
|
if(maxYRenderDistanceNew == 0) maxYRenderDistanceNew -= size/2;
|
|
var maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
|
if(maxYRenderDistanceOld == 0) maxYRenderDistanceOld -= size/2;
|
|
|
|
const minYOld = olderPy -% maxYRenderDistanceOld & invMask;
|
|
const maxYOld = olderPy +% maxYRenderDistanceOld +% size & invMask;
|
|
const minYNew = lastPy -% maxYRenderDistanceNew & invMask;
|
|
const maxYNew = lastPy +% maxYRenderDistanceNew +% size & invMask;
|
|
|
|
var yValues: [storageSize]i32 = undefined;
|
|
var yValuesLen: usize = 0;
|
|
if(minYNew -% minYOld > 0) {
|
|
var y = minYOld;
|
|
while(y != minYNew and y != maxYOld) : (y +%= size) {
|
|
yValues[yValuesLen] = y;
|
|
yValuesLen += 1;
|
|
}
|
|
}
|
|
if(maxYOld -% maxYNew > 0) {
|
|
var y = minYOld +% @max(0, maxYNew -% minYOld);
|
|
while(y != maxYOld) : (y +%= size) {
|
|
yValues[yValuesLen] = y;
|
|
yValuesLen += 1;
|
|
}
|
|
}
|
|
|
|
for(yValues[0..yValuesLen]) |y| {
|
|
const yIndex = @divExact(y, size) & storageMask;
|
|
const index = xIndex*storageSize + yIndex;
|
|
|
|
const mapPointer = &mapStorageLists[_lod][@intCast(index)];
|
|
mutex.lock();
|
|
const oldMap = mapPointer.*;
|
|
mapPointer.* = null;
|
|
mutex.unlock();
|
|
if(oldMap) |map| {
|
|
map.decreaseRefCount();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn createNewMeshes(olderPx: i32, olderPy: i32, olderPz: i32, olderRD: u16, meshRequests: *main.List(chunk.ChunkPosition), mapRequests: *main.List(LightMap.MapFragmentPosition)) void { // MARK: createNewMeshes()
|
|
for(0..settings.highestLod + 1) |_lod| {
|
|
const lod: u5 = @intCast(_lod);
|
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
|
const size: u31 = chunk.chunkSize << lod;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size - 1, size) + 2 <= storageSize);
|
|
|
|
const minX = lastPx -% maxRenderDistanceNew & invMask;
|
|
const maxX = lastPx +% maxRenderDistanceNew +% size & invMask;
|
|
var x = minX;
|
|
while(x != maxX) : (x +%= size) {
|
|
const xIndex = @divExact(x, size) & storageMask;
|
|
var deltaXNew: i64 = @abs(x +% size/2 -% lastPx);
|
|
deltaXNew = @max(0, deltaXNew - size/2);
|
|
var deltaXOld: i64 = @abs(x +% size/2 -% olderPx);
|
|
deltaXOld = @max(0, deltaXOld - size/2);
|
|
const maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
|
const maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
|
|
|
const minY = lastPy -% maxYRenderDistanceNew & invMask;
|
|
const maxY = lastPy +% maxYRenderDistanceNew +% size & invMask;
|
|
var y = minY;
|
|
while(y != maxY) : (y +%= size) {
|
|
const yIndex = @divExact(y, size) & storageMask;
|
|
var deltaYOld: i64 = @abs(y +% size/2 -% olderPy);
|
|
deltaYOld = @max(0, deltaYOld - size/2);
|
|
var deltaYNew: i64 = @abs(y +% size/2 -% lastPy);
|
|
deltaYNew = @max(0, deltaYNew - size/2);
|
|
var maxZRenderDistanceNew: i32 = reduceRenderDistance(maxYRenderDistanceNew, deltaYNew);
|
|
if(maxZRenderDistanceNew == 0) maxZRenderDistanceNew -= size/2;
|
|
var maxZRenderDistanceOld: i32 = reduceRenderDistance(maxYRenderDistanceOld, deltaYOld);
|
|
if(maxZRenderDistanceOld == 0) maxZRenderDistanceOld -= size/2;
|
|
|
|
const minZOld = olderPz -% maxZRenderDistanceOld & invMask;
|
|
const maxZOld = olderPz +% maxZRenderDistanceOld +% size & invMask;
|
|
const minZNew = lastPz -% maxZRenderDistanceNew & invMask;
|
|
const maxZNew = lastPz +% maxZRenderDistanceNew +% size & invMask;
|
|
|
|
var zValues: [storageSize]i32 = undefined;
|
|
var zValuesLen: usize = 0;
|
|
if(minZOld -% minZNew > 0) {
|
|
var z = minZNew;
|
|
while(z != minZOld and z != maxZNew) : (z +%= size) {
|
|
zValues[zValuesLen] = z;
|
|
zValuesLen += 1;
|
|
}
|
|
}
|
|
if(maxZNew -% maxZOld > 0) {
|
|
var z = minZNew +% @max(0, maxZOld -% minZNew);
|
|
while(z != maxZNew) : (z +%= size) {
|
|
zValues[zValuesLen] = z;
|
|
zValuesLen += 1;
|
|
}
|
|
}
|
|
|
|
for(zValues[0..zValuesLen]) |z| {
|
|
const zIndex = @divExact(z, size) & storageMask;
|
|
const index = (xIndex*storageSize + yIndex)*storageSize + zIndex;
|
|
const pos = chunk.ChunkPosition{.wx = x, .wy = y, .wz = z, .voxelSize = @as(u31, 1) << lod};
|
|
|
|
const node = &storageLists[_lod][@intCast(index)];
|
|
node.mutex.lock();
|
|
node.pos = pos;
|
|
if(node.mesh) |mesh| {
|
|
std.debug.assert(std.meta.eql(pos, mesh.pos));
|
|
} else {
|
|
meshRequests.append(pos);
|
|
}
|
|
node.mutex.unlock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for(0..settings.highestLod + 1) |_lod| {
|
|
const lod: u5 = @intCast(_lod);
|
|
const maxRenderDistanceNew = lastRD*chunk.chunkSize << lod;
|
|
const maxRenderDistanceOld = olderRD*chunk.chunkSize << lod;
|
|
const size: u31 = @as(u31, LightMap.LightMapFragment.mapSize) << lod;
|
|
const mask: i32 = size - 1;
|
|
const invMask: i32 = ~mask;
|
|
|
|
std.debug.assert(@divFloor(2*maxRenderDistanceNew + size - 1, size) + 2 <= storageSize);
|
|
|
|
const minX = lastPx -% maxRenderDistanceNew & invMask;
|
|
const maxX = lastPx +% maxRenderDistanceNew +% size & invMask;
|
|
var x = minX;
|
|
while(x != maxX) : (x +%= size) {
|
|
const xIndex = @divExact(x, size) & storageMask;
|
|
var deltaXNew: i64 = @abs(x +% size/2 -% lastPx);
|
|
deltaXNew = @max(0, deltaXNew - size/2);
|
|
var deltaXOld: i64 = @abs(x +% size/2 -% olderPx);
|
|
deltaXOld = @max(0, deltaXOld - size/2);
|
|
var maxYRenderDistanceNew: i32 = reduceRenderDistance(maxRenderDistanceNew, deltaXNew);
|
|
if(maxYRenderDistanceNew == 0) maxYRenderDistanceNew -= size/2;
|
|
var maxYRenderDistanceOld: i32 = reduceRenderDistance(maxRenderDistanceOld, deltaXOld);
|
|
if(maxYRenderDistanceOld == 0) maxYRenderDistanceOld -= size/2;
|
|
|
|
const minYOld = olderPy -% maxYRenderDistanceOld & invMask;
|
|
const maxYOld = olderPy +% maxYRenderDistanceOld +% size & invMask;
|
|
const minYNew = lastPy -% maxYRenderDistanceNew & invMask;
|
|
const maxYNew = lastPy +% maxYRenderDistanceNew +% size & invMask;
|
|
|
|
var yValues: [storageSize]i32 = undefined;
|
|
var yValuesLen: usize = 0;
|
|
if(minYOld -% minYNew > 0) {
|
|
var y = minYNew;
|
|
while(y != minYOld and y != maxYNew) : (y +%= size) {
|
|
yValues[yValuesLen] = y;
|
|
yValuesLen += 1;
|
|
}
|
|
}
|
|
if(maxYNew -% maxYOld > 0) {
|
|
var y = minYNew +% @max(0, maxYOld -% minYNew);
|
|
while(y != maxYNew) : (y +%= size) {
|
|
yValues[yValuesLen] = y;
|
|
yValuesLen += 1;
|
|
}
|
|
}
|
|
|
|
for(yValues[0..yValuesLen]) |y| {
|
|
const yIndex = @divExact(y, size) & storageMask;
|
|
const index = xIndex*storageSize + yIndex;
|
|
const pos = LightMap.MapFragmentPosition{.wx = x, .wy = y, .voxelSize = @as(u31, 1) << lod, .voxelSizeShift = lod};
|
|
|
|
const node = &mapStorageLists[_lod][@intCast(index)];
|
|
mutex.lock();
|
|
if(node.*) |map| {
|
|
std.debug.assert(std.meta.eql(pos, map.pos));
|
|
} else {
|
|
mapRequests.append(pos);
|
|
}
|
|
mutex.unlock();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub noinline fn updateAndGetRenderChunks(conn: *network.Connection, frustum: *const main.renderer.Frustum, playerPos: Vec3d, renderDistance: u16) []*chunk_meshing.ChunkMesh { // MARK: updateAndGetRenderChunks()
|
|
meshList.clearRetainingCapacity();
|
|
|
|
const playerPosInt: Vec3i = @intFromFloat(@floor(playerPos));
|
|
|
|
var meshRequests = main.List(chunk.ChunkPosition).init(main.stackAllocator);
|
|
defer meshRequests.deinit();
|
|
var mapRequests = main.List(LightMap.MapFragmentPosition).init(main.stackAllocator);
|
|
defer mapRequests.deinit();
|
|
|
|
const olderPx = lastPx;
|
|
const olderPy = lastPy;
|
|
const olderPz = lastPz;
|
|
const olderRD = lastRD;
|
|
mutex.lock();
|
|
lastPx = @intFromFloat(playerPos[0]);
|
|
lastPy = @intFromFloat(playerPos[1]);
|
|
lastPz = @intFromFloat(playerPos[2]);
|
|
lastRD = renderDistance;
|
|
mutex.unlock();
|
|
freeOldMeshes(olderPx, olderPy, olderPz, olderRD);
|
|
|
|
createNewMeshes(olderPx, olderPy, olderPz, olderRD, &meshRequests, &mapRequests);
|
|
|
|
// Make requests as soon as possible to reduce latency:
|
|
network.Protocols.lightMapRequest.sendRequest(conn, mapRequests.items);
|
|
network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items, .{lastPx, lastPy, lastPz}, lastRD);
|
|
|
|
// Finds all visible chunks and lod chunks using a breadth-first hierarchical search.
|
|
|
|
var searchList = main.utils.CircularBufferQueue(*ChunkMeshNode).init(main.stackAllocator, 1024);
|
|
defer searchList.deinit();
|
|
{
|
|
var firstPos = chunk.ChunkPosition{
|
|
.wx = @intFromFloat(@floor(playerPos[0])),
|
|
.wy = @intFromFloat(@floor(playerPos[1])),
|
|
.wz = @intFromFloat(@floor(playerPos[2])),
|
|
.voxelSize = 1,
|
|
};
|
|
const lod: u3 = settings.highestLod;
|
|
firstPos.wx &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
|
|
firstPos.wy &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
|
|
firstPos.wz &= ~@as(i32, chunk.chunkMask << lod | (@as(i32, 1) << lod) - 1);
|
|
firstPos.voxelSize <<= lod;
|
|
const node = getNodePointer(firstPos);
|
|
const hasMesh = node.finishedMeshing;
|
|
if(hasMesh) {
|
|
node.active = true;
|
|
node.rendered = true;
|
|
searchList.enqueue(node);
|
|
}
|
|
}
|
|
var nodeList = main.List(*ChunkMeshNode).initCapacity(main.stackAllocator, 1024);
|
|
defer nodeList.deinit();
|
|
while(searchList.dequeue()) |node| {
|
|
std.debug.assert(node.finishedMeshing);
|
|
std.debug.assert(node.active);
|
|
if(!node.active) continue;
|
|
node.active = false;
|
|
|
|
const pos = node.pos;
|
|
|
|
const relPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{pos.wx, pos.wy, pos.wz})) - playerPos;
|
|
const relPosFloat: Vec3f = @floatCast(relPos);
|
|
|
|
if(pos.voxelSize == @as(i32, 1) << settings.highestLod) {
|
|
for(chunk.Neighbor.iterable) |neighbor| {
|
|
const component = neighbor.extractDirectionComponent(relPosFloat);
|
|
if(neighbor.isPositive() and component + @as(f32, @floatFromInt(chunk.chunkSize*pos.voxelSize)) <= 0) continue;
|
|
if(!neighbor.isPositive() and component >= 0) continue;
|
|
const neighborPos = chunk.ChunkPosition{
|
|
.wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize,
|
|
.wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize,
|
|
.wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize,
|
|
.voxelSize = pos.voxelSize,
|
|
};
|
|
const node2 = getNodePointer(neighborPos);
|
|
if(!node2.active and node2.finishedMeshing) {
|
|
if(!frustum.testAAB(relPosFloat + @as(Vec3f, @floatFromInt(Vec3i{neighbor.relX()*chunk.chunkSize*pos.voxelSize, neighbor.relY()*chunk.chunkSize*pos.voxelSize, neighbor.relZ()*chunk.chunkSize*pos.voxelSize})), @splat(@floatFromInt(chunk.chunkSize*pos.voxelSize))))
|
|
continue;
|
|
node2.active = true;
|
|
node2.rendered = true;
|
|
searchList.enqueue(node2);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(node.finishedMeshingHigherResolution == 0xff) {
|
|
node.rendered = false;
|
|
const lowerLodBit: i32 = pos.voxelSize*chunk.chunkSize >> 1;
|
|
const startPos: chunk.ChunkPosition = .{
|
|
.wx = pos.wx | if((pos.wx | lowerLodBit) -% playerPosInt[0] > 0) lowerLodBit else 0,
|
|
.wy = pos.wy | if((pos.wy | lowerLodBit) -% playerPosInt[1] > 0) lowerLodBit else 0,
|
|
.wz = pos.wz | if((pos.wz | lowerLodBit) -% playerPosInt[2] > 0) lowerLodBit else 0,
|
|
.voxelSize = pos.voxelSize >> 1,
|
|
};
|
|
for(0..2) |dx| {
|
|
for(0..2) |dy| {
|
|
for(0..2) |dz| {
|
|
var nextPos = startPos;
|
|
if(dx == 1) nextPos.wx ^= lowerLodBit;
|
|
if(dy == 1) nextPos.wy ^= lowerLodBit;
|
|
if(dz == 1) nextPos.wz ^= lowerLodBit;
|
|
const node2 = getNodePointer(nextPos);
|
|
const relNextPos: Vec3d = @as(Vec3d, @floatFromInt(Vec3i{nextPos.wx, nextPos.wy, nextPos.wz})) - playerPos;
|
|
if(!frustum.testAAB(@floatCast(relNextPos), @splat(@floatFromInt(chunk.chunkSize*nextPos.voxelSize))))
|
|
continue;
|
|
std.debug.assert(node2.finishedMeshing);
|
|
node2.active = true;
|
|
node2.rendered = true;
|
|
searchList.enqueue_back(node2);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
nodeList.append(node);
|
|
}
|
|
}
|
|
for(nodeList.items) |node| {
|
|
const pos = node.pos;
|
|
var isNeighborLod: [6]bool = @splat(false);
|
|
if(pos.voxelSize != @as(i32, 1) << settings.highestLod) {
|
|
for(chunk.Neighbor.iterable) |neighbor| {
|
|
var neighborPos = chunk.ChunkPosition{
|
|
.wx = pos.wx +% neighbor.relX()*chunk.chunkSize*pos.voxelSize,
|
|
.wy = pos.wy +% neighbor.relY()*chunk.chunkSize*pos.voxelSize,
|
|
.wz = pos.wz +% neighbor.relZ()*chunk.chunkSize*pos.voxelSize,
|
|
.voxelSize = pos.voxelSize,
|
|
};
|
|
neighborPos.wx &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize);
|
|
neighborPos.wy &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize);
|
|
neighborPos.wz &= ~@as(i32, neighborPos.voxelSize*chunk.chunkSize);
|
|
neighborPos.voxelSize *= 2;
|
|
const node2 = getNodePointer(neighborPos);
|
|
isNeighborLod[neighbor.toInt()] = node2.finishedMeshingHigherResolution != 0xff;
|
|
}
|
|
}
|
|
if(!std.meta.eql(node.isNeighborLod, isNeighborLod)) {
|
|
const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
|
|
mesh.isNeighborLod = isNeighborLod;
|
|
node.isNeighborLod = isNeighborLod;
|
|
mesh.uploadData();
|
|
}
|
|
}
|
|
for(nodeList.items) |node| {
|
|
node.rendered = false;
|
|
if(!node.finishedMeshing) continue;
|
|
|
|
const mesh = node.mesh.?; // No need to lock the mutex, since no other thread is allowed to overwrite the mesh (unless it's null).
|
|
|
|
node.mutex.lock();
|
|
if(mesh.needsMeshUpdate) {
|
|
mesh.uploadData();
|
|
mesh.needsMeshUpdate = false;
|
|
}
|
|
node.mutex.unlock();
|
|
// Remove empty meshes.
|
|
if(!mesh.isEmpty()) {
|
|
meshList.append(mesh);
|
|
}
|
|
}
|
|
|
|
return meshList.items;
|
|
}
|
|
|
|
pub fn updateMeshes(targetTime: i64) void { // MARK: updateMeshes()=
|
|
if(!blockUpdateList.empty()) batchUpdateBlocks();
|
|
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
for(clearList.items) |mesh| {
|
|
mesh.deinit();
|
|
meshMemoryPool.destroy(mesh);
|
|
}
|
|
clearList.clearRetainingCapacity();
|
|
while(priorityMeshUpdateList.dequeue()) |mesh| {
|
|
if(!mesh.needsMeshUpdate) {
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
mesh.decreaseRefCount();
|
|
continue;
|
|
}
|
|
mesh.needsMeshUpdate = false;
|
|
const node = getNodePointer(mesh.pos);
|
|
node.mutex.lock();
|
|
if(node.mesh != mesh) {
|
|
node.mutex.unlock();
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
mesh.decreaseRefCount();
|
|
continue;
|
|
}
|
|
node.mutex.unlock();
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
mesh.decreaseRefCount();
|
|
mesh.uploadData();
|
|
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
|
|
}
|
|
while(mapUpdatableList.dequeue()) |map| {
|
|
if(!isMapInRenderDistance(map.pos)) {
|
|
map.decreaseRefCount();
|
|
} else {
|
|
const mapPointer = getMapPiecePointer(map.pos.wx, map.pos.wy, map.pos.voxelSize);
|
|
if(mapPointer.*) |old| {
|
|
old.decreaseRefCount();
|
|
}
|
|
mapPointer.* = map;
|
|
}
|
|
}
|
|
while(updatableList.items.len != 0) {
|
|
// TODO: Find a faster solution than going through the entire list every frame.
|
|
var closestPriority: f32 = -std.math.floatMax(f32);
|
|
var closestIndex: usize = 0;
|
|
const playerPos = game.Player.getEyePosBlocking();
|
|
{
|
|
var i: usize = 0;
|
|
while(i < updatableList.items.len) {
|
|
const mesh = updatableList.items[i];
|
|
if(!isInRenderDistance(mesh.pos)) {
|
|
_ = updatableList.swapRemove(i);
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
mesh.decreaseRefCount();
|
|
continue;
|
|
}
|
|
const priority = mesh.pos.getPriority(playerPos);
|
|
if(priority > closestPriority) {
|
|
closestPriority = priority;
|
|
closestIndex = i;
|
|
}
|
|
i += 1;
|
|
}
|
|
if(updatableList.items.len == 0) break;
|
|
}
|
|
const mesh = updatableList.swapRemove(closestIndex);
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
if(isInRenderDistance(mesh.pos)) {
|
|
const node = getNodePointer(mesh.pos);
|
|
std.debug.assert(std.meta.eql(node.pos, mesh.pos));
|
|
node.finishedMeshing = true;
|
|
mesh.finishedMeshing = true;
|
|
updateHigherLodNodeFinishedMeshing(mesh.pos, true);
|
|
mesh.uploadData();
|
|
node.mutex.lock();
|
|
const oldMesh = node.mesh;
|
|
node.mesh = mesh;
|
|
node.mutex.unlock();
|
|
if(oldMesh) |_oldMesh| {
|
|
_oldMesh.decreaseRefCount();
|
|
}
|
|
} else {
|
|
mesh.decreaseRefCount();
|
|
}
|
|
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
|
|
}
|
|
}
|
|
|
|
fn batchUpdateBlocks() void {
|
|
var lightRefreshList = main.List(*ChunkMesh).init(main.stackAllocator);
|
|
defer lightRefreshList.deinit();
|
|
|
|
var regenerateMeshList = main.List(*ChunkMesh).init(main.stackAllocator);
|
|
defer regenerateMeshList.deinit();
|
|
|
|
// First of all process all the block updates:
|
|
while(blockUpdateList.dequeue()) |blockUpdate| {
|
|
const pos = chunk.ChunkPosition{.wx = blockUpdate.x, .wy = blockUpdate.y, .wz = blockUpdate.z, .voxelSize = 1};
|
|
if(getMeshAndIncreaseRefCount(pos)) |mesh| {
|
|
mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock, &lightRefreshList, ®enerateMeshList);
|
|
mesh.decreaseRefCount();
|
|
} // TODO: It seems like we simply ignore the block update if we don't have the mesh yet.
|
|
}
|
|
for(regenerateMeshList.items) |mesh| {
|
|
mesh.generateMesh(&lightRefreshList);
|
|
}
|
|
{
|
|
for(lightRefreshList.items) |mesh| {
|
|
if(mesh.needsLightRefresh.load(.unordered)) {
|
|
mesh.scheduleLightRefreshAndDecreaseRefCount();
|
|
} else {
|
|
mesh.decreaseRefCount();
|
|
}
|
|
}
|
|
}
|
|
for(regenerateMeshList.items) |mesh| {
|
|
mesh.uploadData();
|
|
mesh.decreaseRefCount();
|
|
}
|
|
}
|
|
|
|
// MARK: adders
|
|
|
|
pub fn addMeshToClearListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void {
|
|
std.debug.assert(mesh.refCount.load(.monotonic) == 0);
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
clearList.append(mesh);
|
|
}
|
|
|
|
pub fn addToUpdateListAndDecreaseRefCount(mesh: *chunk_meshing.ChunkMesh) void {
|
|
std.debug.assert(mesh.refCount.load(.monotonic) != 0);
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
if(mesh.finishedMeshing) {
|
|
priorityMeshUpdateList.enqueue(mesh);
|
|
mesh.needsMeshUpdate = true;
|
|
} else {
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
mesh.decreaseRefCount();
|
|
}
|
|
}
|
|
|
|
pub fn addMeshToStorage(mesh: *chunk_meshing.ChunkMesh) error{AlreadyStored}!void {
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
if(isInRenderDistance(mesh.pos)) {
|
|
const node = getNodePointer(mesh.pos);
|
|
node.mutex.lock();
|
|
defer node.mutex.unlock();
|
|
if(node.mesh != null) {
|
|
return error.AlreadyStored;
|
|
}
|
|
node.mesh = mesh;
|
|
node.finishedMeshing = mesh.finishedMeshing;
|
|
updateHigherLodNodeFinishedMeshing(mesh.pos, mesh.finishedMeshing);
|
|
mesh.increaseRefCount();
|
|
}
|
|
}
|
|
|
|
pub fn finishMesh(mesh: *chunk_meshing.ChunkMesh) void {
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
mesh.increaseRefCount();
|
|
updatableList.append(mesh);
|
|
}
|
|
|
|
pub const MeshGenerationTask = struct { // MARK: MeshGenerationTask
|
|
mesh: *chunk.Chunk,
|
|
|
|
pub const vtable = utils.ThreadPool.VTable{
|
|
.getPriority = main.utils.castFunctionSelfToAnyopaque(getPriority),
|
|
.isStillNeeded = main.utils.castFunctionSelfToAnyopaque(isStillNeeded),
|
|
.run = main.utils.castFunctionSelfToAnyopaque(run),
|
|
.clean = main.utils.castFunctionSelfToAnyopaque(clean),
|
|
.taskType = .meshgenAndLighting,
|
|
};
|
|
|
|
pub fn schedule(mesh: *chunk.Chunk) void {
|
|
const task = main.globalAllocator.create(MeshGenerationTask);
|
|
task.* = MeshGenerationTask{
|
|
.mesh = mesh,
|
|
};
|
|
main.threadPool.addTask(task, &vtable);
|
|
}
|
|
|
|
pub fn getPriority(self: *MeshGenerationTask) f32 {
|
|
return self.mesh.pos.getPriority(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time.
|
|
}
|
|
|
|
pub fn isStillNeeded(self: *MeshGenerationTask) bool {
|
|
const distanceSqr = self.mesh.pos.getMinDistanceSquared(@intFromFloat(game.Player.getPosBlocking())); // TODO: This is called in loop, find a way to do this without calling the mutex every time.
|
|
var maxRenderDistance = settings.renderDistance*chunk.chunkSize*self.mesh.pos.voxelSize;
|
|
maxRenderDistance += 2*self.mesh.pos.voxelSize*chunk.chunkSize;
|
|
return distanceSqr < maxRenderDistance*maxRenderDistance;
|
|
}
|
|
|
|
pub fn run(self: *MeshGenerationTask) void {
|
|
defer main.globalAllocator.destroy(self);
|
|
const pos = self.mesh.pos;
|
|
const mesh = meshMemoryPool.create();
|
|
mesh.init(pos, self.mesh);
|
|
defer mesh.decreaseRefCount();
|
|
mesh.generateLightingData() catch return;
|
|
}
|
|
|
|
pub fn clean(self: *MeshGenerationTask) void {
|
|
self.mesh.deinit();
|
|
main.globalAllocator.destroy(self);
|
|
}
|
|
};
|
|
|
|
// MARK: updaters
|
|
|
|
pub fn updateBlock(update: BlockUpdate) void {
|
|
blockUpdateList.enqueue(update);
|
|
}
|
|
|
|
pub fn updateChunkMesh(mesh: *chunk.Chunk) void {
|
|
MeshGenerationTask.schedule(mesh);
|
|
}
|
|
|
|
pub fn updateLightMap(map: *LightMap.LightMapFragment) void {
|
|
mapUpdatableList.enqueue(map);
|
|
}
|
|
|
|
// MARK: Block breaking animation
|
|
|
|
pub fn addBreakingAnimation(pos: Vec3i, breakingProgress: f32) void {
|
|
const animationFrame: usize = @intFromFloat(breakingProgress*@as(f32, @floatFromInt(main.blocks.meshes.blockBreakingTextures.items.len)));
|
|
const texture = main.blocks.meshes.blockBreakingTextures.items[animationFrame];
|
|
|
|
const block = getBlock(pos[0], pos[1], pos[2]) orelse return;
|
|
const model = main.blocks.meshes.model(block).model();
|
|
|
|
for(model.internalQuads) |quadIndex| {
|
|
addBreakingAnimationFace(pos, quadIndex, texture, null, block.transparent());
|
|
}
|
|
for(&model.neighborFacingQuads, 0..) |quads, n| {
|
|
for(quads) |quadIndex| {
|
|
addBreakingAnimationFace(pos, quadIndex, texture, @enumFromInt(n), block.transparent());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn addBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, texture: u16, neighbor: ?chunk.Neighbor, isTransparent: bool) void {
|
|
const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
|
|
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask));
|
|
const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
|
|
defer mesh.decreaseRefCount();
|
|
mesh.mutex.lock();
|
|
defer mesh.mutex.unlock();
|
|
const lightIndex = blk: {
|
|
const meshData = if(isTransparent) &mesh.transparentMesh else &mesh.opaqueMesh;
|
|
meshData.lock.lockRead();
|
|
defer meshData.lock.unlockRead();
|
|
for(meshData.completeList.getEverything()) |face| {
|
|
if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) {
|
|
break :blk face.position.lightIndex;
|
|
}
|
|
}
|
|
// The face doesn't exist.
|
|
return;
|
|
};
|
|
mesh.blockBreakingFacesChanged = true;
|
|
mesh.blockBreakingFaces.append(.{
|
|
.position = .{
|
|
.x = @intCast(relPos[0]),
|
|
.y = @intCast(relPos[1]),
|
|
.z = @intCast(relPos[2]),
|
|
.isBackFace = false,
|
|
.lightIndex = lightIndex,
|
|
},
|
|
.blockAndQuad = .{
|
|
.texture = texture,
|
|
.quadIndex = quadIndex,
|
|
},
|
|
});
|
|
}
|
|
|
|
fn removeBreakingAnimationFace(pos: Vec3i, quadIndex: main.models.QuadIndex, neighbor: ?chunk.Neighbor) void {
|
|
const worldPos = pos +% if(neighbor) |n| n.relPos() else Vec3i{0, 0, 0};
|
|
const relPos = worldPos & @as(Vec3i, @splat(main.chunk.chunkMask));
|
|
const mesh = getMeshAndIncreaseRefCount(.{.wx = worldPos[0], .wy = worldPos[1], .wz = worldPos[2], .voxelSize = 1}) orelse return;
|
|
defer mesh.decreaseRefCount();
|
|
for(mesh.blockBreakingFaces.items, 0..) |face, i| {
|
|
if(face.position.x == relPos[0] and face.position.y == relPos[1] and face.position.z == relPos[2] and face.blockAndQuad.quadIndex == quadIndex) {
|
|
_ = mesh.blockBreakingFaces.swapRemove(i);
|
|
mesh.blockBreakingFacesChanged = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn removeBreakingAnimation(pos: Vec3i) void {
|
|
const block = getBlock(pos[0], pos[1], pos[2]) orelse return;
|
|
const model = main.blocks.meshes.model(block).model();
|
|
|
|
for(model.internalQuads) |quadIndex| {
|
|
removeBreakingAnimationFace(pos, quadIndex, null);
|
|
}
|
|
for(&model.neighborFacingQuads, 0..) |quads, n| {
|
|
for(quads) |quadIndex| {
|
|
removeBreakingAnimationFace(pos, quadIndex, @enumFromInt(n));
|
|
}
|
|
}
|
|
}
|