const std = @import("std"); const blocks = @import("blocks.zig"); const chunk_zig = @import("chunk.zig"); const ServerChunk = chunk_zig.ServerChunk; const game = @import("game.zig"); const World = game.World; const ServerWorld = main.server.ServerWorld; const graphics = @import("graphics.zig"); const c = graphics.c; const items = @import("items.zig"); const ItemStack = items.ItemStack; const ZonElement = @import("zon.zig").ZonElement; const main = @import("main.zig"); const random = @import("random.zig"); const settings = @import("settings.zig"); const utils = @import("utils.zig"); const vec = @import("vec.zig"); const Mat4f = vec.Mat4f; const Vec3d = vec.Vec3d; const Vec3f = vec.Vec3f; const Vec3i = vec.Vec3i; const NeverFailingAllocator = main.heap.NeverFailingAllocator; const ItemDrop = struct { // MARK: ItemDrop pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32, reverseIndex: u16, }; pub const ItemDropManager = struct { // MARK: ItemDropManager /// Half the side length of all item entities hitboxes as a cube. pub const radius: f64 = 0.1; /// Side length of all item entities hitboxes as a cube. pub const diameter: f64 = 2*radius; pub const pickupRange: f64 = 1.0; const maxSpeed = 10; const maxCapacity = 65536; allocator: NeverFailingAllocator, list: std.MultiArrayList(ItemDrop), indices: [maxCapacity]u16 = undefined, emptyMutex: std.Thread.Mutex = .{}, isEmpty: std.bit_set.ArrayBitSet(usize, maxCapacity), changeQueue: main.utils.ConcurrentQueue(union(enum) {add: struct {u16, ItemDrop}, remove: u16}), world: ?*ServerWorld, gravity: f64, airDragFactor: f64, size: u32 = 0, pub fn init(self: *ItemDropManager, allocator: NeverFailingAllocator, world: ?*ServerWorld, gravity: f64) void { self.* = ItemDropManager{ .allocator = allocator, .list = std.MultiArrayList(ItemDrop){}, .isEmpty = .initFull(), .changeQueue = .init(allocator, 16), .world = world, .gravity = gravity, .airDragFactor = gravity/maxSpeed, }; self.list.resize(self.allocator.allocator, maxCapacity) catch unreachable; } pub fn deinit(self: *ItemDropManager) void { self.processChanges(); self.changeQueue.deinit(); for(self.indices[0..self.size]) |i| { if(self.list.items(.itemStack)[i].item) |item| { item.deinit(); } } self.list.deinit(self.allocator.allocator); } pub fn loadFrom(self: *ItemDropManager, zon: ZonElement) void { const zonArray = zon.getChild("array"); for(zonArray.toSlice()) |elem| { self.addFromZon(elem); } } fn addFromZon(self: *ItemDropManager, zon: ZonElement) void { const item = items.Item.init(zon) catch |err| { const msg = zon.toStringEfficient(main.stackAllocator, ""); defer main.stackAllocator.free(msg); std.log.err("Ignoring invalid item drop {s} which caused {s}", .{msg, @errorName(err)}); return; }; const properties = .{ zon.get(Vec3d, "pos", .{0, 0, 0}), zon.get(Vec3d, "vel", .{0, 0, 0}), random.nextFloatVector(3, &main.seed)*@as(Vec3f, @splat(2*std.math.pi)), items.ItemStack{.item = item, .amount = zon.get(u16, "amount", 1)}, zon.get(i32, "despawnTime", 60), 0, }; if(zon.get(?u16, "i", null)) |i| { @call(.auto, addWithIndex, .{self, i} ++ properties); } else { @call(.auto, add, .{self} ++ properties); } } pub fn getPositionAndVelocityData(self: *ItemDropManager, allocator: NeverFailingAllocator) []u8 { const _data = allocator.alloc(u8, self.size*50); var data = _data; for(self.indices[0..self.size]) |i| { std.mem.writeInt(u16, data[0..2], i, .big); std.mem.writeInt(u64, data[2..10], @bitCast(self.list.items(.pos)[i][0]), .big); std.mem.writeInt(u64, data[10..18], @bitCast(self.list.items(.pos)[i][1]), .big); std.mem.writeInt(u64, data[18..26], @bitCast(self.list.items(.pos)[i][2]), .big); std.mem.writeInt(u64, data[26..34], @bitCast(self.list.items(.vel)[i][0]), .big); std.mem.writeInt(u64, data[34..42], @bitCast(self.list.items(.vel)[i][1]), .big); std.mem.writeInt(u64, data[42..50], @bitCast(self.list.items(.vel)[i][2]), .big); data = data[50..]; } return _data; } pub fn getInitialList(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement { self.processChanges(); // Make sure all the items from the queue are included. var list = ZonElement.initArray(allocator); var ii: u32 = 0; while(ii < self.size) : (ii += 1) { const i = self.indices[ii]; list.array.append(self.storeSingle(allocator, i)); } return list; } fn storeDrop(allocator: NeverFailingAllocator, itemDrop: ItemDrop, i: u16) ZonElement { const obj = ZonElement.initObject(allocator); obj.put("i", i); obj.put("pos", itemDrop.pos); obj.put("vel", itemDrop.vel); itemDrop.itemStack.storeToZon(allocator, obj); obj.put("despawnTime", itemDrop.despawnTime); return obj; } fn storeSingle(self: *ItemDropManager, allocator: NeverFailingAllocator, i: u16) ZonElement { return storeDrop(allocator, self.list.get(i), i); } pub fn store(self: *ItemDropManager, allocator: NeverFailingAllocator) ZonElement { const zonArray = ZonElement.initArray(allocator); for(self.indices[0..self.size]) |i| { const item = self.storeSingle(allocator, i); zonArray.array.append(item); } const zon = ZonElement.initObject(allocator); zon.put("array", zonArray); return zon; } pub fn update(self: *ItemDropManager, deltaTime: f32) void { std.debug.assert(self.world != null); self.processChanges(); const pos = self.list.items(.pos); const vel = self.list.items(.vel); const pickupCooldown = self.list.items(.pickupCooldown); const despawnTime = self.list.items(.despawnTime); var ii: u32 = 0; while(ii < self.size) { const i = self.indices[ii]; if(self.world.?.getSimulationChunkAndIncreaseRefCount(@intFromFloat(pos[i][0]), @intFromFloat(pos[i][1]), @intFromFloat(pos[i][2]))) |simChunk| { defer simChunk.decreaseRefCount(); if(simChunk.getChunk()) |chunk| { // Check collision with blocks: self.updateEnt(chunk, &pos[i], &vel[i], deltaTime); } } pickupCooldown[i] -= 1; despawnTime[i] -= 1; if(despawnTime[i] < 0) { self.directRemove(i); } else { ii += 1; } } } pub fn add(self: *ItemDropManager, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { self.emptyMutex.lock(); const i: u16 = @intCast(self.isEmpty.findFirstSet() orelse { self.emptyMutex.unlock(); const zon = itemStack.store(main.stackAllocator); defer zon.deinit(main.stackAllocator); const string = zon.toString(main.stackAllocator); defer main.stackAllocator.free(string); std.log.err("Item drop capacitiy limit reached. Failed to add itemStack: {s}", .{string}); if(itemStack.item) |item| { item.deinit(); } return; }); self.isEmpty.unset(i); const drop = ItemDrop{ .pos = pos, .vel = vel, .rot = rot, .itemStack = itemStack, .despawnTime = despawnTime, .pickupCooldown = pickupCooldown, .reverseIndex = undefined, }; if(self.world != null) { const list = ZonElement.initArray(main.stackAllocator); defer list.deinit(main.stackAllocator); list.array.append(.null); list.array.append(storeDrop(main.stackAllocator, drop, i)); const updateData = list.toStringEfficient(main.stackAllocator, &.{}); defer main.stackAllocator.free(updateData); const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator); defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList); for(userList) |user| { main.network.Protocols.entity.send(user.conn, updateData); } } self.emptyMutex.unlock(); self.changeQueue.enqueue(.{.add = .{i, drop}}); } fn addWithIndex(self: *ItemDropManager, i: u16, pos: Vec3d, vel: Vec3d, rot: Vec3f, itemStack: ItemStack, despawnTime: i32, pickupCooldown: i32) void { self.emptyMutex.lock(); std.debug.assert(self.isEmpty.isSet(i)); self.isEmpty.unset(i); const drop = ItemDrop{ .pos = pos, .vel = vel, .rot = rot, .itemStack = itemStack, .despawnTime = despawnTime, .pickupCooldown = pickupCooldown, .reverseIndex = undefined, }; if(self.world != null) { const list = ZonElement.initArray(main.stackAllocator); defer list.deinit(main.stackAllocator); list.array.append(.null); list.array.append(storeDrop(main.stackAllocator, drop, i)); const updateData = list.toStringEfficient(main.stackAllocator, &.{}); defer main.stackAllocator.free(updateData); const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator); defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList); for(userList) |user| { main.network.Protocols.entity.send(user.conn, updateData); } } self.emptyMutex.unlock(); self.changeQueue.enqueue(.{.add = .{i, drop}}); } fn processChanges(self: *ItemDropManager) void { while(self.changeQueue.dequeue()) |data| { switch(data) { .add => |addData| { self.internalAdd(addData[0], addData[1]); }, .remove => |index| { self.internalRemove(index); }, } } } fn internalAdd(self: *ItemDropManager, i: u16, drop_: ItemDrop) void { var drop = drop_; if(self.world == null) { ClientItemDropManager.clientSideInternalAdd(self, i, drop); } drop.reverseIndex = @intCast(self.size); self.list.set(i, drop); self.indices[self.size] = i; self.size += 1; } fn internalRemove(self: *ItemDropManager, i: u16) void { self.size -= 1; const ii = self.list.items(.reverseIndex)[i]; self.list.items(.itemStack)[i].clear(); self.indices[ii] = self.indices[self.size]; self.list.items(.reverseIndex)[self.indices[self.size]] = ii; } fn directRemove(self: *ItemDropManager, i: u16) void { std.debug.assert(self.world != null); self.emptyMutex.lock(); self.isEmpty.set(i); const list = ZonElement.initArray(main.stackAllocator); defer list.deinit(main.stackAllocator); list.array.append(.null); list.array.append(.{.int = i}); const updateData = list.toStringEfficient(main.stackAllocator, &.{}); defer main.stackAllocator.free(updateData); const userList = main.server.getUserListAndIncreaseRefCount(main.stackAllocator); defer main.server.freeUserListAndDecreaseRefCount(main.stackAllocator, userList); for(userList) |user| { main.network.Protocols.entity.send(user.conn, updateData); } self.emptyMutex.unlock(); self.internalRemove(i); } fn updateEnt(self: *ItemDropManager, chunk: *ServerChunk, pos: *Vec3d, vel: *Vec3d, deltaTime: f64) void { const hitBox = main.game.collision.Box{.min = @splat(-radius), .max = @splat(radius)}; if(main.game.collision.collides(.server, .x, 0, pos.*, hitBox) != null) { self.fixStuckInBlock(chunk, pos, vel, deltaTime); return; } vel.* += Vec3d{0, 0, -self.gravity*deltaTime}; inline for(0..3) |i| { const move = vel.*[i]*deltaTime; // + acceleration[i]*deltaTime; if(main.game.collision.collides(.server, @enumFromInt(i), move, pos.*, hitBox)) |box| { if(move < 0) { pos.*[i] = box.max[i] + radius; } else { pos.*[i] = box.max[i] - radius; } vel.*[i] = 0; } else { pos.*[i] += move; } } // Apply drag: vel.* *= @splat(@max(0, 1 - self.airDragFactor*deltaTime)); } fn fixStuckInBlock(self: *ItemDropManager, chunk: *ServerChunk, pos: *Vec3d, vel: *Vec3d, deltaTime: f64) void { const centeredPos = pos.* - @as(Vec3d, @splat(0.5)); const pos0: Vec3i = @intFromFloat(@floor(centeredPos)); var closestEmptyBlock: Vec3i = @splat(-1); var closestDist = std.math.floatMax(f64); var delta = Vec3i{0, 0, 0}; while(delta[0] <= 1) : (delta[0] += 1) { delta[1] = 0; while(delta[1] <= 1) : (delta[1] += 1) { delta[2] = 0; while(delta[2] <= 1) : (delta[2] += 1) { const isSolid = self.checkBlock(chunk, pos, pos0 + delta); if(!isSolid) { const dist = vec.lengthSquare(@as(Vec3d, @floatFromInt(pos0 + delta)) - centeredPos); if(dist < closestDist) { closestDist = dist; closestEmptyBlock = delta; } } } } } vel.* = @splat(0); const unstuckVelocity: f64 = 1; if(closestDist == std.math.floatMax(f64)) { // Surrounded by solid blocks → move upwards vel.*[2] = unstuckVelocity; pos.*[2] += vel.*[2]*deltaTime; } else { vel.* = @as(Vec3d, @splat(unstuckVelocity))*(@as(Vec3d, @floatFromInt(pos0 + closestEmptyBlock)) - centeredPos); pos.* += (vel.*)*@as(Vec3d, @splat(deltaTime)); } } fn checkBlock(self: *ItemDropManager, chunk: *ServerChunk, pos: *Vec3d, blockPos: Vec3i) bool { // Transform to chunk-relative coordinates: const chunkPos = blockPos & ~@as(Vec3i, @splat(main.chunk.chunkMask)); var block: blocks.Block = undefined; if(chunk.super.pos.wx == chunkPos[0] and chunk.super.pos.wy == chunkPos[1] and chunk.super.pos.wz == chunkPos[2]) { chunk.mutex.lock(); defer chunk.mutex.unlock(); block = chunk.getBlock(blockPos[0] - chunk.super.pos.wx, blockPos[1] - chunk.super.pos.wy, blockPos[2] - chunk.super.pos.wz); } else { const otherChunk = self.world.?.getSimulationChunkAndIncreaseRefCount(chunkPos[0], chunkPos[1], chunkPos[2]) orelse return true; defer otherChunk.decreaseRefCount(); const ch = otherChunk.getChunk() orelse return true; ch.mutex.lock(); defer ch.mutex.unlock(); block = ch.getBlock(blockPos[0] - ch.super.pos.wx, blockPos[1] - ch.super.pos.wy, blockPos[2] - ch.super.pos.wz); } return main.game.collision.collideWithBlock(block, blockPos[0], blockPos[1], blockPos[2], pos.*, @splat(radius), @splat(0)) != null; } pub fn checkEntity(self: *ItemDropManager, user: *main.server.User) void { var ii: u32 = 0; while(ii < self.size) { const i = self.indices[ii]; if(self.list.items(.pickupCooldown)[i] > 0) { ii += 1; continue; } const hitbox = main.game.Player.outerBoundingBox; const min = user.player.pos + hitbox.min; const max = user.player.pos + hitbox.max; const itemPos = self.list.items(.pos)[i]; const dist = @max(min - itemPos, itemPos - max); if(@reduce(.Max, dist) < radius + pickupRange) { const itemStack = &self.list.items(.itemStack)[i]; main.items.Inventory.Sync.ServerSide.tryCollectingToPlayerInventory(user, itemStack); if(itemStack.amount == 0) { self.directRemove(i); continue; } } ii += 1; } } }; pub const ClientItemDropManager = struct { // MARK: ClientItemDropManager const maxf64Capacity = ItemDropManager.maxCapacity*@sizeOf(Vec3d)/@sizeOf(f64); super: ItemDropManager, lastTime: i16, timeDifference: utils.TimeDifference = .{}, interpolation: utils.GenericInterpolation(maxf64Capacity) align(64) = undefined, var instance: ?*ClientItemDropManager = null; var mutex: std.Thread.Mutex = .{}; pub fn init(self: *ClientItemDropManager, allocator: NeverFailingAllocator, world: *World) void { std.debug.assert(instance == null); // Only one instance allowed. instance = self; self.* = .{ .super = undefined, .lastTime = @as(i16, @truncate(std.time.milliTimestamp())) -% settings.entityLookback, }; self.super.init(allocator, null, world.gravity); self.interpolation.init( @ptrCast(self.super.list.items(.pos).ptr), @ptrCast(self.super.list.items(.vel).ptr), ); } pub fn deinit(self: *ClientItemDropManager) void { std.debug.assert(instance != null); // Double deinit. instance = null; self.super.deinit(); } pub fn readPosition(self: *ClientItemDropManager, _data: []const u8, time: i16) void { var data = _data; self.timeDifference.addDataPoint(time); var pos: [ItemDropManager.maxCapacity]Vec3d = undefined; var vel: [ItemDropManager.maxCapacity]Vec3d = undefined; while(data.len != 0) { const i = std.mem.readInt(u16, data[0..2], .big); pos[i][0] = @bitCast(std.mem.readInt(u64, data[2..10], .big)); pos[i][1] = @bitCast(std.mem.readInt(u64, data[10..18], .big)); pos[i][2] = @bitCast(std.mem.readInt(u64, data[18..26], .big)); vel[i][0] = @bitCast(std.mem.readInt(u64, data[26..34], .big)); vel[i][1] = @bitCast(std.mem.readInt(u64, data[34..42], .big)); vel[i][2] = @bitCast(std.mem.readInt(u64, data[42..50], .big)); data = data[50..]; } mutex.lock(); defer mutex.unlock(); self.interpolation.updatePosition(@ptrCast(&pos), @ptrCast(&vel), time); // TODO: Only update the ones we actually changed. } pub fn updateInterpolationData(self: *ClientItemDropManager) void { self.super.processChanges(); var time = @as(i16, @truncate(std.time.milliTimestamp())) -% settings.entityLookback; time -%= self.timeDifference.difference.load(.monotonic); { mutex.lock(); defer mutex.unlock(); self.interpolation.updateIndexed(time, self.lastTime, self.super.indices[0..self.super.size], 4); } self.lastTime = time; } fn clientSideInternalAdd(_: *ItemDropManager, i: u16, drop: ItemDrop) void { mutex.lock(); defer mutex.unlock(); for(&instance.?.interpolation.lastVel) |*lastVel| { @as(*align(8) [ItemDropManager.maxCapacity]Vec3d, @ptrCast(lastVel))[i] = Vec3d{0, 0, 0}; } for(&instance.?.interpolation.lastPos) |*lastPos| { @as(*align(8) [ItemDropManager.maxCapacity]Vec3d, @ptrCast(lastPos))[i] = drop.pos; } } pub fn remove(self: *ClientItemDropManager, i: u16) void { self.super.emptyMutex.lock(); self.super.isEmpty.set(i); self.super.emptyMutex.unlock(); self.super.changeQueue.enqueue(.{.remove = i}); } pub fn loadFrom(self: *ClientItemDropManager, zon: ZonElement) void { self.super.loadFrom(zon); } pub fn addFromZon(self: *ClientItemDropManager, zon: ZonElement) void { self.super.addFromZon(zon); } }; pub const ItemDropRenderer = struct { // MARK: ItemDropRenderer var itemShader: graphics.Shader = undefined; var itemUniforms: struct { projectionMatrix: c_int, modelMatrix: c_int, viewMatrix: c_int, modelPosition: c_int, ambientLight: c_int, modelIndex: c_int, block: c_int, time: c_int, texture_sampler: c_int, emissionSampler: c_int, reflectivityAndAbsorptionSampler: c_int, reflectionMap: c_int, reflectionMapSize: c_int, contrast: c_int, } = undefined; var itemModelSSBO: graphics.SSBO = undefined; var modelData: main.List(u32) = undefined; var freeSlots: main.List(*ItemVoxelModel) = undefined; const ItemVoxelModel = struct { index: u31 = undefined, len: u31 = undefined, item: items.Item, fn getSlot(len: u31) u31 { for(freeSlots.items, 0..) |potentialSlot, i| { if(len == potentialSlot.len) { _ = freeSlots.swapRemove(i); const result = potentialSlot.index; main.globalAllocator.destroy(potentialSlot); return result; } } const result: u31 = @intCast(modelData.items.len); modelData.resize(result + len); return result; } fn init(template: ItemVoxelModel) *ItemVoxelModel { const self = main.globalAllocator.create(ItemVoxelModel); self.* = ItemVoxelModel{ .item = template.item, }; if(self.item == .baseItem and self.item.baseItem.block != null and self.item.baseItem.image.imageData.ptr == graphics.Image.defaultImage.imageData.ptr) { // Find sizes and free index: var block = blocks.Block{.typ = self.item.baseItem.block.?, .data = 0}; block.data = block.mode().naturalStandard; const modelIndex = blocks.meshes.model(block); const model = &main.models.models.items[modelIndex]; var data = main.List(u32).init(main.stackAllocator); defer data.deinit(); for(model.internalQuads) |quad| { const textureIndex = blocks.meshes.textureIndex(block, main.models.quads.items[quad].textureSlot); data.append(@as(u32, quad) << 16 | textureIndex); // modelAndTexture data.append(0); // offsetByNormal } for(model.neighborFacingQuads) |list| { for(list) |quad| { const textureIndex = blocks.meshes.textureIndex(block, main.models.quads.items[quad].textureSlot); data.append(@as(u32, quad) << 16 | textureIndex); // modelAndTexture data.append(1); // offsetByNormal } } self.len = @intCast(data.items.len); self.index = getSlot(self.len); @memcpy(modelData.items[self.index..][0..self.len], data.items); } else { // Find sizes and free index: const img = self.item.getImage(); const size = Vec3i{img.width, 1, img.height}; self.len = @intCast(3 + @reduce(.Mul, size)); self.index = getSlot(self.len); var dataSection: []u32 = undefined; dataSection = modelData.items[self.index..][0..self.len]; dataSection[0] = @intCast(size[0]); dataSection[1] = @intCast(size[1]); dataSection[2] = @intCast(size[2]); var i: u32 = 3; var z: u32 = 0; while(z < 1) : (z += 1) { var x: u32 = 0; while(x < img.width) : (x += 1) { var y: u32 = 0; while(y < img.height) : (y += 1) { dataSection[i] = img.getRGB(x, y).toARBG(); i += 1; } } } } itemModelSSBO.bufferData(u32, modelData.items); return self; } fn deinit(self: *ItemVoxelModel) void { freeSlots.append(self); } pub fn equals(self: ItemVoxelModel, other: ?*ItemVoxelModel) bool { if(other == null) return false; return std.meta.eql(self.item, other.?.item); } pub fn hashCode(self: ItemVoxelModel) u32 { return self.item.hashCode(); } }; pub fn init() void { itemShader = graphics.Shader.initAndGetUniforms("assets/cubyz/shaders/item_drop.vs", "assets/cubyz/shaders/item_drop.fs", "", &itemUniforms); itemModelSSBO = .init(); itemModelSSBO.bufferData(i32, &[3]i32{1, 1, 1}); itemModelSSBO.bind(2); modelData = .init(main.globalAllocator); freeSlots = .init(main.globalAllocator); } pub fn deinit() void { itemShader.deinit(); itemModelSSBO.deinit(); modelData.deinit(); voxelModels.clear(); for(freeSlots.items) |freeSlot| { main.globalAllocator.destroy(freeSlot); } freeSlots.deinit(); } var voxelModels: utils.Cache(ItemVoxelModel, 32, 32, ItemVoxelModel.deinit) = .{}; fn getModel(item: items.Item) *ItemVoxelModel { const compareObject = ItemVoxelModel{.item = item}; return voxelModels.findOrCreate(compareObject, ItemVoxelModel.init, null); } pub fn renderItemDrops(projMatrix: Mat4f, ambientLight: Vec3f, playerPos: Vec3d, time: u32) void { game.world.?.itemDrops.updateInterpolationData(); itemShader.bind(); c.glUniform1i(itemUniforms.texture_sampler, 0); c.glUniform1i(itemUniforms.emissionSampler, 1); c.glUniform1i(itemUniforms.reflectivityAndAbsorptionSampler, 2); c.glUniform1i(itemUniforms.reflectionMap, 4); c.glUniform1f(itemUniforms.reflectionMapSize, main.renderer.reflectionCubeMapSize); c.glUniform1i(itemUniforms.time, @as(u31, @truncate(time))); c.glUniformMatrix4fv(itemUniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix)); c.glUniform3fv(itemUniforms.ambientLight, 1, @ptrCast(&ambientLight)); c.glUniformMatrix4fv(itemUniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&game.camera.viewMatrix)); c.glUniform1f(itemUniforms.contrast, 0.12); const itemDrops = &game.world.?.itemDrops.super; for(itemDrops.indices[0..itemDrops.size]) |i| { if(itemDrops.list.items(.itemStack)[i].item) |item| { var pos = itemDrops.list.items(.pos)[i]; const rot = itemDrops.list.items(.rot)[i]; const blockPos: Vec3i = @intFromFloat(@floor(pos)); 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( 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; const model = getModel(item); c.glUniform1i(itemUniforms.modelIndex, model.index); var vertices: u31 = 36; var scale: f32 = 0.3; if(item == .baseItem and item.baseItem.block != null and item.baseItem.image.imageData.ptr == graphics.Image.defaultImage.imageData.ptr) { const blockType = item.baseItem.block.?; c.glUniform1i(itemUniforms.block, blockType); vertices = model.len/2*6; } else { c.glUniform1i(itemUniforms.block, 0); scale = 0.5; } var modelMatrix = Mat4f.translation(@floatCast(pos)); modelMatrix = modelMatrix.mul(Mat4f.rotationX(-rot[0])); modelMatrix = modelMatrix.mul(Mat4f.rotationY(-rot[1])); modelMatrix = modelMatrix.mul(Mat4f.rotationZ(-rot[2])); modelMatrix = modelMatrix.mul(Mat4f.scale(@splat(scale))); modelMatrix = modelMatrix.mul(Mat4f.translation(@splat(-0.5))); 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); } } } };