mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00
756 lines
27 KiB
Zig
756 lines
27 KiB
Zig
const std = @import("std");
|
||
const Allocator = std.mem.Allocator;
|
||
|
||
const blocks = @import("blocks.zig");
|
||
const Block = blocks.Block;
|
||
const game = @import("game.zig");
|
||
const graphics = @import("graphics.zig");
|
||
const c = graphics.c;
|
||
const Shader = graphics.Shader;
|
||
const SSBO = graphics.SSBO;
|
||
const main = @import("main.zig");
|
||
const renderer = @import("renderer.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.
|
||
pub const Neighbors = struct {
|
||
/// How many neighbors there are.
|
||
pub const neighbors: u32 = 6;
|
||
/// Directions → Index
|
||
pub const dirUp: u32 = 0;
|
||
/// Directions → Index
|
||
pub const dirDown: u32 = 1;
|
||
/// Directions → Index
|
||
pub const dirPosX: u32 = 2;
|
||
/// Directions → Index
|
||
pub const dirNegX: u32 = 3;
|
||
/// Directions → Index
|
||
pub const dirPosZ: u32 = 4;
|
||
/// Directions → Index
|
||
pub const dirNegZ: u32 = 5;
|
||
/// Index to relative position
|
||
pub const relX = [_]ChunkCoordinate {0, 0, 1, -1, 0, 0};
|
||
/// Index to relative position
|
||
pub const relY = [_]ChunkCoordinate {1, -1, 0, 0, 0, 0};
|
||
/// Index to relative position
|
||
pub const relZ = [_]ChunkCoordinate {0, 0, 0, 0, 1, -1};
|
||
/// Index to bitMask for bitmap direction data
|
||
pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20};
|
||
/// To iterate over all neighbors easily
|
||
pub const iterable = [_]u3 {0, 1, 2, 3, 4, 5};
|
||
};
|
||
|
||
/// Gets the index of a given position inside this chunk.
|
||
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,
|
||
wy: ChunkCoordinate,
|
||
wz: ChunkCoordinate,
|
||
voxelSize: UChunkCoordinate,
|
||
|
||
// TODO(mabye?):
|
||
// public int hashCode() {
|
||
// int shift = Math.min(Integer.numberOfTrailingZeros(wx), Math.min(Integer.numberOfTrailingZeros(wy), Integer.numberOfTrailingZeros(wz)));
|
||
// return (((wx >> shift) * 31 + (wy >> shift)) * 31 + (wz >> shift)) * 31 + voxelSize;
|
||
// }
|
||
|
||
pub fn getMinDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 {
|
||
var halfWidth = @intToFloat(f64, self.voxelSize*@divExact(chunkSize, 2));
|
||
var dx = @fabs(@intToFloat(f64, self.wx) + halfWidth - playerPosition.x);
|
||
var dy = @fabs(@intToFloat(f64, self.wy) + halfWidth - playerPosition.y);
|
||
var dz = @fabs(@intToFloat(f64, self.wz) + halfWidth - playerPosition.z);
|
||
dx = @maximum(0, dx - halfWidth);
|
||
dy = @maximum(0, dy - halfWidth);
|
||
dz = @maximum(0, dz - halfWidth);
|
||
return dx*dx + dy*dy + dz*dz;
|
||
}
|
||
};
|
||
|
||
pub const Chunk = struct {
|
||
pos: ChunkPosition,
|
||
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.
|
||
wasCleaned: bool = false,
|
||
generated: bool = false,
|
||
|
||
width: ChunkCoordinate,
|
||
voxelSizeShift: u5,
|
||
voxelSizeMask: ChunkCoordinate,
|
||
widthShift: u5,
|
||
mutex: std.Thread.Mutex,
|
||
|
||
pub fn init(self: *Chunk, pos: ChunkPosition) void {
|
||
std.debug.assert((pos.voxelSize - 1 & pos.voxelSize) == 0);
|
||
std.debug.assert(@mod(pos.wx, pos.voxelSize) == 0 and @mod(pos.wy, pos.voxelSize) == 0 and @mod(pos.wz, pos.voxelSize) == 0);
|
||
const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, pos.voxelSize));
|
||
self.* = Chunk {
|
||
.pos = pos,
|
||
.width = pos.voxelSize*chunkSize,
|
||
.voxelSizeShift = voxelSizeShift,
|
||
.voxelSizeMask = pos.voxelSize - 1,
|
||
.widthShift = voxelSizeShift + chunkShift,
|
||
.mutex = std.Thread.Mutex{},
|
||
};
|
||
}
|
||
|
||
pub fn setChanged(self: *Chunk) void {
|
||
self.wasChanged = true;
|
||
{
|
||
self.mutex.lock();
|
||
if(self.wasCleaned) {
|
||
self.save();
|
||
}
|
||
self.mutex.unlock();
|
||
}
|
||
}
|
||
|
||
pub fn clean(self: *Chunk) void {
|
||
{
|
||
self.mutex.lock();
|
||
self.wasCleaned = true;
|
||
self.save();
|
||
self.mutex.unlock();
|
||
}
|
||
}
|
||
|
||
pub fn unclean(self: *Chunk) void {
|
||
{
|
||
self.mutex.lock();
|
||
self.wasCleaned = false;
|
||
self.save();
|
||
self.mutex.unlock();
|
||
}
|
||
}
|
||
|
||
/// Checks if the given relative coordinates lie within the bounds of this chunk.
|
||
pub fn liesInChunk(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) bool {
|
||
return x >= 0 and x < self.width
|
||
and y >= 0 and y < self.width
|
||
and z >= 0 and z < self.width;
|
||
}
|
||
|
||
/// This is useful to convert for loops to work for reduced resolution:
|
||
/// Instead of using
|
||
/// for(int x = start; x < end; x++)
|
||
/// for(int x = chunk.startIndex(start); x < end; x += chunk.getVoxelSize())
|
||
/// should be used to only activate those voxels that are used in Cubyz's downscaling technique.
|
||
pub fn startIndex(self: *const Chunk, start: ChunkCoordinate) ChunkCoordinate {
|
||
return start+self.voxelSizeMask & ~self.voxelSizeMask; // Rounds up to the nearest valid voxel coordinate.
|
||
}
|
||
|
||
/// 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: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||
x >>= self.voxelSizeShift;
|
||
y >>= self.voxelSizeShift;
|
||
z >>= self.voxelSizeShift;
|
||
var index = getIndex(x, y, z);
|
||
if (self.blocks[index] == 0 || self.blocks[index].degradable()) {
|
||
self.blocks[index] = newBlock;
|
||
}
|
||
}
|
||
|
||
/// 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: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||
x >>= self.voxelSizeShift;
|
||
y >>= self.voxelSizeShift;
|
||
z >>= self.voxelSizeShift;
|
||
var index = getIndex(x, y, z);
|
||
self.blocks[index] = newBlock;
|
||
}
|
||
|
||
/// 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: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void {
|
||
x >>= self.voxelSizeShift;
|
||
y >>= self.voxelSizeShift;
|
||
z >>= self.voxelSizeShift;
|
||
var index = getIndex(x, y, z);
|
||
self.blocks[index] = newBlock;
|
||
}
|
||
|
||
/// Gets 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 getBlock(self: *const Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block {
|
||
var x = _x >> self.voxelSizeShift;
|
||
var y = _y >> self.voxelSizeShift;
|
||
var z = _z >> self.voxelSizeShift;
|
||
var index = getIndex(x, y, z);
|
||
return self.blocks[index];
|
||
}
|
||
|
||
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;
|
||
|
||
var x: ChunkCoordinate = 0;
|
||
while(x < chunkSize/2): (x += 1) {
|
||
var y: ChunkCoordinate = 0;
|
||
while(y < chunkSize/2): (y += 1) {
|
||
var z: ChunkCoordinate = 0;
|
||
while(z < chunkSize/2): (z += 1) {
|
||
// Count the neighbors for each subblock. An transparent block counts 5. A chunk border(unknown block) only counts 1.
|
||
var neighborCount: [8]u32 = undefined;
|
||
var octantBlocks: [8]Block = undefined;
|
||
var maxCount: u32 = 0;
|
||
var dx: ChunkCoordinate = 0;
|
||
while(dx <= 1): (dx += 1) {
|
||
var dy: ChunkCoordinate = 0;
|
||
while(dy <= 1): (dy += 1) {
|
||
var dz: ChunkCoordinate = 0;
|
||
while(dz <= 1): (dz += 1) {
|
||
const index = getIndex(x*2 + dx, y*2 + dy, z*2 + dz);
|
||
const i = dx*4 + dz*2 + dy;
|
||
octantBlocks[i] = other.blocks[index];
|
||
if(octantBlocks[i] == 0) continue; // I don't care about air blocks.
|
||
|
||
var count: u32 = 0;
|
||
for(Neighbors.iterable) |n| {
|
||
const nx = x*2 + dx + Neighbors.relX[n];
|
||
const ny = y*2 + dy + Neighbors.relY[n];
|
||
const nz = z*2 + dz + Neighbors.relZ[n];
|
||
if((nx & chunkMask) == nx and (ny & chunkMask) == ny and (nz & chunkMask) == nz) { // If it's inside the chunk.
|
||
const neighborIndex = getIndex(nx, ny, nz);
|
||
if(other.blocks[neighborIndex].transparent()) {
|
||
count += 5;
|
||
}
|
||
} else {
|
||
count += 1;
|
||
}
|
||
}
|
||
maxCount = @maximum(maxCount, count);
|
||
neighborCount[i] = count;
|
||
}
|
||
}
|
||
}
|
||
// Uses a specific permutation here that keeps high resolution patterns in lower resolution.
|
||
const permutationStart = (x & 1)*4 + (z & 1)*2 + (y & 1);
|
||
const block = Block{.typ = 0, .data = 0};
|
||
for(neighborCount) |_, i| {
|
||
const appliedPermutation = permutationStart ^ i;
|
||
if(neighborCount[appliedPermutation] >= maxCount - 1) { // Avoid pattern breaks at chunk borders.
|
||
block = blocks[appliedPermutation];
|
||
}
|
||
}
|
||
// Update the block:
|
||
const thisIndex = getIndex(x + xOffset, y + yOffset, z + zOffset);
|
||
self.blocks[thisIndex] = block;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Create updated meshes and send to client:
|
||
// TODO:
|
||
//for(int x = 0; x <= 2*xOffset; x += chunkSize) {
|
||
// for(int y = 0; y <= 2*yOffset; y += chunkSize) {
|
||
// for(int z = 0; z <= 2*zOffset; z += chunkSize) {
|
||
// int wx = this.wx + x*voxelSize - Chunk.chunkSize;
|
||
// int wy = this.wy + y*voxelSize - Chunk.chunkSize;
|
||
// int wz = this.wz + z*voxelSize - Chunk.chunkSize;
|
||
// if(voxelSize == 32) {
|
||
// wx -= chunkSize*voxelSize/2;
|
||
// wy -= chunkSize*voxelSize/2;
|
||
// wz -= chunkSize*voxelSize/2;
|
||
// }
|
||
// world.queueChunks(new ChunkData[] {new ChunkData(wx, wy, wz, voxelSize)});
|
||
// }
|
||
// }
|
||
//}
|
||
|
||
self.setChanged();
|
||
}
|
||
// TODO: Move this outside.
|
||
// /**
|
||
// * Generates this chunk.
|
||
// * If the chunk was already saved it is loaded from file instead.
|
||
// * @param seed
|
||
// * @param terrainGenerationProfile
|
||
// */
|
||
// public void generate(World world, long seed, TerrainGenerationProfile terrainGenerationProfile) {
|
||
// assert !generated : "Seriously, why would you generate this chunk twice???";
|
||
// if(!ChunkIO.loadChunkFromFile(world, this)) {
|
||
// CaveMap caveMap = new CaveMap(this);
|
||
// CaveBiomeMap biomeMap = new CaveBiomeMap(this);
|
||
//
|
||
// for (Generator g : terrainGenerationProfile.generators) {
|
||
// g.generate(seed ^ g.getGeneratorSeed(), wx, wy, wz, this, caveMap, biomeMap);
|
||
// }
|
||
// }
|
||
// generated = true;
|
||
// }
|
||
|
||
|
||
// TODO:
|
||
pub fn save(chunk: *const Chunk) void {
|
||
_ = chunk;
|
||
// /**
|
||
// * Saves this chunk.
|
||
// */
|
||
// public void save(World world) {
|
||
// if(wasChanged) {
|
||
// ChunkIO.storeChunkToFile(world, this);
|
||
// wasChanged = false;
|
||
// // Update the next lod chunk:
|
||
// if(voxelSize != 1 << Constants.HIGHEST_LOD) {
|
||
// if(world instanceof ServerWorld) {
|
||
// ReducedChunk chunk = ((ServerWorld)world).chunkManager.getOrGenerateReducedChunk(wx, wy, wz, voxelSize*2);
|
||
// chunk.updateFromLowerResolution(this);
|
||
// } else {
|
||
// Logger.error("Not implemented: ");
|
||
// Logger.error(new Exception());
|
||
// }
|
||
// }
|
||
// }
|
||
}
|
||
};
|
||
|
||
|
||
|
||
|
||
|
||
|
||
// TODO: Check if/how they are needed:
|
||
//
|
||
// public Vector3d getMin() {
|
||
// return new Vector3d(wx, wy, wz);
|
||
// }
|
||
//
|
||
// public Vector3d getMax() {
|
||
// return new Vector3d(wx + width, wy + width, wz + width);
|
||
// }
|
||
//
|
||
// @Override
|
||
// public byte[] saveToByteArray() {
|
||
// byte[] data = new byte[4*blocks.length];
|
||
// for(int i = 0; i < blocks.length; i++) {
|
||
// Bits.putInt(data, i*4, blocks[i]);
|
||
// }
|
||
// return data;
|
||
// }
|
||
//
|
||
// @Override
|
||
// public boolean loadFromByteArray(byte[] data, int outputLength) {
|
||
// if(outputLength != 4*blocks.length) {
|
||
// Logger.error("Chunk is corrupted(invalid data length "+outputLength+") : " + this);
|
||
// return false;
|
||
// }
|
||
// for(int i = 0; i < blocks.length; i++) {
|
||
// blocks[i] = Bits.getInt(data, i*4);
|
||
// }
|
||
// generated = true;
|
||
// return true;
|
||
// }
|
||
|
||
pub const VisibleBlock = struct {
|
||
block: Block,
|
||
x: u8,
|
||
y: u8,
|
||
z: u8,
|
||
neighbors: u8,
|
||
};
|
||
pub const ChunkVisibilityData = struct {
|
||
pos: ChunkPosition,
|
||
visibles: std.ArrayList(VisibleBlock),
|
||
voxelSizeShift: u5,
|
||
|
||
/// Finds a block in the surrounding 8 chunks using relative corrdinates.
|
||
fn getBlock(self: ChunkVisibilityData, chunks: *const [8]Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block {
|
||
var x = _x + (self.pos.wx - chunks[0].pos.wx) >> self.voxelSizeShift;
|
||
var y = _y + (self.pos.wy - chunks[0].pos.wy) >> self.voxelSizeShift;
|
||
var z = _z + (self.pos.wz - chunks[0].pos.wz) >> self.voxelSizeShift;
|
||
var chunk = &chunks[@intCast(usize, (x >> chunkShift)*4 + (y >> chunkShift)*2 + (z >> chunkShift))];
|
||
x &= chunkMask;
|
||
y &= chunkMask;
|
||
z &= chunkMask;
|
||
return chunk.blocks[getIndex(x, y, z)];
|
||
}
|
||
|
||
pub fn initEmpty(allocator: Allocator, pos: ChunkPosition, initialCapacity: usize) !ChunkVisibilityData {
|
||
return ChunkVisibilityData {
|
||
.pos = pos,
|
||
.visibles = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity),
|
||
.voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize),
|
||
};
|
||
}
|
||
|
||
pub fn init(allocator: Allocator, pos: ChunkPosition, initialCapacity: usize) !ChunkVisibilityData {
|
||
var self = ChunkVisibilityData {
|
||
.pos = pos,
|
||
.visibles = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity),
|
||
.voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize),
|
||
};
|
||
|
||
const width = pos.voxelSize*chunkSize;
|
||
const widthMask = width - 1;
|
||
|
||
// Get or generate the 8 surrounding chunks:
|
||
var chunks: [8]Chunk = undefined;
|
||
var x: u8 = 0;
|
||
while(x <= 1): (x += 1) {
|
||
var y: u8 = 0;
|
||
while(y <= 1): (y += 1) {
|
||
var z: u8 = 0;
|
||
while(z <= 1): (z += 1) {
|
||
chunks[x*4 + y*2 + z] = Chunk.init((pos.wx & ~widthMask) + x*width, (pos.wy & ~widthMask) + y*width, (pos.wz & ~widthMask) + z*width, pos.voxelSize);
|
||
// TODO: world.chunkManager.getOrGenerateReducedChunk((wx & ~widthMask) + x*width, (wy & ~widthMask) + y*width, (wz & ~widthMask) + z*width, voxelSize);
|
||
}
|
||
}
|
||
}
|
||
|
||
const halfMask = chunkMask >> 1;
|
||
x = 0;
|
||
while(x < chunkSize): (x += 1) {
|
||
var y: u8 = 0;
|
||
while(y < chunkSize): (y += 1) {
|
||
var z: u8 = 0;
|
||
while(z < chunkSize): (z += 1) {
|
||
const block = self.getBlock(&chunks, x, y, z);
|
||
if(block.typ == 0) continue;
|
||
// Check all neighbors:
|
||
var neighborVisibility: u8 = 0;
|
||
for(Neighbors.iterable) |i| {
|
||
const x2 = x + Neighbors.relX[i];
|
||
const y2 = y + Neighbors.relY[i];
|
||
const z2 = z + Neighbors.relZ[i];
|
||
const neighborBlock = self.getBlock(&chunks, x2, y2, z2);
|
||
var isVisible = neighborBlock.typ == 0;
|
||
if(!isVisible) {
|
||
// If the chunk is at a border, more neighbors need to be checked to prevent cracks at LOD changes:
|
||
// TODO: Find a better way to do this. This method doesn't always work and adds a lot of additional triangles.
|
||
if(x & halfMask == (x2 & halfMask) ^ halfMask or y & halfMask == (y2 & halfMask) ^ halfMask or z & halfMask == (z2 & halfMask) ^ halfMask) {
|
||
for(Neighbors.iterable) |j| {
|
||
const x3 = x2 + Neighbors.relX[j];
|
||
const y3 = y2 + Neighbors.relY[j];
|
||
const z3 = z2 + Neighbors.relZ[j];
|
||
if(self.getBlock(&chunks, x3, y3, z3).typ == 0) {
|
||
isVisible = true;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if(isVisible) {
|
||
neighborVisibility |= Neighbors.bitMask[i];
|
||
}
|
||
}
|
||
if(neighborVisibility != 0) {
|
||
try self.visibles.append(VisibleBlock{
|
||
.block = block,
|
||
.x = x,
|
||
.y = y,
|
||
.z = z,
|
||
.neighbors = neighborVisibility,
|
||
});
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return self;
|
||
}
|
||
|
||
// TODO: Check how this constructor is actually needed.
|
||
// public ReducedChunkVisibilityData(int wx, int wy, int wz, int voxelSize, byte[] x, byte[] y, byte[] z, byte[] neighbors, int[] visibleBlocks) {
|
||
// super(wx, wy, wz, voxelSize);
|
||
// voxelSizeShift = 31 - Integer.numberOfLeadingZeros(voxelSize); // log2
|
||
// assert x.length == y.length && y.length == z.length && z.length == neighbors.length && neighbors.length == visibleBlocks.length : "Size of input parameters doesn't match.";
|
||
// this.x = x;
|
||
// this.y = y;
|
||
// this.z = z;
|
||
// this.neighbors = neighbors;
|
||
// this.visibleBlocks = visibleBlocks;
|
||
// capacity = size = x.length;
|
||
// }
|
||
//}
|
||
};
|
||
|
||
pub const meshing = struct {
|
||
var shader: Shader = undefined;
|
||
pub var uniforms: struct {
|
||
projectionMatrix: c_int,
|
||
viewMatrix: c_int,
|
||
modelPosition: c_int,
|
||
ambientLight: c_int,
|
||
@"fog.activ": c_int,
|
||
@"fog.color": c_int,
|
||
@"fog.density": c_int,
|
||
texture_sampler: c_int,
|
||
emissionSampler: c_int,
|
||
@"waterFog.activ": c_int,
|
||
@"waterFog.color": c_int,
|
||
@"waterFog.density": c_int,
|
||
time: c_int,
|
||
} = undefined;
|
||
var vao: c_uint = undefined;
|
||
var vbo: c_uint = undefined;
|
||
var faces: std.ArrayList(u32) = undefined;
|
||
|
||
pub fn init() !void {
|
||
shader = try Shader.create("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs");
|
||
uniforms = shader.bulkGetUniformLocation(@TypeOf(uniforms));
|
||
|
||
var rawData: [6*3 << (3*chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block
|
||
const lut = [_]u32{0, 1, 2, 2, 1, 3};
|
||
for(rawData) |_, i| {
|
||
rawData[i] = @intCast(u32, i)/6*4 + lut[i%6];
|
||
}
|
||
|
||
c.glGenVertexArrays(1, &vao);
|
||
c.glBindVertexArray(vao);
|
||
c.glGenBuffers(1, &vbo);
|
||
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbo);
|
||
c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW);
|
||
c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null);
|
||
c.glBindVertexArray(0);
|
||
|
||
faces = try std.ArrayList(u32).initCapacity(std.heap.page_allocator, 65536);
|
||
}
|
||
|
||
pub fn deinit() void {
|
||
shader.delete();
|
||
c.glDeleteVertexArrays(1, &vao);
|
||
c.glDeleteBuffers(1, &vbo);
|
||
faces.deinit();
|
||
}
|
||
|
||
pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, time: u32) void {
|
||
shader.bind();
|
||
|
||
c.glUniform1i(uniforms.@"fog.activ", if(game.fog.active) 1 else 0);
|
||
c.glUniform3fv(uniforms.@"fog.color", 1, @ptrCast([*c]f32, &game.fog.color));
|
||
c.glUniform1f(uniforms.@"fog.density", game.fog.density);
|
||
|
||
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &projMatrix));
|
||
|
||
c.glUniform1i(uniforms.texture_sampler, 0);
|
||
c.glUniform1i(uniforms.emissionSampler, 1);
|
||
|
||
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast([*c]f32, &game.camera.viewMatrix));
|
||
|
||
c.glUniform3f(uniforms.ambientLight, ambient.x, ambient.y, ambient.z);
|
||
|
||
c.glUniform1i(uniforms.time, @bitCast(i32, time));
|
||
|
||
c.glBindVertexArray(vao);
|
||
}
|
||
|
||
pub const ChunkMesh = struct {
|
||
pos: ChunkPosition,
|
||
size: ChunkCoordinate,
|
||
chunk: ?*Chunk,
|
||
faces: std.ArrayList(u32),
|
||
faceData: SSBO,
|
||
coreCount: u31 = 0,
|
||
neighborStart: [7]u31 = [_]u31{0} ** 7,
|
||
vertexCount: u31 = 0,
|
||
generated: bool = false,
|
||
mutex: std.Thread.Mutex = std.Thread.Mutex{},
|
||
|
||
pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh {
|
||
return ChunkMesh{
|
||
.pos = pos,
|
||
.size = chunkSize*pos.voxelSize,
|
||
.faces = std.ArrayList(u32).init(allocator),
|
||
.chunk = null,
|
||
.faceData = SSBO.init(),
|
||
};
|
||
}
|
||
|
||
pub fn deinit(self: *ChunkMesh) void {
|
||
self.faceData.deinit();
|
||
self.faces.deinit();
|
||
if(self.chunk) |ch| {
|
||
renderer.RenderStructure.allocator.destroy(ch);
|
||
}
|
||
}
|
||
|
||
pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void {
|
||
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
|
||
self.faces.clearRetainingCapacity();
|
||
try self.faces.append(chunk.pos.voxelSize);
|
||
var n: u32 = 0;
|
||
var x: u8 = 0;
|
||
while(x < chunkSize): (x += 1) {
|
||
var y: u8 = 0;
|
||
while(y < chunkSize): (y += 1) {
|
||
var z: u8 = 0;
|
||
while(z < chunkSize): (z += 1) {
|
||
const block = (&chunk.blocks)[getIndex(x, y, z)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug.
|
||
if(block.typ == 0) continue;
|
||
// Check all neighbors:
|
||
for(Neighbors.iterable) |i| {
|
||
n += 1;
|
||
const x2 = x + Neighbors.relX[i];
|
||
const y2 = y + Neighbors.relY[i];
|
||
const z2 = z + Neighbors.relZ[i];
|
||
if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk.
|
||
const neighborBlock = (&chunk.blocks)[getIndex(x2, y2, z2)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug.
|
||
var isVisible = neighborBlock.typ == 0; // TODO: Transparency
|
||
if(isVisible) {
|
||
const normal: u32 = i;
|
||
const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10;
|
||
const textureNormal = blocks.meshes.textureIndices(block)[i] | normal<<24;
|
||
try self.faces.append(position);
|
||
try self.faces.append(textureNormal);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if(self.chunk) |oldChunk| {
|
||
renderer.RenderStructure.allocator.destroy(oldChunk);
|
||
}
|
||
self.chunk = chunk;
|
||
self.coreCount = @intCast(u31, self.faces.items.len);
|
||
self.neighborStart = [_]u31{self.coreCount} ** 7;
|
||
}
|
||
|
||
pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void {
|
||
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
|
||
if(self.chunk == null) return; // In the mean-time the mesh was discarded and recreated and all the data was lost.
|
||
self.generated = true;
|
||
self.faces.shrinkRetainingCapacity(self.coreCount);
|
||
for(Neighbors.iterable) |neighbor| {
|
||
self.neighborStart[neighbor] = @intCast(u31, self.faces.items.len);
|
||
var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, neighbor);
|
||
if(nullNeighborMesh) |neighborMesh| {
|
||
std.debug.assert(neighborMesh != self);
|
||
neighborMesh.mutex.lock();
|
||
defer neighborMesh.mutex.unlock();
|
||
if(neighborMesh.generated) {
|
||
var additionalNeighborFaces = std.ArrayList(u32).init(main.threadAllocator);
|
||
defer additionalNeighborFaces.deinit();
|
||
var x3: u8 = if(neighbor & 1 == 0) @intCast(u8, chunkMask) else 0;
|
||
var x1: u8 = 0;
|
||
while(x1 < chunkSize): (x1 += 1) {
|
||
var x2: u8 = 0;
|
||
while(x2 < chunkSize): (x2 += 1) {
|
||
var x: u8 = undefined;
|
||
var y: u8 = undefined;
|
||
var z: u8 = undefined;
|
||
if(Neighbors.relX[neighbor] != 0) {
|
||
x = x3;
|
||
y = x1;
|
||
z = x2;
|
||
} else if(Neighbors.relY[neighbor] != 0) {
|
||
x = x1;
|
||
y = x3;
|
||
z = x2;
|
||
} else {
|
||
x = x2;
|
||
y = x1;
|
||
z = x3;
|
||
}
|
||
var otherX = @intCast(u8, x+%Neighbors.relX[neighbor] & chunkMask);
|
||
var otherY = @intCast(u8, y+%Neighbors.relY[neighbor] & chunkMask);
|
||
var otherZ = @intCast(u8, z+%Neighbors.relZ[neighbor] & chunkMask);
|
||
var block = self.chunk.?.blocks[getIndex(x, y, z)];
|
||
var otherBlock = neighborMesh.chunk.?.blocks[getIndex(otherX, otherY, otherZ)];
|
||
if(otherBlock.typ == 0 and block.typ != 0) { // TODO: Transparency
|
||
const normal: u32 = neighbor;
|
||
const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10;
|
||
const textureNormal = blocks.meshes.textureIndices(block)[neighbor] | normal<<24;
|
||
try self.faces.append(position);
|
||
try self.faces.append(textureNormal);
|
||
}
|
||
if(block.typ == 0 and otherBlock.typ != 0) { // TODO: Transparency
|
||
const normal: u32 = neighbor ^ 1;
|
||
const position: u32 = @as(u32, otherX) | @as(u32, otherY)<<5 | @as(u32, otherZ)<<10;
|
||
const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor] | normal<<24;
|
||
try additionalNeighborFaces.append(position);
|
||
try additionalNeighborFaces.append(textureNormal);
|
||
}
|
||
}
|
||
}
|
||
var rangeStart = neighborMesh.neighborStart[neighbor ^ 1];
|
||
var rangeEnd = neighborMesh.neighborStart[(neighbor ^ 1)+1];
|
||
try neighborMesh.faces.replaceRange(rangeStart, rangeEnd - rangeStart, additionalNeighborFaces.items);
|
||
for(neighborMesh.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| {
|
||
neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @intCast(u31, additionalNeighborFaces.items.len);
|
||
}
|
||
neighborMesh.vertexCount = @intCast(u31, 6*(neighborMesh.faces.items.len-1)/2);
|
||
neighborMesh.faceData.bufferData(u32, neighborMesh.faces.items);
|
||
} else {
|
||
// TODO: Resolution boundary.
|
||
}
|
||
} else {
|
||
// TODO: Resolution boundary.
|
||
}
|
||
}
|
||
self.neighborStart[6] = @intCast(u31, self.faces.items.len);
|
||
self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2);
|
||
self.faceData.bufferData(u32, self.faces.items);
|
||
}
|
||
|
||
pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void {
|
||
if(!self.generated) {
|
||
return;
|
||
}
|
||
if(self.vertexCount == 0) return;
|
||
c.glUniform3f(
|
||
uniforms.modelPosition,
|
||
@floatCast(f32, @intToFloat(f64, self.pos.wx) - playerPosition.x),
|
||
@floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y),
|
||
@floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z)
|
||
);
|
||
self.faceData.bind(3);
|
||
c.glDrawElements(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null);
|
||
}
|
||
};
|
||
}; |