Start working on the client-side entity code.

This commit is contained in:
IntegratedQuantum 2022-10-09 10:29:18 +02:00
parent 6a5ea7f1d9
commit d0d4b624c1
4 changed files with 311 additions and 0 deletions

136
src/entity.zig Normal file
View File

@ -0,0 +1,136 @@
const std = @import("std");
const JsonElement = @import("json.zig").JsonElement;
const renderer = @import("renderer.zig");
const settings = @import("settings.zig");
const utils = @import("utils.zig");
const vec = @import("vec.zig");
const Vec3d = vec.Vec3d;
const Vec3f = vec.Vec3f;
pub const ClientEntity = struct {
interpolatedValues: utils.GenericInterpolation(6) = undefined,
width: f64,
height: f64,
// TODO:
// public final EntityType type;
pos: Vec3d = undefined,
rot: Vec3f = undefined,
id: u32,
name: []const u8,
pub fn init(self: *ClientEntity) void {
self.interpolatedValues.init();
}
pub fn getRenderPosition(self: *ClientEntity) Vec3d {
return Vec3d{.x = self.pos.x, .y = self.pos.y + self.height/2, .z = self.pos.z};
}
pub fn updatePosition(self: *ClientEntity, pos: [3]f64, vel: [3]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.x = self.interpolatedValues.outPos[0];
self.pos.y = self.interpolatedValues.outPos[1];
self.pos.z = self.interpolatedValues.outPos[2];
self.rot.x = @floatCast(f32, self.interpolatedValues.outPos[3]);
self.rot.y = @floatCast(f32, self.interpolatedValues.outPos[4]);
self.rot.z = @floatCast(f32, self.interpolatedValues.outPos[5]);
}
};
pub const ClientEntityManager = struct {
var lastTime: i16 = 0;
var timeDifference: utils.TimeDifference = utils.TimeDifference{};
pub var entities: std.ArrayList(ClientEntity) = undefined;
pub var mutex: std.Thread.Mutex = std.Thread.Mutex{};
pub fn init() void {
entities = std.ArrayList(ClientEntity).init(renderer.RenderStructure.allocator); // TODO: Use world allocator.
}
pub fn deinit() void {
entities.deinit();
}
pub fn clear() void {
entities.clearRetainingCapacity();
timeDifference = utils.TimeDifference{};
}
pub fn update() void {
mutex.lock();
defer mutex.unlock();
var time = @intCast(i16, std.time.milliTimestamp() & 65535);
time -%= timeDifference.difference;
for(entities.items) |*ent| {
ent.update(time, lastTime);
}
lastTime = time;
}
pub fn addEntity(json: JsonElement) !void {
mutex.lock();
defer mutex.unlock();
var ent = try entities.addOne();
ent.* = ClientEntity{
.id = json.get(u32, "id", std.math.maxInt(u32)),
// TODO:
// CubyzRegistries.ENTITY_REGISTRY.getByID(json.getString("type", null)),
.width = json.get(f64, "width", 1),
.height = json.get(f64, "height", 1),
.name = json.get([]const u8, "name", 1),
};
ent.init();
}
pub fn removeEntity(id: u32) void {
mutex.lock();
defer mutex.unlock();
for(entities.items) |*ent, i| {
if(ent.id == id) {
entities.swapRemove(i);
break;
}
}
}
pub fn serverUpdate(time: i16, data: []const u8) !void {
mutex.lock();
defer mutex.unlock();
timeDifference.addDataPoint(time);
std.debug.assert(data.len%(4 + 24 + 12 + 24) == 0);
var remaining = data;
while(remaining.len != 0) {
const id = std.mem.readIntBig(u32, remaining[0..4]);
remaining = remaining[4..];
const pos = [_]f64 {
@bitCast(f64, std.mem.readIntBig(u64, remaining[0..8])),
@bitCast(f64, std.mem.readIntBig(u64, remaining[8..16])),
@bitCast(f64, std.mem.readIntBig(u64, remaining[16..24])),
@floatCast(f64, @bitCast(f32, std.mem.readIntBig(u32, remaining[24..28]))),
@floatCast(f64, @bitCast(f32, std.mem.readIntBig(u32, remaining[28..32]))),
@floatCast(f64, @bitCast(f32, std.mem.readIntBig(u32, remaining[32..36]))),
};
remaining = remaining[36..];
const vel = [_]f64 {
@bitCast(f64, std.mem.readIntBig(u64, remaining[0..8])),
@bitCast(f64, std.mem.readIntBig(u64, remaining[8..16])),
@bitCast(f64, std.mem.readIntBig(u64, remaining[16..24])),
0, 0, 0,
};
remaining = remaining[24..];
for(entities.items) |*ent| {
if(ent.id == id) {
ent.updatePosition(pos, vel, time);
}
}
}
}
};

View File

@ -3,6 +3,7 @@ const std = @import("std");
const assets = @import("assets.zig"); const assets = @import("assets.zig");
const blocks = @import("blocks.zig"); const blocks = @import("blocks.zig");
const chunk = @import("chunk.zig"); const chunk = @import("chunk.zig");
const entity = @import("entity.zig");
const game = @import("game.zig"); const game = @import("game.zig");
const graphics = @import("graphics.zig"); const graphics = @import("graphics.zig");
const renderer = @import("renderer.zig"); const renderer = @import("renderer.zig");
@ -245,6 +246,9 @@ pub fn main() !void {
try renderer.init(); try renderer.init();
defer renderer.deinit(); defer renderer.deinit();
entity.ClientEntityManager.init();
defer entity.ClientEntityManager.deinit();
network.init(); network.init();
try renderer.RenderStructure.init(); try renderer.RenderStructure.init();

View File

@ -3,6 +3,8 @@
pub const defaultPort: u16 = 47649; pub const defaultPort: u16 = 47649;
pub const connectionTimeout = 60000; pub const connectionTimeout = 60000;
pub const entityLookback: i16 = 100;
pub const version = "0.12.0"; pub const version = "0.12.0";
pub const highestLOD: u5 = 5; pub const highestLOD: u5 = 5;

View File

@ -334,4 +334,173 @@ pub const ThreadPool = struct {
defer self.loadList.mutex.unlock(); defer self.loadList.mutex.unlock();
return self.loadList.size; return self.loadList.size;
} }
};
pub fn GenericInterpolation(comptime elements: comptime_int) type {
const frames: usize = 8;
return struct {
lastPos: [frames][elements]f64,
lastVel: [frames][elements]f64,
lastTimes: [frames]i16,
frontIndex: u32,
currentPoint: i32,
outPos: [elements]f64,
outVel: [elements]f64,
pub fn initPosition(self: *@This(), initialPosition: *[elements]f64) void {
std.mem.copy(f64, &self.outPos, initialPosition);
std.mem.set([elements]f64, &self.lastPos, self.outPos);
std.mem.set(f64, &self.outVel, 0);
std.mem.set([elements]f64, &self.lastVel, self.outVel);
self.frontIndex = 0;
self.currentPoint = -1;
}
pub fn init(self: *@This(), initialPosition: *[elements]f64, initialVelocity: *[elements]f64) void {
std.mem.copy(f64, &self.outPos, initialPosition);
std.mem.set([elements]f64, &self.lastPos, self.outPos);
std.mem.copy(f64, &self.outVel, initialVelocity);
std.mem.set([elements]f64, &self.lastVel, self.outVel);
self.frontIndex = 0;
self.currentPoint = -1;
}
pub fn updatePosition(self: *@This(), pos: *[elements]f64, vel: *[elements]f64, time: i16) void {
self.frontIndex = (self.frontIndex + 1)%frames;
std.mem.copy(f64, &self.lastPos[self.frontIndex], pos);
std.mem.copy(f64, &self.lastVel[self.frontIndex], vel);
self.lastTimes[self.frontIndex] = time;
}
fn evaluateSplineAt(_t: f64, tScale: f64, p0: f64, _m0: f64, p1: f64, _m1: f64) [2]f64 {
// https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_(0,_1)
const t = _t/tScale;
const m0 = _m0*tScale;
const m1 = _m1*tScale;
const t2 = t*t;
const t3 = t2*t;
const a0 = p0;
const a1 = m0;
const a2 = -3*p0 - 2*m0 + 3*p1 - m1;
const a3 = 2*p0 + m0 - 2*p1 + m1;
return [_]f64 {
a0 + a1*t + a2*t2 + a3*t3, // value
(a1 + 2*a2*t + 3*a3*t2)/tScale, // first derivative
};
}
fn interpolateCoordinate(self: *@This(), i: u32, t: f64, tScale: f64) void {
if(self.outVel[i] == 0 and self.lastVel[self.currentPoint][i] == 0) {
self.outPos += (self.lastPos[self.currentPoint][i] - self.outPos[i])*t/tScale;
} else {
// Use cubic interpolation to interpolate the velocity as well.
const newValue = evaluateSplineAt(t, tScale, self.outPos[i], self.outVel[i], self.lastPos[self.currentPoint][i], self.lastVel[self.currentPoint][i]);
self.outPos = newValue[0];
self.outVel = newValue[1];
}
}
fn determineNextDataPoint(self: *@This(), time: i16, lastTime: *i16) void {
if(self.currentPoint != -1 and self.lastTimes[self.currentPoint] -% time <= 0) {
// Jump to the last used value and adjust the time to start at that point.
lastTime.* = self.lastTimes[self.currentPoint];
std.mem.copy(f64, &self.outPos, &self.lastPos[self.currentPoint]);
std.mem.copy(f64, &self.outVel, &self.lastVel[self.currentPoint]);
self.currentPoint = -1;
}
if(self.currentPoint == -1) {
// Need a new point:
var smallestTime: i16 = std.math.maxInt(i16);
var smallestIndex: i32 = -1;
for(self.lastTimes) |_, i| {
// Only using a future time value that is far enough away to prevent jumping.
if(self.lastTimes[i] -% time >= 50 and self.lastTimes[i] -% time < smallestTime) {
smallestTime = self.lastTimes[i] -% time;
smallestIndex = i;
}
}
self.currentPoint = smallestIndex;
}
}
pub fn update(self: *@This(), time: i16, _lastTime: i16) void {
var lastTime = _lastTime;
self.determineNextDataPoint(time, &lastTime);
var deltaTime = @intToFloat(f64, time -% lastTime)/1000;
if(deltaTime < 0) {
std.log.err("Experienced time travel. Current time: {} Last time: {}", .{time, lastTime});
deltaTime = 0;
}
if(self.currentPoint == -1) {
for(self.outPos) |*pos, i| {
// Just move on with the current velocity.
pos.* += self.outVel[i]*deltaTime;
// Add some drag to prevent moving far away on short connection loss.
self.outVel[i] *= std.math.pow(f64, 0.5, deltaTime);
}
} else {
const tScale = @intToFloat(f64, self.lastTimes[self.currentPoint] -% lastTime)/1000;
const t = deltaTime;
for(self.outPos) |_, i| {
self.interpolateCoordinate(i, t, tScale);
}
}
}
pub fn updateIndexed(self: *@This(), time: i16, _lastTime: i16, indices: []u16, coordinatesPerIndex: comptime_int) void {
var lastTime = _lastTime;
self.determineNextDataPoint(time, &lastTime);
var deltaTime = @intToFloat(f64, time -% lastTime)/1000;
if(deltaTime < 0) {
std.log.err("Experienced time travel. Current time: {} Last time: {}", .{time, lastTime});
deltaTime = 0;
}
if(self.currentPoint == -1) {
for(indices) |i| {
const index = i*coordinatesPerIndex;
var j: u32 = 0;
while(j < coordinatesPerIndex): (j += 1) {
// Just move on with the current velocity.
self.outPos[index + j] += self.outVel[index + j]*deltaTime;
// Add some drag to prevent moving far away on short connection loss.
self.outVel[index + j] *= std.math.pow(f64, 0.5, deltaTime);
}
}
} else {
const tScale = @intToFloat(f64, self.lastTimes[self.currentPoint] -% lastTime)/1000;
const t = deltaTime;
for(indices) |i| {
const index = i*coordinatesPerIndex;
var j: u32 = 0;
while(j < coordinatesPerIndex): (j += 1) {
self.interpolateCoordinate(index + j, t, tScale);
}
}
}
}
};
}
pub const TimeDifference = struct {
difference: i16 = 0,
firstValue: bool = true,
pub fn addDataPoint(self: *TimeDifference, time: i16) void {
const currentTime = @intCast(i16, std.time.milliTimestamp() & 65535);
const timeDifference = currentTime -% time;
if(self.firstValue) {
self.difference = timeDifference;
self.firstValue = false;
}
if(timeDifference -% self.difference > 0) {
self.difference +%= 1;
} else if(timeDifference -% self.difference < 0) {
self.difference -%= 1;
}
}
}; };