diff --git a/src/game.zig b/src/game.zig index 1324d5bc..a5d6863d 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,8 +1,11 @@ const std = @import("std"); const assets = @import("assets.zig"); +const main = @import("main.zig"); +const keyboard = &main.keyboard; const vec = @import("vec.zig"); const Vec3f = vec.Vec3f; +const Vec3d = vec.Vec3d; const Mat4f = vec.Mat4f; const graphics = @import("graphics.zig"); const Fog = graphics.Fog; @@ -30,6 +33,9 @@ pub const camera = struct { } }; +pub var playerPos: Vec3d = Vec3d{.x=0, .y=0, .z=0}; +pub var isFlying: bool = true; + pub var blockPalette: *assets.BlockPalette = undefined; pub const World = u1; // TODO pub var testWorld: World = 0; @@ -38,4 +44,53 @@ pub var world: ?*World = &testWorld; pub var projectionMatrix: Mat4f = Mat4f.identity(); pub var lodProjectionMatrix: Mat4f = Mat4f.identity(); -pub var fog = Fog{.active = true, .color=.{.x=0, .y=1, .z=0.5}, .density=1.0/15.0/256.0}; \ No newline at end of file +pub var fog = Fog{.active = true, .color=.{.x=0, .y=1, .z=0.5}, .density=1.0/15.0/256.0}; + + +pub fn update(deltaTime: f64) void { + var movement = Vec3d{.x=0, .y=0, .z=0}; + var forward = Vec3d.rotateY(Vec3d{.x=0, .y=0, .z=-1}, -camera.rotation.y); + var right = Vec3d{.x=forward.z, .y=0, .z=-forward.x}; + if(keyboard.forward.pressed) { + if(keyboard.sprint.pressed) { + if(isFlying) { + movement.addEqual(forward.mulScalar(64)); + } else { + movement.addEqual(forward.mulScalar(8)); + } + } else { + movement.addEqual(forward.mulScalar(4)); + } + } + if(keyboard.backward.pressed) { + movement.addEqual(forward.mulScalar(-4)); + } + if(keyboard.left.pressed) { + movement.addEqual(right.mulScalar(4)); + } + if(keyboard.right.pressed) { + movement.addEqual(right.mulScalar(-4)); + } + if(keyboard.jump.pressed) { + if(isFlying) { + if(keyboard.sprint.pressed) { + movement.y = 59.45; + } else { + movement.y = 5.45; + } + } else { // TODO: if (Cubyz.player.isOnGround()) + movement.y = 5.45; + } + } + if(keyboard.fall.pressed) { + if(isFlying) { + if(keyboard.sprint.pressed) { + movement.y = -59.45; + } else { + movement.y = -5.45; + } + } + } + + playerPos.addEqual(movement.mulScalar(deltaTime)); +} \ No newline at end of file diff --git a/src/main.zig b/src/main.zig index 8f5884e9..e24106b7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -11,6 +11,7 @@ const settings = @import("settings.zig"); const utils = @import("utils.zig"); const Vec2f = @import("vec.zig").Vec2f; +const Vec3d = @import("vec.zig").Vec3d; pub const c = @cImport ({ @cInclude("glad/glad.h"); @@ -43,20 +44,55 @@ pub fn log( nosuspend std.io.getStdErr().writer().print(color ++ format ++ "\x1b[0m\n", args) catch {}; } +const Key = struct { + pressed: bool = false, + key: c_int = c.GLFW_KEY_UNKNOWN, + scancode: c_int = 0, + releaseAction: ?*const fn() void = null, +}; +pub var keyboard: struct { + forward: Key = Key{.key = c.GLFW_KEY_W}, + left: Key = Key{.key = c.GLFW_KEY_A}, + backward: Key = Key{.key = c.GLFW_KEY_S}, + right: Key = Key{.key = c.GLFW_KEY_D}, + sprint: Key = Key{.key = c.GLFW_KEY_LEFT_CONTROL}, + jump: Key = Key{.key = c.GLFW_KEY_SPACE}, + fall: Key = Key{.key = c.GLFW_KEY_LEFT_SHIFT}, + fullscreen: Key = Key{.key = c.GLFW_KEY_F11, .releaseAction = &Window.toggleFullscreen}, +} = .{}; + pub const Window = struct { var isFullscreen: bool = false; pub var width: u31 = 1280; pub var height: u31 = 720; var window: *c.GLFWwindow = undefined; + pub var grabbed: bool = false; const GLFWCallbacks = struct { fn errorCallback(errorCode: c_int, description: [*c]const u8) callconv(.C) void { std.log.err("GLFW Error({}): {s}", .{errorCode, description}); } fn keyCallback(_: ?*c.GLFWwindow, key: c_int, scancode: c_int, action: c_int, mods: c_int) callconv(.C) void { - std.log.info("Key pressed: {}, {}, {}, {}", .{key, scancode, action, mods}); - if(key == c.GLFW_KEY_F11 and action == c.GLFW_RELEASE) { - toggleFullscreen(); + if(action == c.GLFW_PRESS) { + inline for(@typeInfo(@TypeOf(keyboard)).Struct.fields) |field| { + if(key == @field(keyboard, field.name).key) { + if(key != c.GLFW_KEY_UNKNOWN or scancode == @field(keyboard, field.name).scancode) { + @field(keyboard, field.name).pressed = true; + } + } + } + } else if(action == c.GLFW_RELEASE) { + inline for(@typeInfo(@TypeOf(keyboard)).Struct.fields) |field| { + if(key == @field(keyboard, field.name).key) { + if(key != c.GLFW_KEY_UNKNOWN or scancode == @field(keyboard, field.name).scancode) { + @field(keyboard, field.name).pressed = false; + if(@field(keyboard, field.name).releaseAction) |releaseAction| { + releaseAction(); + } + } + } + } } + std.log.info("Key pressed: {}, {}, {}, {}", .{key, scancode, action, mods}); } fn framebufferSize(_: ?*c.GLFWwindow, newWidth: c_int, newHeight: c_int) callconv(.C) void { std.log.info("Framebuffer: {}, {}", .{newWidth, newHeight}); @@ -64,6 +100,31 @@ pub const Window = struct { height = @intCast(u31, newHeight); renderer.updateViewport(width, height, settings.fov); } + // Mouse deltas are averaged over multiple frames using a circular buffer: + const deltasLen: u2 = 3; + var deltas: [deltasLen]Vec2f = [_]Vec2f{Vec2f{.x=0, .y=0}} ** 3; + var deltaBufferPosition: u2 = 0; + var currentPos: Vec2f = Vec2f{.x=0, .y=0}; + var ignoreDataAfterRecentGrab: bool = true; + fn cursorPosition(_: ?*c.GLFWwindow, x: f64, y: f64) callconv(.C) void { + const newPos = Vec2f { + .x = @floatCast(f32, x), + .y = @floatCast(f32, y), + }; + if(grabbed and !ignoreDataAfterRecentGrab) { + deltas[deltaBufferPosition].addEqual(newPos.sub(currentPos).mulScalar(settings.mouseSensitivity)); + var averagedDelta: Vec2f = Vec2f{.x=0, .y=0}; + for(deltas) |delta| { + averagedDelta.addEqual(delta); + } + averagedDelta.divEqualScalar(deltasLen); + game.camera.moveRotation(averagedDelta.x*0.0089, averagedDelta.y*0.0089); + deltaBufferPosition = (deltaBufferPosition + 1)%deltasLen; + deltas[deltaBufferPosition] = Vec2f{.x=0, .y=0}; + } + ignoreDataAfterRecentGrab = false; + currentPos = newPos; + } fn glDebugOutput(_: c_uint, typ: c_uint, _: c_uint, severity: c_uint, length: c_int, message: [*c]const u8, _: ?*const anyopaque) callconv(.C) void { if(typ == c.GL_DEBUG_TYPE_ERROR or typ == c.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR or typ == c.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR or typ == c.GL_DEBUG_TYPE_PORTABILITY or typ == c.GL_DEBUG_TYPE_PERFORMANCE) { std.log.err("OpenGL {}:{s}", .{severity, message[0..@intCast(usize, length)]}); @@ -72,6 +133,20 @@ pub const Window = struct { } }; + pub fn setMouseGrabbed(grab: bool) void { + if(grabbed != grab) { + if(!grab) { + c.glfwSetInputMode(window, c.GLFW_CURSOR, c.GLFW_CURSOR_NORMAL); + } else { + c.glfwSetInputMode(window, c.GLFW_CURSOR, c.GLFW_CURSOR_DISABLED); + if (c.glfwRawMouseMotionSupported() != 0) + c.glfwSetInputMode(window, c.GLFW_RAW_MOUSE_MOTION, c.GLFW_TRUE); + GLFWCallbacks.ignoreDataAfterRecentGrab = true; + } + grabbed = grab; + } + } + fn init() !void { _ = c.glfwSetErrorCallback(GLFWCallbacks.errorCallback); @@ -89,6 +164,7 @@ pub const Window = struct { _ = c.glfwSetKeyCallback(window, GLFWCallbacks.keyCallback); _ = c.glfwSetFramebufferSizeCallback(window, GLFWCallbacks.framebufferSize); + _ = c.glfwSetCursorPosCallback(window, GLFWCallbacks.cursorPosition); c.glfwMakeContextCurrent(window); @@ -182,6 +258,8 @@ pub fn main() !void { try network.Protocols.handShake.clientSide(conn2, "quanturmdoelvloper"); + Window.setMouseGrabbed(true); + try assets.loadWorldAssets("serverAssets", game.blockPalette); try blocks.meshes.generateTextureArray(); @@ -190,6 +268,7 @@ pub fn main() !void { c.glEnable(c.GL_BLEND); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); Window.GLFWCallbacks.framebufferSize(null, Window.width, Window.height); + var lastTime = std.time.milliTimestamp(); while(c.glfwWindowShouldClose(Window.window) == 0) { { // Check opengl errors: @@ -198,14 +277,17 @@ pub fn main() !void { std.log.err("Got opengl error: {}", .{err}); } } - game.camera.moveRotation(0.01, 0); c.glfwSwapBuffers(Window.window); c.glfwPollEvents(); - try renderer.RenderOctree.update(conn2, .{.x = 25, .y = 11, .z = -703}, 4, 2.0); + var newTime = std.time.milliTimestamp(); + var deltaTime = @intToFloat(f64, newTime -% lastTime)/1000.0; + lastTime = newTime; + game.update(deltaTime); + try renderer.RenderOctree.update(conn2, game.playerPos, 4, 2.0); { // Render the game c.glEnable(c.GL_CULL_FACE); c.glEnable(c.GL_DEPTH_TEST); - try renderer.render(.{.x = 25, .y = 11, .z = -703}); + try renderer.render(game.playerPos); } { // Render the GUI diff --git a/src/settings.zig b/src/settings.zig index f2f74caf..5c297b71 100644 --- a/src/settings.zig +++ b/src/settings.zig @@ -9,4 +9,6 @@ pub const highestLOD: u5 = 5; -pub var fov: f32 = 45; \ No newline at end of file +pub var fov: f32 = 45; + +pub var mouseSensitivity: f32 = 1; \ No newline at end of file diff --git a/src/vec.zig b/src/vec.zig index f0b60cef..eecec48c 100644 --- a/src/vec.zig +++ b/src/vec.zig @@ -8,45 +8,9 @@ pub const Vec3f = extern struct {// This one gets a bit of extra functionality f x: f32, y: f32, z: f32, + pub usingnamespace Vec3RotationMath(@This(), f32); + pub usingnamespace Vec3SpecificMath(@This(), f32); pub usingnamespace GenericVectorMath(@This(), f32); - - pub fn rotateX(self: Vec3f, angle: f32) Vec3f { - const sin = @sin(angle); - const cos = @cos(angle); // TODO: Consider using sqrt here. - return Vec3f{ - .x = self.x, - .y = self.y*cos - self.z*sin, - .z = self.y*sin + self.z*cos, - }; - } - - pub fn rotateY(self: Vec3f, angle: f32) Vec3f { - const sin = @sin(angle); - const cos = @cos(angle); // TODO: Consider using sqrt here. - return Vec3f{ - .x = self.x*cos + self.z*sin, - .y = self.y, - .z = -self.x*sin + self.z*cos, - }; - } - - pub fn rotateZ(self: Vec3f, angle: f32) Vec3f { - const sin = @sin(angle); - const cos = @cos(angle); // TODO: Consider using sqrt here. - return Vec3f{ - .x = self.x*cos - self.y*sin, - .y = self.x*sin + self.y*cos, - .z = self.z, - }; - } - - pub fn cross(self: @This(), other: @This()) @This() { - return @This() { - .x = self.y*other.z - self.z*other.y, - .y = self.z*other.x - self.x*other.z, - .z = self.x*other.y - self.y*other.x, - }; - } pub fn xyz(self: Vec4f) Vec3f { return Vec3f{.x=self.x, .y=self.y, .z=self.z}; @@ -57,6 +21,57 @@ pub const Vec4i = GenericVector4(i32); pub const Vec4f = GenericVector4(f32); pub const Vec4d = GenericVector4(f64); +fn Vec3RotationMath(comptime Vec: type, comptime T: type) type { + if(@typeInfo(Vec).Struct.fields.len == 3 and @typeInfo(T) == .Float) { + return struct{ + pub fn rotateX(self: Vec, angle: T) Vec { + const sin = @sin(angle); + const cos = @cos(angle); // TODO: Consider using sqrt here. + return Vec{ + .x = self.x, + .y = self.y*cos - self.z*sin, + .z = self.y*sin + self.z*cos, + }; + } + + pub fn rotateY(self: Vec, angle: T) Vec { + const sin = @sin(angle); + const cos = @cos(angle); // TODO: Consider using sqrt here. + return Vec{ + .x = self.x*cos + self.z*sin, + .y = self.y, + .z = -self.x*sin + self.z*cos, + }; + } + + pub fn rotateZ(self: Vec, angle: T) Vec { + const sin = @sin(angle); + const cos = @cos(angle); // TODO: Consider using sqrt here. + return Vec{ + .x = self.x*cos - self.y*sin, + .y = self.x*sin + self.y*cos, + .z = self.z, + }; + } + }; + } else { + return struct{}; + } +} + +fn Vec3SpecificMath(comptime Vec: type, comptime T: type) type { + _ = T; + return struct { + pub fn cross(self: Vec, other: Vec) Vec { + return Vec { + .x = self.y*other.z - self.z*other.y, + .y = self.z*other.x - self.x*other.z, + .z = self.x*other.y - self.y*other.x, + }; + } + }; +} + fn GenericVectorMath(comptime Vec: type, comptime T: type) type { return struct { pub fn add(self: Vec, other: Vec) Vec { @@ -103,6 +118,18 @@ fn GenericVectorMath(comptime Vec: type, comptime T: type) type { } } + pub fn divScalar(self: Vec, scalar: T) Vec { + if(@typeInfo(T) == .Float) { + var result: Vec = undefined; + inline for(@typeInfo(Vec).Struct.fields) |field| { + @field(result, field.name) = @field(self, field.name) / scalar; + } + return result; + } else { + @compileError("Not supported for integer types."); + } + } + pub fn minimum(self: Vec, other: Vec) Vec { var result: Vec = undefined; inline for(@typeInfo(Vec).Struct.fields) |field| { @@ -153,6 +180,16 @@ fn GenericVectorMath(comptime Vec: type, comptime T: type) type { } } + pub fn divEqualScalar(self: *Vec, scalar: T) void { + if(@typeInfo(T) == .Float) { + inline for(@typeInfo(Vec).Struct.fields) |field| { + @field(self, field.name) /= scalar; + } + } else { + @compileError("Not supported for integer types."); + } + } + pub fn dot(self: Vec, other: Vec) T { var result: T = 0; inline for(@typeInfo(Vec).Struct.fields) |field| { @@ -176,6 +213,8 @@ fn GenericVector3(comptime T: type) type { x: T, y: T, z: T, + pub usingnamespace Vec3RotationMath(@This(), T); + pub usingnamespace Vec3SpecificMath(@This(), T); pub usingnamespace GenericVectorMath(@This(), T); }; }