Add the chunk data structure for rendering and render chunks received from the network.

This commit is contained in:
IntegratedQuantum 2022-09-14 22:09:26 +02:00
parent 307044a4ed
commit e6659675b2
12 changed files with 722 additions and 171 deletions

View File

@ -30,8 +30,7 @@ vec4 calcFog(vec3 pos, vec4 color, Fog fog) {
return vec4(resultColor.xyz, color.w + 1 - fogFactor);
}
void main()
{
void main() {
fragColor = texture(texture_sampler, vec3(outTexCoord, textureIndex))*vec4((1 - dot(directionalLight, outNormal))*ambientLight, 1);
if (fragColor.a <= 0.1f) discard;
if (fog.activ) {

View File

@ -57,8 +57,7 @@ const ivec3[6] textureY = ivec3[6](
ivec3(0, 0, 1)
);
void main()
{
void main() {
int faceID = gl_VertexID/4;
int vertexID = gl_VertexID%4;
int encodedPosition = faceData[faceID].encodedPosition;

View File

@ -129,7 +129,61 @@ pub fn registerBlock(assetFolder: []const u8, id: []const u8, info: JsonElement)
// }
}
pub fn loadWorldAssets(assetFolder: []const u8) !void {
pub const BlockPalette = struct {
palette: std.ArrayList([]const u8),
pub fn init(allocator: Allocator, jsonObject: JsonElement) !*BlockPalette {
var self = try allocator.create(BlockPalette);
self.* = BlockPalette {
.palette = std.ArrayList([]const u8).init(allocator),
};
errdefer self.deinit();
if(jsonObject != .JsonObject or jsonObject.JsonObject.count() == 0) {
try self.palette.append(try allocator.dupe(u8, "cubyz:air"));
} else {
var palette = try main.threadAllocator.alloc(?[]const u8, jsonObject.JsonObject.count());
for(palette) |*val| {
val.* = null;
}
defer main.threadAllocator.free(palette);
var iterator = jsonObject.JsonObject.iterator();
while(iterator.next()) |entry| {
palette[entry.value_ptr.as(usize, std.math.maxInt(usize))] = entry.key_ptr.*;
}
std.debug.assert(std.mem.eql(u8, palette[0].?, "cubyz:air"));
for(palette) |val| {
std.log.info("palette[{}]: {s}", .{self.palette.items.len, val.?});
try self.palette.append(try allocator.dupe(u8, val orelse return error.MissingKeyInPalette));
}
}
return self;
}
pub fn deinit(self: *BlockPalette) void {
for(self.palette.items) |item| {
self.palette.allocator.free(item);
}
var allocator = self.palette.allocator;
self.palette.deinit();
allocator.destroy(self);
}
pub fn add(self: *BlockPalette, id: []const u8) !void {
try self.palette.append(id);
}
pub fn save(self: *BlockPalette, allocator: Allocator) !JsonElement {
var jsonObject = JsonElement{
.JsonObject = std.StringHashMap(JsonElement).init(allocator),
};
errdefer jsonObject.free(allocator);
for(self.palette.items) |item, i| {
jsonObject.JsonObject.put(try allocator.dupe(u8, item), JsonElement{.JsonInt = @intCast(i64, i)});
}
return jsonObject;
}
};
pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void {
var blocks = try commonBlocks.cloneWithAllocator(main.threadAllocator);
defer blocks.clearAndFree();
var biomes = try commonBiomes.cloneWithAllocator(main.threadAllocator);
@ -138,24 +192,28 @@ pub fn loadWorldAssets(assetFolder: []const u8) !void {
try readAssets(arenaAllocator, assetFolder, &blocks, &biomes);
var block: u32 = 0;
// TODO:
// for(; block < palette.size(); block++) {
// Resource id = palette.getResource(block);
// JsonObject json = perWorldBlocks.remove(id);
// if(json == null) {
// Logger.error("Missing block: " + id + ". Replacing it with default block.");
// json = new JsonObject();
// }
// registerBlock(block, id, json, registries, oreRegistry);
// }
var iterator = commonBlocks.iterator();
for(palette.palette.items) |id| {
var nullKeyValue = blocks.fetchRemove(id);
var jsonObject: JsonElement = undefined;
if(nullKeyValue) |keyValue| {
jsonObject = keyValue.value;
} else {
std.log.err("Missing block: {s}. Replacing it with default block.", .{id});
var map: *std.StringHashMap(JsonElement) = try main.threadAllocator.create(std.StringHashMap(JsonElement));
map.* = std.StringHashMap(JsonElement).init(main.threadAllocator);
jsonObject = JsonElement{.JsonObject=map};
}
defer if(nullKeyValue == null) jsonObject.free(main.threadAllocator);
try registerBlock(assetFolder, id, jsonObject);
block += 1;
}
var iterator = blocks.iterator();
while(iterator.next()) |entry| {
try registerBlock(assetFolder, entry.key_ptr.*, entry.value_ptr.*);
try palette.palette.append(entry.key_ptr.*);
block += 1;
// TODO:
// palette.addResource(entry.getKey());
}
//
// public void registerBlocks(Registry<DataOrientedRegistry> registries, NoIDRegistry<Ore> oreRegistry, BlockPalette palette) {
// HashMap<Resource, JsonObject> perWorldBlocks = new HashMap<>(commonBlocks);
// readAllJsonObjects("blocks", (json, id) -> {

View File

@ -4,6 +4,7 @@ const JsonElement = @import("json.zig").JsonElement;
const Neighbors = @import("chunk.zig").Neighbors;
const SSBO = @import("graphics.zig").SSBO;
const Image = @import("graphics.zig").Image;
const Color = @import("graphics.zig").Color;
const TextureArray = @import("graphics.zig").TextureArray;
pub const BlockClass = enum(u8) {
@ -219,15 +220,18 @@ pub const meshes = struct {
pub var blockTextureArray: TextureArray = undefined;
pub var emissionTextureArray: TextureArray = undefined;
var undefinedTexture = [_]u32 {0xffff00ff, 0xff000000, 0xff000000, 0xffff00ff};
const black: Color = Color{.r=0, .g=0, .b=0, .a=255};
const magenta: Color = Color{.r=255, .g=0, .b=255, .a=255};
var undefinedTexture = [_]Color {magenta, black, black, magenta};
const undefinedImage = Image{.width = 2, .height = 2, .imageData = undefinedTexture[0..]};
var emptyTexture = [_]u32 {0};
var emptyTexture = [_]Color {black};
const emptyImage = Image{.width = 1, .height = 1, .imageData = emptyTexture[0..]};
pub fn init() void {
animationTimesSSBO = SSBO.init();
animationTimesSSBO.bind(0);
animationFramesSSBO = SSBO.init();
animationFramesSSBO.bind(1);
blockTextureArray = TextureArray.init();
emissionTextureArray = TextureArray.init();
arenaForArrayLists = std.heap.ArenaAllocator.init(std.heap.page_allocator);
@ -276,7 +280,7 @@ pub const meshes = struct {
const mod = splitter.first();
const id = splitter.rest();
var buffer: [1024]u8 = undefined;
var path = try std.fmt.bufPrint(&buffer, "{s}{s}/blocks/textures/{s}.png", .{assetFolder, mod, id});
var path = try std.fmt.bufPrint(&buffer, "{s}/{s}/blocks/textures/{s}.png", .{assetFolder, mod, id});
// Test if it's already in the list:
for(textureIDs.items) |other, j| {
if(std.mem.eql(u8, other, path)) {
@ -287,7 +291,10 @@ pub const meshes = struct {
var file = std.fs.cwd().openFile(path, .{}) catch |err| blk: {
if(err == error.FileNotFound) {
path = try std.fmt.bufPrint(&buffer, "assets/{s}/blocks/textures/{s}.png", .{mod, id}); // Default to global assets.
break :blk try std.fs.cwd().openFile(path, .{});
break :blk std.fs.cwd().openFile(path, .{}) catch |err2| {
std.log.err("File not found. Searched in \"{s}\" and also in the assetFolder \"{s}\"", .{path, assetFolder});
return err2;
};
} else {
return err;
}
@ -324,11 +331,14 @@ pub const meshes = struct {
const mod = splitter.first();
const id = splitter.rest();
var buffer: [1024]u8 = undefined;
var path = try std.fmt.bufPrint(&buffer, "{s}{s}/blocks/textures/{s}.png", .{assetFolder, mod, id});
var path = try std.fmt.bufPrint(&buffer, "{s}/{s}/blocks/textures/{s}.png", .{assetFolder, mod, id});
var file = std.fs.cwd().openFile(path, .{}) catch |err| blk: {
if(err == error.FileNotFound) {
path = try std.fmt.bufPrint(&buffer, "assets/{s}/blocks/textures/{s}.png", .{mod, id}); // Default to global assets.
break :blk try std.fs.cwd().openFile(path, .{});
break :blk std.fs.cwd().openFile(path, .{}) catch |err2| {
std.log.err("File not found. Searched in \"{s}\" and also in the assetFolder \"{s}\"", .{path, assetFolder});
return err2;
};
} else {
return err;
}
@ -402,12 +412,12 @@ pub const meshes = struct {
// }
// }
pub fn generateTextureArray() void {
blockTextureArray.generate(blockTextures.items);
emissionTextureArray.generate(emissionTextures.items);
pub fn generateTextureArray() !void {
try blockTextureArray.generate(blockTextures.items);
try emissionTextureArray.generate(emissionTextures.items);
// Also generate additional buffers:
animationTimesSSBO.bufferID(animationTimes.items);
animationFramesSSBO.bufferID(animationFrames.items);
animationTimesSSBO.bufferData(u32, animationTimes.items);
animationFramesSSBO.bufferData(u32, animationFrames.items);
}
};

View File

@ -12,7 +12,8 @@ const Vec3f = @import("vec.zig").Vec3f;
const Vec3d = @import("vec.zig").Vec3d;
const Mat4f = @import("vec.zig").Mat4f;
pub const ChunkCoordinate = u32;
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;
@ -35,11 +36,11 @@ pub const Neighbors = struct {
/// Directions Index
pub const dirNegZ: u32 = 5;
/// Index to relative position
pub const relX = [_]u32 {0, 0, 1, -1, 0, 0};
pub const relX = [_]i32 {0, 0, 1, -1, 0, 0};
/// Index to relative position
pub const relY = [_]u32 {1, -1, 0, 0, 0, 0};
pub const relY = [_]i32 {1, -1, 0, 0, 0, 0};
/// Index to relative position
pub const relZ = [_]u32 {0, 0, 0, 0, 1, -1};
pub const relZ = [_]i32 {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
@ -56,7 +57,7 @@ pub const ChunkPosition = struct {
wx: ChunkCoordinate,
wy: ChunkCoordinate,
wz: ChunkCoordinate,
voxelSize: ChunkCoordinate,
voxelSize: UChunkCoordinate,
// TODO(mabye?):
// public int hashCode() {
@ -68,16 +69,17 @@ pub const ChunkPosition = struct {
// int halfWidth = voxelSize * Chunk.chunkSize / 2;
// return -(float) source.getPosition().distance(wx + halfWidth, wy + halfWidth, wz + halfWidth) / voxelSize;
// }
// public double getMinDistanceSquared(double px, double py, double pz) {
// int halfWidth = voxelSize * Chunk.chunkSize / 2;
// double dx = Math.abs(wx + halfWidth - px);
// double dy = Math.abs(wy + halfWidth - py);
// double dz = Math.abs(wz + halfWidth - pz);
// dx = Math.max(0, dx - halfWidth);
// dy = Math.max(0, dy - halfWidth);
// dz = Math.max(0, dz - halfWidth);
// return dx*dx + dy*dy + dz*dz;
// }
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 {
@ -95,17 +97,18 @@ pub const Chunk = struct {
widthShift: u5,
mutex: std.Thread.Mutex,
pub fn init(wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: ChunkCoordinate) Chunk {
std.debug.assert((voxelSize - 1 & voxelSize) == 0, "the voxel size must be a power of 2.");
std.debug.assert(wx % voxelSize == 0 and wy % voxelSize == 0 and wz % voxelSize == 0);
pub fn init(wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) Chunk {
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 {
.pos = ChunkPosition {
.wx = wx, .wy = wy, .wz = wz, .voxelSize = voxelSize
},
.width = voxelSize*chunkSize,
.voxelSizeShift = @intCast(u5, std.math.log2_int(u32, voxelSize)),
.voxelSizeShift = voxelSizeShift,
.voxelSizeMask = voxelSize - 1,
.widthShift = .voxelSizeShift + chunkShift,
.widthShift = voxelSizeShift + chunkShift,
.mutex = std.Thread.Mutex{},
};
}
@ -373,22 +376,30 @@ pub const ChunkVisibilityData = struct {
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 relX = x + (self.pos.wx - chunks[0].pos.wx) >> self.voxelSizeShift;
var relY = y + (self.pos.wy - chunks[0].pos.wy) >> self.voxelSizeShift;
var relZ = z + (self.pos.wz - chunks[0].pos.wz) >> self.voxelSizeShift;
var chunk = &chunks[(x >> chunkShift)*4 + (y >> chunkShift)*2 + (z >> chunkShift)];
relX &= chunkMask;
relY &= chunkMask;
relZ &= chunkMask;
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 init(allocator: Allocator, pos: ChunkPosition) !ChunkVisibilityData {
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 = std.ArrayList(VisibleBlock).init(allocator),
.voxelSizeShift = std.math.log2_int(pos.voxelSize),
.visibles = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity),
.voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize),
};
const width = pos.voxelSize*chunkSize;
@ -415,7 +426,7 @@ pub const ChunkVisibilityData = struct {
while(y < chunkSize): (y += 1) {
var z: u8 = 0;
while(z < chunkSize): (z += 1) {
const block = self.getBlock(chunks, x, y, z);
const block = self.getBlock(&chunks, x, y, z);
if(block.typ == 0) continue;
// Check all neighbors:
var neighborVisibility: u8 = 0;
@ -423,7 +434,7 @@ pub const ChunkVisibilityData = struct {
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);
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:
@ -433,7 +444,7 @@ pub const ChunkVisibilityData = struct {
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) == 0) {
if(self.getBlock(&chunks, x3, y3, z3).typ == 0) {
isVisible = true;
break;
}
@ -559,7 +570,7 @@ pub const meshing = struct {
size: ChunkCoordinate,
replacement: ?*ChunkMesh,
faceData: SSBO,
vertexCount: u32,
vertexCount: c_int = 0,
generated: bool = false,
pub fn init(pos: ChunkPosition, replacement: ?*ChunkMesh) ChunkMesh {
@ -575,11 +586,11 @@ pub const meshing = struct {
self.faceData.deinit();
}
pub fn regenerateMesh(self: *ChunkMesh, visDat: ChunkVisibilityData) void {
pub fn regenerateMesh(self: *ChunkMesh, visDat: *ChunkVisibilityData) !void {
self.generated = true;
faces.clearRetainingCapacity();
faces.add(visDat.voxelSizeShift);
try faces.append(visDat.pos.voxelSize);
for(visDat.visibles.items) |visible| {
const block = visible.block;
@ -588,49 +599,49 @@ pub const meshing = struct {
const z = visible.z;
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegX] != 0) {
const normal: u32 = 0;
const position: u32 = x | y << 6 | z << 12;
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegX] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosX] != 0) {
const normal: u32 = 1;
const position: u32 = x+1 | y << 6 | z << 12;
const position: u32 = @as(u32, x+1) | @as(u32, y) << 6 | @as(u32, z) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosX] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirDown] != 0) {
const normal: u32 = 4;
const position: u32 = x | y << 6 | z << 12;
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirDown] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirUp] != 0) {
const normal: u32 = 5;
const position: u32 = x | (y+1) << 6 | z << 12;
const position: u32 = @as(u32, x) | @as(u32, y+1) << 6 | @as(u32, z) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirUp] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegZ] != 0) {
const normal: u32 = 2;
const position: u32 = x | y << 6 | z << 12;
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegZ] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosZ] != 0) {
const normal: u32 = 3;
const position: u32 = x | y << 6 | (z + 1) << 12;
const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z+1) << 12;
const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosZ] | (normal << 24);
try faces.append(position);
try faces.append(textureNormal);
}
}
self.vertexCount = 6*(faces.size-1)/2;
self.vertexCount = @intCast(c_int, 6*(faces.items.len-1)/2);
self.faceData.bufferData(u32, faces.items);
}
@ -639,15 +650,15 @@ pub const meshing = struct {
if(self.replacement == null) return;
c.glUniform3f(
uniforms.lowerBounds,
@floatCast(f32, self.pos.wx - playerPosition.x - 0.001),
@floatCast(f32, self.pos.wy - playerPosition.y - 0.001),
@floatCast(f32, self.pos.wz - playerPosition.z - 0.001)
@floatCast(f32, @intToFloat(f64, self.pos.wx) - playerPosition.x - 0.001),
@floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y - 0.001),
@floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z - 0.001)
);
c.glUniform3f(
uniforms.upperBounds,
@floatCast(f32, self.pos.wx + self.size - playerPosition.x + 0.001),
@floatCast(f32, self.pos.wy + self.size - playerPosition.y + 0.001),
@floatCast(f32, self.pos.wz + self.size - playerPosition.z + 0.001)
@floatCast(f32, @intToFloat(f64, self.pos.wx + self.size) - playerPosition.x + 0.001),
@floatCast(f32, @intToFloat(f64, self.pos.wy + self.size) - playerPosition.y + 0.001),
@floatCast(f32, @intToFloat(f64, self.pos.wz + self.size) - playerPosition.z + 0.001)
);
self.replacement.?.render(playerPosition);
@ -656,11 +667,12 @@ pub const meshing = struct {
c.glUniform3f(uniforms.upperBounds, std.math.inf_f32, std.math.inf_f32, std.math.inf_f32);
return;
}
if(self.vertexCount == 0) return;
c.glUniform3f(
uniforms.modelPosition,
@floatCast(f32, self.pos.wx - playerPosition.x),
@floatCast(f32, self.pos.wy - playerPosition.y),
@floatCast(f32, self.pos.wz - playerPosition.z)
@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);

View File

@ -1,5 +1,6 @@
const std = @import("std");
const assets = @import("assets.zig");
const vec = @import("vec.zig");
const Vec3f = vec.Vec3f;
const Mat4f = vec.Mat4f;
@ -29,6 +30,7 @@ pub const camera = struct {
}
};
pub var blockPalette: *assets.BlockPalette = undefined;
pub const World = u1; // TODO
pub var testWorld: World = 0;
pub var world: ?*World = &testWorld;

View File

@ -418,9 +418,9 @@ pub const SSBO = struct {
c.glBindBufferBase(c.GL_SHADER_STORAGE_BUFFER, binding, self.bufferID);
}
pub fn bufferData(self: SSBO, T: type, data: []T) void {
pub fn bufferData(self: SSBO, comptime T: type, data: []T) void {
c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID);
c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, data.len*@sizeOf(T), data.ptr, c.GL_STATIC_DRAW);
c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(c_long, data.len*@sizeOf(T)), data.ptr, c.GL_STATIC_DRAW);
c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0);
}
};
@ -442,29 +442,29 @@ pub const TextureArray = struct {
c.glBindTexture(c.GL_TEXTURE_2D_ARRAY, self.textureID);
}
fn lodColorInterpolation(colors: [4]u32, isTransparent: bool) u32 {
fn lodColorInterpolation(colors: [4]Color, isTransparent: bool) Color {
var r: [4]u32 = undefined;
var g: [4]u32 = undefined;
var b: [4]u32 = undefined;
var a: [4]u32 = undefined;
for(colors) |_, i| {
r[i] = colors[i]>>24;
g[i] = colors[i]>>16 & 0xFF;
b[i] = colors[i]>>8 & 0xFF;
a[i] = colors[i] & 0xFF;
r[i] = colors[i].r;
g[i] = colors[i].g;
b[i] = colors[i].b;
a[i] = colors[i].a;
}
// Use gamma corrected average(https://stackoverflow.com/a/832314/13082649):
var aSum = 0;
var rSum = 0;
var gSum = 0;
var bSum = 0;
var aSum: u32 = 0;
var rSum: u32 = 0;
var gSum: u32 = 0;
var bSum: u32 = 0;
for(colors) |_, i| {
aSum += a[i]*a[i];
rSum += r[i]*r[i];
gSum += g[i]*g[i];
bSum += b[i]*b[i];
}
aSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, aSum))));
aSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, aSum)))/2);
if(!isTransparent) {
if(aSum < 128) {
aSum = 0;
@ -472,47 +472,47 @@ pub const TextureArray = struct {
aSum = 255;
}
}
rSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, rSum))));
gSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, gSum))));
bSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, bSum))));
return rSum<<24 | gSum<<16 | bSum<<8 | aSum;
rSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, rSum)))/2);
gSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, gSum)))/2);
bSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, bSum)))/2);
return Color{.r=@intCast(u8, rSum), .g=@intCast(u8, gSum), .b=@intCast(u8, bSum), .a=@intCast(u8, aSum)};
}
/// (Re-)Generates the GPU buffer.
pub fn generate(self: TextureArray, images: []Image) void {
var maxWidth: u32 = 0;
var maxHeight: u32 = 0;
pub fn generate(self: TextureArray, images: []Image) !void {
var maxWidth: u31 = 0;
var maxHeight: u31 = 0;
for(images) |image| {
maxWidth = @maximum(maxWidth, image.width);
maxHeight = @maximum(maxHeight, image.height);
}
// Make sure the width and height use a power of 2:
if(maxWidth-1 & maxWidth != 0) {
maxWidth = 2 << std.math.log2_int(u32, maxWidth);
maxWidth = @as(u31, 2) << std.math.log2_int(u31, maxWidth);
}
if(maxHeight-1 & maxHeight != 0) {
maxHeight = 2 << std.math.log2_int(u32, maxHeight);
maxHeight = @as(u31, 2) << std.math.log2_int(u31, maxHeight);
}
std.log.debug("Creating Texture Array of size {}×{} with {} layers.", .{maxWidth, maxHeight, images.len});
self.bind();
const maxLOD = 1 + std.mat.log2_int(@minimum(maxWidth, maxHeight));
c.glTexStorage3D(c.GL_TEXTURE_2D_ARRAY, maxLOD, c.GL_RGBA8, maxWidth, maxHeight, images.len);
const maxLOD = 1 + std.math.log2_int(u31, @minimum(maxWidth, maxHeight));
c.glTexStorage3D(c.GL_TEXTURE_2D_ARRAY, maxLOD, c.GL_RGBA8, maxWidth, maxHeight, @intCast(c_int, images.len));
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
var lodBuffer: [][]u32 = try arena.allocator().alloc([]u32, maxLOD);
var lodBuffer: [][]Color = try arena.allocator().alloc([]Color, maxLOD);
for(lodBuffer) |*buffer, i| {
buffer.* = try arena.allocator().alloc(u32, (maxWidth >> i)*(maxHeight >> i));
buffer.* = try arena.allocator().alloc(Color, (maxWidth >> @intCast(u5, i))*(maxHeight >> @intCast(u5, i)));
}
for(images) |image, i| {
// Check if the image contains non-binary alpha values, which makes it transparent.
var isTransparent = false;
for(image.imageData) |color| {
if(color >> 24 != 0 or color >> 24 != 255) {
isTransparent[i] = true;
if(color.a != 0 or color.a != 255) {
isTransparent = true;
break;
}
}
@ -524,14 +524,13 @@ pub const TextureArray = struct {
while(y < maxHeight): (y += 1) {
const index = x + y*maxWidth;
const imageIndex = (x*image.width)/maxWidth + image.width*(y*image.height)/maxHeight;
const argb = image.imageData[imageIndex];
const rgba = argb<<8 | argb>>24;
lodBuffer[0][index] = rgba;
lodBuffer[0][index] = image.imageData[imageIndex];
}
}
// Calculate the mipmap levels:
for(lodBuffer) |_, lod| {
for(lodBuffer) |_, _lod| {
const lod = @intCast(u5, _lod);
const curWidth = maxWidth >> lod;
const curHeight = maxHeight >> lod;
if(lod != 0) {
@ -541,7 +540,7 @@ pub const TextureArray = struct {
while(y < curHeight): (y += 1) {
const index = x + y*curWidth;
const index2 = 2*x + 2*y*2*curWidth;
const colors = [4]u32 {
const colors = [4]Color {
lodBuffer[lod-1][index2],
lodBuffer[lod-1][index2 + 1],
lodBuffer[lod-1][index2 + curWidth*2],
@ -551,7 +550,7 @@ pub const TextureArray = struct {
}
}
}
c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, i, curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, &lodBuffer[lod]);
c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, @intCast(c_int, i), curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, lodBuffer[lod].ptr);
}
}
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAX_LOD, maxLOD);
@ -564,10 +563,17 @@ pub const TextureArray = struct {
}
};
pub const Color = packed struct(u32) {
r: u8,
g: u8,
b: u8,
a: u8
};
pub const Image = struct {
width: u32,
height: u32,
imageData: []u32,
width: u31,
height: u31,
imageData: []Color,
pub fn readFromFile(allocator: Allocator, path: []const u8) !Image {
var result: Image = undefined;
var channel: c_int = undefined;
@ -576,7 +582,9 @@ pub const Image = struct {
const data = stb_image.stbi_load(nullTerminatedPath.ptr, @ptrCast([*c]c_int, &result.width), @ptrCast([*c]c_int, &result.height), &channel, 4) orelse {
return error.FileNotFound;
};
result.imageData = try allocator.dupe(u32, @ptrCast([*]u32, data)[0..result.width*result.height]);
std.log.info("Image sample direct: {} {} {} {}", .{data[0], data[1], data[2], data[3]});
result.imageData = try allocator.dupe(Color, @ptrCast([*]Color, data)[0..result.width*result.height]);
std.log.info("Image sample: {} {} {} {}", .{result.imageData[0].r, result.imageData[0].g, result.imageData[0].b, result.imageData[0].a});
stb_image.stbi_image_free(data);
return result;
}

View File

@ -3,6 +3,7 @@ const std = @import("std");
const assets = @import("assets.zig");
const blocks = @import("blocks.zig");
const chunk = @import("chunk.zig");
const game = @import("game.zig");
const graphics = @import("graphics.zig");
const renderer = @import("renderer.zig");
const network = @import("network.zig");
@ -36,13 +37,9 @@ pub fn log(
std.debug.getStderrMutex().lock();
defer std.debug.getStderrMutex().unlock();
const fileMessage = std.fmt.allocPrint(threadAllocator, "[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch return;
defer threadAllocator.free(fileMessage);
logFile.writeAll(fileMessage) catch return;
logFile.writer().print("[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch {};
const terminalMessage = std.fmt.allocPrint(threadAllocator, color ++ format ++ "\x1b[0m\n", args) catch return;
defer threadAllocator.free(terminalMessage);
nosuspend std.io.getStdErr().writeAll(terminalMessage) catch return;
nosuspend std.io.getStdErr().writer().print(color ++ format ++ "\x1b[0m\n", args) catch {};
}
pub const Window = struct {
@ -136,7 +133,7 @@ pub const Window = struct {
};
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){};
threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
@ -147,7 +144,6 @@ pub fn main() !void {
defer logFile.close();
var poolgpa = std.heap.GeneralPurposeAllocator(.{}){};
threadAllocator = gpa.allocator();
defer if(poolgpa.deinit()) {
@panic("Memory leak");
};
@ -172,8 +168,6 @@ pub fn main() !void {
try renderer.init();
defer renderer.deinit();
try assets.loadWorldAssets("saves");
network.init();
var conn = try network.ConnectionManager.init(12347, true);
@ -182,9 +176,15 @@ pub fn main() !void {
var conn2 = try network.Connection.init(conn, "127.0.0.1");
defer conn2.deinit();
try renderer.RenderOctree.init();
defer renderer.RenderOctree.deinit();
try network.Protocols.handShake.clientSide(conn2, "quanturmdoelvloper");
c.glEnable(c.GL_CULL_FACE);
try assets.loadWorldAssets("serverAssets", game.blockPalette);
try blocks.meshes.generateTextureArray();
c.glCullFace(c.GL_BACK);
c.glEnable(c.GL_BLEND);
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
@ -198,19 +198,21 @@ pub fn main() !void {
}
c.glfwSwapBuffers(Window.window);
c.glfwPollEvents();
try renderer.RenderOctree.update(conn2, .{.x = 25, .y = 11, .z = -703}, 4, 2.0);
{ // Render the game
c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST);
renderer.render(.{.x = 0, .y = 0, .z = 0});
try renderer.render(.{.x = 25, .y = 11, .z = -703});
}
{ // Render the GUI
c.glDisable(c.GL_DEPTH_TEST);
graphics.Draw.setColor(0xff0000ff);
graphics.Draw.rect(Vec2f{.x = 100, .y = 100}, Vec2f{.x = 200, .y = 100});
graphics.Draw.circle(Vec2f{.x = 200, .y = 200}, 59);
graphics.Draw.setColor(0xffff00ff);
graphics.Draw.line(Vec2f{.x = 0, .y = 0}, Vec2f{.x = 1920, .y = 1080});
//graphics.Draw.setColor(0xff0000ff);
//graphics.Draw.rect(Vec2f{.x = 100, .y = 100}, Vec2f{.x = 200, .y = 100});
//graphics.Draw.circle(Vec2f{.x = 200, .y = 200}, 59);
//graphics.Draw.setColor(0xffff00ff);
//graphics.Draw.line(Vec2f{.x = 0, .y = 0}, Vec2f{.x = 1920, .y = 1080});
}
}
}

View File

@ -1,12 +1,14 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const assets = @import("assets.zig");
const chunk = @import("chunk.zig");
const main = @import("main.zig");
const game = @import("game.zig");
const settings = @import("settings.zig");
const json = @import("json.zig");
const JsonElement = json.JsonElement;
const renderer = @import("renderer.zig");
const utils = @import("utils.zig");
//TODO: Might want to use SSL or something similar to encode the message
@ -321,6 +323,7 @@ pub const ConnectionManager = struct {
allocator: std.mem.Allocator = undefined,
mutex: std.Thread.Mutex = std.Thread.Mutex{},
waitingToFinishReceive: std.Thread.Condition = std.Thread.Condition{},
receiveBuffer: [Connection.maxPacketSize]u8 = undefined,
@ -343,13 +346,13 @@ pub const ConnectionManager = struct {
}
pub fn deinit(self: *ConnectionManager) void {
self.running.store(false, .Monotonic);
self.thread.join();
Socket.deinit(self.socket);
for(self.connections.items) |conn| {
conn.disconnect() catch |err| {std.log.warn("Error while disconnecting: {s}", .{@errorName(err)});};
}
self.running.store(false, .Monotonic);
self.thread.join();
Socket.deinit(self.socket);
self.connections.deinit();
for(self.requests.items) |request| {
request.requestNotifier.signal();
@ -413,6 +416,12 @@ pub const ConnectionManager = struct {
try self.connections.append(conn);
}
pub fn finishCurrentReceive(self: *ConnectionManager) void {
self.mutex.lock();
defer self.mutex.unlock();
self.waitingToFinishReceive.wait(&self.mutex);
}
pub fn removeConnection(self: *ConnectionManager, conn: *Connection) void {
self.mutex.lock();
defer self.mutex.unlock();
@ -459,7 +468,7 @@ pub const ConnectionManager = struct {
pub fn run(self: *ConnectionManager) !void {
self.threadId = std.Thread.getCurrentId();
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){};
main.threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");
@ -467,6 +476,7 @@ pub const ConnectionManager = struct {
var lastTime = std.time.milliTimestamp();
while(self.running.load(.Monotonic)) {
self.waitingToFinishReceive.broadcast();
var source: Address = undefined;
if(self.socket.receive(&self.receiveBuffer, 100, &source)) |data| {
try self.onReceive(data, source);
@ -598,6 +608,9 @@ pub const Protocols = blk: {
var jsonObject = json.parseFromString(main.threadAllocator, data[1..]);
defer jsonObject.free(main.threadAllocator);
// TODO: ((ServerConnection)conn).world.finishHandshake(json);
{// TODO: Move this into world loading finishHandshake().
game.blockPalette = try assets.BlockPalette.init(renderer.RenderOctree.allocator, jsonObject.getChild("blockPalette")); // TODO: Use the world allocator and free this.
}
conn.handShakeState = stepComplete;
conn.handShakeWaiting.broadcast(); // Notify the waiting client thread.
},
@ -641,10 +654,10 @@ pub const Protocols = blk: {
var remaining = data[0..];
while(remaining.len >= 16) {
const request = chunk.ChunkPosition{
.wx = std.mem.readIntBig(chunk.ChunkCoordinate, data[0..4]),
.wy = std.mem.readIntBig(chunk.ChunkCoordinate, data[4..8]),
.wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]),
.voxelSize = std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16]),
.wx = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[0..4]),
.wy = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[4..8]),
.wz = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[8..12]),
.voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, remaining[12..16])),
};
_ = request;
_ = conn;
@ -654,19 +667,128 @@ pub const Protocols = blk: {
}
pub fn sendRequest(conn: *Connection, requests: []chunk.ChunkPosition) !void {
if(requests.len == 0) return;
var data = main.threadAllocator.alloc(16*requests.len);
var data = try main.threadAllocator.alloc(u8, 16*requests.len);
defer main.threadAllocator.free(data);
var remaining = data;
for(requests) |req| {
std.mem.writeIntBig(chunk.ChunkCoordinate, data[0..4], req.wx);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[4..8], req.wy);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[8..12], req.wz);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[12..16], req.voxelSize);
std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[0..4], req.wx);
std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[4..8], req.wy);
std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[8..12], req.wz);
std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[12..16], req.voxelSize);
std.log.info("Requested some chunk: {}", .{req.voxelSize});
remaining = remaining[16..];
}
conn.sendImportant(id, data);
try conn.sendImportant(id, data);
}
}),
chunkTransmission: type = addProtocol(&comptimeList, struct {
const id: u8 = 3;
fn receive(_: *Connection, _data: []const u8) !void {
var data = _data;
var pos = chunk.ChunkPosition{
.wx = std.mem.readIntBig(chunk.ChunkCoordinate, data[0..4]),
.wy = std.mem.readIntBig(chunk.ChunkCoordinate, data[4..8]),
.wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]),
.voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16])),
};
data = data[16..];
if(pos.voxelSize == 1) {
// TODO:
// byte[] chunkData = ChunkIO.decompressChunk(data, offset, length);
// if(chunkData == null)
// return;
// VisibleChunk ch = new VisibleChunk(Cubyz.world, wx, wy, wz);
// ch.loadFromByteArray(chunkData, chunkData.length);
// ThreadPool.addTask(new ChunkLoadTask(ch));
} else {
data = try utils.Compression.inflate(main.threadAllocator, data);
defer main.threadAllocator.free(data);
var size = @divExact(data.len, 8);
var x = data[0..size];
var y = data[size..2*size];
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);
for(x) |_, i| {
var block = result.visibles.addOneAssumeCapacity();
block.x = x[i];
block.y = y[i];
block.z = z[i];
block.neighbors = neighbors[i];
var blockTypeAndData = std.mem.readIntBig(u32, visibleBlocks[4*i..][0..4]);
block.block.typ = @intCast(u16, blockTypeAndData & 0xffff);
block.block.data = @intCast(u16, blockTypeAndData >> 16);
}
try renderer.RenderOctree.updateChunkMesh(result);
}
}
pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void {
var data = try main.threadAllocator.alloc(u8, 16 + 8*visData.visibles.items.len);
defer main.threadAllocator.free(data);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[0..4], visData.pos.wx);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[4..8], visData.pos.wy);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[8..12], visData.pos.wz);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[12..16], visData.pos.voxelSize);
var size = visData.visibles.items.len;
var x = data[16..][0..size];
var y = data[16..][size..2*size];
var z = data[16..][2*size..3*size];
var neighbors = data[16..][3*size..4*size];
var visibleBlocks = data[16..][4*size..];
for(visData.visibles.items) |block, i| {
x[i] = block.x;
y[i] = block.y;
z[i] = block.z;
neighbors[i] = block.neighbors;
var blockTypeAndData = @as(u32, block.block.data) << 16 | block.block.typ;
std.mem.writeIntBig(u32, visibleBlocks[4*i..][0..4], blockTypeAndData);
}
var compressed = try utils.Compression.deflate(main.threadAllocator, data);
defer main.threadAllocator.free(compressed);
try conn.sendImportant(id, compressed);
}
// TODO:
// public void sendChunk(UDPConnection conn, ChunkData ch) {
// byte[] data;
// if(ch instanceof NormalChunk) {
// byte[] compressedChunk = ChunkIO.compressChunk((NormalChunk)ch);
// data = new byte[compressedChunk.length + 16];
// System.arraycopy(compressedChunk, 0, data, 16, compressedChunk.length);
// } else {
// assert false: "Invalid chunk class to send over the network " + ch.getClass() + ".";
// return;
// }
// Bits.putInt(data, 0, ch.wx);
// Bits.putInt(data, 4, ch.wy);
// Bits.putInt(data, 8, ch.wz);
// Bits.putInt(data, 12, ch.voxelSize);
// conn.sendImportant(this, data);
// }
//
// private static class ChunkLoadTask extends ThreadPool.Task {
// private final VisibleChunk ch;
// public ChunkLoadTask(VisibleChunk ch) {
// this.ch = ch;
// }
// @Override
// public float getPriority() {
// return ch.getPriority(Cubyz.player);
// }
//
// @Override
// public boolean isStillNeeded() {
// return Cubyz.chunkTree.findNode(ch) != null;
// }
//
// @Override
// public void run() {
// ch.load();
// }
// }
}),
disconnect: type = addProtocol(&comptimeList, struct {
const id: u8 = 5;
fn receive(conn: *Connection, _: []const u8) !void {
@ -779,6 +901,10 @@ pub const Connection = struct {
pub fn deinit(self: *Connection) void {
self.disconnect() catch |err| {std.log.warn("Error while disconnecting: {s}", .{@errorName(err)});};
self.manager.finishCurrentReceive(); // Wait until all currently received packets are done.
for(self.unconfirmedPackets.items) |packet| {
self.allocator.free(packet.data);
}
self.unconfirmedPackets.deinit();
self.receivedPackets[0].deinit();
self.receivedPackets[1].deinit();

View File

@ -13,7 +13,10 @@ const Mat4f = vec.Mat4f;
const game = @import("game.zig");
const World = game.World;
const chunk = @import("chunk.zig");
const Window = @import("main.zig").Window;
const main = @import("main.zig");
const network = @import("network.zig");
const settings = @import("settings.zig");
const Window = main.Window;
/// The number of milliseconds after which no more chunk meshes are created. This allows the game to run smoother on movement.
const maximumMeshTime = 12;
@ -139,7 +142,7 @@ pub fn updateViewport(width: u31, height: u31, fov: f32) void {
buffers.updateBufferSize(width, height);
}
pub fn render(playerPosition: Vec3d) void {
pub fn render(playerPosition: Vec3d) !void {
var startTime = std.time.milliTimestamp();
// TODO: BlockMeshes.loadMeshes(); // Loads all meshes that weren't loaded yet
// if (Cubyz.player != null) {
@ -188,18 +191,11 @@ pub fn render(playerPosition: Vec3d) void {
// // Set intensity:
// light.setDirection(light.getDirection().mul(0.1f*Cubyz.world.getGlobalLighting()/light.getDirection().length()));
// Window.setClearColor(clearColor);
renderWorld(world, Vec3f{.x=0, .y=0, .z=0}, Vec3f{.x=0, .y=1, .z=0.5}, playerPosition);
// TODO:
_ = startTime;
// // Update meshes:
// do { // A do while loop is used so even when the framerate is low at least one mesh gets updated per frame.
// ChunkMesh mesh = Meshes.getNextQueuedMesh();
// if (mesh == null) break;
// mesh.regenerateMesh();
// } while (System.currentTimeMillis() - startTime <= maximumMeshTime);
try renderWorld(world, Vec3f{.x=1, .y=1, .z=1}, Vec3f{.x=0.3, .y=-1, .z=0.5}, playerPosition);
try RenderOctree.updateMeshes(startTime + maximumMeshTime);
} else {
// clearColor.y = clearColor.z = 0.7f;
// clearColor.x = 0.1f;
// clearColor.x = 0.1f;@import("main.zig")
//
// Window.setClearColor(clearColor);
//
@ -209,9 +205,8 @@ pub fn render(playerPosition: Vec3d) void {
// Keyboard.release(); // TODO: Why is this called in the render thread???
}
pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, playerPosition: Vec3d) void {
pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, playerPos: Vec3d) !void {
_ = world;
_ = playerPosition; // TODO
buffers.bind();
buffers.clearAndBind(Vec4f{.x=0, .y=1, .z=0.5, .w=1});
// TODO:// Clean up old chunk meshes:
@ -228,7 +223,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
const waterFog = Fog{.active=true, .color=.{.x=0.0, .y=0.1, .z=0.2}, .density=0.1};
// Update the uniforms. The uniforms are needed to render the replacement meshes.
chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, directionalLight, time);
chunk.meshing.bindShaderAndUniforms(game.lodProjectionMatrix, ambientLight, directionalLight, time);
//TODO: NormalChunkMesh.bindShader(ambientLight, directionalLight.getDirection(), time);
@ -246,6 +241,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// SimpleList<NormalChunkMesh> visibleChunks = new SimpleList<NormalChunkMesh>(new NormalChunkMesh[64]);
// 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, &meshes);
// for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
// if (mesh instanceof NormalChunkMesh) {
// visibleChunks.add((NormalChunkMesh)mesh);
@ -270,6 +268,10 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
c.glUniform3fv(chunk.meshing.uniforms.waterFog_color, 1, @ptrCast([*c]f32, &waterFog.color));
c.glUniform1f(chunk.meshing.uniforms.waterFog_density, waterFog.density);
for(meshes.items) |mesh| {
mesh.render(playerPos);
}
// for(int i = 0; i < visibleReduced.size; i++) {
// ReducedChunkMesh mesh = visibleReduced.array[i];
// mesh.render(playerPosition);
@ -391,4 +393,319 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f,
// }
// return output;
// }
//}
//}
pub const RenderOctree = 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,
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, 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, meshes);
}
} else {
// TODO: if(testFrustum(frustumInt, x0, y0, z0)) {
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));
// }
};
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 updatableList: std.ArrayList(*chunk.ChunkVisibilityData) = undefined;
var lastRD: i32 = 0;
var lastFactor: f32 = 0;
var mutex = std.Thread.Mutex{};
pub fn init() !void {
lastRD = 0;
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);
}
pub fn deinit() void {
var iterator = roots.valueIterator();
while(iterator.next()) |value| {
value.*.deinit();
}
roots.deinit();
for(updatableList.items) |updatable| {
updatable.visibles.deinit();
allocator.destroy(updatable);
}
updatableList.deinit();
game.blockPalette.deinit();
if(gpa.deinit()) {
@panic("Memory leak");
}
}
pub fn update(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32) !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);
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.threadAllocator);
defer meshRequests.deinit();
var x = minX;
std.log.info("In the thing to request:{} {}", .{minX, maxX});
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;
}
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;
}
}
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) {
std.log.info("Generating mesh.", .{});
const mesh = updatableList.pop();
const nullNode = findNode(mesh.pos);
if(nullNode) |node| {
node.mutex.lock();
defer node.mutex.unlock();
try node.mesh.regenerateMesh(mesh);
}
mesh.visibles.deinit();
allocator.destroy(mesh);
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
}
}
pub fn updateChunkMesh(mesh: *chunk.ChunkVisibilityData) !void {
mutex.lock();
defer mutex.unlock();
try updatableList.append(mesh);
}
pub fn getRenderChunks(playerPos: Vec3d, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void {
mutex.lock();
defer mutex.unlock();
var iterator = roots.valueIterator();
while(iterator.next()) |node| {
try node.*.getChunks(playerPos, meshes);
}
}
// TODO:
// public void updateChunkMesh(VisibleChunk mesh) {
// OctTreeNode node = findNode(mesh);
// if (node != null) {
// ((NormalChunkMesh)node.mesh).updateChunk(mesh);
// }
// }
};

View File

@ -3,4 +3,6 @@
pub const defaultPort: u16 = 47649;
pub const connectionTimeout = 60000;
pub const version = "0.12.0";
pub const version = "0.12.0";
pub const highestLOD: u5 = 5;

View File

@ -4,8 +4,24 @@ const Allocator = std.mem.Allocator;
const main = @import("main.zig");
pub const Compression = struct {
pub fn deflate(allocator: Allocator, data: []const u8) ![]u8 {
var result = std.ArrayList(u8).init(allocator);
var comp = try std.compress.deflate.compressor(main.threadAllocator, result.writer(), .{.level = .default_compression});
try comp.write(data);
try comp.close();
comp.deinit();
return result.toOwnedSlice();
}
pub fn inflate(allocator: Allocator, data: []const u8) ![]u8 {
var stream = std.io.fixedBufferStream(data);
var decomp = try std.compress.deflate.decompressor(main.threadAllocator, stream.reader(), null);
defer decomp.deinit();
return try decomp.reader().readAllAlloc(allocator, std.math.maxInt(usize));
}
pub fn pack(sourceDir: std.fs.IterableDir, writer: anytype) !void {
var comp = try std.compress.deflate.compressor(main.threadAllocator, writer, .{.level = .no_compression});
var comp = try std.compress.deflate.compressor(main.threadAllocator, writer, .{.level = .default_compression});
defer comp.deinit();
var walker = try sourceDir.walk(main.threadAllocator);
defer walker.deinit();
@ -246,7 +262,7 @@ pub const ThreadPool = struct {
fn run(self: ThreadPool) void {
// In case any of the tasks wants to allocate memory:
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){};
main.threadAllocator = gpa.allocator();
defer if(gpa.deinit()) {
@panic("Memory leak");