mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-09-08 19:50:23 -04:00
897 lines
34 KiB
Zig
897 lines
34 KiB
Zig
const std = @import("std");
|
|
|
|
const blocks = @import("blocks.zig");
|
|
const entity = @import("entity.zig");
|
|
const graphics = @import("graphics.zig");
|
|
const c = graphics.c;
|
|
const Fog = graphics.Fog;
|
|
const Shader = graphics.Shader;
|
|
const vec = @import("vec.zig");
|
|
const Vec3f = vec.Vec3f;
|
|
const Vec3d = vec.Vec3d;
|
|
const Vec4f = vec.Vec4f;
|
|
const Mat4f = vec.Mat4f;
|
|
const game = @import("game.zig");
|
|
const World = game.World;
|
|
const chunk = @import("chunk.zig");
|
|
const main = @import("main.zig");
|
|
const network = @import("network.zig");
|
|
const settings = @import("settings.zig");
|
|
const utils = @import("utils.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;
|
|
const zNear = 0.1;
|
|
const zFar = 10000.0;
|
|
const zNearLOD = 2.0;
|
|
const zFarLOD = 65536.0;
|
|
|
|
var fogShader: graphics.Shader = undefined;
|
|
var fogUniforms: struct {
|
|
fog_activ: c_int,
|
|
fog_color: c_int,
|
|
fog_density: c_int,
|
|
position: c_int,
|
|
color: c_int,
|
|
} = undefined;
|
|
var deferredRenderPassShader: graphics.Shader = undefined;
|
|
var deferredUniforms: struct {
|
|
position: c_int,
|
|
color: c_int,
|
|
} = undefined;
|
|
|
|
pub fn init() !void {
|
|
fogShader = try Shader.create("assets/cubyz/shaders/fog_vertex.vs", "assets/cubyz/shaders/fog_fragment.fs");
|
|
fogUniforms = fogShader.bulkGetUniformLocation(@TypeOf(fogUniforms));
|
|
deferredRenderPassShader = try Shader.create("assets/cubyz/shaders/deferred_render_pass.vs", "assets/cubyz/shaders/deferred_render_pass.fs");
|
|
deferredUniforms = deferredRenderPassShader.bulkGetUniformLocation(@TypeOf(deferredUniforms));
|
|
buffers.init();
|
|
try Bloom.init();
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
fogShader.delete();
|
|
deferredRenderPassShader.delete();
|
|
buffers.deinit();
|
|
Bloom.deinit();
|
|
}
|
|
|
|
const buffers = struct {
|
|
var buffer: c_uint = undefined;
|
|
var colorTexture: c_uint = undefined;
|
|
var positionTexture: c_uint = undefined;
|
|
var depthBuffer: c_uint = undefined;
|
|
fn init() void {
|
|
c.glGenFramebuffers(1, &buffer);
|
|
c.glGenRenderbuffers(1, &depthBuffer);
|
|
c.glGenTextures(1, &colorTexture);
|
|
c.glGenTextures(1, &positionTexture);
|
|
|
|
updateBufferSize(Window.width, Window.height);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
|
|
|
|
c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT0, c.GL_TEXTURE_2D, colorTexture, 0);
|
|
c.glFramebufferTexture2D(c.GL_FRAMEBUFFER, c.GL_COLOR_ATTACHMENT1, c.GL_TEXTURE_2D, positionTexture, 0);
|
|
|
|
c.glFramebufferRenderbuffer(c.GL_FRAMEBUFFER, c.GL_DEPTH_STENCIL_ATTACHMENT, c.GL_RENDERBUFFER, depthBuffer);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
fn deinit() void {
|
|
c.glDeleteFramebuffers(1, &buffer);
|
|
c.glDeleteRenderbuffers(1, &depthBuffer);
|
|
c.glDeleteTextures(1, &colorTexture);
|
|
c.glDeleteTextures(1, &positionTexture);
|
|
}
|
|
|
|
fn regenTexture(texture: c_uint, internalFormat: c_int, format: c_uint, width: u31, height: u31) void {
|
|
c.glBindTexture(c.GL_TEXTURE_2D, texture);
|
|
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MIN_FILTER, c.GL_NEAREST);
|
|
c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAG_FILTER, c.GL_NEAREST);
|
|
c.glTexImage2D(c.GL_TEXTURE_2D, 0, internalFormat, width, height, 0, format, c.GL_UNSIGNED_BYTE, null);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
fn updateBufferSize(width: u31, height: u31) void {
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
|
|
|
|
regenTexture(colorTexture, c.GL_RGB10_A2, c.GL_RGB, width, height);
|
|
regenTexture(positionTexture, c.GL_RGB16F, c.GL_RGB, width, height);
|
|
|
|
c.glBindRenderbuffer(c.GL_RENDERBUFFER, depthBuffer);
|
|
c.glRenderbufferStorage(c.GL_RENDERBUFFER, c.GL_DEPTH24_STENCIL8, width, height);
|
|
c.glBindRenderbuffer(c.GL_RENDERBUFFER, 0);
|
|
|
|
const attachments = [_]c_uint{c.GL_COLOR_ATTACHMENT0, c.GL_COLOR_ATTACHMENT1};
|
|
c.glDrawBuffers(attachments.len, &attachments);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
fn bindTextures() void {
|
|
c.glActiveTexture(c.GL_TEXTURE3);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, colorTexture);
|
|
c.glActiveTexture(c.GL_TEXTURE4);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, positionTexture);
|
|
}
|
|
|
|
fn bind() void {
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
|
|
}
|
|
|
|
fn unbind() void {
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
}
|
|
|
|
fn clearAndBind(clearColor: Vec4f) void {
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
|
|
c.glClearColor(clearColor.x, clearColor.y, clearColor.z, 1);
|
|
c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT);
|
|
// Clears the position separately to prevent issues with default value.
|
|
const positionClearColor = [_]f32 {0, 0, 6.55e4, 1}; // z value corresponds to the highest 16-bit float value.
|
|
c.glClearBufferfv(c.GL_COLOR, 1, &positionClearColor);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, buffer);
|
|
}
|
|
};
|
|
|
|
pub fn updateViewport(width: u31, height: u31, fov: f32) void {
|
|
c.glViewport(0, 0, width, height);
|
|
game.projectionMatrix = Mat4f.perspective(std.math.degreesToRadians(f32, fov), @intToFloat(f32, width)/@intToFloat(f32, height), zNear, zFar);
|
|
game.lodProjectionMatrix = Mat4f.perspective(std.math.degreesToRadians(f32, fov), @intToFloat(f32, width)/@intToFloat(f32, height), zNearLOD, zFarLOD);
|
|
// TODO: Transformation.updateProjectionMatrix(frustumProjectionMatrix, (float)Math.toRadians(fov), width, height, Z_NEAR, Z_FAR_LOD); // Need to combine both for frustum intersection
|
|
buffers.updateBufferSize(width, height);
|
|
}
|
|
|
|
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) {
|
|
// if (Cubyz.playerInc.x != 0 || Cubyz.playerInc.z != 0) { // while walking
|
|
// if (bobbingUp) {
|
|
// playerBobbing += 0.005f;
|
|
// if (playerBobbing >= 0.05f) {
|
|
// bobbingUp = false;
|
|
// }
|
|
// } else {
|
|
// playerBobbing -= 0.005f;
|
|
// if (playerBobbing <= -0.05f) {
|
|
// bobbingUp = true;
|
|
// }
|
|
// }
|
|
// }
|
|
// if (Cubyz.playerInc.y != 0) {
|
|
// Cubyz.player.vy = Cubyz.playerInc.y;
|
|
// }
|
|
// if (Cubyz.playerInc.x != 0) {
|
|
// Cubyz.player.vx = Cubyz.playerInc.x;
|
|
// }
|
|
// playerPosition.y += Player.cameraHeight + playerBobbing;
|
|
// }
|
|
//
|
|
// while (!Cubyz.renderDeque.isEmpty()) {
|
|
// Cubyz.renderDeque.pop().run();
|
|
// }
|
|
if(game.world) |world| {
|
|
// // TODO: Handle colors and sun position in the world.
|
|
var ambient: Vec3f = undefined;
|
|
ambient.x = @max(0.1, world.ambientLight);
|
|
ambient.y = @max(0.1, world.ambientLight);
|
|
ambient.z = @max(0.1, world.ambientLight);
|
|
var skyColor = Vec3f.xyz(world.clearColor);
|
|
game.fog.color = skyColor;
|
|
// TODO:
|
|
// Cubyz.fog.setActive(ClientSettings.FOG_COEFFICIENT != 0);
|
|
// Cubyz.fog.setDensity(1 / (ClientSettings.EFFECTIVE_RENDER_DISTANCE*ClientSettings.FOG_COEFFICIENT));
|
|
skyColor.mulEqualScalar(0.25);
|
|
|
|
try renderWorld(world, ambient, skyColor, playerPosition);
|
|
try RenderStructure.updateMeshes(startTime + maximumMeshTime);
|
|
} else {
|
|
// TODO:
|
|
// clearColor.y = clearColor.z = 0.7f;
|
|
// clearColor.x = 0.1f;@import("main.zig")
|
|
//
|
|
// Window.setClearColor(clearColor);
|
|
//
|
|
// BackgroundScene.renderBackground();
|
|
}
|
|
// Cubyz.gameUI.render();
|
|
// Keyboard.release(); // TODO: Why is this called in the render thread???
|
|
}
|
|
|
|
pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPos: Vec3d) !void {
|
|
_ = world;
|
|
buffers.clearAndBind(Vec4f{.x=skyColor.x, .y=skyColor.y, .z=skyColor.z, .w=1});
|
|
// TODO:// Clean up old chunk meshes:
|
|
// Meshes.cleanUp();
|
|
game.camera.updateViewMatrix();
|
|
|
|
// Uses FrustumCulling on the chunks.
|
|
var frustum = Frustum.init(Vec3f{.x=0, .y=0, .z=0}, game.camera.viewMatrix, settings.fov, zFarLOD, main.Window.width, main.Window.height);
|
|
|
|
const time = @intCast(u32, std.time.milliTimestamp() & std.math.maxInt(u32));
|
|
var 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.lodProjectionMatrix, ambientLight, time);
|
|
|
|
//TODO: NormalChunkMesh.bindShader(ambientLight, directionalLight.getDirection(), time);
|
|
|
|
c.glActiveTexture(c.GL_TEXTURE0);
|
|
blocks.meshes.blockTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE1);
|
|
blocks.meshes.emissionTextureArray.bind();
|
|
|
|
//TODO: BlockInstance selected = null;
|
|
// if (Cubyz.msd.getSelected() instanceof BlockInstance) {
|
|
// selected = (BlockInstance)Cubyz.msd.getSelected();
|
|
// }
|
|
|
|
c.glDepthRange(0, 0.05);
|
|
|
|
// 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 RenderStructure.updateAndGetRenderChunks(game.world.?.conn, playerPos, settings.renderDistance, settings.LODFactor, frustum, &meshes);
|
|
// for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) {
|
|
// if (mesh instanceof NormalChunkMesh) {
|
|
// visibleChunks.add((NormalChunkMesh)mesh);
|
|
//
|
|
// mesh.render(playerPosition);
|
|
// } else if (mesh instanceof ReducedChunkMesh) {
|
|
// visibleReduced.add((ReducedChunkMesh)mesh);
|
|
// }
|
|
// }
|
|
// if(selected != null && !Blocks.transparent(selected.getBlock())) {
|
|
// BlockBreakingRenderer.render(selected, playerPosition);
|
|
c.glActiveTexture(c.GL_TEXTURE0);
|
|
blocks.meshes.blockTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE1);
|
|
blocks.meshes.emissionTextureArray.bind();
|
|
// }
|
|
|
|
// Render the far away ReducedChunks:
|
|
c.glDepthRangef(0.05, 1.0); // ← Used to fix z-fighting.
|
|
chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, time);
|
|
c.glUniform1i(chunk.meshing.uniforms.@"waterFog.activ", if(waterFog.active) 1 else 0);
|
|
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);
|
|
// }
|
|
c.glDepthRangef(0, 0.05);
|
|
|
|
entity.ClientEntityManager.render(game.projectionMatrix, ambientLight, .{.x=1, .y=0.5, .z=0.25}, playerPos);
|
|
|
|
// BlockDropRenderer.render(frustumInt, ambientLight, directionalLight, playerPosition);
|
|
|
|
// // Render transparent chunk meshes:
|
|
// NormalChunkMesh.bindTransparentShader(ambientLight, directionalLight.getDirection(), time);
|
|
|
|
buffers.bindTextures();
|
|
|
|
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_activ, waterFog.isActive());
|
|
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_color, waterFog.getColor());
|
|
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_waterFog_density, waterFog.getDensity());
|
|
|
|
// NormalChunkMesh[] meshes = sortChunks(visibleChunks.toArray(), x0/Chunk.chunkSize - 0.5f, y0/Chunk.chunkSize - 0.5f, z0/Chunk.chunkSize - 0.5f);
|
|
// for (NormalChunkMesh mesh : meshes) {
|
|
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_drawFrontFace, false);
|
|
// glCullFace(GL_FRONT);
|
|
// mesh.renderTransparent(playerPosition);
|
|
|
|
// NormalChunkMesh.transparentShader.setUniform(NormalChunkMesh.TransparentUniforms.loc_drawFrontFace, true);
|
|
// glCullFace(GL_BACK);
|
|
// mesh.renderTransparent(playerPosition);
|
|
// }
|
|
|
|
// if(selected != null && Blocks.transparent(selected.getBlock())) {
|
|
// BlockBreakingRenderer.render(selected, playerPosition);
|
|
// glActiveTexture(GL_TEXTURE0);
|
|
// Meshes.blockTextureArray.bind();
|
|
// glActiveTexture(GL_TEXTURE1);
|
|
// Meshes.emissionTextureArray.bind();
|
|
// }
|
|
|
|
fogShader.bind();
|
|
// Draw the water fog if the player is underwater:
|
|
// Player player = Cubyz.player;
|
|
// int block = Cubyz.world.getBlock((int)Math.round(player.getPosition().x), (int)(player.getPosition().y + player.height), (int)Math.round(player.getPosition().z));
|
|
// if (block != 0 && !Blocks.solid(block)) {
|
|
// if (Blocks.id(block).toString().equals("cubyz:water")) {
|
|
// fogShader.setUniform(FogUniforms.loc_fog_activ, waterFog.isActive());
|
|
// fogShader.setUniform(FogUniforms.loc_fog_color, waterFog.getColor());
|
|
// fogShader.setUniform(FogUniforms.loc_fog_density, waterFog.getDensity());
|
|
// glUniform1i(FogUniforms.loc_color, 3);
|
|
// glUniform1i(FogUniforms.loc_position, 4);
|
|
|
|
// glBindVertexArray(Graphics.rectVAO);
|
|
// glDisable(GL_DEPTH_TEST);
|
|
// glDisable(GL_CULL_FACE);
|
|
// glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
// }
|
|
// }
|
|
if(settings.bloom) {
|
|
Bloom.render(main.Window.width, main.Window.height);
|
|
}
|
|
buffers.unbind();
|
|
buffers.bindTextures();
|
|
deferredRenderPassShader.bind();
|
|
c.glUniform1i(deferredUniforms.color, 3);
|
|
c.glUniform1i(deferredUniforms.position, 4);
|
|
|
|
// if(Window.getRenderTarget() != null)
|
|
// Window.getRenderTarget().bind();
|
|
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDisable(c.GL_DEPTH_TEST);
|
|
c.glDisable(c.GL_CULL_FACE);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
// if(Window.getRenderTarget() != null)
|
|
// Window.getRenderTarget().unbind();
|
|
|
|
//TODO EntityRenderer.renderNames(playerPosition);
|
|
}
|
|
|
|
// private float playerBobbing;
|
|
// private boolean bobbingUp;
|
|
//
|
|
// private Vector3f ambient = new Vector3f();
|
|
// private Vector4f clearColor = new Vector4f(0.1f, 0.7f, 0.7f, 1f);
|
|
// private DirectionalLight light = new DirectionalLight(new Vector3f(1.0f, 1.0f, 1.0f), new Vector3f(0.0f, 1.0f, 0.0f).mul(0.1f));
|
|
//
|
|
// /**
|
|
// * Sorts the chunks based on their distance from the player to reduce complexity when sorting the transparent blocks.
|
|
// * @param toSort
|
|
// * @param playerX
|
|
// * @param playerZ
|
|
// * @return sorted chunk array
|
|
// */
|
|
// public NormalChunkMesh[] sortChunks(NormalChunkMesh[] toSort, double playerX, double playerY, double playerZ) {
|
|
// NormalChunkMesh[] output = new NormalChunkMesh[toSort.length];
|
|
// double[] distances = new double[toSort.length];
|
|
// System.arraycopy(toSort, 0, output, 0, toSort.length);
|
|
// for(int i = 0; i < output.length; i++) {
|
|
// distances[i] = (playerX - output[i].wx)*(playerX - output[i].wx) + (playerY - output[i].wy)*(playerY - output[i].wy) + (playerZ - output[i].wz)*(playerZ - output[i].wz);
|
|
// }
|
|
// // Insert sort them:
|
|
// for(int i = 1; i < output.length; i++) {
|
|
// for(int j = i-1; j >= 0; j--) {
|
|
// if (distances[j] < distances[j+1]) {
|
|
// // Swap them:
|
|
// distances[j] += distances[j+1];
|
|
// distances[j+1] = distances[j] - distances[j+1];
|
|
// distances[j] -= distances[j+1];
|
|
// NormalChunkMesh local = output[j+1];
|
|
// output[j+1] = output[j];
|
|
// output[j] = local;
|
|
// } else {
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// return output;
|
|
// }
|
|
//}
|
|
|
|
const Bloom = struct {
|
|
var buffer1: graphics.FrameBuffer = undefined;
|
|
var buffer2: graphics.FrameBuffer = undefined;
|
|
var extractedBuffer: graphics.FrameBuffer = undefined;
|
|
var width: u31 = std.math.maxInt(u31);
|
|
var height: u31 = std.math.maxInt(u31);
|
|
var firstPassShader: graphics.Shader = undefined;
|
|
var secondPassShader: graphics.Shader = undefined;
|
|
var colorExtractShader: graphics.Shader = undefined;
|
|
var scaleShader: graphics.Shader = undefined;
|
|
|
|
pub fn init() !void {
|
|
buffer1.init(false);
|
|
buffer2.init(false);
|
|
extractedBuffer.init(false);
|
|
firstPassShader = try graphics.Shader.create("assets/cubyz/shaders/bloom/first_pass.vs", "assets/cubyz/shaders/bloom/first_pass.fs");
|
|
secondPassShader = try graphics.Shader.create("assets/cubyz/shaders/bloom/second_pass.vs", "assets/cubyz/shaders/bloom/second_pass.fs");
|
|
colorExtractShader = try graphics.Shader.create("assets/cubyz/shaders/bloom/color_extractor.vs", "assets/cubyz/shaders/bloom/color_extractor.fs");
|
|
scaleShader = try graphics.Shader.create("assets/cubyz/shaders/bloom/scale.vs", "assets/cubyz/shaders/bloom/scale.fs");
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
buffer1.deinit();
|
|
buffer2.deinit();
|
|
extractedBuffer.deinit();
|
|
firstPassShader.delete();
|
|
secondPassShader.delete();
|
|
colorExtractShader.delete();
|
|
scaleShader.delete();
|
|
}
|
|
|
|
fn extractImageData() void {
|
|
colorExtractShader.bind();
|
|
buffers.bindTextures();
|
|
extractedBuffer.bind();
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn downscale() void {
|
|
scaleShader.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE3);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, extractedBuffer.texture);
|
|
buffer1.bind();
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn firstPass() void {
|
|
firstPassShader.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE3);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, buffer1.texture);
|
|
buffer2.bind();
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn secondPass() void {
|
|
secondPassShader.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE3);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, buffer2.texture);
|
|
buffer1.bind();
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn upscale() void {
|
|
scaleShader.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE3);
|
|
c.glBindTexture(c.GL_TEXTURE_2D, buffer1.texture);
|
|
buffers.bind();
|
|
c.glBindVertexArray(graphics.Draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
pub fn render(currentWidth: u31, currentHeight: u31) void {
|
|
if(width != currentWidth or height != currentHeight) {
|
|
width = currentWidth;
|
|
height = currentHeight;
|
|
buffer1.updateSize(width/2, height/2, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
std.debug.assert(buffer1.validate());
|
|
buffer2.updateSize(width/2, height/2, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
std.debug.assert(buffer2.validate());
|
|
extractedBuffer.updateSize(width, height, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
std.debug.assert(extractedBuffer.validate());
|
|
}
|
|
c.glDisable(c.GL_DEPTH_TEST);
|
|
c.glDisable(c.GL_CULL_FACE);
|
|
|
|
extractImageData();
|
|
c.glViewport(0, 0, width/2, height/2);
|
|
downscale();
|
|
firstPass();
|
|
secondPass();
|
|
c.glViewport(0, 0, width, height);
|
|
c.glBlendFunc(c.GL_ONE, c.GL_ONE);
|
|
upscale();
|
|
|
|
c.glEnable(c.GL_DEPTH_TEST);
|
|
c.glEnable(c.GL_CULL_FACE);
|
|
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
};
|
|
|
|
pub const Frustum = struct {
|
|
const Plane = struct {
|
|
pos: Vec3f,
|
|
norm: Vec3f,
|
|
};
|
|
planes: [5]Plane, // Who cares about the near plane anyways?
|
|
|
|
pub fn init(cameraPos: Vec3f, rotationMatrix: Mat4f, fovY: f32, _zFar: f32, width: u31, height: u31) Frustum {
|
|
var invRotationMatrix = rotationMatrix.transpose();
|
|
var cameraDir = Vec3f.xyz(invRotationMatrix.mulVec(Vec4f{.x=0, .y=0, .z=1, .w=1}));
|
|
var cameraUp = Vec3f.xyz(invRotationMatrix.mulVec(Vec4f{.x=0, .y=1, .z=0, .w=1}));
|
|
var cameraRight = Vec3f.xyz(invRotationMatrix.mulVec(Vec4f{.x=1, .y=0, .z=0, .w=1}));
|
|
|
|
const halfVSide = _zFar*std.math.tan(std.math.degreesToRadians(f32, fovY)*0.5);
|
|
const halfHSide = halfVSide*@intToFloat(f32, width)/@intToFloat(f32, height);
|
|
const frontMultFar = cameraDir.mulScalar(_zFar);
|
|
|
|
var self: Frustum = undefined;
|
|
self.planes[0] = Plane{.pos = cameraPos.add(frontMultFar), .norm=cameraDir.mulScalar(-1)}; // far
|
|
self.planes[1] = Plane{.pos = cameraPos, .norm=cameraUp.cross(frontMultFar.add(cameraRight.mulScalar(halfHSide)))}; // right
|
|
self.planes[2] = Plane{.pos = cameraPos, .norm=frontMultFar.sub(cameraRight.mulScalar(halfHSide)).cross(cameraUp)}; // left
|
|
self.planes[3] = Plane{.pos = cameraPos, .norm=cameraRight.cross(frontMultFar.sub(cameraUp.mulScalar(halfVSide)))}; // top
|
|
self.planes[4] = Plane{.pos = cameraPos, .norm=frontMultFar.add(cameraUp.mulScalar(halfVSide)).cross(cameraRight)}; // bottom
|
|
return self;
|
|
}
|
|
|
|
pub fn testAAB(self: Frustum, pos: Vec3f, dim: Vec3f) bool {
|
|
inline for(self.planes) |plane| {
|
|
var dist: f32 = pos.sub(plane.pos).dot(plane.norm);
|
|
// Find the most positive corner:
|
|
dist += @max(0, dim.x*plane.norm.x);
|
|
dist += @max(0, dim.y*plane.norm.y);
|
|
dist += @max(0, dim.z*plane.norm.z);
|
|
if(dist < 128) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
pub const RenderStructure = struct {
|
|
pub var allocator: std.mem.Allocator = undefined;
|
|
var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined;
|
|
|
|
const ChunkMeshNode = struct {
|
|
mesh: chunk.meshing.ChunkMesh,
|
|
shouldBeRemoved: bool, // Internal use.
|
|
drawableChildren: u32, // How many children can be renderer. If this is 8 then there is no need to render this mesh.
|
|
};
|
|
var storageLists: [settings.highestLOD + 1][]?*ChunkMeshNode = undefined;
|
|
var updatableList: std.ArrayList(chunk.ChunkPosition) = undefined;
|
|
var clearList: std.ArrayList(*ChunkMeshNode) = undefined;
|
|
var lastRD: i32 = 0;
|
|
var lastFactor: f32 = 0;
|
|
var lastX: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
|
var lastY: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
|
var lastZ: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
|
var lastSize: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
|
|
var lodMutex: [settings.highestLOD + 1]std.Thread.Mutex = [_]std.Thread.Mutex{std.Thread.Mutex{}} ** (settings.highestLOD + 1);
|
|
var mutex = std.Thread.Mutex{};
|
|
var blockUpdateMutex = std.Thread.Mutex{};
|
|
const BlockUpdate = struct {
|
|
x: chunk.ChunkCoordinate,
|
|
y: chunk.ChunkCoordinate,
|
|
z: chunk.ChunkCoordinate,
|
|
newBlock: blocks.Block,
|
|
};
|
|
var blockUpdateList: std.ArrayList(BlockUpdate) = undefined;
|
|
|
|
pub fn init() !void {
|
|
lastRD = 0;
|
|
lastFactor = 0;
|
|
gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
allocator = gpa.allocator();
|
|
updatableList = std.ArrayList(chunk.ChunkPosition).init(allocator);
|
|
blockUpdateList = std.ArrayList(BlockUpdate).init(allocator);
|
|
clearList = std.ArrayList(*ChunkMeshNode).init(allocator);
|
|
for(storageLists) |*storageList| {
|
|
storageList.* = try allocator.alloc(?*ChunkMeshNode, 0);
|
|
}
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
for(storageLists) |storageList| {
|
|
for(storageList) |nullChunkMesh| {
|
|
if(nullChunkMesh) |chunkMesh| {
|
|
chunkMesh.mesh.deinit();
|
|
allocator.destroy(chunkMesh);
|
|
}
|
|
}
|
|
allocator.free(storageList);
|
|
}
|
|
updatableList.deinit();
|
|
for(clearList.items) |chunkMesh| {
|
|
chunkMesh.mesh.deinit();
|
|
allocator.destroy(chunkMesh);
|
|
}
|
|
blockUpdateList.deinit();
|
|
clearList.deinit();
|
|
game.world.?.blockPalette.deinit();
|
|
if(gpa.deinit()) {
|
|
@panic("Memory leak");
|
|
}
|
|
}
|
|
|
|
fn _getNode(pos: chunk.ChunkPosition) ?*ChunkMeshNode {
|
|
var lod = std.math.log2_int(chunk.UChunkCoordinate, pos.voxelSize);
|
|
lodMutex[lod].lock();
|
|
defer lodMutex[lod].unlock();
|
|
var xIndex = pos.wx-%(&lastX[lod]).* >> lod+chunk.chunkShift;
|
|
var yIndex = pos.wy-%(&lastY[lod]).* >> lod+chunk.chunkShift;
|
|
var zIndex = pos.wz-%(&lastZ[lod]).* >> lod+chunk.chunkShift;
|
|
if(xIndex < 0 or xIndex >= (&lastSize[lod]).*) return null;
|
|
if(yIndex < 0 or yIndex >= (&lastSize[lod]).*) return null;
|
|
if(zIndex < 0 or zIndex >= (&lastSize[lod]).*) return null;
|
|
var index = (xIndex*(&lastSize[lod]).* + yIndex)*(&lastSize[lod]).* + zIndex;
|
|
return (&storageLists[lod]).*[@intCast(usize, index)]; // TODO: Wait for #12205 to be fixed and remove the weird (&...).* workaround.
|
|
}
|
|
|
|
pub fn getNeighbor(_pos: chunk.ChunkPosition, resolution: chunk.UChunkCoordinate, neighbor: u3) ?*chunk.meshing.ChunkMesh {
|
|
var pos = _pos;
|
|
pos.wx += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relX[neighbor];
|
|
pos.wy += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relY[neighbor];
|
|
pos.wz += pos.voxelSize*chunk.chunkSize*chunk.Neighbors.relZ[neighbor];
|
|
pos.voxelSize = resolution;
|
|
var node = _getNode(pos) orelse return null;
|
|
return &node.mesh;
|
|
}
|
|
|
|
pub fn updateAndGetRenderChunks(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32, frustum: Frustum, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void {
|
|
if(lastRD != renderDistance and lastFactor != LODFactor) {
|
|
try network.Protocols.genericUpdate.sendRenderDistance(conn, renderDistance, LODFactor);
|
|
}
|
|
const px = @floatToInt(chunk.ChunkCoordinate, playerPos.x);
|
|
const py = @floatToInt(chunk.ChunkCoordinate, playerPos.y);
|
|
const pz = @floatToInt(chunk.ChunkCoordinate, playerPos.z);
|
|
|
|
var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.threadAllocator);
|
|
defer meshRequests.deinit();
|
|
|
|
for(storageLists) |_, _lod| {
|
|
const lod = @intCast(u5, _lod);
|
|
var maxRenderDistance = renderDistance*chunk.chunkSize << lod;
|
|
if(lod != 0) maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, maxRenderDistance)*LODFactor));
|
|
var sizeShift = chunk.chunkShift + lod;
|
|
const size = @intCast(chunk.UChunkCoordinate, chunk.chunkSize << lod);
|
|
const mask: chunk.ChunkCoordinate = size - 1;
|
|
const invMask: chunk.ChunkCoordinate = ~mask;
|
|
|
|
const maxSideLength = @intCast(chunk.UChunkCoordinate, @divFloor(2*maxRenderDistance + size-1, size) + 2);
|
|
var newList = try allocator.alloc(?*ChunkMeshNode, maxSideLength*maxSideLength*maxSideLength);
|
|
std.mem.set(?*ChunkMeshNode, newList, null);
|
|
|
|
const startX = size*(@divFloor(px, size) -% maxSideLength/2);
|
|
const startY = size*(@divFloor(py, size) -% maxSideLength/2);
|
|
const startZ = size*(@divFloor(pz, size) -% maxSideLength/2);
|
|
|
|
const minX = px-%maxRenderDistance-%1 & invMask;
|
|
const maxX = px+%maxRenderDistance+%size & invMask;
|
|
var x = minX;
|
|
while(x != maxX): (x +%= size) {
|
|
const xIndex = @divExact(x -% startX, size);
|
|
var deltaX: i64 = std.math.absInt(@as(i64, x +% size/2 -% px)) catch unreachable;
|
|
deltaX = @max(0, deltaX - size/2);
|
|
const maxYRenderDistanceSquare = @as(i64, maxRenderDistance)*@as(i64, maxRenderDistance) - deltaX*deltaX;
|
|
const maxYRenderDistance = @floatToInt(i32, @ceil(@sqrt(@intToFloat(f64, maxYRenderDistanceSquare))));
|
|
|
|
const minY = py-%maxYRenderDistance-%1 & invMask;
|
|
const maxY = py+%maxYRenderDistance+%size & invMask;
|
|
var y = minY;
|
|
while(y != maxY): (y +%= size) {
|
|
const yIndex = @divExact(y -% startY, size);
|
|
var deltaY: i64 = std.math.absInt(@as(i64, y +% size/2 -% py)) catch unreachable;
|
|
deltaY = @max(0, deltaY - size/2);
|
|
const maxZRenderDistanceSquare = @as(i64, maxYRenderDistance)*@as(i64, maxYRenderDistance) - deltaY*deltaY;
|
|
const maxZRenderDistance = @floatToInt(i32, @ceil(@sqrt(@intToFloat(f64, maxZRenderDistanceSquare))));
|
|
|
|
const minZ = pz-%maxZRenderDistance-%1 & invMask;
|
|
const maxZ = pz+%maxZRenderDistance+%size & invMask;
|
|
var z = minZ;
|
|
while(z != maxZ): (z +%= size) {
|
|
const zIndex = @divExact(z -% startZ, size);
|
|
const index = (xIndex*maxSideLength + yIndex)*maxSideLength + zIndex;
|
|
const pos = chunk.ChunkPosition{.wx=x, .wy=y, .wz=z, .voxelSize=@as(chunk.UChunkCoordinate, 1)<<lod};
|
|
var node = _getNode(pos);
|
|
if(node) |_node| {
|
|
_node.shouldBeRemoved = false;
|
|
} else {
|
|
node = try allocator.create(ChunkMeshNode);
|
|
node.?.mesh = chunk.meshing.ChunkMesh.init(allocator, pos);
|
|
node.?.shouldBeRemoved = true; // Might be removed in the next iteration.
|
|
try meshRequests.append(pos);
|
|
}
|
|
if(frustum.testAAB(Vec3f{
|
|
.x = @floatCast(f32, @intToFloat(f64, x) - playerPos.x),
|
|
.y = @floatCast(f32, @intToFloat(f64, y) - playerPos.y),
|
|
.z = @floatCast(f32, @intToFloat(f64, z) - playerPos.z),
|
|
}, Vec3f{
|
|
.x = @intToFloat(f32, size),
|
|
.y = @intToFloat(f32, size),
|
|
.z = @intToFloat(f32, size),
|
|
}) and node.?.mesh.visibilityMask != 0) {
|
|
try meshes.append(&node.?.mesh);
|
|
}
|
|
if(lod+1 != storageLists.len and node.?.mesh.generated) {
|
|
if(_getNode(.{.wx=x, .wy=y, .wz=z, .voxelSize=@as(chunk.UChunkCoordinate, 1)<<(lod+1)})) |parent| {
|
|
const octantIndex = @intCast(u3, (x>>sizeShift & 1) | (y>>sizeShift & 1)<<1 | (z>>sizeShift & 1)<<2);
|
|
parent.mesh.visibilityMask &= ~(@as(u8, 1) << octantIndex);
|
|
}
|
|
}
|
|
node.?.drawableChildren = 0;
|
|
newList[@intCast(usize, index)] = node;
|
|
}
|
|
}
|
|
}
|
|
|
|
var oldList = storageLists[lod];
|
|
{
|
|
lodMutex[lod].lock();
|
|
defer lodMutex[lod].unlock();
|
|
lastX[lod] = startX;
|
|
lastY[lod] = startY;
|
|
lastZ[lod] = startZ;
|
|
lastSize[lod] = maxSideLength;
|
|
storageLists[lod] = newList;
|
|
}
|
|
for(oldList) |nullMesh| {
|
|
if(nullMesh) |mesh| {
|
|
if(mesh.shouldBeRemoved) {
|
|
if(mesh.mesh.pos.voxelSize != 1 << settings.highestLOD) {
|
|
if(_getNode(.{.wx=mesh.mesh.pos.wx, .wy=mesh.mesh.pos.wy, .wz=mesh.mesh.pos.wz, .voxelSize=2*mesh.mesh.pos.voxelSize})) |parent| {
|
|
const octantIndex = @intCast(u3, (mesh.mesh.pos.wx>>sizeShift & 1) | (mesh.mesh.pos.wy>>sizeShift & 1)<<1 | (mesh.mesh.pos.wz>>sizeShift & 1)<<2);
|
|
parent.mesh.visibilityMask |= @as(u8, 1) << octantIndex;
|
|
}
|
|
}
|
|
// Update the neighbors, so we don't get cracks when we look back:
|
|
for(chunk.Neighbors.iterable) |neighbor| {
|
|
if(getNeighbor(mesh.mesh.pos, mesh.mesh.pos.voxelSize, neighbor)) |neighborMesh| {
|
|
if(neighborMesh.generated) {
|
|
neighborMesh.mutex.lock();
|
|
defer neighborMesh.mutex.unlock();
|
|
try neighborMesh.uploadDataAndFinishNeighbors();
|
|
}
|
|
}
|
|
}
|
|
if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing.
|
|
mesh.mesh.mutex.unlock();
|
|
mesh.mesh.deinit();
|
|
allocator.destroy(mesh);
|
|
} else {
|
|
try clearList.append(mesh);
|
|
}
|
|
} else {
|
|
mesh.shouldBeRemoved = true;
|
|
}
|
|
}
|
|
}
|
|
allocator.free(oldList);
|
|
}
|
|
|
|
var i: usize = 0;
|
|
while(i < clearList.items.len) {
|
|
const mesh = clearList.items[i];
|
|
if(mesh.mesh.mutex.tryLock()) { // Make sure there is no task currently running on the thing.
|
|
mesh.mesh.mutex.unlock();
|
|
mesh.mesh.deinit();
|
|
allocator.destroy(mesh);
|
|
_ = clearList.swapRemove(i);
|
|
} else {
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
pub fn updateMeshes(targetTime: i64) !void {
|
|
{ // First of all process all the block updates:
|
|
blockUpdateMutex.lock();
|
|
defer blockUpdateMutex.unlock();
|
|
for(blockUpdateList.items) |blockUpdate| {
|
|
const pos = chunk.ChunkPosition{.wx=blockUpdate.x, .wy=blockUpdate.y, .wz=blockUpdate.z, .voxelSize=1};
|
|
if(_getNode(pos)) |node| {
|
|
try node.mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock);
|
|
}
|
|
}
|
|
blockUpdateList.clearRetainingCapacity();
|
|
}
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
while(updatableList.items.len != 0) {
|
|
// TODO: Find a faster solution than going through the entire list.
|
|
var closestPriority: f32 = -std.math.floatMax(f32);
|
|
var closestIndex: usize = 0;
|
|
const playerPos = game.Player.getPosBlocking();
|
|
for(updatableList.items) |pos, i| {
|
|
const priority = pos.getPriority(playerPos);
|
|
if(priority > closestPriority) {
|
|
closestPriority = priority;
|
|
closestIndex = i;
|
|
}
|
|
}
|
|
const pos = updatableList.orderedRemove(closestIndex);
|
|
mutex.unlock();
|
|
defer mutex.lock();
|
|
const nullNode = _getNode(pos);
|
|
if(nullNode) |node| {
|
|
node.mesh.mutex.lock();
|
|
defer node.mesh.mutex.unlock();
|
|
node.mesh.uploadDataAndFinishNeighbors() catch |err| {
|
|
if(err == error.LODMissing) {
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
try updatableList.append(pos);
|
|
} else {
|
|
return err;
|
|
}
|
|
};
|
|
}
|
|
if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh.
|
|
}
|
|
}
|
|
|
|
pub const MeshGenerationTask = struct {
|
|
mesh: *chunk.Chunk,
|
|
|
|
pub const vtable = utils.ThreadPool.VTable{
|
|
.getPriority = @ptrCast(*const fn(*anyopaque) f32, &getPriority),
|
|
.isStillNeeded = @ptrCast(*const fn(*anyopaque) bool, &isStillNeeded),
|
|
.run = @ptrCast(*const fn(*anyopaque) void, &run),
|
|
.clean = @ptrCast(*const fn(*anyopaque) void, &clean),
|
|
};
|
|
|
|
pub fn schedule(mesh: *chunk.Chunk) !void {
|
|
var task = try allocator.create(MeshGenerationTask);
|
|
task.* = MeshGenerationTask {
|
|
.mesh = mesh,
|
|
};
|
|
try main.threadPool.addTask(task, &vtable);
|
|
}
|
|
|
|
pub fn getPriority(self: *MeshGenerationTask) f32 {
|
|
return self.mesh.pos.getPriority(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time.
|
|
}
|
|
|
|
pub fn isStillNeeded(self: *MeshGenerationTask) bool {
|
|
var distanceSqr = self.mesh.pos.getMinDistanceSquared(game.Player.getPosBlocking()); // TODO: This is called in loop, find a way to do this without calling the mutex every time.
|
|
var maxRenderDistance = settings.renderDistance*chunk.chunkSize*self.mesh.pos.voxelSize;
|
|
if(self.mesh.pos.voxelSize != 1) maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, maxRenderDistance)*settings.LODFactor));
|
|
maxRenderDistance += 2*self.mesh.pos.voxelSize*chunk.chunkSize;
|
|
return distanceSqr < @intToFloat(f64, maxRenderDistance*maxRenderDistance);
|
|
}
|
|
|
|
pub fn run(self: *MeshGenerationTask) void {
|
|
const pos = self.mesh.pos;
|
|
const nullNode = _getNode(pos);
|
|
if(nullNode) |node| {
|
|
{
|
|
node.mesh.mutex.lock();
|
|
defer node.mesh.mutex.unlock();
|
|
node.mesh.regenerateMainMesh(self.mesh) catch |err| {
|
|
std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)});
|
|
if(@errorReturnTrace()) |trace| {
|
|
std.log.err("Trace: {}", .{trace});
|
|
}
|
|
allocator.destroy(self.mesh);
|
|
allocator.destroy(self);
|
|
return;
|
|
};
|
|
}
|
|
mutex.lock();
|
|
defer mutex.unlock();
|
|
updatableList.append(pos) catch |err| {
|
|
std.log.err("Error while regenerating mesh: {s}", .{@errorName(err)});
|
|
if(@errorReturnTrace()) |trace| {
|
|
std.log.err("Trace: {}", .{trace});
|
|
}
|
|
allocator.destroy(self.mesh);
|
|
};
|
|
} else {
|
|
allocator.destroy(self.mesh);
|
|
}
|
|
allocator.destroy(self);
|
|
}
|
|
|
|
pub fn clean(self: *MeshGenerationTask) void {
|
|
allocator.destroy(self.mesh);
|
|
allocator.destroy(self);
|
|
}
|
|
};
|
|
|
|
pub fn updateBlock(x: chunk.ChunkCoordinate, y: chunk.ChunkCoordinate, z: chunk.ChunkCoordinate, newBlock: blocks.Block) !void {
|
|
blockUpdateMutex.lock();
|
|
try blockUpdateList.append(BlockUpdate{.x=x, .y=y, .z=z, .newBlock=newBlock});
|
|
defer blockUpdateMutex.unlock();
|
|
}
|
|
|
|
pub fn updateChunkMesh(mesh: *chunk.Chunk) !void {
|
|
try MeshGenerationTask.schedule(mesh);
|
|
}
|
|
};
|