Add player item display (#1109)

* extract item model storage from itemdrop renderer to its own struct

* render player held item

* render display item on top of everything

* add custom projection matrix that ignores fov settings

* fixed item display position

* change indentation from spaces to tabs

* improved item display, added interpolation and hiding

* fix formatting

* cleanup

* cleanup, made item movement

* localize item display projection matrix

* fix formatting

* add lighting interpolation, remove item sway

* fix formatting

* switch to trilinear interpolation

* fix light interpolation

* fix formatting

* small tweaks

* small tweaks, move item hiding to f2 button

* some rendering tweaks

* fix item clipping

* resolve conflict
This commit is contained in:
MnHs 2025-03-30 13:09:24 +03:00 committed by GitHub
parent 4a8c0a6327
commit f8210eb7b4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 180 additions and 21 deletions

View File

@ -519,6 +519,29 @@ pub const ClientItemDropManager = struct { // MARK: ClientItemDropManager
} }
}; };
// Going to handle item animations and other things like - bobbing, interpolation, movement reactions
pub const ItemDisplayManager = struct { // MARK: ItemDisplayManager
pub var showItem: bool = true;
var cameraFollow: Vec3f = @splat(0);
var cameraFollowVel: Vec3f = @splat(0);
const damping: Vec3f = @splat(130);
pub fn update(deltaTime: f64) void {
if(deltaTime == 0) return;
const dt: f32 = @floatCast(deltaTime);
var playerVel: Vec3f = .{@floatCast((game.Player.super.vel[2]*0.009 + game.Player.eyeVel[2]*0.0075)), 0, 0};
playerVel = vec.clampMag(playerVel, 0.32);
// TODO: add *smooth* item sway
const n1: Vec3f = cameraFollowVel - (cameraFollow - playerVel)*damping*damping*@as(Vec3f, @splat(dt));
const n2: Vec3f = @as(Vec3f, @splat(1)) + damping*@as(Vec3f, @splat(dt));
cameraFollowVel = n1/(n2*n2);
cameraFollow += cameraFollowVel*@as(Vec3f, @splat(dt));
}
};
pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
var itemShader: graphics.Shader = undefined; var itemShader: graphics.Shader = undefined;
var itemUniforms: struct { var itemUniforms: struct {
@ -539,7 +562,6 @@ pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
} = undefined; } = undefined;
var itemModelSSBO: graphics.SSBO = undefined; var itemModelSSBO: graphics.SSBO = undefined;
var modelData: main.List(u32) = undefined; var modelData: main.List(u32) = undefined;
var freeSlots: main.List(*ItemVoxelModel) = undefined; var freeSlots: main.List(*ItemVoxelModel) = undefined;
@ -659,8 +681,7 @@ pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
return voxelModels.findOrCreate(compareObject, ItemVoxelModel.init, null); return voxelModels.findOrCreate(compareObject, ItemVoxelModel.init, null);
} }
pub fn renderItemDrops(projMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d, time: u32) void { fn bindCommonUniforms(projMatrix: Mat4f, viewMatrix: Mat4f, ambientLight: Vec3f, time: u32) void {
game.world.?.itemDrops.updateInterpolationData();
itemShader.bind(); itemShader.bind();
c.glUniform1i(itemUniforms.texture_sampler, 0); c.glUniform1i(itemUniforms.texture_sampler, 0);
c.glUniform1i(itemUniforms.emissionSampler, 1); c.glUniform1i(itemUniforms.emissionSampler, 1);
@ -670,8 +691,32 @@ pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
c.glUniform1i(itemUniforms.time, @as(u31, @truncate(time))); c.glUniform1i(itemUniforms.time, @as(u31, @truncate(time)));
c.glUniformMatrix4fv(itemUniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix)); c.glUniformMatrix4fv(itemUniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix));
c.glUniform3fv(itemUniforms.ambientLight, 1, @ptrCast(&ambientLight)); c.glUniform3fv(itemUniforms.ambientLight, 1, @ptrCast(&ambientLight));
c.glUniformMatrix4fv(itemUniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix)); c.glUniformMatrix4fv(itemUniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&viewMatrix));
c.glUniform1f(itemUniforms.contrast, 0.12); c.glUniform1f(itemUniforms.contrast, 0.12);
}
fn bindLightUniform(light: [6]u8, ambientLight: Vec3f) void {
c.glUniform3fv(itemUniforms.ambientLight, 1, @ptrCast(&@max(
ambientLight*@as(Vec3f, @as(Vec3f, @floatFromInt(Vec3i{light[0], light[1], light[2]}))/@as(Vec3f, @splat(255))),
@as(Vec3f, @floatFromInt(Vec3i{light[3], light[4], light[5]}))/@as(Vec3f, @splat(255)),
)));
}
fn bindModelUniforms(modelIndex: u31, blockType: u16) void {
c.glUniform1i(itemUniforms.modelIndex, modelIndex);
c.glUniform1i(itemUniforms.block, blockType);
}
fn drawItem(vertices: u31, modelMatrix: Mat4f) void {
c.glUniformMatrix4fv(itemUniforms.modelMatrix, 1, c.GL_TRUE, @ptrCast(&modelMatrix));
c.glBindVertexArray(main.renderer.chunk_meshing.vao);
c.glDrawElements(c.GL_TRIANGLES, vertices, c.GL_UNSIGNED_INT, null);
}
pub fn renderItemDrops(projMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d, time: u32) void {
game.world.?.itemDrops.updateInterpolationData();
bindCommonUniforms(projMatrix, game.camera.viewMatrix, ambientLight, time);
const itemDrops = &game.world.?.itemDrops.super; const itemDrops = &game.world.?.itemDrops.super;
for(itemDrops.indices[0..itemDrops.size]) |i| { for(itemDrops.indices[0..itemDrops.size]) |i| {
if(itemDrops.list.items(.itemStack)[i].item) |item| { if(itemDrops.list.items(.itemStack)[i].item) |item| {
@ -679,25 +724,21 @@ pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
const rot = itemDrops.list.items(.rot)[i]; const rot = itemDrops.list.items(.rot)[i];
const blockPos: Vec3i = @intFromFloat(@floor(pos)); const blockPos: Vec3i = @intFromFloat(@floor(pos));
const light: [6]u8 = main.renderer.mesh_storage.getLight(blockPos[0], blockPos[1], blockPos[2]) orelse @splat(0); const light: [6]u8 = main.renderer.mesh_storage.getLight(blockPos[0], blockPos[1], blockPos[2]) orelse @splat(0);
c.glUniform3fv(itemUniforms.ambientLight, 1, @ptrCast(&@max( bindLightUniform(light, ambientLight);
ambientLight*@as(Vec3f, @as(Vec3f, @floatFromInt(Vec3i{light[0], light[1], light[2]}))/@as(Vec3f, @splat(255))),
@as(Vec3f, @floatFromInt(Vec3i{light[3], light[4], light[5]}))/@as(Vec3f, @splat(255)),
)));
pos -= playerPos; pos -= playerPos;
const model = getModel(item); const model = getModel(item);
c.glUniform1i(itemUniforms.modelIndex, model.index);
var vertices: u31 = 36; var vertices: u31 = 36;
var scale: f32 = 0.3; var scale: f32 = 0.3;
var blockType: u16 = 0;
if(item == .baseItem and item.baseItem.block != null and item.baseItem.image.imageData.ptr == graphics.Image.defaultImage.imageData.ptr) { if(item == .baseItem and item.baseItem.block != null and item.baseItem.image.imageData.ptr == graphics.Image.defaultImage.imageData.ptr) {
const blockType = item.baseItem.block.?; blockType = item.baseItem.block.?;
c.glUniform1i(itemUniforms.block, blockType);
vertices = model.len/2*6; vertices = model.len/2*6;
} else { } else {
c.glUniform1i(itemUniforms.block, 0);
scale = 0.5; scale = 0.5;
} }
bindModelUniforms(model.index, blockType);
var modelMatrix = Mat4f.translation(@floatCast(pos)); var modelMatrix = Mat4f.translation(@floatCast(pos));
modelMatrix = modelMatrix.mul(Mat4f.rotationX(-rot[0])); modelMatrix = modelMatrix.mul(Mat4f.rotationX(-rot[0]));
@ -705,11 +746,108 @@ pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer
modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-rot[2])); modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-rot[2]));
modelMatrix = modelMatrix.mul(Mat4f.scale(@splat(scale))); modelMatrix = modelMatrix.mul(Mat4f.scale(@splat(scale)));
modelMatrix = modelMatrix.mul(Mat4f.translation(@splat(-0.5))); modelMatrix = modelMatrix.mul(Mat4f.translation(@splat(-0.5)));
c.glUniformMatrix4fv(itemUniforms.modelMatrix, 1, c.GL_TRUE, @ptrCast(&modelMatrix)); drawItem(vertices, modelMatrix);
c.glBindVertexArray(main.renderer.chunk_meshing.vao);
c.glDrawElements(c.GL_TRIANGLES, vertices, c.GL_UNSIGNED_INT, null);
} }
} }
} }
inline fn getIndex(x: u8, y: u8, z: u8) u32 {
return (z*4) + (y*2) + (x);
}
inline fn blendColors(a: [6]f32, b: [6]f32, t: f32) [6]f32 {
var result: [6]f32 = .{0, 0, 0, 0, 0, 0};
inline for(0..6) |i| {
result[i] = std.math.lerp(a[i], b[i], t);
}
return result;
}
pub fn renderDisplayItems(ambientLight: Vec3f, playerPos: Vec3d, time: u32) void {
if(!ItemDisplayManager.showItem) return;
const projMatrix: Mat4f = Mat4f.perspective(std.math.degreesToRadians(65), @as(f32, @floatFromInt(main.renderer.lastWidth))/@as(f32, @floatFromInt(main.renderer.lastHeight)), 0.01, 3);
const viewMatrix = Mat4f.identity();
bindCommonUniforms(projMatrix, viewMatrix, ambientLight, time);
const selectedItem = game.Player.inventory.getItem(game.Player.selectedSlot);
if(selectedItem) |item| {
var pos: Vec3d = Vec3d{0, 0, 0};
const rot: Vec3f = ItemDisplayManager.cameraFollow;
const lightPos = @as(Vec3f, @floatCast(playerPos)) - @as(Vec3f, @splat(0.5));
const blockPos: Vec3i = @intFromFloat(@floor(lightPos));
const localBlockPos = lightPos - @as(Vec3f, @floatFromInt(blockPos));
var samples: [8][6]f32 = @splat(@splat(0));
inline for(0..2) |z| {
inline for(0..2) |y| {
inline for(0..2) |x| {
const light: [6]u8 = main.renderer.mesh_storage.getLight(
blockPos[0] +% @as(i32, @intCast(x)),
blockPos[1] +% @as(i32, @intCast(y)),
blockPos[2] +% @as(i32, @intCast(z)),
) orelse @splat(0);
inline for(0..6) |i| {
samples[getIndex(x, y, z)][i] = @as(f32, @floatFromInt(light[i]));
}
}
}
}
inline for(0..2) |y| {
inline for(0..2) |x| {
samples[getIndex(x, y, 0)] = blendColors(samples[getIndex(x, y, 0)], samples[getIndex(x, y, 1)], localBlockPos[2]);
}
}
inline for(0..2) |x| {
samples[getIndex(x, 0, 0)] = blendColors(samples[getIndex(x, 0, 0)], samples[getIndex(x, 1, 0)], localBlockPos[1]);
}
var result: [6]u8 = .{0, 0, 0, 0, 0, 0};
inline for(0..6) |i| {
const val = std.math.lerp(samples[getIndex(0, 0, 0)][i], samples[getIndex(1, 0, 0)][i], localBlockPos[0]);
result[i] = @as(u8, @intFromFloat(@floor(val)));
}
bindLightUniform(result, ambientLight);
const model = getModel(item);
var vertices: u31 = 36;
const isBlock: bool = item == .baseItem and item.baseItem.block != null and item.baseItem.image.imageData.ptr == graphics.Image.defaultImage.imageData.ptr;
var scale: f32 = 0;
var blockType: u16 = 0;
if(isBlock) {
blockType = item.baseItem.block.?;
vertices = model.len/2*6;
scale = 0.3;
pos = Vec3d{0.4, 0.55, -0.32};
} else {
scale = 0.57;
pos = Vec3d{0.4, 0.65, -0.3};
}
bindModelUniforms(model.index, blockType);
var modelMatrix = Mat4f.rotationZ(-rot[2]);
modelMatrix = modelMatrix.mul(Mat4f.rotationY(-rot[1]));
modelMatrix = modelMatrix.mul(Mat4f.rotationX(-rot[0]));
modelMatrix = modelMatrix.mul(Mat4f.translation(@floatCast(pos)));
if(!isBlock) {
if(item == .tool) {
modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-std.math.pi*0.47));
modelMatrix = modelMatrix.mul(Mat4f.rotationY(std.math.pi*0.25));
} else {
modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-std.math.pi*0.45));
}
} else {
modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-std.math.pi*0.2));
}
modelMatrix = modelMatrix.mul(Mat4f.scale(@splat(scale)));
modelMatrix = modelMatrix.mul(Mat4f.translation(@splat(-0.5)));
drawItem(vertices, modelMatrix);
}
}
}; };

View File

@ -307,11 +307,17 @@ fn openCommand() void {
} }
fn takeBackgroundImageFn() void { fn takeBackgroundImageFn() void {
if(game.world == null) return; if(game.world == null) return;
const showItem = itemdrop.ItemDisplayManager.showItem;
itemdrop.ItemDisplayManager.showItem = false;
renderer.MenuBackGround.takeBackgroundImage(); renderer.MenuBackGround.takeBackgroundImage();
itemdrop.ItemDisplayManager.showItem = showItem;
} }
fn toggleHideGui() void { fn toggleHideGui() void {
gui.hideGui = !gui.hideGui; gui.hideGui = !gui.hideGui;
} }
fn toggleHideDisplayItem() void {
itemdrop.ItemDisplayManager.showItem = !itemdrop.ItemDisplayManager.showItem;
}
fn toggleDebugOverlay() void { fn toggleDebugOverlay() void {
gui.toggleWindow("debug"); gui.toggleWindow("debug");
} }
@ -416,6 +422,7 @@ pub const KeyBoard = struct { // MARK: KeyBoard
.{.name = "cameraDown", .gamepadAxis = .{.axis = c.GLFW_GAMEPAD_AXIS_RIGHT_Y, .positive = true}}, .{.name = "cameraDown", .gamepadAxis = .{.axis = c.GLFW_GAMEPAD_AXIS_RIGHT_Y, .positive = true}},
// debug: // debug:
.{.name = "hideMenu", .key = c.GLFW_KEY_F1, .pressAction = &toggleHideGui}, .{.name = "hideMenu", .key = c.GLFW_KEY_F1, .pressAction = &toggleHideGui},
.{.name = "hideDisplayItem", .key = c.GLFW_KEY_F2, .pressAction = &toggleHideDisplayItem},
.{.name = "debugOverlay", .key = c.GLFW_KEY_F3, .pressAction = &toggleDebugOverlay}, .{.name = "debugOverlay", .key = c.GLFW_KEY_F3, .pressAction = &toggleDebugOverlay},
.{.name = "performanceOverlay", .key = c.GLFW_KEY_F4, .pressAction = &togglePerformanceOverlay}, .{.name = "performanceOverlay", .key = c.GLFW_KEY_F4, .pressAction = &togglePerformanceOverlay},
.{.name = "gpuPerformanceOverlay", .key = c.GLFW_KEY_F5, .pressAction = &toggleGPUPerformanceOverlay}, .{.name = "gpuPerformanceOverlay", .key = c.GLFW_KEY_F5, .pressAction = &toggleGPUPerformanceOverlay},
@ -693,7 +700,7 @@ pub fn main() void { // MARK: main()
if(!isHidden) { if(!isHidden) {
c.glEnable(c.GL_CULL_FACE); c.glEnable(c.GL_CULL_FACE);
c.glEnable(c.GL_DEPTH_TEST); c.glEnable(c.GL_DEPTH_TEST);
renderer.render(game.Player.getEyePosBlocking()); renderer.render(game.Player.getEyePosBlocking(), deltaTime);
// Render the GUI // Render the GUI
gui.windowlist.gpu_performance_measuring.startQuery(.gui); gui.windowlist.gpu_performance_measuring.startQuery(.gui);
c.glDisable(c.GL_CULL_FACE); c.glDisable(c.GL_CULL_FACE);

View File

@ -114,8 +114,8 @@ fn initReflectionCubeMap() void {
var worldFrameBuffer: graphics.FrameBuffer = undefined; var worldFrameBuffer: graphics.FrameBuffer = undefined;
var lastWidth: u31 = 0; pub var lastWidth: u31 = 0;
var lastHeight: u31 = 0; pub var lastHeight: u31 = 0;
var lastFov: f32 = 0; var lastFov: f32 = 0;
pub fn updateViewport(width: u31, height: u31, fov: f32) void { pub fn updateViewport(width: u31, height: u31, fov: f32) void {
lastWidth = @intFromFloat(@as(f32, @floatFromInt(width))*main.settings.resolutionScale); lastWidth = @intFromFloat(@as(f32, @floatFromInt(width))*main.settings.resolutionScale);
@ -126,7 +126,7 @@ pub fn updateViewport(width: u31, height: u31, fov: f32) void {
worldFrameBuffer.unbind(); worldFrameBuffer.unbind();
} }
pub fn render(playerPosition: Vec3d) void { pub fn render(playerPosition: Vec3d, deltaTime: f64) void {
// TODO: player bobbing // TODO: player bobbing
if(game.world) |world| { if(game.world) |world| {
// TODO: Handle colors and sun position in the world. // TODO: Handle colors and sun position in the world.
@ -137,6 +137,7 @@ pub fn render(playerPosition: Vec3d) void {
const skyColor = vec.xyz(world.clearColor); const skyColor = vec.xyz(world.clearColor);
game.fog.skyColor = skyColor; game.fog.skyColor = skyColor;
itemdrop.ItemDisplayManager.update(deltaTime);
renderWorld(world, ambient, skyColor, playerPosition); renderWorld(world, ambient, skyColor, playerPosition);
const startTime = std.time.milliTimestamp(); const startTime = std.time.milliTimestamp();
mesh_storage.updateMeshes(startTime + maximumMeshTime); mesh_storage.updateMeshes(startTime + maximumMeshTime);
@ -250,6 +251,11 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, skyColor: Vec3f, playerPo
c.glDepthMask(c.GL_TRUE); c.glDepthMask(c.GL_TRUE);
c.glDepthFunc(c.GL_LESS); c.glDepthFunc(c.GL_LESS);
c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA);
c.glDepthRange(0, 0.001);
itemdrop.ItemDropRenderer.renderDisplayItems(ambientLight, playerPos, time);
c.glDepthRange(0.001, 1);
chunk_meshing.endRender(); chunk_meshing.endRender();
worldFrameBuffer.bindTexture(c.GL_TEXTURE3); worldFrameBuffer.bindTexture(c.GL_TEXTURE3);
@ -579,7 +585,7 @@ pub const MenuBackGround = struct {
// Draw to frame buffer. // Draw to frame buffer.
buffer.bind(); buffer.bind();
c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT); c.glClear(c.GL_DEPTH_BUFFER_BIT | c.GL_STENCIL_BUFFER_BIT | c.GL_COLOR_BUFFER_BIT);
main.renderer.render(game.Player.getEyePosBlocking()); main.renderer.render(game.Player.getEyePosBlocking(), 0);
// Copy the pixels directly from OpenGL // Copy the pixels directly from OpenGL
buffer.bind(); buffer.bind();
c.glReadPixels(0, 0, size, size, c.GL_RGBA, c.GL_UNSIGNED_BYTE, pixels.ptr); c.glReadPixels(0, 0, size, size, c.GL_RGBA, c.GL_UNSIGNED_BYTE, pixels.ptr);

View File

@ -38,6 +38,14 @@ pub fn normalize(self: anytype) @TypeOf(self) {
return self/@as(@TypeOf(self), @splat(length(self))); return self/@as(@TypeOf(self), @splat(length(self)));
} }
pub fn clampMag(self: anytype, maxMag: @typeInfo(@TypeOf(self)).vector.child) @TypeOf(self) {
if(lengthSquare(self) > maxMag*maxMag) {
return normalize(self)*@as(@TypeOf(self), @splat(maxMag));
}
return self;
}
pub fn cross(self: anytype, other: @TypeOf(self)) @TypeOf(self) { pub fn cross(self: anytype, other: @TypeOf(self)) @TypeOf(self) {
if(@typeInfo(@TypeOf(self)).vector.len != 3) @compileError("Only available for vectors of length 3."); if(@typeInfo(@TypeOf(self)).vector.len != 3) @compileError("Only available for vectors of length 3.");
return @TypeOf(self){ return @TypeOf(self){