const Player = @This(); const std = @import("std"); const c = @import("const.zig"); const glfw = @import("zglfw"); const math = @import("math.zig"); const Blocks = @import("assets/Blocks.zig"); const Chunks = @import("Chunks.zig"); const Iterator2 = math.Iterator2; const Vector2 = math.Vector2; const Vector3 = math.Vector3; const Vector2Int = math.Vector2Int; const Vector3Int = math.Vector3Int; const AxisState = enum { none, positive, negative, both_positive, both_negative, pub fn getComponentFloat(self: AxisState) f32 { return switch (self) { .none => 0, .positive, .both_positive => 1, .negative, .both_negative => -1, }; } pub fn getComponentInt(self: AxisState) i32 { return switch (self) { .none => 0, .positive, .both_positive => 1, .negative, .both_negative => -1, }; } pub fn getComponentIntFrac(self: AxisState) i32 { return switch (self) { .none => 0, .positive, .both_positive => std.math.maxInt(i32), .negative, .both_negative => -std.math.maxInt(i32), }; } }; const ButtonState = struct { current: bool = false, pressed_this_frame: bool = false, released_this_frame: bool = false, pub fn reset(self: *ButtonState) void { self.pressed_this_frame = false; self.released_this_frame = false; } }; const Axis = struct { state: AxisState = .none, positive_key: glfw.Key, negative_key: glfw.Key, pub fn init(positive_key: glfw.Key, negative_key: glfw.Key) Axis { return .{ .positive_key = positive_key, .negative_key = negative_key, }; } pub fn onKeyDown(self: *Axis, key_code: glfw.Key) void { if (key_code == self.positive_key) { switch (self.state) { .none => self.state = .positive, .negative => self.state = .both_positive, else => unreachable, } } else if (key_code == self.negative_key) { switch (self.state) { .none => self.state = .negative, .positive => self.state = .both_negative, else => unreachable, } } } pub fn onKeyUp(self: *Axis, key_code: glfw.Key) void { if (key_code == self.positive_key) { switch (self.state) { .positive => self.state = .none, .both_positive, .both_negative => self.state = .negative, else => unreachable, } } else if (key_code == self.negative_key) { switch (self.state) { .negative => self.state = .none, .both_positive, .both_negative => self.state = .positive, else => unreachable, } } } pub fn getComponentFloat(self: Axis) f32 { return self.state.getComponentFloat(); } pub fn getComponentInt(self: Axis) i32 { return self.state.getComponentInt(); } pub fn getComponentIntFrac(self: Axis) i32 { return self.state.getComponentIntFrac(); } }; const Button = struct { state: ButtonState = .{}, key: glfw.Key, pub fn init(key: glfw.Key) Button { return .{ .key = key, }; } pub fn onKeyDown(self: *Button, key_code: glfw.Key) void { if (key_code == self.key) { self.state.current = true; self.state.pressed_this_frame = true; } } pub fn onKeyUp(self: *Button, key_code: glfw.Key) void { if (key_code == self.key) { self.state.current = false; self.state.released_this_frame = true; } } pub fn reset(self: *Button) void { self.state.reset(); } pub inline fn isDown(self: Button) bool { return self.state.current; } pub inline fn isPressed(self: Button) bool { return self.state.pressed_this_frame; } pub inline fn isReleased(self: Button) bool { return self.state.released_this_frame; } }; const MovementState = union(enum) { ground: struct { horizontal_velocity_sv: Vector2Int, }, air: struct { horizontal_velocity_sv: Vector2Int, vertical_velocity_sv: i32, }, flight: void, }; position_sv: Vector3Int, pitch_rad: f32, yaw_rad: f32, x_input: Axis = .init(.d, .a), y_input: Axis = .init(.w, .s), z_input: Axis = .init(.space, .left_shift), jump_input: Button = .init(.space), mouse_sensitivity: f32 = 0.002, vertical_fov_deg: f32 = 80, movement_state: MovementState, pub const camera_height_vx = 1.62; pub const horizontal_speed_sv = sv(11.0); pub const vertical_speed_sv = sv(7.49); pub const min_pitch_rad = -0.5 * std.math.pi; pub const max_pitch_rad = 0.5 * std.math.pi; pub const collision_half_width_sv = sv(0.4); pub const collision_height_sv = sv(1.8); pub const speed_sv = sv(5.612); pub const step_up_sv = sv(0.53125); pub const step_down_sv = sv(0.53125); pub const jump_velocity_sv = sv(6.1237245); pub const gravity_sv = sv(15); pub const vertical_velocity_cap_sv = sv(30); pub const ground_acceleration_sv = sv(56.12); pub const air_acceleration_sv = sv(56.12); pub const air_speed_limit_sv = sv(0.5612); pub fn init(position_sv: Vector3Int, pitch_rad: f32, yaw_rad: f32) Player { return .{ .position_sv = position_sv, .pitch_rad = pitch_rad, .yaw_rad = yaw_rad, .movement_state = .flight, }; } pub fn onKeyDown(self: *Player, key: glfw.Key) void { self.x_input.onKeyDown(key); self.y_input.onKeyDown(key); self.z_input.onKeyDown(key); } pub fn onKeyUp(self: *Player, key: glfw.Key) void { self.x_input.onKeyUp(key); self.y_input.onKeyUp(key); self.z_input.onKeyUp(key); } pub fn onMouseMove(self: *Player, dx: f32, dy: f32) void { self.pitch_rad = std.math.clamp(self.pitch_rad - dy * self.mouse_sensitivity, min_pitch_rad, max_pitch_rad); self.yaw_rad = @mod(self.yaw_rad - dx * self.mouse_sensitivity, std.math.tau); } pub fn update(self: *Player, dt: f32, chunks: *const Chunks) void { defer self.resetAllButtons(); // --- GATHER INPUTS ------------------------------------------------------- const horizontal_input_vector_frac = blk: { var ret = Vector2 .init(self.x_input.getComponentFloat(), self.y_input.getComponentFloat()) .rotate(self.yaw_rad); const len_squared = ret.lenSquared(); if (len_squared > 1) { ret = ret.divScalar(@sqrt(len_squared)); } break :blk ret.asVector2IntFrac(); }; const vertical_input_vector_frac = self.z_input.getComponentIntFrac(); // --- STATE TRANSITIONS --------------------------------------------------- switch (self.movement_state) { .ground => |ground| { if (self.jump_input.isPressed()) { self.movement_state = .{ .air = .{ .horizontal_velocity_sv = ground.horizontal_velocity_sv, .vertical_velocity_sv = jump_velocity_sv, }, }; } }, else => {}, } // --- STATE HANDLING ------------------------------------------------------ switch (self.movement_state) { .ground => {}, .air => {}, .flight => { const horizontal_velocity_sv = horizontal_input_vector_frac.mulScalarFrac(horizontal_speed_sv); const vertical_velocity_sv = math.mulFrac(vertical_input_vector_frac, vertical_speed_sv); var horizontal_displacement_sv = horizontal_velocity_sv.mulScalarFloat(dt); var vertical_displacement_sv = math.mulIntFloat(vertical_velocity_sv, dt); var position_sv = self.position_sv; var min_sv = position_sv.add(.init(-collision_half_width_sv, -collision_half_width_sv, 0)); var max_sv = position_sv.add(.init(collision_half_width_sv, collision_half_width_sv, collision_height_sv)); if (vertical_displacement_sv > 0) { if (chunks.sweepCastUp(min_sv, max_sv, vertical_displacement_sv)) |hit| { vertical_displacement_sv -= hit.projected_distance_sv; } position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv)); min_sv = .add(min_sv, .init(0, 0, vertical_displacement_sv)); max_sv = .add(max_sv, .init(0, 0, vertical_displacement_sv)); } var i: usize = 0; while (i < 2) : (i += 1) { if (chunks.sweepCastHorizontal(min_sv, max_sv, horizontal_displacement_sv)) |hit| { const adjustment = hit.normal_frac .asVector2Int() .mulScalarFrac(hit.projected_distance_sv); std.debug.print("i={} | pos={X} | n={} | d={X} | adj={X} | disp={X}->", .{ i, position_sv.vector, hit.normal_frac.asVector3Frac().vector, hit.projected_distance_sv, adjustment.vector, horizontal_displacement_sv.vector, }); horizontal_displacement_sv = .add( horizontal_displacement_sv, adjustment, ); std.debug.print("{X}\n", .{horizontal_displacement_sv.vector}); } else { break; } } position_sv = .add(position_sv, horizontal_displacement_sv.asVector3Int(0)); if (vertical_displacement_sv < 0.0) { min_sv = .add(min_sv, horizontal_displacement_sv.asVector3Int(0)); max_sv = .add(max_sv, horizontal_displacement_sv.asVector3Int(0)); if (chunks.sweepCastDown(min_sv, max_sv, -vertical_displacement_sv)) |hit| { vertical_displacement_sv += hit.projected_distance_sv; } position_sv = .add(position_sv, .init(0, 0, vertical_displacement_sv)); } self.position_sv = position_sv; }, } } fn resetAllButtons(self: *Player) void { inline for (@typeInfo(Player).@"struct".fields) |field| { if (field.type == Button) { @field(self, field.name).reset(); } } } inline fn sv(comptime vx: comptime_float) comptime_int { return @intFromFloat(@round(vx * c.sv_per_vx)); }