Cubyz/src/entity.zig
IntegratedQuantum 09fc516828 Move all the allocators to a separate file. They can now be imported with main.heap (analogous to std.heap)
I also removed the unused BufferFallbackAllocator which was from a time where I was still experimenting with different allocators.
2025-03-09 16:26:41 +01:00

263 lines
8.5 KiB
Zig

const std = @import("std");
const chunk = @import("chunk.zig");
const game = @import("game.zig");
const graphics = @import("graphics.zig");
const c = graphics.c;
const ZonElement = @import("zon.zig").ZonElement;
const main = @import("main.zig");
const renderer = @import("renderer.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 Vec4f = vec.Vec4f;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const BinaryReader = main.utils.BinaryReader;
pub const ClientEntity = struct {
interpolatedValues: utils.GenericInterpolation(6) = undefined,
_interpolationPos: [6]f64 = undefined,
_interpolationVel: [6]f64 = undefined,
width: f64,
height: f64,
pos: Vec3d = undefined,
rot: Vec3f = undefined,
id: u32,
name: []const u8,
pub fn init(self: *ClientEntity, zon: ZonElement, allocator: NeverFailingAllocator) void {
self.* = ClientEntity{
.id = zon.get(u32, "id", std.math.maxInt(u32)),
.width = zon.get(f64, "width", 1),
.height = zon.get(f64, "height", 1),
.name = allocator.dupe(u8, zon.get([]const u8, "name", "")),
};
self._interpolationPos = [_]f64{
self.pos[0],
self.pos[1],
self.pos[2],
@floatCast(self.rot[0]),
@floatCast(self.rot[1]),
@floatCast(self.rot[2]),
};
self._interpolationVel = @splat(0);
self.interpolatedValues.init(&self._interpolationPos, &self._interpolationVel);
}
pub fn deinit(self: ClientEntity, allocator: NeverFailingAllocator) void {
allocator.free(self.name);
}
pub fn getRenderPosition(self: *const ClientEntity) Vec3d {
return Vec3d{self.pos[0], self.pos[1], self.pos[2]};
}
pub fn updatePosition(self: *ClientEntity, pos: *const [6]f64, vel: *const [6]f64, time: i16) void {
self.interpolatedValues.updatePosition(pos, vel, time);
}
pub fn update(self: *ClientEntity, time: i16, lastTime: i16) void {
self.interpolatedValues.update(time, lastTime);
self.pos[0] = self.interpolatedValues.outPos[0];
self.pos[1] = self.interpolatedValues.outPos[1];
self.pos[2] = self.interpolatedValues.outPos[2];
self.rot[0] = @floatCast(self.interpolatedValues.outPos[3]);
self.rot[1] = @floatCast(self.interpolatedValues.outPos[4]);
self.rot[2] = @floatCast(self.interpolatedValues.outPos[5]);
}
};
pub const ClientEntityManager = struct {
var lastTime: i16 = 0;
var timeDifference: utils.TimeDifference = utils.TimeDifference{};
var uniforms: struct {
projectionMatrix: c_int,
viewMatrix: c_int,
texture_sampler: c_int,
light: c_int,
contrast: c_int,
ambientLight: c_int,
directionalLight: c_int,
} = undefined;
var modelBuffer: main.graphics.SSBO = undefined;
var modelSize: c_int = 0;
var modelTexture: main.graphics.Texture = undefined;
var shader: graphics.Shader = undefined; // Entities are sometimes small and sometimes big. Therefor it would mean a lot of work to still use smooth lighting. Therefor the non-smooth shader is used for those.
pub var entities: main.VirtualList(ClientEntity, 1 << 20) = undefined;
pub var mutex: std.Thread.Mutex = .{};
pub fn init() void {
entities = .init();
shader = graphics.Shader.initAndGetUniforms("assets/cubyz/shaders/entity_vertex.vs", "assets/cubyz/shaders/entity_fragment.fs", "", &uniforms);
modelTexture = main.graphics.Texture.initFromFile("assets/cubyz/entity/textures/snail_player.png");
const modelFile = main.files.read(main.stackAllocator, "assets/cubyz/entity/models/snail_player.obj") catch |err| blk: {
std.log.err("Error while reading player model: {s}", .{@errorName(err)});
break :blk &.{};
};
defer main.stackAllocator.free(modelFile);
const quadInfos = main.models.Model.loadRawModelDataFromObj(main.stackAllocator, modelFile);
defer main.stackAllocator.free(quadInfos);
modelBuffer = .initStatic(main.models.QuadInfo, quadInfos);
modelBuffer.bind(11);
modelSize = @intCast(quadInfos.len);
}
pub fn deinit() void {
for(entities.items()) |ent| {
ent.deinit(main.globalAllocator);
}
entities.deinit();
shader.deinit();
}
pub fn clear() void {
entities.clearRetainingCapacity();
timeDifference = utils.TimeDifference{};
}
fn update() void {
main.utils.assertLocked(&mutex);
var time: i16 = @truncate(std.time.milliTimestamp() -% settings.entityLookback);
time -%= timeDifference.difference.load(.monotonic);
for(entities.items()) |*ent| {
ent.update(time, lastTime);
}
lastTime = time;
}
pub fn renderNames(projMatrix: Mat4f, playerPos: Vec3d) void {
mutex.lock();
defer mutex.unlock();
for(entities.items()) |ent| {
if(ent.id == game.Player.id or ent.name.len == 0) continue; // don't render local player
const pos3d = ent.getRenderPosition() - playerPos;
const pos4f = Vec4f{
@floatCast(pos3d[0]),
@floatCast(pos3d[1]),
@floatCast(pos3d[2] + 1.0),
1,
};
const rotatedPos = game.camera.viewMatrix.mulVec(pos4f);
const projectedPos = projMatrix.mulVec(rotatedPos);
if(projectedPos[2] < 0) continue;
const xCenter = (1 + projectedPos[0]/projectedPos[3])*@as(f32, @floatFromInt(main.Window.width/2));
const yCenter = (1 - projectedPos[1]/projectedPos[3])*@as(f32, @floatFromInt(main.Window.height/2));
graphics.draw.setColor(0xff000000);
var buf = graphics.TextBuffer.init(main.stackAllocator, ent.name, .{.color = 0}, false, .center);
defer buf.deinit();
const size = buf.calculateLineBreaks(32, 1024);
buf.render(xCenter - size[0]/2, yCenter - size[1], 32);
}
}
pub fn render(projMatrix: Mat4f, ambientLight: Vec3f, directionalLight: Vec3f, playerPos: Vec3d) void {
mutex.lock();
defer mutex.unlock();
update();
shader.bind();
c.glBindVertexArray(main.renderer.chunk_meshing.vao);
c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_TRUE, @ptrCast(&projMatrix));
modelTexture.bindTo(0);
c.glUniform1i(uniforms.texture_sampler, 0);
c.glUniform3fv(uniforms.ambientLight, 1, @ptrCast(&ambientLight));
c.glUniform3fv(uniforms.directionalLight, 1, @ptrCast(&directionalLight));
c.glUniform1f(uniforms.contrast, 0.12);
for(entities.items()) |ent| {
if(ent.id == game.Player.id) continue; // don't render local player
const blockPos: vec.Vec3i = @intFromFloat(@floor(ent.pos));
const lightVals: [6]u8 = main.renderer.mesh_storage.getLight(blockPos[0], blockPos[1], blockPos[2]) orelse @splat(0);
const light = (@as(u32, lightVals[0] >> 3) << 25 |
@as(u32, lightVals[1] >> 3) << 20 |
@as(u32, lightVals[2] >> 3) << 15 |
@as(u32, lightVals[3] >> 3) << 10 |
@as(u32, lightVals[4] >> 3) << 5 |
@as(u32, lightVals[5] >> 3) << 0);
c.glUniform1ui(uniforms.light, @bitCast(@as(u32, light)));
const pos: Vec3d = ent.getRenderPosition() - playerPos;
const modelMatrix = (Mat4f.identity()
.mul(Mat4f.translation(Vec3f{
@floatCast(pos[0]),
@floatCast(pos[1]),
@floatCast(pos[2] - 1.0 + 0.09375),
}))
.mul(Mat4f.rotationZ(-ent.rot[2]))
//.mul(Mat4f.rotationY(-ent.rot[1]))
//.mul(Mat4f.rotationX(-ent.rot[0]))
);
const modelViewMatrix = game.camera.viewMatrix.mul(modelMatrix);
c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_TRUE, @ptrCast(&modelViewMatrix));
c.glDrawElements(c.GL_TRIANGLES, 6*modelSize, c.GL_UNSIGNED_INT, null);
}
}
pub fn addEntity(zon: ZonElement) void {
mutex.lock();
defer mutex.unlock();
var ent = entities.addOne();
ent.init(zon, main.globalAllocator);
}
pub fn removeEntity(id: u32) void {
mutex.lock();
defer mutex.unlock();
for(entities.items(), 0..) |*ent, i| {
if(ent.id == id) {
ent.deinit(main.globalAllocator);
_ = entities.swapRemove(i);
if(i != entities.len) {
entities.items()[i].interpolatedValues.outPos = &entities.items()[i]._interpolationPos;
entities.items()[i].interpolatedValues.outVel = &entities.items()[i]._interpolationVel;
}
break;
}
}
}
pub fn serverUpdate(time: i16, reader: *BinaryReader) !void {
mutex.lock();
defer mutex.unlock();
timeDifference.addDataPoint(time);
while(reader.remaining.len != 0) {
const id = try reader.readInt(u32);
const pos = [_]f64{
try reader.readFloat(f64),
try reader.readFloat(f64),
try reader.readFloat(f64),
@floatCast(try reader.readFloat(f32)),
@floatCast(try reader.readFloat(f32)),
@floatCast(try reader.readFloat(f32)),
};
const vel = [_]f64{
try reader.readFloat(f64),
try reader.readFloat(f64),
try reader.readFloat(f64),
0,
0,
0,
};
for(entities.items()) |*ent| {
if(ent.id == id) {
ent.updatePosition(&pos, &vel, time);
break;
}
}
}
}
};