mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 11:17:05 -04:00
Add the chunk data structure for rendering and render chunks received from the network.
This commit is contained in:
parent
307044a4ed
commit
e6659675b2
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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) -> {
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
118
src/chunk.zig
118
src/chunk.zig
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
36
src/main.zig
36
src/main.zig
@ -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});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
156
src/network.zig
156
src/network.zig
@ -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();
|
||||
|
349
src/renderer.zig
349
src/renderer.zig
@ -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);
|
||||
// }
|
||||
// }
|
||||
};
|
||||
|
@ -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;
|
@ -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");
|
||||
|
Loading…
x
Reference in New Issue
Block a user