mirror of
https://github.com/PixelGuys/Cubyz.git
synced 2025-08-03 03:06:55 -04:00

Until we have a proper tool for it #760 it should break the corners in one hit independently of resistance
1160 lines
42 KiB
Zig
1160 lines
42 KiB
Zig
const std = @import("std");
|
|
const Atomic = std.atomic.Value;
|
|
|
|
const blocks = @import("blocks.zig");
|
|
const chunk = @import("chunk.zig");
|
|
const entity = @import("entity.zig");
|
|
const graphics = @import("graphics.zig");
|
|
const particles = @import("particles.zig");
|
|
const c = graphics.c;
|
|
const game = @import("game.zig");
|
|
const World = game.World;
|
|
const itemdrop = @import("itemdrop.zig");
|
|
const main = @import("main");
|
|
const Window = main.Window;
|
|
const models = @import("models.zig");
|
|
const network = @import("network.zig");
|
|
const settings = @import("settings.zig");
|
|
const vec = @import("vec.zig");
|
|
const gpu_performance_measuring = main.gui.windowlist.gpu_performance_measuring;
|
|
const crosshair = main.gui.windowlist.crosshair;
|
|
const Vec2f = vec.Vec2f;
|
|
const Vec3i = vec.Vec3i;
|
|
const Vec3f = vec.Vec3f;
|
|
const Vec3d = vec.Vec3d;
|
|
const Vec4f = vec.Vec4f;
|
|
const Mat4f = vec.Mat4f;
|
|
|
|
pub const chunk_meshing = @import("renderer/chunk_meshing.zig");
|
|
pub const mesh_storage = @import("renderer/mesh_storage.zig");
|
|
|
|
/// The number of milliseconds after which no more chunk meshes are created. This allows the game to run smoother on movement.
|
|
const maximumMeshTime = 12;
|
|
pub const zNear = 0.1;
|
|
pub const zFar = 65536.0; // TODO: Fix z-fighting problems.
|
|
|
|
var deferredRenderPassPipeline: graphics.Pipeline = undefined;
|
|
var deferredUniforms: struct {
|
|
@"fog.color": c_int,
|
|
@"fog.density": c_int,
|
|
@"fog.fogLower": c_int,
|
|
@"fog.fogHigher": c_int,
|
|
tanXY: c_int,
|
|
zNear: c_int,
|
|
zFar: c_int,
|
|
invViewMatrix: c_int,
|
|
playerPositionInteger: c_int,
|
|
playerPositionFraction: c_int,
|
|
} = undefined;
|
|
var fakeReflectionPipeline: graphics.Pipeline = undefined;
|
|
var fakeReflectionUniforms: struct {
|
|
normalVector: c_int,
|
|
upVector: c_int,
|
|
rightVector: c_int,
|
|
frequency: c_int,
|
|
reflectionMapSize: c_int,
|
|
} = undefined;
|
|
|
|
pub var activeFrameBuffer: c_uint = 0;
|
|
|
|
pub const reflectionCubeMapSize = 64;
|
|
var reflectionCubeMap: graphics.CubeMapTexture = undefined;
|
|
|
|
pub fn init() void {
|
|
deferredRenderPassPipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/deferred_render_pass.vert",
|
|
"assets/cubyz/shaders/deferred_render_pass.frag",
|
|
"",
|
|
&deferredUniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
fakeReflectionPipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/fake_reflection.vert",
|
|
"assets/cubyz/shaders/fake_reflection.frag",
|
|
"",
|
|
&fakeReflectionUniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
worldFrameBuffer.init(true, c.GL_NEAREST, c.GL_CLAMP_TO_EDGE);
|
|
worldFrameBuffer.updateSize(Window.width, Window.height, c.GL_RGB16F);
|
|
Bloom.init();
|
|
MeshSelection.init();
|
|
MenuBackGround.init() catch |err| {
|
|
std.log.err("Failed to initialize the Menu Background: {s}", .{@errorName(err)});
|
|
};
|
|
Skybox.init();
|
|
chunk_meshing.init();
|
|
mesh_storage.init();
|
|
reflectionCubeMap = .init();
|
|
reflectionCubeMap.generate(reflectionCubeMapSize, reflectionCubeMapSize);
|
|
initReflectionCubeMap();
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
deferredRenderPassPipeline.deinit();
|
|
fakeReflectionPipeline.deinit();
|
|
worldFrameBuffer.deinit();
|
|
Bloom.deinit();
|
|
MeshSelection.deinit();
|
|
MenuBackGround.deinit();
|
|
Skybox.deinit();
|
|
mesh_storage.deinit();
|
|
chunk_meshing.deinit();
|
|
reflectionCubeMap.deinit();
|
|
}
|
|
|
|
fn initReflectionCubeMap() void {
|
|
c.glViewport(0, 0, reflectionCubeMapSize, reflectionCubeMapSize);
|
|
var framebuffer: graphics.FrameBuffer = undefined;
|
|
framebuffer.init(false, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
defer framebuffer.deinit();
|
|
framebuffer.bind();
|
|
fakeReflectionPipeline.bind(null);
|
|
c.glUniform1f(fakeReflectionUniforms.frequency, 1);
|
|
c.glUniform1f(fakeReflectionUniforms.reflectionMapSize, reflectionCubeMapSize);
|
|
for(0..6) |face| {
|
|
c.glUniform3fv(fakeReflectionUniforms.normalVector, 1, @ptrCast(&graphics.CubeMapTexture.faceNormal(face)));
|
|
c.glUniform3fv(fakeReflectionUniforms.upVector, 1, @ptrCast(&graphics.CubeMapTexture.faceUp(face)));
|
|
c.glUniform3fv(fakeReflectionUniforms.rightVector, 1, @ptrCast(&graphics.CubeMapTexture.faceRight(face)));
|
|
reflectionCubeMap.bindToFramebuffer(framebuffer, @intCast(face));
|
|
c.glBindVertexArray(graphics.draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
}
|
|
|
|
var worldFrameBuffer: graphics.FrameBuffer = undefined;
|
|
|
|
pub var lastWidth: u31 = 0;
|
|
pub var lastHeight: u31 = 0;
|
|
var lastFov: f32 = 0;
|
|
pub fn updateViewport(width: u31, height: u31, fov: f32) void {
|
|
lastWidth = @intFromFloat(@as(f32, @floatFromInt(width))*main.settings.resolutionScale);
|
|
lastHeight = @intFromFloat(@as(f32, @floatFromInt(height))*main.settings.resolutionScale);
|
|
lastFov = fov;
|
|
game.projectionMatrix = Mat4f.perspective(std.math.degreesToRadians(fov), @as(f32, @floatFromInt(lastWidth))/@as(f32, @floatFromInt(lastHeight)), zNear, zFar);
|
|
worldFrameBuffer.updateSize(lastWidth, lastHeight, c.GL_RGB16F);
|
|
worldFrameBuffer.unbind();
|
|
}
|
|
|
|
pub fn render(playerPosition: Vec3d, deltaTime: f64) void {
|
|
// TODO: player bobbing
|
|
if(game.world) |world| {
|
|
// TODO: Handle colors and sun position in the world.
|
|
var ambient: Vec3f = undefined;
|
|
ambient[0] = @max(0.1, world.ambientLight);
|
|
ambient[1] = @max(0.1, world.ambientLight);
|
|
ambient[2] = @max(0.1, world.ambientLight);
|
|
game.fog.skyColor = vec.xyz(world.clearColor);
|
|
|
|
itemdrop.ItemDisplayManager.update(deltaTime);
|
|
renderWorld(world, ambient, Skybox.getSkyColor(), playerPosition);
|
|
const startTime = std.time.milliTimestamp();
|
|
mesh_storage.updateMeshes(startTime + maximumMeshTime);
|
|
} else {
|
|
c.glViewport(0, 0, main.Window.width, main.Window.height);
|
|
MenuBackGround.render();
|
|
}
|
|
}
|
|
|
|
pub fn crosshairDirection(rotationMatrix: Mat4f, fovY: f32, width: u31, height: u31) Vec3f {
|
|
// stolen code from Frustum.init
|
|
const invRotationMatrix = rotationMatrix.transpose();
|
|
const cameraDir = vec.xyz(invRotationMatrix.mulVec(Vec4f{0, 1, 0, 1}));
|
|
const cameraUp = vec.xyz(invRotationMatrix.mulVec(Vec4f{0, 0, 1, 1}));
|
|
const cameraRight = vec.xyz(invRotationMatrix.mulVec(Vec4f{1, 0, 0, 1}));
|
|
|
|
const screenSize = Vec2f{@floatFromInt(width), @floatFromInt(height)};
|
|
const screenCoord = (crosshair.window.pos + crosshair.window.contentSize*Vec2f{0.5, 0.5}*@as(Vec2f, @splat(crosshair.window.scale)))*@as(Vec2f, @splat(main.gui.scale*main.settings.resolutionScale));
|
|
|
|
const halfVSide = std.math.tan(std.math.degreesToRadians(fovY)*0.5);
|
|
const halfHSide = halfVSide*screenSize[0]/screenSize[1];
|
|
const sides = Vec2f{halfHSide, halfVSide};
|
|
|
|
const scale = (Vec2f{-1, 1} + Vec2f{2, -2}*screenCoord/screenSize)*sides;
|
|
const forwards = cameraDir;
|
|
const horizontal = cameraRight*@as(Vec3f, @splat(scale[0]));
|
|
const vertical = cameraUp*@as(Vec3f, @splat(scale[1])); // adjust for y coordinate
|
|
|
|
const adjusted = forwards + horizontal + vertical;
|
|
return adjusted;
|
|
}
|
|
|
|
pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPos: Vec3d) void { // MARK: renderWorld()
|
|
worldFrameBuffer.bind();
|
|
c.glViewport(0, 0, lastWidth, lastHeight);
|
|
gpu_performance_measuring.startQuery(.clear);
|
|
worldFrameBuffer.clear(Vec4f{skyColor[0], skyColor[1], skyColor[2], 1});
|
|
gpu_performance_measuring.stopQuery();
|
|
game.camera.updateViewMatrix();
|
|
|
|
// Uses FrustumCulling on the chunks.
|
|
const frustum = Frustum.init(Vec3f{0, 0, 0}, game.camera.viewMatrix, lastFov, lastWidth, lastHeight);
|
|
|
|
const time: u32 = @intCast(std.time.milliTimestamp() & std.math.maxInt(u32));
|
|
|
|
gpu_performance_measuring.startQuery(.skybox);
|
|
Skybox.render();
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
gpu_performance_measuring.startQuery(.animation);
|
|
blocks.meshes.preProcessAnimationData(time);
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
// Update the uniforms. The uniforms are needed to render the replacement meshes.
|
|
chunk_meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, playerPos);
|
|
|
|
c.glActiveTexture(c.GL_TEXTURE0);
|
|
blocks.meshes.blockTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE1);
|
|
blocks.meshes.emissionTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE2);
|
|
blocks.meshes.reflectivityAndAbsorptionTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE5);
|
|
blocks.meshes.ditherTexture.bind();
|
|
reflectionCubeMap.bindTo(4);
|
|
|
|
chunk_meshing.quadsDrawn = 0;
|
|
chunk_meshing.transparentQuadsDrawn = 0;
|
|
const meshes = mesh_storage.updateAndGetRenderChunks(world.conn, &frustum, playerPos, settings.renderDistance);
|
|
|
|
gpu_performance_measuring.startQuery(.chunk_rendering_preparation);
|
|
const direction = crosshairDirection(game.camera.viewMatrix, lastFov, lastWidth, lastHeight);
|
|
MeshSelection.select(playerPos, direction, game.Player.inventory.getItem(game.Player.selectedSlot));
|
|
|
|
chunk_meshing.beginRender();
|
|
|
|
var chunkLists: [main.settings.highestSupportedLod + 1]main.List(u32) = @splat(main.List(u32).init(main.stackAllocator));
|
|
defer for(chunkLists) |list| list.deinit();
|
|
for(meshes) |mesh| {
|
|
mesh.prepareRendering(&chunkLists);
|
|
}
|
|
gpu_performance_measuring.stopQuery();
|
|
gpu_performance_measuring.startQuery(.chunk_rendering);
|
|
chunk_meshing.drawChunksIndirect(&chunkLists, game.projectionMatrix, ambientLight, playerPos, false);
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
gpu_performance_measuring.startQuery(.entity_rendering);
|
|
entity.ClientEntityManager.render(game.projectionMatrix, ambientLight, playerPos);
|
|
|
|
itemdrop.ItemDropRenderer.renderItemDrops(game.projectionMatrix, ambientLight, playerPos);
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
gpu_performance_measuring.startQuery(.block_entity_rendering);
|
|
main.block_entity.renderAll(game.projectionMatrix, ambientLight, playerPos);
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
gpu_performance_measuring.startQuery(.particle_rendering);
|
|
particles.ParticleSystem.render(game.projectionMatrix, game.camera.viewMatrix, ambientLight);
|
|
gpu_performance_measuring.stopQuery();
|
|
|
|
// Rebind block textures back to their original slots
|
|
c.glActiveTexture(c.GL_TEXTURE0);
|
|
blocks.meshes.blockTextureArray.bind();
|
|
c.glActiveTexture(c.GL_TEXTURE1);
|
|
blocks.meshes.emissionTextureArray.bind();
|
|
|
|
MeshSelection.render(game.projectionMatrix, game.camera.viewMatrix, playerPos);
|
|
|
|
// Render transparent chunk meshes:
|
|
worldFrameBuffer.bindDepthTexture(c.GL_TEXTURE5);
|
|
|
|
gpu_performance_measuring.startQuery(.transparent_rendering_preparation);
|
|
c.glTextureBarrier();
|
|
|
|
{
|
|
for(&chunkLists) |*list| list.clearRetainingCapacity();
|
|
var i: usize = meshes.len;
|
|
while(true) {
|
|
if(i == 0) break;
|
|
i -= 1;
|
|
meshes[i].prepareTransparentRendering(playerPos, &chunkLists);
|
|
}
|
|
gpu_performance_measuring.stopQuery();
|
|
gpu_performance_measuring.startQuery(.transparent_rendering);
|
|
chunk_meshing.drawChunksIndirect(&chunkLists, game.projectionMatrix, ambientLight, playerPos, true);
|
|
gpu_performance_measuring.stopQuery();
|
|
}
|
|
|
|
c.glDepthRange(0, 0.001);
|
|
itemdrop.ItemDropRenderer.renderDisplayItems(ambientLight, playerPos);
|
|
c.glDepthRange(0.001, 1);
|
|
|
|
chunk_meshing.endRender();
|
|
|
|
worldFrameBuffer.bindTexture(c.GL_TEXTURE3);
|
|
|
|
const playerBlock = mesh_storage.getBlockFromAnyLod(@intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
|
|
|
|
if(settings.bloom) {
|
|
Bloom.render(lastWidth, lastHeight, playerBlock, playerPos, game.camera.viewMatrix);
|
|
} else {
|
|
Bloom.bindReplacementImage();
|
|
}
|
|
gpu_performance_measuring.startQuery(.final_copy);
|
|
if(activeFrameBuffer == 0) c.glViewport(0, 0, main.Window.width, main.Window.height);
|
|
worldFrameBuffer.bindTexture(c.GL_TEXTURE3);
|
|
worldFrameBuffer.bindDepthTexture(c.GL_TEXTURE4);
|
|
worldFrameBuffer.unbind();
|
|
deferredRenderPassPipeline.bind(null);
|
|
if(!blocks.meshes.hasFog(playerBlock)) {
|
|
c.glUniform3fv(deferredUniforms.@"fog.color", 1, @ptrCast(&game.fog.skyColor));
|
|
c.glUniform1f(deferredUniforms.@"fog.density", game.fog.density);
|
|
c.glUniform1f(deferredUniforms.@"fog.fogLower", game.fog.fogLower);
|
|
c.glUniform1f(deferredUniforms.@"fog.fogHigher", game.fog.fogHigher);
|
|
} else {
|
|
const fogColor = blocks.meshes.fogColor(playerBlock);
|
|
c.glUniform3f(deferredUniforms.@"fog.color", @as(f32, @floatFromInt(fogColor >> 16 & 255))/255.0, @as(f32, @floatFromInt(fogColor >> 8 & 255))/255.0, @as(f32, @floatFromInt(fogColor >> 0 & 255))/255.0);
|
|
c.glUniform1f(deferredUniforms.@"fog.density", blocks.meshes.fogDensity(playerBlock));
|
|
c.glUniform1f(deferredUniforms.@"fog.fogLower", 1e10);
|
|
c.glUniform1f(deferredUniforms.@"fog.fogHigher", 1e10);
|
|
}
|
|
c.glUniformMatrix4fv(deferredUniforms.invViewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix.transpose()));
|
|
c.glUniform3i(deferredUniforms.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
|
|
c.glUniform3f(deferredUniforms.playerPositionFraction, @floatCast(@mod(playerPos[0], 1)), @floatCast(@mod(playerPos[1], 1)), @floatCast(@mod(playerPos[2], 1)));
|
|
c.glUniform1f(deferredUniforms.zNear, zNear);
|
|
c.glUniform1f(deferredUniforms.zFar, zFar);
|
|
c.glUniform2f(deferredUniforms.tanXY, 1.0/game.projectionMatrix.rows[0][0], 1.0/game.projectionMatrix.rows[1][2]);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, activeFrameBuffer);
|
|
|
|
c.glBindVertexArray(graphics.draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
|
|
entity.ClientEntityManager.renderNames(game.projectionMatrix, playerPos);
|
|
gpu_performance_measuring.stopQuery();
|
|
}
|
|
|
|
const Bloom = struct { // MARK: Bloom
|
|
var buffer1: graphics.FrameBuffer = undefined;
|
|
var buffer2: graphics.FrameBuffer = undefined;
|
|
var emptyBuffer: graphics.Texture = undefined;
|
|
var width: u31 = std.math.maxInt(u31);
|
|
var height: u31 = std.math.maxInt(u31);
|
|
var firstPassPipeline: graphics.Pipeline = undefined;
|
|
var secondPassPipeline: graphics.Pipeline = undefined;
|
|
var colorExtractAndDownsamplePipeline: graphics.Pipeline = undefined;
|
|
var colorExtractUniforms: struct {
|
|
zNear: c_int,
|
|
zFar: c_int,
|
|
tanXY: c_int,
|
|
@"fog.color": c_int,
|
|
@"fog.density": c_int,
|
|
@"fog.fogLower": c_int,
|
|
@"fog.fogHigher": c_int,
|
|
invViewMatrix: c_int,
|
|
playerPositionInteger: c_int,
|
|
playerPositionFraction: c_int,
|
|
} = undefined;
|
|
|
|
pub fn init() void {
|
|
buffer1.init(false, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
buffer2.init(false, c.GL_LINEAR, c.GL_CLAMP_TO_EDGE);
|
|
emptyBuffer = .init();
|
|
emptyBuffer.generate(graphics.Image.emptyImage);
|
|
firstPassPipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/bloom/first_pass.vert",
|
|
"assets/cubyz/shaders/bloom/first_pass.frag",
|
|
"",
|
|
null,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
secondPassPipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/bloom/second_pass.vert",
|
|
"assets/cubyz/shaders/bloom/second_pass.frag",
|
|
"",
|
|
null,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
colorExtractAndDownsamplePipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/bloom/color_extractor_downsample.vert",
|
|
"assets/cubyz/shaders/bloom/color_extractor_downsample.frag",
|
|
"",
|
|
&colorExtractUniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
buffer1.deinit();
|
|
buffer2.deinit();
|
|
firstPassPipeline.deinit();
|
|
secondPassPipeline.deinit();
|
|
colorExtractAndDownsamplePipeline.deinit();
|
|
}
|
|
|
|
fn extractImageDataAndDownsample(playerBlock: blocks.Block, playerPos: Vec3d, viewMatrix: Mat4f) void {
|
|
colorExtractAndDownsamplePipeline.bind(null);
|
|
worldFrameBuffer.bindTexture(c.GL_TEXTURE3);
|
|
worldFrameBuffer.bindDepthTexture(c.GL_TEXTURE4);
|
|
buffer1.bind();
|
|
if(!blocks.meshes.hasFog(playerBlock)) {
|
|
c.glUniform3fv(colorExtractUniforms.@"fog.color", 1, @ptrCast(&game.fog.skyColor));
|
|
c.glUniform1f(colorExtractUniforms.@"fog.density", game.fog.density);
|
|
c.glUniform1f(colorExtractUniforms.@"fog.fogLower", game.fog.fogLower);
|
|
c.glUniform1f(colorExtractUniforms.@"fog.fogHigher", game.fog.fogHigher);
|
|
} else {
|
|
const fogColor = blocks.meshes.fogColor(playerBlock);
|
|
c.glUniform3f(colorExtractUniforms.@"fog.color", @as(f32, @floatFromInt(fogColor >> 16 & 255))/255.0, @as(f32, @floatFromInt(fogColor >> 8 & 255))/255.0, @as(f32, @floatFromInt(fogColor >> 0 & 255))/255.0);
|
|
c.glUniform1f(colorExtractUniforms.@"fog.density", blocks.meshes.fogDensity(playerBlock));
|
|
c.glUniform1f(colorExtractUniforms.@"fog.fogLower", 1e10);
|
|
c.glUniform1f(colorExtractUniforms.@"fog.fogHigher", 1e10);
|
|
}
|
|
|
|
c.glUniformMatrix4fv(colorExtractUniforms.invViewMatrix, 1, c.GL_TRUE, @ptrCast(&viewMatrix.transpose()));
|
|
c.glUniform3i(colorExtractUniforms.playerPositionInteger, @intFromFloat(@floor(playerPos[0])), @intFromFloat(@floor(playerPos[1])), @intFromFloat(@floor(playerPos[2])));
|
|
c.glUniform3f(colorExtractUniforms.playerPositionFraction, @floatCast(@mod(playerPos[0], 1)), @floatCast(@mod(playerPos[1], 1)), @floatCast(@mod(playerPos[2], 1)));
|
|
c.glUniform1f(colorExtractUniforms.zNear, zNear);
|
|
c.glUniform1f(colorExtractUniforms.zFar, zFar);
|
|
c.glUniform2f(colorExtractUniforms.tanXY, 1.0/game.projectionMatrix.rows[0][0], 1.0/game.projectionMatrix.rows[1][2]);
|
|
c.glBindVertexArray(graphics.draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn firstPass() void {
|
|
firstPassPipeline.bind(null);
|
|
buffer1.bindTexture(c.GL_TEXTURE3);
|
|
buffer2.bind();
|
|
c.glBindVertexArray(graphics.draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn secondPass() void {
|
|
secondPassPipeline.bind(null);
|
|
buffer2.bindTexture(c.GL_TEXTURE3);
|
|
buffer1.bind();
|
|
c.glBindVertexArray(graphics.draw.rectVAO);
|
|
c.glDrawArrays(c.GL_TRIANGLE_STRIP, 0, 4);
|
|
}
|
|
|
|
fn render(currentWidth: u31, currentHeight: u31, playerBlock: blocks.Block, playerPos: Vec3d, viewMatrix: Mat4f) void {
|
|
if(width != currentWidth or height != currentHeight) {
|
|
width = currentWidth;
|
|
height = currentHeight;
|
|
buffer1.updateSize(width/4, height/4, c.GL_R11F_G11F_B10F);
|
|
std.debug.assert(buffer1.validate());
|
|
buffer2.updateSize(width/4, height/4, c.GL_R11F_G11F_B10F);
|
|
std.debug.assert(buffer2.validate());
|
|
}
|
|
gpu_performance_measuring.startQuery(.bloom_extract_downsample);
|
|
|
|
c.glViewport(0, 0, width/4, height/4);
|
|
extractImageDataAndDownsample(playerBlock, playerPos, viewMatrix);
|
|
gpu_performance_measuring.stopQuery();
|
|
gpu_performance_measuring.startQuery(.bloom_first_pass);
|
|
firstPass();
|
|
gpu_performance_measuring.stopQuery();
|
|
gpu_performance_measuring.startQuery(.bloom_second_pass);
|
|
secondPass();
|
|
|
|
c.glViewport(0, 0, width, height);
|
|
buffer1.bindTexture(c.GL_TEXTURE5);
|
|
|
|
gpu_performance_measuring.stopQuery();
|
|
}
|
|
|
|
fn bindReplacementImage() void {
|
|
emptyBuffer.bindTo(5);
|
|
}
|
|
};
|
|
|
|
pub const MenuBackGround = struct {
|
|
var pipeline: graphics.Pipeline = undefined;
|
|
var uniforms: struct {
|
|
viewMatrix: c_int,
|
|
projectionMatrix: c_int,
|
|
} = undefined;
|
|
|
|
var vao: c_uint = undefined;
|
|
var vbos: [2]c_uint = undefined;
|
|
var texture: graphics.Texture = undefined;
|
|
|
|
var angle: f32 = 0;
|
|
var lastTime: i128 = undefined;
|
|
|
|
fn init() !void {
|
|
lastTime = std.time.nanoTimestamp();
|
|
pipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/background/vertex.vert",
|
|
"assets/cubyz/shaders/background/fragment.frag",
|
|
"",
|
|
&uniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.noBlending}},
|
|
);
|
|
// 4 sides of a simple cube with some panorama texture on it.
|
|
const rawData = [_]f32{
|
|
-1, 1, -1, 1, 1,
|
|
-1, 1, 1, 1, 0,
|
|
-1, -1, -1, 0.75, 1,
|
|
-1, -1, 1, 0.75, 0,
|
|
1, -1, -1, 0.5, 1,
|
|
1, -1, 1, 0.5, 0,
|
|
1, 1, -1, 0.25, 1,
|
|
1, 1, 1, 0.25, 0,
|
|
-1, 1, -1, 0, 1,
|
|
-1, 1, 1, 0, 0,
|
|
};
|
|
|
|
const indices = [_]c_int{
|
|
0, 1, 2,
|
|
2, 3, 1,
|
|
2, 3, 4,
|
|
4, 5, 3,
|
|
4, 5, 6,
|
|
6, 7, 5,
|
|
6, 7, 8,
|
|
8, 9, 7,
|
|
};
|
|
|
|
c.glGenVertexArrays(1, &vao);
|
|
c.glBindVertexArray(vao);
|
|
c.glGenBuffers(2, &vbos);
|
|
c.glBindBuffer(c.GL_ARRAY_BUFFER, vbos[0]);
|
|
c.glBufferData(c.GL_ARRAY_BUFFER, @intCast(rawData.len*@sizeOf(f32)), &rawData, c.GL_STATIC_DRAW);
|
|
c.glVertexAttribPointer(0, 3, c.GL_FLOAT, c.GL_FALSE, 5*@sizeOf(f32), null);
|
|
c.glVertexAttribPointer(1, 2, c.GL_FLOAT, c.GL_FALSE, 5*@sizeOf(f32), @ptrFromInt(3*@sizeOf(f32)));
|
|
c.glEnableVertexAttribArray(0);
|
|
c.glEnableVertexAttribArray(1);
|
|
c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
|
|
c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, @intCast(indices.len*@sizeOf(c_int)), &indices, c.GL_STATIC_DRAW);
|
|
|
|
// Load a random texture from the backgrounds folder. The player may make their own pictures which can be chosen as well.
|
|
texture = .{.textureID = 0};
|
|
var dir = try std.fs.cwd().makeOpenPath("assets/backgrounds", .{.iterate = true});
|
|
defer dir.close();
|
|
|
|
var walker = try dir.walk(main.stackAllocator.allocator);
|
|
defer walker.deinit();
|
|
var fileList = main.List([]const u8).init(main.stackAllocator);
|
|
defer {
|
|
for(fileList.items) |fileName| {
|
|
main.stackAllocator.free(fileName);
|
|
}
|
|
fileList.deinit();
|
|
}
|
|
|
|
while(try walker.next()) |entry| {
|
|
if(entry.kind == .file and std.ascii.endsWithIgnoreCase(entry.basename, ".png")) {
|
|
fileList.append(main.stackAllocator.dupe(u8, entry.path));
|
|
}
|
|
}
|
|
if(fileList.items.len == 0) {
|
|
std.log.warn("Couldn't find any background scene images in \"assets/backgrounds\".", .{});
|
|
return;
|
|
}
|
|
const theChosenOne = main.random.nextIntBounded(u32, &main.seed, @as(u32, @intCast(fileList.items.len)));
|
|
const theChosenPath = std.fmt.allocPrint(main.stackAllocator.allocator, "assets/backgrounds/{s}", .{fileList.items[theChosenOne]}) catch unreachable;
|
|
defer main.stackAllocator.free(theChosenPath);
|
|
texture = graphics.Texture.initFromFile(theChosenPath);
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
pipeline.deinit();
|
|
c.glDeleteVertexArrays(1, &vao);
|
|
c.glDeleteBuffers(2, &vbos);
|
|
}
|
|
|
|
pub fn hasImage() bool {
|
|
return texture.textureID != 0;
|
|
}
|
|
|
|
pub fn render() void {
|
|
if(texture.textureID == 0) return;
|
|
|
|
// Use a simple rotation around the z axis, with a steadily increasing angle.
|
|
const newTime = std.time.nanoTimestamp();
|
|
angle += @as(f32, @floatFromInt(newTime - lastTime))/2e10;
|
|
lastTime = newTime;
|
|
const viewMatrix = Mat4f.rotationZ(angle);
|
|
pipeline.bind(null);
|
|
updateViewport(main.Window.width, main.Window.height, 70.0);
|
|
|
|
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&viewMatrix));
|
|
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&game.projectionMatrix));
|
|
|
|
texture.bindTo(0);
|
|
|
|
c.glBindVertexArray(vao);
|
|
c.glDrawElements(c.GL_TRIANGLES, 24, c.GL_UNSIGNED_INT, null);
|
|
}
|
|
|
|
pub fn takeBackgroundImage() void {
|
|
const size: usize = 1024; // Use a power of 2 here, to reduce video memory waste.
|
|
const pixels: []u32 = main.stackAllocator.alloc(u32, size*size);
|
|
defer main.stackAllocator.free(pixels);
|
|
|
|
// Change the viewport and the matrices to render 4 cube faces:
|
|
|
|
const oldResolutionScale = main.settings.resolutionScale;
|
|
main.settings.resolutionScale = 1;
|
|
updateViewport(size, size, 90.0);
|
|
main.settings.resolutionScale = oldResolutionScale;
|
|
defer updateViewport(Window.width, Window.height, settings.fov);
|
|
|
|
var buffer: graphics.FrameBuffer = undefined;
|
|
buffer.init(true, c.GL_NEAREST, c.GL_REPEAT);
|
|
defer buffer.deinit();
|
|
buffer.updateSize(size, size, c.GL_RGBA8);
|
|
|
|
activeFrameBuffer = buffer.frameBuffer;
|
|
defer activeFrameBuffer = 0;
|
|
|
|
const oldRotation = game.camera.rotation;
|
|
defer game.camera.rotation = oldRotation;
|
|
|
|
const angles = [_]f32{std.math.pi/2.0, std.math.pi, std.math.pi*3/2.0, std.math.pi*2};
|
|
|
|
// All 4 sides are stored in a single image.
|
|
const image = graphics.Image.init(main.stackAllocator, 4*size, size);
|
|
defer image.deinit(main.stackAllocator);
|
|
|
|
for(0..4) |i| {
|
|
c.glDepthFunc(c.GL_LESS);
|
|
c.glDepthMask(c.GL_TRUE);
|
|
c.glDisable(c.GL_SCISSOR_TEST);
|
|
game.camera.rotation = .{0, 0, angles[i]};
|
|
// Draw to frame buffer.
|
|
buffer.bind();
|
|
c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT);
|
|
main.renderer.render(game.Player.getEyePosBlocking(), 0);
|
|
// Copy the pixels directly from OpenGL
|
|
buffer.bind();
|
|
c.glReadPixels(0, 0, size, size, c.GL_RGBA, c.GL_UNSIGNED_BYTE, pixels.ptr);
|
|
|
|
for(0..size) |y| {
|
|
for(0..size) |x| {
|
|
const index = x + y*size;
|
|
// Needs to flip the image in y-direction.
|
|
image.setRGB(x + size*i, size - 1 - y, @bitCast(pixels[index]));
|
|
}
|
|
}
|
|
}
|
|
c.glBindFramebuffer(c.GL_FRAMEBUFFER, 0);
|
|
|
|
const fileName = std.fmt.allocPrint(main.stackAllocator.allocator, "assets/backgrounds/{s}_{}.png", .{game.world.?.name, game.world.?.gameTime.load(.monotonic)}) catch unreachable;
|
|
defer main.stackAllocator.free(fileName);
|
|
image.exportToFile(fileName) catch |err| {
|
|
std.log.err("Cannot write file {s} due to {s}", .{fileName, @errorName(err)});
|
|
};
|
|
// TODO: Performance is terrible even with -O3. Consider using qoi instead.
|
|
}
|
|
};
|
|
|
|
pub const Skybox = struct {
|
|
var starPipeline: graphics.Pipeline = undefined;
|
|
var starUniforms: struct {
|
|
mvp: c_int,
|
|
starOpacity: c_int,
|
|
} = undefined;
|
|
|
|
var starVao: c_uint = undefined;
|
|
|
|
var starSsbo: graphics.SSBO = undefined;
|
|
|
|
const numStars = 10000;
|
|
|
|
fn getStarPos(seed: *u64) Vec3f {
|
|
const x: f32 = @floatCast(main.random.nextFloatGauss(seed));
|
|
const y: f32 = @floatCast(main.random.nextFloatGauss(seed));
|
|
const z: f32 = @floatCast(main.random.nextFloatGauss(seed));
|
|
|
|
const r = std.math.cbrt(main.random.nextFloat(seed))*5000.0;
|
|
|
|
return vec.normalize(Vec3f{x, y, z})*@as(Vec3f, @splat(r));
|
|
}
|
|
|
|
fn getStarColor(temperature: f32, light: f32, image: graphics.Image) Vec3f {
|
|
const rgbCol = image.getRGB(@intFromFloat(std.math.clamp(temperature/15000.0*@as(f32, @floatFromInt(image.width)), 0.0, @as(f32, @floatFromInt(image.width - 1)))), 0);
|
|
var rgb: Vec3f = @floatFromInt(Vec3i{rgbCol.r, rgbCol.g, rgbCol.b});
|
|
rgb /= @splat(255.0);
|
|
|
|
rgb *= @as(Vec3f, @splat(light));
|
|
|
|
const m = @reduce(.Max, rgb);
|
|
if(m > 1.0) {
|
|
rgb /= @as(Vec3f, @splat(m));
|
|
}
|
|
|
|
return rgb;
|
|
}
|
|
|
|
fn init() void {
|
|
const starColorImage = graphics.Image.readFromFile(main.stackAllocator, "assets/cubyz/star.png") catch |err| {
|
|
std.log.err("Failed to load star image: {s}", .{@errorName(err)});
|
|
return;
|
|
};
|
|
defer starColorImage.deinit(main.stackAllocator);
|
|
|
|
starPipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/skybox/star.vert",
|
|
"assets/cubyz/shaders/skybox/star.frag",
|
|
"",
|
|
&starUniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = false, .depthWrite = false},
|
|
.{.attachments = &.{.{
|
|
.srcColorBlendFactor = .one,
|
|
.dstColorBlendFactor = .one,
|
|
.colorBlendOp = .add,
|
|
.srcAlphaBlendFactor = .one,
|
|
.dstAlphaBlendFactor = .one,
|
|
.alphaBlendOp = .add,
|
|
}}},
|
|
);
|
|
|
|
var starData: [numStars*20]f32 = undefined;
|
|
|
|
const starDist = 200.0;
|
|
|
|
const off: f32 = @sqrt(3.0)/6.0;
|
|
|
|
const triVertA = Vec3f{0.5, starDist, -off};
|
|
const triVertB = Vec3f{-0.5, starDist, -off};
|
|
const triVertC = Vec3f{0.0, starDist, @sqrt(3.0)/2.0 - off};
|
|
|
|
var seed: u64 = 0;
|
|
|
|
for(0..numStars) |i| {
|
|
var pos: Vec3f = undefined;
|
|
|
|
var radius: f32 = undefined;
|
|
|
|
var temperature: f32 = undefined;
|
|
|
|
var light: f32 = 0;
|
|
|
|
while(light < 0.1) {
|
|
pos = getStarPos(&seed);
|
|
|
|
radius = @floatCast(main.random.nextFloatExp(&seed)*4 + 0.2);
|
|
|
|
temperature = @floatCast(@abs(main.random.nextFloatGauss(&seed)*3000.0 + 5000.0) + 1000.0);
|
|
|
|
// 3.6e-12 can be modified to change the brightness of the stars
|
|
light = (3.6e-12*radius*radius*temperature*temperature*temperature*temperature)/(vec.dot(pos, pos));
|
|
}
|
|
|
|
pos = vec.normalize(pos)*@as(Vec3f, @splat(starDist));
|
|
|
|
const normPos = vec.normalize(pos);
|
|
|
|
const color = getStarColor(temperature, light, starColorImage);
|
|
|
|
const latitude: f32 = @floatCast(std.math.asin(normPos[2]));
|
|
const longitude: f32 = @floatCast(std.math.atan2(-normPos[0], normPos[1]));
|
|
|
|
const mat = Mat4f.rotationZ(longitude).mul(Mat4f.rotationX(latitude));
|
|
|
|
const posA = vec.xyz(mat.mulVec(.{triVertA[0], triVertA[1], triVertA[2], 1.0}));
|
|
const posB = vec.xyz(mat.mulVec(.{triVertB[0], triVertB[1], triVertB[2], 1.0}));
|
|
const posC = vec.xyz(mat.mulVec(.{triVertC[0], triVertC[1], triVertC[2], 1.0}));
|
|
|
|
starData[i*20 ..][0..3].* = posA;
|
|
starData[i*20 + 4 ..][0..3].* = posB;
|
|
starData[i*20 + 8 ..][0..3].* = posC;
|
|
|
|
starData[i*20 + 12 ..][0..3].* = pos;
|
|
starData[i*20 + 16 ..][0..3].* = color;
|
|
}
|
|
|
|
starSsbo = graphics.SSBO.initStatic(f32, &starData);
|
|
|
|
c.glGenVertexArrays(1, &starVao);
|
|
c.glBindVertexArray(starVao);
|
|
c.glEnableVertexAttribArray(0);
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
starPipeline.deinit();
|
|
starSsbo.deinit();
|
|
c.glDeleteVertexArrays(1, &starVao);
|
|
}
|
|
|
|
pub fn getSkyColor() Vec3f {
|
|
return game.fog.skyColor*@as(Vec3f, @splat(@reduce(.Add, game.fog.skyColor)/3.0));
|
|
}
|
|
|
|
pub fn render() void {
|
|
const viewMatrix = game.camera.viewMatrix;
|
|
|
|
const time = game.world.?.gameTime.load(.monotonic);
|
|
|
|
var starOpacity: f32 = 0;
|
|
const dayTime = @abs(@mod(time, game.World.dayCycle) -% game.World.dayCycle/2);
|
|
if(dayTime < game.World.dayCycle/4 - game.World.dayCycle/16) {
|
|
starOpacity = 1;
|
|
} else if(dayTime > game.World.dayCycle/4 + game.World.dayCycle/16) {
|
|
starOpacity = 0;
|
|
} else {
|
|
starOpacity = 1 - @as(f32, @floatFromInt(dayTime - (game.World.dayCycle/4 - game.World.dayCycle/16)))/@as(f32, @floatFromInt(game.World.dayCycle/8));
|
|
}
|
|
|
|
if(starOpacity != 0) {
|
|
starPipeline.bind(null);
|
|
|
|
const starMatrix = game.projectionMatrix.mul(viewMatrix.mul(Mat4f.rotationX(@as(f32, @floatFromInt(time))/@as(f32, @floatFromInt(main.game.World.dayCycle)))));
|
|
|
|
starSsbo.bind(12);
|
|
|
|
c.glUniform1f(starUniforms.starOpacity, starOpacity);
|
|
c.glUniformMatrix4fv(starUniforms.mvp, 1, c.GL_TRUE, @ptrCast(&starMatrix));
|
|
|
|
c.glBindVertexArray(starVao);
|
|
c.glDrawArrays(c.GL_TRIANGLES, 0, numStars*3);
|
|
|
|
c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const Frustum = struct { // MARK: Frustum
|
|
const Plane = struct {
|
|
pos: Vec3f,
|
|
norm: Vec3f,
|
|
};
|
|
planes: [4]Plane, // Who cares about the near/far plane anyways?
|
|
|
|
pub fn init(cameraPos: Vec3f, rotationMatrix: Mat4f, fovY: f32, width: u31, height: u31) Frustum {
|
|
const invRotationMatrix = rotationMatrix.transpose();
|
|
const cameraDir = vec.xyz(invRotationMatrix.mulVec(Vec4f{0, 1, 0, 1}));
|
|
const cameraUp = vec.xyz(invRotationMatrix.mulVec(Vec4f{0, 0, 1, 1}));
|
|
const cameraRight = vec.xyz(invRotationMatrix.mulVec(Vec4f{1, 0, 0, 1}));
|
|
|
|
const halfVSide = std.math.tan(std.math.degreesToRadians(fovY)*0.5);
|
|
const halfHSide = halfVSide*@as(f32, @floatFromInt(width))/@as(f32, @floatFromInt(height));
|
|
|
|
var self: Frustum = undefined;
|
|
self.planes[0] = Plane{.pos = cameraPos, .norm = vec.cross(cameraUp, cameraDir + cameraRight*@as(Vec3f, @splat(halfHSide)))}; // right
|
|
self.planes[1] = Plane{.pos = cameraPos, .norm = vec.cross(cameraDir - cameraRight*@as(Vec3f, @splat(halfHSide)), cameraUp)}; // left
|
|
self.planes[2] = Plane{.pos = cameraPos, .norm = vec.cross(cameraRight, cameraDir - cameraUp*@as(Vec3f, @splat(halfVSide)))}; // top
|
|
self.planes[3] = Plane{.pos = cameraPos, .norm = vec.cross(cameraDir + cameraUp*@as(Vec3f, @splat(halfVSide)), cameraRight)}; // bottom
|
|
return self;
|
|
}
|
|
|
|
pub fn testAAB(self: Frustum, pos: Vec3f, dim: Vec3f) bool {
|
|
inline for(self.planes) |plane| {
|
|
var dist: f32 = vec.dot(pos - plane.pos, plane.norm);
|
|
// Find the most positive corner:
|
|
dist += @reduce(.Add, @max(Vec3f{0, 0, 0}, dim*plane.norm));
|
|
if(dist < 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
pub const MeshSelection = struct { // MARK: MeshSelection
|
|
var pipeline: graphics.Pipeline = undefined;
|
|
var uniforms: struct {
|
|
projectionMatrix: c_int,
|
|
viewMatrix: c_int,
|
|
modelPosition: c_int,
|
|
lowerBounds: c_int,
|
|
upperBounds: c_int,
|
|
lineSize: c_int,
|
|
} = undefined;
|
|
|
|
pub fn init() void {
|
|
pipeline = graphics.Pipeline.init(
|
|
"assets/cubyz/shaders/block_selection_vertex.vert",
|
|
"assets/cubyz/shaders/block_selection_fragment.frag",
|
|
"",
|
|
&uniforms,
|
|
.{.cullMode = .none},
|
|
.{.depthTest = true, .depthWrite = true},
|
|
.{.attachments = &.{.alphaBlending}},
|
|
);
|
|
}
|
|
|
|
pub fn deinit() void {
|
|
pipeline.deinit();
|
|
}
|
|
|
|
var posBeforeBlock: Vec3i = undefined;
|
|
var neighborOfSelection: chunk.Neighbor = undefined;
|
|
pub var selectedBlockPos: ?Vec3i = null;
|
|
var lastSelectedBlockPos: Vec3i = undefined;
|
|
var currentBlockProgress: f32 = 0;
|
|
var currentSwingProgress: f32 = 0;
|
|
var currentSwingTime: f32 = 0;
|
|
var selectionMin: Vec3f = undefined;
|
|
var selectionMax: Vec3f = undefined;
|
|
var selectionFace: chunk.Neighbor = undefined;
|
|
var lastPos: Vec3d = undefined;
|
|
var lastDir: Vec3f = undefined;
|
|
pub fn select(pos: Vec3d, _dir: Vec3f, item: ?main.items.Item) void {
|
|
lastPos = pos;
|
|
const dir: Vec3d = @floatCast(_dir);
|
|
lastDir = _dir;
|
|
|
|
// Test blocks:
|
|
const closestDistance: f64 = 6.0; // selection now limited
|
|
// Implementation of "A Fast Voxel Traversal Algorithm for Ray Tracing" http://www.cse.yorku.ca/~amana/research/grid.pdf
|
|
const step: Vec3i = @intFromFloat(std.math.sign(dir));
|
|
const invDir = @as(Vec3d, @splat(1))/dir;
|
|
const tDelta = @abs(invDir);
|
|
var tMax = (@floor(pos) - pos)*invDir;
|
|
tMax = @max(tMax, tMax + tDelta*@as(Vec3f, @floatFromInt(step)));
|
|
tMax = @select(f64, dir == @as(Vec3d, @splat(0)), @as(Vec3d, @splat(std.math.inf(f64))), tMax);
|
|
var voxelPos: Vec3i = @intFromFloat(@floor(pos));
|
|
|
|
var total_tMax: f64 = 0;
|
|
|
|
selectedBlockPos = null;
|
|
|
|
while(total_tMax < closestDistance) {
|
|
const block = mesh_storage.getBlock(voxelPos[0], voxelPos[1], voxelPos[2]) orelse break;
|
|
if(block.typ != 0) blk: {
|
|
const fluidPlaceable = item != null and item.? == .baseItem and item.?.baseItem.hasTag(.fluidPlaceable);
|
|
for(block.blockTags()) |tag| {
|
|
if(tag == .fluid and !fluidPlaceable or tag == .air) break :blk; // TODO: Buckets could select fluids
|
|
}
|
|
const relativePlayerPos: Vec3f = @floatCast(pos - @as(Vec3d, @floatFromInt(voxelPos)));
|
|
if(block.mode().rayIntersection(block, item, relativePlayerPos, _dir)) |intersection| {
|
|
if(intersection.distance <= closestDistance) {
|
|
selectedBlockPos = voxelPos;
|
|
selectionMin = intersection.min;
|
|
selectionMax = intersection.max;
|
|
selectionFace = intersection.face;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
posBeforeBlock = voxelPos;
|
|
if(tMax[0] < tMax[1]) {
|
|
if(tMax[0] < tMax[2]) {
|
|
voxelPos[0] +%= step[0];
|
|
total_tMax = tMax[0];
|
|
tMax[0] += tDelta[0];
|
|
neighborOfSelection = if(step[0] == 1) .dirPosX else .dirNegX;
|
|
} else {
|
|
voxelPos[2] +%= step[2];
|
|
total_tMax = tMax[2];
|
|
tMax[2] += tDelta[2];
|
|
neighborOfSelection = if(step[2] == 1) .dirUp else .dirDown;
|
|
}
|
|
} else {
|
|
if(tMax[1] < tMax[2]) {
|
|
voxelPos[1] +%= step[1];
|
|
total_tMax = tMax[1];
|
|
tMax[1] += tDelta[1];
|
|
neighborOfSelection = if(step[1] == 1) .dirPosY else .dirNegY;
|
|
} else {
|
|
voxelPos[2] +%= step[2];
|
|
total_tMax = tMax[2];
|
|
tMax[2] += tDelta[2];
|
|
neighborOfSelection = if(step[2] == 1) .dirUp else .dirDown;
|
|
}
|
|
}
|
|
}
|
|
// TODO: Test entities
|
|
}
|
|
|
|
fn canPlaceBlock(pos: Vec3i, block: main.blocks.Block) bool {
|
|
if(main.game.collision.collideWithBlock(block, pos[0], pos[1], pos[2], main.game.Player.getPosBlocking() + main.game.Player.outerBoundingBox.center(), main.game.Player.outerBoundingBox.extent() - @as(Vec3d, @splat(0.00005)), .{0, 0, 0}) != null) {
|
|
return false;
|
|
}
|
|
return true; // TODO: Check other entities
|
|
}
|
|
|
|
pub fn placeBlock(inventory: main.items.Inventory, slot: u32) void {
|
|
if(selectedBlockPos) |selectedPos| {
|
|
var oldBlock = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return;
|
|
var block = oldBlock;
|
|
if(inventory.getItem(slot)) |item| {
|
|
switch(item) {
|
|
.baseItem => |baseItem| {
|
|
if(baseItem.block()) |itemBlock| {
|
|
const rotationMode = blocks.Block.mode(.{.typ = itemBlock, .data = 0});
|
|
var neighborDir = Vec3i{0, 0, 0};
|
|
// Check if stuff can be added to the block itself:
|
|
if(itemBlock == block.typ) {
|
|
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos)));
|
|
if(rotationMode.generateData(main.game.world.?, selectedPos, relPos, lastDir, neighborDir, null, &block, .{.typ = 0, .data = 0}, false)) {
|
|
if(!canPlaceBlock(selectedPos, block)) return;
|
|
updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], oldBlock, block);
|
|
return;
|
|
}
|
|
} else {
|
|
if(rotationMode.modifyBlock(&block, itemBlock)) {
|
|
if(!canPlaceBlock(selectedPos, block)) return;
|
|
updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], oldBlock, block);
|
|
return;
|
|
}
|
|
}
|
|
// Check the block in front of it:
|
|
const neighborPos = posBeforeBlock;
|
|
neighborDir = selectedPos - posBeforeBlock;
|
|
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(neighborPos)));
|
|
const neighborBlock = block;
|
|
oldBlock = mesh_storage.getBlock(neighborPos[0], neighborPos[1], neighborPos[2]) orelse return;
|
|
block = oldBlock;
|
|
if(block.typ == itemBlock) {
|
|
if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, false)) {
|
|
if(!canPlaceBlock(neighborPos, block)) return;
|
|
updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block);
|
|
return;
|
|
}
|
|
} else {
|
|
if(!block.replacable()) return;
|
|
block.typ = itemBlock;
|
|
block.data = 0;
|
|
if(rotationMode.generateData(main.game.world.?, neighborPos, relPos, lastDir, neighborDir, neighborOfSelection, &block, neighborBlock, true)) {
|
|
if(!canPlaceBlock(neighborPos, block)) return;
|
|
updateBlockAndSendUpdate(inventory, slot, neighborPos[0], neighborPos[1], neighborPos[2], oldBlock, block);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if(std.mem.eql(u8, baseItem.id(), "cubyz:selection_wand")) {
|
|
game.Player.selectionPosition2 = selectedPos;
|
|
main.network.Protocols.genericUpdate.sendWorldEditPos(main.game.world.?.conn, .selectedPos2, selectedPos);
|
|
return;
|
|
}
|
|
},
|
|
.tool => |tool| {
|
|
_ = tool; // TODO: Tools might change existing blocks.
|
|
},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn breakBlock(inventory: main.items.Inventory, slot: u32, deltaTime: f64) void {
|
|
if(selectedBlockPos) |selectedPos| {
|
|
const stack = inventory.getStack(slot);
|
|
const isSelectionWand = stack.item != null and stack.item.? == .baseItem and std.mem.eql(u8, stack.item.?.baseItem.id(), "cubyz:selection_wand");
|
|
if(isSelectionWand) {
|
|
game.Player.selectionPosition1 = selectedPos;
|
|
main.network.Protocols.genericUpdate.sendWorldEditPos(main.game.world.?.conn, .selectedPos1, selectedPos);
|
|
return;
|
|
}
|
|
|
|
if(@reduce(.Or, lastSelectedBlockPos != selectedPos)) {
|
|
mesh_storage.removeBreakingAnimation(lastSelectedBlockPos);
|
|
lastSelectedBlockPos = selectedPos;
|
|
currentBlockProgress = 0;
|
|
}
|
|
const block = mesh_storage.getBlock(selectedPos[0], selectedPos[1], selectedPos[2]) orelse return;
|
|
if(block.hasTag(.fluid) or block.hasTag(.air)) {
|
|
return;
|
|
}
|
|
const relPos: Vec3f = @floatCast(lastPos - @as(Vec3d, @floatFromInt(selectedPos)));
|
|
|
|
main.items.Inventory.Sync.ClientSide.mutex.lock();
|
|
if(!game.Player.isCreative()) {
|
|
var damage: f32 = 1;
|
|
const isTool = stack.item != null and stack.item.? == .tool;
|
|
if(isTool) {
|
|
damage = stack.item.?.tool.getBlockDamage(block);
|
|
}
|
|
const isChisel = stack.item != null and stack.item.? == .baseItem and std.mem.eql(u8, stack.item.?.baseItem.id(), "cubyz:chisel");
|
|
if(isChisel and block.mode() == main.rotation.getByID("cubyz:stairs")) { // TODO: Remove once the chisel is a tool.
|
|
damage = block.blockHealth() + block.blockResistance();
|
|
}
|
|
damage -= block.blockResistance();
|
|
if(damage > 0) {
|
|
const swingTime = if(isTool) stack.item.?.tool.swingTime else 0.5;
|
|
if(currentSwingTime != swingTime) {
|
|
currentSwingProgress = 0;
|
|
currentSwingTime = swingTime;
|
|
}
|
|
currentSwingProgress += @floatCast(deltaTime);
|
|
while(currentSwingProgress > currentSwingTime) {
|
|
currentSwingProgress -= currentSwingTime;
|
|
currentBlockProgress += damage/block.blockHealth();
|
|
if(currentBlockProgress > 1) break;
|
|
}
|
|
if(currentBlockProgress < 1) {
|
|
mesh_storage.removeBreakingAnimation(lastSelectedBlockPos);
|
|
if(currentBlockProgress != 0) {
|
|
mesh_storage.addBreakingAnimation(lastSelectedBlockPos, currentBlockProgress);
|
|
}
|
|
main.items.Inventory.Sync.ClientSide.mutex.unlock();
|
|
|
|
return;
|
|
} else {
|
|
currentSwingProgress += (currentBlockProgress - 1)*block.blockHealth()/damage*currentSwingTime;
|
|
mesh_storage.removeBreakingAnimation(lastSelectedBlockPos);
|
|
currentBlockProgress = 0;
|
|
}
|
|
} else {
|
|
main.items.Inventory.Sync.ClientSide.mutex.unlock();
|
|
return;
|
|
}
|
|
}
|
|
|
|
var newBlock = block;
|
|
block.mode().onBlockBreaking(inventory.getStack(slot).item, relPos, lastDir, &newBlock);
|
|
main.items.Inventory.Sync.ClientSide.mutex.unlock();
|
|
|
|
if(newBlock != block) {
|
|
updateBlockAndSendUpdate(inventory, slot, selectedPos[0], selectedPos[1], selectedPos[2], block, newBlock);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn updateBlockAndSendUpdate(source: main.items.Inventory, slot: u32, x: i32, y: i32, z: i32, oldBlock: blocks.Block, newBlock: blocks.Block) void {
|
|
main.items.Inventory.Sync.ClientSide.executeCommand(.{
|
|
.updateBlock = .{
|
|
.source = .{.inv = source, .slot = slot},
|
|
.pos = .{x, y, z},
|
|
.dropLocation = .{
|
|
.dir = selectionFace,
|
|
.min = selectionMin,
|
|
.max = selectionMax,
|
|
},
|
|
.oldBlock = oldBlock,
|
|
.newBlock = newBlock,
|
|
},
|
|
});
|
|
mesh_storage.updateBlock(.{.x = x, .y = y, .z = z, .newBlock = newBlock, .blockEntityData = &.{}});
|
|
}
|
|
|
|
pub fn drawCube(projectionMatrix: Mat4f, viewMatrix: Mat4f, relativePositionToPlayer: Vec3d, min: Vec3f, max: Vec3f) void {
|
|
pipeline.bind(null);
|
|
|
|
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projectionMatrix));
|
|
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&viewMatrix));
|
|
|
|
c.glUniform3f(
|
|
uniforms.modelPosition,
|
|
@floatCast(relativePositionToPlayer[0]),
|
|
@floatCast(relativePositionToPlayer[1]),
|
|
@floatCast(relativePositionToPlayer[2]),
|
|
);
|
|
c.glUniform3f(uniforms.lowerBounds, min[0], min[1], min[2]);
|
|
c.glUniform3f(uniforms.upperBounds, max[0], max[1], max[2]);
|
|
c.glUniform1f(uniforms.lineSize, 1.0/128.0);
|
|
|
|
c.glBindVertexArray(main.renderer.chunk_meshing.vao);
|
|
c.glDrawElements(c.GL_TRIANGLES, 12*6*6, c.GL_UNSIGNED_INT, null);
|
|
}
|
|
|
|
pub fn render(projectionMatrix: Mat4f, viewMatrix: Mat4f, playerPos: Vec3d) void {
|
|
if(main.gui.hideGui) return;
|
|
if(selectedBlockPos) |_selectedBlockPos| {
|
|
drawCube(projectionMatrix, viewMatrix, @as(Vec3d, @floatFromInt(_selectedBlockPos)) - playerPos, selectionMin, selectionMax);
|
|
}
|
|
if(game.Player.selectionPosition1) |pos1| {
|
|
if(game.Player.selectionPosition2) |pos2| {
|
|
const bottomLeft: Vec3i = @min(pos1, pos2);
|
|
const topRight: Vec3i = @max(pos1, pos2);
|
|
drawCube(projectionMatrix, viewMatrix, @as(Vec3d, @floatFromInt(bottomLeft)) - playerPos, .{0, 0, 0}, @floatFromInt(topRight - bottomLeft + Vec3i{1, 1, 1}));
|
|
}
|
|
}
|
|
}
|
|
};
|