mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00
Store chunks in an array instead of an octree.
This commit is contained in:
parent
95eb578719
commit
76b9b118a1
@ -8,15 +8,19 @@ const graphics = @import("graphics.zig");
|
||||
const c = graphics.c;
|
||||
const Shader = graphics.Shader;
|
||||
const SSBO = graphics.SSBO;
|
||||
const Vec3f = @import("vec.zig").Vec3f;
|
||||
const Vec3d = @import("vec.zig").Vec3d;
|
||||
const Mat4f = @import("vec.zig").Mat4f;
|
||||
const main = @import("main.zig");
|
||||
const vec = @import("vec.zig");
|
||||
const Vec3f = vec.Vec3f;
|
||||
const Vec3d = vec.Vec3d;
|
||||
const Mat4f = vec.Mat4f;
|
||||
|
||||
pub const ChunkCoordinate = i32;
|
||||
pub const UChunkCoordinate = u31;
|
||||
pub const chunkShift: u5 = 5;
|
||||
pub const chunkShift2: u5 = chunkShift*2;
|
||||
pub const chunkSize: ChunkCoordinate = 1 << chunkShift;
|
||||
pub const chunkSizeIterator: [chunkSize]u0 = undefined;
|
||||
pub const chunkVolume: UChunkCoordinate = 1 << 3*chunkShift;
|
||||
pub const chunkMask: ChunkCoordinate = chunkSize - 1;
|
||||
|
||||
/// Contains a bunch of constants used to describe neighboring blocks.
|
||||
@ -52,6 +56,18 @@ fn getIndex(x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) u32 {
|
||||
std.debug.assert((x & chunkMask) == x and (y & chunkMask) == y and (z & chunkMask) == z);
|
||||
return (@intCast(u32, x) << chunkShift) | (@intCast(u32, y) << chunkShift2) | @intCast(u32, z);
|
||||
}
|
||||
/// Gets the x coordinate from a given index inside this chunk.
|
||||
fn extractXFromIndex(index: usize) ChunkCoordinate {
|
||||
return @intCast(ChunkCoordinate, index >> chunkShift & chunkMask);
|
||||
}
|
||||
/// Gets the y coordinate from a given index inside this chunk.
|
||||
fn extractYFromIndex(index: usize) ChunkCoordinate {
|
||||
return @intCast(ChunkCoordinate, index >> chunkShift2 & chunkMask);
|
||||
}
|
||||
/// Gets the z coordinate from a given index inside this chunk.
|
||||
fn extractZFromIndex(index: usize) ChunkCoordinate {
|
||||
return @intCast(ChunkCoordinate, index & chunkMask);
|
||||
}
|
||||
|
||||
pub const ChunkPosition = struct {
|
||||
wx: ChunkCoordinate,
|
||||
@ -84,7 +100,7 @@ pub const ChunkPosition = struct {
|
||||
|
||||
pub const Chunk = struct {
|
||||
pos: ChunkPosition,
|
||||
blocks: [chunkSize*chunkSize*chunkSize]Block = undefined,
|
||||
blocks: [chunkVolume]Block = undefined,
|
||||
|
||||
wasChanged: bool = false,
|
||||
/// When a chunk is cleaned, it won't be saved by the ChunkManager anymore, so following changes need to be saved directly.
|
||||
@ -97,11 +113,11 @@ pub const Chunk = struct {
|
||||
widthShift: u5,
|
||||
mutex: std.Thread.Mutex,
|
||||
|
||||
pub fn init(wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) Chunk {
|
||||
pub fn init(self: *Chunk, wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) void {
|
||||
std.debug.assert((voxelSize - 1 & voxelSize) == 0);
|
||||
std.debug.assert(@mod(wx, voxelSize) == 0 and @mod(wy, voxelSize) == 0 and @mod(wz, voxelSize) == 0);
|
||||
const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, voxelSize));
|
||||
return Chunk {
|
||||
self.* = Chunk {
|
||||
.pos = ChunkPosition {
|
||||
.wx = wx, .wy = wy, .wz = wz, .voxelSize = voxelSize
|
||||
},
|
||||
@ -113,7 +129,7 @@ pub const Chunk = struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setChanged(self: *const Chunk) void {
|
||||
pub fn setChanged(self: *Chunk) void {
|
||||
self.wasChanged = true;
|
||||
{
|
||||
self.mutex.lock();
|
||||
@ -124,7 +140,7 @@ pub const Chunk = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clean(self: *const Chunk) void {
|
||||
pub fn clean(self: *Chunk) void {
|
||||
{
|
||||
self.mutex.lock();
|
||||
self.wasCleaned = true;
|
||||
@ -133,7 +149,7 @@ pub const Chunk = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unclean(self: *const Chunk) void {
|
||||
pub fn unclean(self: *Chunk) void {
|
||||
{
|
||||
self.mutex.lock();
|
||||
self.wasCleaned = false;
|
||||
@ -160,7 +176,7 @@ pub const Chunk = struct {
|
||||
|
||||
/// Updates a block if current value is air or the current block is degradable.
|
||||
/// Does not do any bound checks. They are expected to be done with the `liesInChunk` function.
|
||||
pub fn updateBlockIfDegradable(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
pub fn updateBlockIfDegradable(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
x >>= self.voxelSizeShift;
|
||||
y >>= self.voxelSizeShift;
|
||||
z >>= self.voxelSizeShift;
|
||||
@ -172,7 +188,7 @@ pub const Chunk = struct {
|
||||
|
||||
/// Updates a block if it is inside this chunk.
|
||||
/// Does not do any bound checks. They are expected to be done with the `liesInChunk` function.
|
||||
pub fn updateBlock(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
pub fn updateBlock(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
x >>= self.voxelSizeShift;
|
||||
y >>= self.voxelSizeShift;
|
||||
z >>= self.voxelSizeShift;
|
||||
@ -182,7 +198,7 @@ pub const Chunk = struct {
|
||||
|
||||
/// Updates a block if it is inside this chunk. Should be used in generation to prevent accidently storing these as changes.
|
||||
/// Does not do any bound checks. They are expected to be done with the `liesInChunk` function.
|
||||
pub fn updateBlockInGeneration(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
pub fn updateBlockInGeneration(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||||
x >>= self.voxelSizeShift;
|
||||
y >>= self.voxelSizeShift;
|
||||
z >>= self.voxelSizeShift;
|
||||
@ -200,7 +216,30 @@ pub const Chunk = struct {
|
||||
return self.blocks[index];
|
||||
}
|
||||
|
||||
pub fn updateFromLowerResolution(self: *const Chunk, other: *const Chunk) void {
|
||||
pub fn getNeighbors(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, neighborsArray: *[6]Block) void {
|
||||
std.debug.assert(neighborsArray.length == 6);
|
||||
x &= chunkMask;
|
||||
y &= chunkMask;
|
||||
z &= chunkMask;
|
||||
for(Neighbors.relX) |_, i| {
|
||||
var xi = x + Neighbors.relX[i];
|
||||
var yi = y + Neighbors.relY[i];
|
||||
var zi = z + Neighbors.relZ[i];
|
||||
if (xi == (xi & chunkMask) and yi == (yi & chunkMask) and zi == (zi & chunkMask)) { // Simple double-bound test for coordinates.
|
||||
neighborsArray[i] = self.getBlock(xi, yi, zi);
|
||||
} else {
|
||||
// TODO: What about other chunks?
|
||||
// NormalChunk ch = world.getChunk(xi + wx, yi + wy, zi + wz);
|
||||
// if (ch != null) {
|
||||
// neighborsArray[i] = ch.getBlock(xi & chunkMask, yi & chunkMask, zi & chunkMask);
|
||||
// } else {
|
||||
// neighborsArray[i] = 1; // Some solid replacement, in case the chunk isn't loaded. TODO: Properly choose a solid block.
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateFromLowerResolution(self: *Chunk, other: *const Chunk) void {
|
||||
const xOffset = if(other.wx != self.wx) chunkSize/2 else 0; // Offsets of the lower resolution chunk in this chunk.
|
||||
const yOffset = if(other.wy != self.wy) chunkSize/2 else 0;
|
||||
const zOffset = if(other.wz != self.wz) chunkSize/2 else 0;
|
||||
@ -279,7 +318,7 @@ pub const Chunk = struct {
|
||||
// }
|
||||
//}
|
||||
|
||||
setChanged();
|
||||
self.setChanged();
|
||||
}
|
||||
// TODO: Move this outside.
|
||||
// /**
|
||||
@ -555,6 +594,9 @@ pub const meshing = struct {
|
||||
|
||||
c.glUniform1i(uniforms.time, @bitCast(i32, time));
|
||||
|
||||
c.glUniform3f(uniforms.lowerBounds, -std.math.inf_f32, -std.math.inf_f32, -std.math.inf_f32);
|
||||
c.glUniform3f(uniforms.upperBounds, std.math.inf_f32, std.math.inf_f32, std.math.inf_f32);
|
||||
|
||||
c.glBindVertexArray(vao);
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ pub const World = struct {
|
||||
|
||||
pub fn finishHandshake(self: *World, jsonObject: JsonElement) !void {
|
||||
// TODO: Consider using a per-world allocator.
|
||||
self.blockPalette = try assets.BlockPalette.init(renderer.RenderOctree.allocator, jsonObject.getChild("blockPalette"));
|
||||
self.blockPalette = try assets.BlockPalette.init(renderer.RenderStructure.allocator, jsonObject.getChild("blockPalette"));
|
||||
var jsonSpawn = jsonObject.getChild("spawn");
|
||||
self.spawn.x = jsonSpawn.get(f32, "x", 0);
|
||||
self.spawn.y = jsonSpawn.get(f32, "y", 0);
|
||||
|
@ -247,8 +247,8 @@ pub fn main() !void {
|
||||
|
||||
network.init();
|
||||
|
||||
try renderer.RenderOctree.init();
|
||||
defer renderer.RenderOctree.deinit();
|
||||
try renderer.RenderStructure.init();
|
||||
defer renderer.RenderStructure.deinit();
|
||||
|
||||
var manager = try network.ConnectionManager.init(12347, true);
|
||||
defer manager.deinit();
|
||||
@ -279,7 +279,6 @@ pub fn main() !void {
|
||||
var deltaTime = @intToFloat(f64, newTime -% lastTime)/1000.0;
|
||||
lastTime = newTime;
|
||||
try game.update(deltaTime);
|
||||
try renderer.RenderOctree.update(game.world.?.conn, game.playerPos, 4, 2.0);
|
||||
{ // Render the game
|
||||
c.glEnable(c.GL_CULL_FACE);
|
||||
c.glEnable(c.GL_DEPTH_TEST);
|
||||
|
@ -706,8 +706,8 @@ pub const Protocols = blk: {
|
||||
var z = data[2*size..3*size];
|
||||
var neighbors = data[3*size..4*size];
|
||||
var visibleBlocks = data[4*size..];
|
||||
var result = try renderer.RenderOctree.allocator.create(chunk.ChunkVisibilityData);
|
||||
result.* = try chunk.ChunkVisibilityData.initEmpty(renderer.RenderOctree.allocator, pos, size);
|
||||
var result = try renderer.RenderStructure.allocator.create(chunk.ChunkVisibilityData);
|
||||
result.* = try chunk.ChunkVisibilityData.initEmpty(renderer.RenderStructure.allocator, pos, size);
|
||||
for(x) |_, i| {
|
||||
var block = result.visibles.addOneAssumeCapacity();
|
||||
block.x = x[i];
|
||||
@ -718,7 +718,7 @@ pub const Protocols = blk: {
|
||||
block.block.typ = @intCast(u16, blockTypeAndData & 0xffff);
|
||||
block.block.data = @intCast(u16, blockTypeAndData >> 16);
|
||||
}
|
||||
try renderer.RenderOctree.updateChunkMesh(result);
|
||||
try renderer.RenderStructure.updateChunkMesh(result);
|
||||
}
|
||||
}
|
||||
pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void {
|
||||
|
386
src/renderer.zig
386
src/renderer.zig
@ -185,7 +185,7 @@ pub fn render(playerPosition: Vec3d) !void {
|
||||
skyColor.mulEqualScalar(0.25);
|
||||
|
||||
try renderWorld(world, ambient, skyColor, playerPosition);
|
||||
try RenderOctree.updateMeshes(startTime + maximumMeshTime);
|
||||
try RenderStructure.updateMeshes(startTime + maximumMeshTime);
|
||||
} else {
|
||||
// TODO:
|
||||
// clearColor.y = clearColor.z = 0.7f;
|
||||
@ -234,7 +234,8 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
|
||||
// SimpleList<ReducedChunkMesh> visibleReduced = new SimpleList<ReducedChunkMesh>(new ReducedChunkMesh[64]);
|
||||
var meshes = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.threadAllocator);
|
||||
defer meshes.deinit();
|
||||
try RenderOctree.getRenderChunks(playerPos, frustum, &meshes);
|
||||
|
||||
try RenderStructure.updateAndGetRenderChunks(game.world.?.conn, game.playerPos, 4, 2.0, frustum, &meshes);
|
||||
// for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
|
||||
// if (mesh instanceof NormalChunkMesh) {
|
||||
// visibleChunks.add((NormalChunkMesh)mesh);
|
||||
@ -422,146 +423,24 @@ pub const Frustum = struct {
|
||||
}
|
||||
};
|
||||
|
||||
pub const RenderOctree = struct {
|
||||
pub const RenderStructure = struct {
|
||||
pub var allocator: std.mem.Allocator = undefined;
|
||||
var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined;
|
||||
pub const Node = struct {
|
||||
shouldBeRemoved: bool = false,
|
||||
children: ?[]*Node = null,
|
||||
size: chunk.ChunkCoordinate,
|
||||
|
||||
const ChunkMeshNode = struct {
|
||||
mesh: chunk.meshing.ChunkMesh,
|
||||
mutex: std.Thread.Mutex = std.Thread.Mutex{},
|
||||
|
||||
fn init(replacement: ?*chunk.meshing.ChunkMesh, pos: chunk.ChunkPosition, size: chunk.ChunkCoordinate, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !*Node {
|
||||
var self = try allocator.create(Node);
|
||||
self.* = Node {
|
||||
.size = size,
|
||||
.mesh = chunk.meshing.ChunkMesh.init(pos, replacement),
|
||||
};
|
||||
try meshRequests.append(pos);
|
||||
std.debug.assert(pos.voxelSize & pos.voxelSize-1 == 0);
|
||||
return self;
|
||||
}
|
||||
|
||||
fn deinit(self: *Node) void {
|
||||
if(self.children) |children| {
|
||||
for(children) |child| {
|
||||
child.deinit();
|
||||
}
|
||||
allocator.free(children);
|
||||
}
|
||||
self.mesh.deinit();
|
||||
allocator.destroy(self);
|
||||
}
|
||||
|
||||
fn update(self: *Node, playerPos: Vec3d, renderDistance: i32, maxRD: i32, minHeight: i32, maxHeight: i32, nearRenderDistance: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
// Calculate the minimum distance between this chunk and the player:
|
||||
var minDist = self.mesh.pos.getMinDistanceSquared(playerPos);
|
||||
// Check if this chunk is outside the nearRenderDistance or outside the height limits:
|
||||
// if (wy + size <= Cubyz.world.chunkManager.getOrGenerateMapFragment(x, z, 32).getMinHeight() || wy > Cubyz.world.chunkManager.getOrGenerateMapFragment(x, z, 32).getMaxHeight()) {
|
||||
if(self.mesh.pos.wy + self.size <= 0 or self.mesh.pos.wy > 1024) {
|
||||
if(minDist > @intToFloat(f64, nearRenderDistance*nearRenderDistance)) {
|
||||
if(self.children) |children| {
|
||||
for(children) |child| {
|
||||
child.deinit();
|
||||
}
|
||||
allocator.free(children);
|
||||
self.children = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Check if parts of this OctTree require using normal chunks:
|
||||
if(self.size == chunk.chunkSize*2 and minDist < @intToFloat(f64, renderDistance*renderDistance)) {
|
||||
if(self.children == null) {
|
||||
self.children = try allocator.alloc(*Node, 8);
|
||||
for(self.children.?) |*child, i| {
|
||||
child.* = try Node.init(&self.mesh, chunk.ChunkPosition{
|
||||
.wx = self.mesh.pos.wx + (if(i & 1 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.wy = self.mesh.pos.wy + (if(i & 2 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.wz = self.mesh.pos.wz + (if(i & 4 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.voxelSize = @divFloor(self.mesh.pos.voxelSize, 2),
|
||||
}, @divFloor(self.size, 2), meshRequests);
|
||||
}
|
||||
}
|
||||
for(self.children.?) |child| {
|
||||
try child.update(playerPos, renderDistance, @divFloor(maxRD, 2), minHeight, maxHeight, nearRenderDistance, meshRequests);
|
||||
}
|
||||
// Check if parts of this OctTree require a higher resolution:
|
||||
} else if(minDist < @intToFloat(f64, maxRD*maxRD)/4 and self.size > chunk.chunkSize*2) {
|
||||
if(self.children == null) {
|
||||
self.children = try allocator.alloc(*Node, 8);
|
||||
for(self.children.?) |*child, i| {
|
||||
child.* = try Node.init(&self.mesh, chunk.ChunkPosition{
|
||||
.wx = self.mesh.pos.wx + (if(i & 1 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.wy = self.mesh.pos.wy + (if(i & 2 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.wz = self.mesh.pos.wz + (if(i & 4 == 0) 0 else @divFloor(self.size, 2)),
|
||||
.voxelSize = @divFloor(self.mesh.pos.voxelSize, 2),
|
||||
}, @divFloor(self.size, 2), meshRequests);
|
||||
}
|
||||
}
|
||||
for(self.children.?) |child| {
|
||||
try child.update(playerPos, renderDistance, @divFloor(maxRD, 2), minHeight, maxHeight, nearRenderDistance, meshRequests);
|
||||
}
|
||||
// This OctTree doesn't require higher resolution:
|
||||
} else {
|
||||
if(self.children) |children| {
|
||||
for(children) |child| {
|
||||
child.deinit();
|
||||
}
|
||||
allocator.free(children);
|
||||
self.children = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn getChunks(self: *Node, playerPos: Vec3d, frustum: Frustum, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void {
|
||||
self.mutex.lock();
|
||||
defer self.mutex.unlock();
|
||||
if(self.children) |children| {
|
||||
for(children) |child| {
|
||||
try child.getChunks(playerPos, frustum, meshes);
|
||||
}
|
||||
} else {
|
||||
if(frustum.testAAB(Vec3f{
|
||||
.x = @floatCast(f32, @intToFloat(f64, self.mesh.pos.wx) - playerPos.x),
|
||||
.y = @floatCast(f32, @intToFloat(f64, self.mesh.pos.wy) - playerPos.y),
|
||||
.z = @floatCast(f32, @intToFloat(f64, self.mesh.pos.wz) - playerPos.z),
|
||||
}, Vec3f{
|
||||
.x = @intToFloat(f32, self.size),
|
||||
.y = @intToFloat(f32, self.size),
|
||||
.z = @intToFloat(f32, self.size),
|
||||
})) {
|
||||
try meshes.append(&self.mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// public boolean testFrustum(FrustumIntersection frustumInt, double x0, double y0, double z0) {
|
||||
// return frustumInt.testAab((float)(wx - x0), (float)(wy - y0), (float)(wz - z0), (float)(wx + size - x0), (float)(wy + size - y0), (float)(wz + size - z0));
|
||||
// }
|
||||
shouldBeRemoved: bool, // Internal use.
|
||||
drawableChildren: u32, // How many children can be renderer. If this is 8 then there is no need to render this mesh.
|
||||
};
|
||||
|
||||
const HashMapKey3D = struct {
|
||||
x: chunk.ChunkCoordinate,
|
||||
y: chunk.ChunkCoordinate,
|
||||
z: chunk.ChunkCoordinate,
|
||||
|
||||
pub fn hash(_: anytype, key: HashMapKey3D) u64 {
|
||||
return @bitCast(u32, ((key.x << 13) | (key.x >> 19)) ^ ((key.y << 7) | (key.y >> 25)) ^ ((key.z << 23) | (key.z >> 9))); // This should be a good hash for now. TODO: Test how good it really is and find a better one.
|
||||
}
|
||||
|
||||
pub fn eql(_: anytype, key: HashMapKey3D, other: HashMapKey3D) bool {
|
||||
return key.x == other.x and key.y == other.y and key.z == other.z;
|
||||
}
|
||||
};
|
||||
|
||||
var roots: std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80) = undefined;
|
||||
var storageLists: [settings.highestLOD + 1][]?*ChunkMeshNode = undefined;
|
||||
var updatableList: std.ArrayList(*chunk.ChunkVisibilityData) = undefined;
|
||||
var lastRD: i32 = 0;
|
||||
var lastFactor: f32 = 0;
|
||||
var lastX: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
||||
var lastY: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
||||
var lastZ: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
||||
var lastSize: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
||||
var lodMutex: [settings.highestLOD + 1]std.Thread.Mutex = [_]std.Thread.Mutex{std.Thread.Mutex{}} ** (settings.highestLOD + 1);
|
||||
var mutex = std.Thread.Mutex{};
|
||||
|
||||
pub fn init() !void {
|
||||
@ -569,16 +448,22 @@ pub const RenderOctree = struct {
|
||||
lastFactor = 0;
|
||||
gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
allocator = gpa.allocator();
|
||||
roots = std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80).initContext(allocator, undefined);
|
||||
updatableList = std.ArrayList(*chunk.ChunkVisibilityData).init(allocator);
|
||||
for(storageLists) |*storageList| {
|
||||
storageList.* = try allocator.alloc(?*ChunkMeshNode, 0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit() void {
|
||||
var iterator = roots.valueIterator();
|
||||
while(iterator.next()) |value| {
|
||||
value.*.deinit();
|
||||
for(storageLists) |storageList| {
|
||||
for(storageList) |nullChunkMesh| {
|
||||
if(nullChunkMesh) |chunkMesh| {
|
||||
chunkMesh.mesh.deinit();
|
||||
allocator.destroy(chunkMesh);
|
||||
}
|
||||
}
|
||||
allocator.free(storageList);
|
||||
}
|
||||
roots.deinit();
|
||||
for(updatableList.items) |updatable| {
|
||||
updatable.visibles.deinit();
|
||||
allocator.destroy(updatable);
|
||||
@ -590,128 +475,144 @@ pub const RenderOctree = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32) !void {
|
||||
fn _getNode(pos: chunk.ChunkPosition) ?*ChunkMeshNode {
|
||||
var lod = std.math.log2_int(chunk.UChunkCoordinate, pos.voxelSize);
|
||||
lodMutex[lod].lock();
|
||||
defer lodMutex[lod].unlock();
|
||||
var xIndex = pos.wx-%lastX[lod] >> lod+chunk.chunkShift;
|
||||
var yIndex = pos.wy-%lastY[lod] >> lod+chunk.chunkShift;
|
||||
var zIndex = pos.wz-%lastZ[lod] >> lod+chunk.chunkShift;
|
||||
if(xIndex < 0 or xIndex >= lastSize[lod]) return null;
|
||||
if(yIndex < 0 or yIndex >= lastSize[lod]) return null;
|
||||
if(zIndex < 0 or zIndex >= lastSize[lod]) return null;
|
||||
var index = (xIndex*lastSize[lod] + yIndex)*lastSize[lod] + zIndex;
|
||||
return storageLists[lod][@intCast(usize, index)];
|
||||
}
|
||||
|
||||
pub fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32, frustum: Frustum, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void {
|
||||
if(lastRD != renderDistance and lastFactor != LODFactor) {
|
||||
// TODO:
|
||||
// Protocols.GENERIC_UPDATE.sendRenderDistance(Cubyz.world.serverConnection, renderDistance, LODFactor);
|
||||
}
|
||||
var px = @floatToInt(chunk.ChunkCoordinate, playerPos.x);
|
||||
var py = @floatToInt(chunk.ChunkCoordinate, playerPos.y);
|
||||
var pz = @floatToInt(chunk.ChunkCoordinate, playerPos.z);
|
||||
var maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, renderDistance*chunk.chunkSize << settings.highestLOD)*LODFactor));
|
||||
var nearRenderDistance = renderDistance*chunk.chunkSize;
|
||||
var LODShift = settings.highestLOD + chunk.chunkShift;
|
||||
var LODSize = chunk.chunkSize << settings.highestLOD;
|
||||
var LODMask = LODSize - 1;
|
||||
var minX = (px - maxRenderDistance - LODMask) & ~LODMask;
|
||||
var maxX = (px + maxRenderDistance + LODMask) & ~LODMask;
|
||||
// The LOD chunks are offset from grid to make generation easier.
|
||||
minX += @divExact(LODSize, 2) - chunk.chunkSize;
|
||||
maxX += @divExact(LODSize, 2) - chunk.chunkSize;
|
||||
var newMap = std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80).initContext(allocator, undefined);
|
||||
const px = @floatToInt(chunk.ChunkCoordinate, playerPos.x);
|
||||
const py = @floatToInt(chunk.ChunkCoordinate, playerPos.y);
|
||||
const pz = @floatToInt(chunk.ChunkCoordinate, playerPos.z);
|
||||
|
||||
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.threadAllocator);
|
||||
defer meshRequests.deinit();
|
||||
var x = minX;
|
||||
while(x <= maxX): (x += LODSize) {
|
||||
var maxYRenderDistanceSquare = @intToFloat(f32, maxRenderDistance)*@intToFloat(f32, maxRenderDistance) - @intToFloat(f32, (x - px))*@intToFloat(f32, (x - px));
|
||||
if(maxYRenderDistanceSquare < 0) continue;
|
||||
var maxYRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxYRenderDistanceSquare)));
|
||||
var minY = (py - maxYRenderDistance - LODMask) & ~LODMask;
|
||||
var maxY = (py + maxYRenderDistance + LODMask) & ~LODMask;
|
||||
// The LOD chunks are offset from grid to make generation easier.
|
||||
minY += @divFloor(LODSize, 2) - chunk.chunkSize;
|
||||
maxY += @divFloor(LODSize, 2) - chunk.chunkSize;
|
||||
var y = minY;
|
||||
while(y <= maxY): (y += LODSize) {
|
||||
var maxZRenderDistanceSquare = @intToFloat(f32, maxYRenderDistance)*@intToFloat(f32, maxYRenderDistance) - @intToFloat(f32, (y - py))*@intToFloat(f32, (y - py));
|
||||
if(maxZRenderDistanceSquare < 0) continue;
|
||||
var maxZRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxZRenderDistanceSquare)));
|
||||
var minZ = (pz - maxZRenderDistance - LODMask) & ~LODMask;
|
||||
var maxZ = (pz + maxZRenderDistance + LODMask) & ~LODMask;
|
||||
// The LOD chunks are offset from grid to make generation easier.
|
||||
minZ += @divFloor(LODSize, 2) - chunk.chunkSize;
|
||||
maxZ += @divFloor(LODSize, 2) - chunk.chunkSize;
|
||||
var z = minZ;
|
||||
while(z <= maxZ): (z += LODSize) {
|
||||
if(y + LODSize <= 0 or y > 1024) {
|
||||
var dx = @maximum(0, try std.math.absInt(x + @divFloor(LODSize, 2) - px) - @divFloor(LODSize, 2));
|
||||
var dy = @maximum(0, try std.math.absInt(y + @divFloor(LODSize, 2) - py) - @divFloor(LODSize, 2));
|
||||
var dz = @maximum(0, try std.math.absInt(z + @divFloor(LODSize, 2) - pz) - @divFloor(LODSize, 2));
|
||||
if(dx*dx + dy*dy + dz*dz > nearRenderDistance*nearRenderDistance) continue;
|
||||
}
|
||||
var rootX = x >> LODShift;
|
||||
var rootY = y >> LODShift;
|
||||
var rootZ = z >> LODShift;
|
||||
|
||||
var key = HashMapKey3D{.x = rootX, .y = rootY, .z = rootZ};
|
||||
var node = roots.get(key);
|
||||
if(node) |_node| {
|
||||
// Mark that this node should not be removed.
|
||||
_node.shouldBeRemoved = false;
|
||||
} else {
|
||||
node = try Node.init(null, .{.wx=x, .wy=y, .wz=z, .voxelSize=@intCast(chunk.UChunkCoordinate, LODSize>>chunk.chunkShift)}, LODSize, &meshRequests);
|
||||
// Mark this node to be potentially removed in the next update:
|
||||
node.?.shouldBeRemoved = true;
|
||||
for(storageLists) |_, _lod| {
|
||||
const lod = @intCast(u5, _lod);
|
||||
var maxRenderDistance = renderDistance*chunk.chunkSize << lod;
|
||||
if(lod != 0) maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, maxRenderDistance)*LODFactor));
|
||||
const size = @intCast(chunk.UChunkCoordinate, chunk.chunkSize << lod);
|
||||
const mask: chunk.ChunkCoordinate = size - 1;
|
||||
const invMask: chunk.ChunkCoordinate = ~mask;
|
||||
|
||||
const maxSideLength = @intCast(chunk.UChunkCoordinate, @divFloor(2*maxRenderDistance + size-1, size) + 2);
|
||||
var newList = try allocator.alloc(?*ChunkMeshNode, maxSideLength*maxSideLength*maxSideLength);
|
||||
std.mem.set(?*ChunkMeshNode, newList, null);
|
||||
|
||||
const startX = size*(@divFloor(px, size) -% maxSideLength/2);
|
||||
const startY = size*(@divFloor(py, size) -% maxSideLength/2);
|
||||
const startZ = size*(@divFloor(pz, size) -% maxSideLength/2);
|
||||
|
||||
const minX = px-%maxRenderDistance-%size & invMask;
|
||||
const maxX = px+%maxRenderDistance & invMask;
|
||||
var x = minX;
|
||||
while(x != maxX): (x +%= size) {
|
||||
const xIndex = @divExact(x -% startX, size);
|
||||
var deltaX: f32 = @fabs(@intToFloat(f32, x + size/2 - px));
|
||||
deltaX = @maximum(0, deltaX - @intToFloat(f32, size/2));
|
||||
const maxYRenderDistanceSquare = @intToFloat(f32, maxRenderDistance)*@intToFloat(f32, maxRenderDistance) - deltaX*deltaX;
|
||||
if(maxYRenderDistanceSquare < 0) continue;
|
||||
const maxYRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxYRenderDistanceSquare)));
|
||||
|
||||
const minY = py-maxYRenderDistance-size & invMask;
|
||||
const maxY = py+maxYRenderDistance & invMask;
|
||||
var y = minY;
|
||||
while(y != maxY): (y +%= size) {
|
||||
const yIndex = @divExact(y -% startY, size);
|
||||
var deltaY: f32 = @fabs(@intToFloat(f32, y + size/2 - py));
|
||||
deltaY = @maximum(0, deltaY - @intToFloat(f32, size/2));
|
||||
const maxZRenderDistanceSquare = @intToFloat(f32, maxYRenderDistance)*@intToFloat(f32, maxYRenderDistance) - deltaY*deltaY;
|
||||
if(maxZRenderDistanceSquare < 0) continue;
|
||||
const maxZRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxZRenderDistanceSquare)));
|
||||
|
||||
const minZ = pz-maxZRenderDistance-size & invMask;
|
||||
const maxZ = pz+maxZRenderDistance & invMask;
|
||||
var z = minZ;
|
||||
while(z != maxZ): (z +%= size) {
|
||||
const zIndex = @divExact(z -% startZ, size);
|
||||
const index = (xIndex*maxSideLength + yIndex)*maxSideLength + zIndex;
|
||||
const pos = chunk.ChunkPosition{.wx=x, .wy=y, .wz=z, .voxelSize=@as(chunk.UChunkCoordinate, 1)<<lod};
|
||||
var node = _getNode(pos);
|
||||
if(node) |_node| {
|
||||
_node.shouldBeRemoved = false;
|
||||
} else {
|
||||
node = try allocator.create(ChunkMeshNode);
|
||||
node.?.mesh = chunk.meshing.ChunkMesh.init(pos, null); // TODO: Replacement mesh?
|
||||
node.?.shouldBeRemoved = true; // Might be removed in the next iteration.
|
||||
try meshRequests.append(pos);
|
||||
}
|
||||
if(frustum.testAAB(Vec3f{
|
||||
.x = @floatCast(f32, @intToFloat(f64, x) - playerPos.x),
|
||||
.y = @floatCast(f32, @intToFloat(f64, y) - playerPos.y),
|
||||
.z = @floatCast(f32, @intToFloat(f64, z) - playerPos.z),
|
||||
}, Vec3f{
|
||||
.x = @intToFloat(f32, size),
|
||||
.y = @intToFloat(f32, size),
|
||||
.z = @intToFloat(f32, size),
|
||||
}) and node.?.drawableChildren < 8) { // TODO: Case where more than 0 and less than 8 exist.
|
||||
try meshes.append(&node.?.mesh);
|
||||
}
|
||||
if(lod+1 != storageLists.len and node.?.mesh.generated) {
|
||||
if(_getNode(.{.wx=x, .wy=y, .wz=z, .voxelSize=@as(chunk.UChunkCoordinate, 1)<<(lod+1)})) |parent| {
|
||||
parent.drawableChildren += 1;
|
||||
}
|
||||
}
|
||||
node.?.drawableChildren = 0;
|
||||
newList[@intCast(usize, index)] = node;
|
||||
}
|
||||
try newMap.put(key, node.?);
|
||||
try node.?.update(playerPos, renderDistance*chunk.chunkSize, maxRenderDistance, 0, 1024, nearRenderDistance, &meshRequests);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Clean memory for unused nodes:
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
var iterator = roots.valueIterator();
|
||||
while(iterator.next()) |node| {
|
||||
if(node.*.shouldBeRemoved) {
|
||||
node.*.deinit();
|
||||
} else {
|
||||
node.*.shouldBeRemoved = true;
|
||||
|
||||
var oldList = storageLists[lod];
|
||||
{
|
||||
lodMutex[lod].lock();
|
||||
defer lodMutex[lod].unlock();
|
||||
lastX[lod] = startX;
|
||||
lastY[lod] = startY;
|
||||
lastZ[lod] = startZ;
|
||||
lastSize[lod] = maxSideLength;
|
||||
storageLists[lod] = newList;
|
||||
}
|
||||
for(oldList) |nullMesh| {
|
||||
if(nullMesh) |mesh| {
|
||||
if(mesh.shouldBeRemoved) {
|
||||
mesh.mesh.deinit();
|
||||
allocator.destroy(mesh);
|
||||
} else {
|
||||
mesh.shouldBeRemoved = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
allocator.free(oldList);
|
||||
}
|
||||
roots.deinit();
|
||||
roots = newMap;
|
||||
|
||||
lastRD = renderDistance;
|
||||
lastFactor = LODFactor;
|
||||
// Make requests after updating the, to avoid concurrency issues and reduce the number of requests:
|
||||
try network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items);
|
||||
}
|
||||
|
||||
fn findNode(pos: chunk.ChunkPosition) ?*Node {
|
||||
var LODShift = settings.highestLOD + chunk.chunkShift;
|
||||
var LODSize = @as(chunk.UChunkCoordinate, 1) << LODShift;
|
||||
var key = HashMapKey3D{
|
||||
.x = (pos.wx - LODSize/2 + chunk.chunkSize) >> LODShift,
|
||||
.y = (pos.wy - LODSize/2 + chunk.chunkSize) >> LODShift,
|
||||
.z = (pos.wz - LODSize/2 + chunk.chunkSize) >> LODShift,
|
||||
};
|
||||
const rootNode: *Node = roots.get(key) orelse return null;
|
||||
rootNode.mutex.lock();
|
||||
defer rootNode.mutex.unlock();
|
||||
|
||||
var node = rootNode;
|
||||
|
||||
while(node.mesh.pos.voxelSize != pos.voxelSize) {
|
||||
var children = node.children orelse return null;
|
||||
var i: u3 = 0;
|
||||
if(pos.wx >= node.mesh.pos.wx + @divFloor(node.size, 2)) i += 1;
|
||||
if(pos.wy >= node.mesh.pos.wy + @divFloor(node.size, 2)) i += 2;
|
||||
if(pos.wz >= node.mesh.pos.wz + @divFloor(node.size, 2)) i += 4;
|
||||
node = children[i];
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn updateMeshes(targetTime: i64) !void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
while(updatableList.items.len != 0) {
|
||||
const mesh = updatableList.pop();
|
||||
const nullNode = findNode(mesh.pos);
|
||||
const nullNode = _getNode(mesh.pos);
|
||||
if(nullNode) |node| {
|
||||
node.mutex.lock();
|
||||
defer node.mutex.unlock();
|
||||
try node.mesh.regenerateMesh(mesh);
|
||||
}
|
||||
mesh.visibles.deinit();
|
||||
@ -725,15 +626,6 @@ pub const RenderOctree = struct {
|
||||
defer mutex.unlock();
|
||||
try updatableList.append(mesh);
|
||||
}
|
||||
|
||||
pub fn getRenderChunks(playerPos: Vec3d, frustum: Frustum, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void {
|
||||
mutex.lock();
|
||||
defer mutex.unlock();
|
||||
var iterator = roots.valueIterator();
|
||||
while(iterator.next()) |node| {
|
||||
try node.*.getChunks(playerPos, frustum, meshes);
|
||||
}
|
||||
}
|
||||
// TODO:
|
||||
// public void updateChunkMesh(VisibleChunk mesh) {
|
||||
// OctTreeNode node = findNode(mesh);
|
||||
|
Loading…
x
Reference in New Issue
Block a user