Implemented camera rotation and zooming

This commit is contained in:
Lorenzo Torres 2025-08-13 03:44:25 +02:00
parent 68ccaf8b68
commit d537d89819
10 changed files with 153 additions and 18 deletions

View file

@ -3,6 +3,11 @@ const Allocator = std.mem.Allocator;
const Input = @This();
pub const ScrollDirection = enum {
up,
down
};
pub const KeyCode = enum(u32) {
space = 32,
apostrophe = 39,

View file

@ -1,3 +1,5 @@
const std = @import("std");
pub const components = @import("components.zig");
pub const entities = @import("entities.zig");
pub const Input = @import("Input.zig");
@ -11,3 +13,31 @@ pub const Pool = entities.Pool;
pub const Resources = entities.Resources;
pub const System = *const fn (*Pool) anyerror!void;
pub const SystemGroup = []const System;
pub const hooks = struct {
pub const Scroll = *const fn (*Pool, direction: Input.ScrollDirection) anyerror!void;
pub const Key = *const fn (*Pool, key: Input.KeyCode) anyerror!void;
pub var key: std.ArrayList(Key) = undefined;
pub var scroll: std.ArrayList(Scroll) = undefined;
pub const Layer = enum {
key,
scroll
};
pub fn init(allocator: std.mem.Allocator) !void {
key = std.ArrayList(Key).init(allocator);
scroll = std.ArrayList(Scroll).init(allocator);
}
pub fn addHook(comptime layer: Layer, hook: anytype) !void {
var list = comptime switch (layer) {
.key => &key,
.scroll => &scroll,
};
try list.append(hook);
}
};

View file

@ -8,7 +8,8 @@ const Camera = rendering.Camera;
const ecs = @import("ecs.zig");
const Input = ecs.Input;
pub const System = ecs.System;
pub const System = *const fn (*Pool) anyerror!void;
pub const SystemGroup = []const System;
pub const SyncGroup = []const System;

24
src/ecs/hooks.zig Normal file
View file

@ -0,0 +1,24 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
pub var key: std.ArrayList(ecs.System) = undefined;
pub var scroll: std.ArrayList(ecs.System) = undefined;
pub const Layer = enum {
key,
scroll
};
pub fn init(allocator: Allocator) !void {
key = std.ArrayList(ecs.System).init(allocator);
scroll = std.ArrayList(ecs.System).init(allocator);
}
pub fn addHook(layer: Layer, hook: ecs.System) !void {
var list = switch (layer) {
.key => &key,
.scroll => &scroll,
};
list.append(hook);
}

View file

@ -3,6 +3,7 @@ pub const tan = std.math.tan;
pub const cos = std.math.cos;
pub const sin = std.math.sin;
pub const rad = std.math.degreesToRadians;
pub const deg = std.math.radiansToDegrees;
pub const sqrt = std.math.sqrt;
pub const Axis = struct {
@ -190,6 +191,31 @@ pub const Quaternion = extern struct {
};
}
//pub fn rotateVector(q: Quaternion, v: @Vector(3, f32)) @Vector(3, f32) {
// const quaternion = q.normalize();
// const u = @Vector(3, f32){quaternion.x, quaternion.y, quaternion.z};
// const s = quaternion.w;
//
// return scaleVector(u, 2.0 * dot(u, v))
// + scaleVector(v, s*s - dot(u, u))
// + scaleVector(cross(u, v), 2.0 * s);
//}
pub fn rotateVector(self: Quaternion, vec: @Vector(3, f32)) @Vector(3, f32) {
const vec_quat: Quaternion = .{ .w = 0, .x = vec[0], .y = vec[1], .z = vec[2] };
const conj: Quaternion = .{
.w = self.w,
.x = -self.x,
.y = -self.y,
.z = -self.z,
};
const rotated = self.mul(vec_quat).mul(conj);
return @Vector(3, f32){ rotated.x, rotated.y, rotated.z };
}
inline fn mul(a: Quaternion, b: Quaternion) Quaternion {
return .{
@ -210,7 +236,7 @@ pub const Quaternion = extern struct {
};
}
fn matrix(q: Quaternion) Matrix {
pub fn matrix(q: Quaternion) Matrix {
const x2 = q.x + q.x;
const y2 = q.y + q.y;
const z2 = q.z + q.z;
@ -247,3 +273,7 @@ pub fn cross(a: @Vector(3, f32), b: @Vector(3, f32)) @Vector(3, f32) {
pub fn normalize(a: @Vector(3, f32)) @Vector(3, f32) {
return a / @as(@Vector(3, f32), @splat(@sqrt(dot(a, a))));
}
pub inline fn scaleVector(a: @Vector(3, f32), s: f32) @Vector(3, f32) {
return a * @as(@Vector(3, f32), @splat(s));
}

View file

@ -14,38 +14,44 @@ pub const Uniform = struct {
position: @Vector(3, f32),
target: @Vector(3, f32) = .{ 0.0, 0.0, -1.0 },
up: @Vector(3, f32) = .{ 0.0, 1.0, 0.0 },
speed: f32 = 5,
speed: f32 = 10,
pitch: f32 = -45,
yaw: f32 = 0,
distance: f32 = 5.0,
pub fn getProjection(width: usize, height: usize) math.Matrix {
return math.Matrix.perspective(math.rad(45.0), (@as(f32, @floatFromInt(width)) / @as(f32, @floatFromInt(height))), 0.1, 100.0);
}
pub fn getView(self: *Camera) math.Matrix {
_ = self.getTarget();
return math.Matrix.lookAt(self.position, math.rad(self.yaw), math.rad(self.pitch));
}
pub fn getTarget(self: *Camera) @Vector(3, f32) {
const direction: @Vector(3, f32) = .{
pub inline fn getDirection(self: *Camera) @Vector(3, f32) {
return .{
math.sin(math.rad(self.yaw)) * math.cos(math.rad(self.pitch)),
math.sin(math.rad(self.pitch)),
math.cos(math.rad(self.yaw)) * math.cos(math.rad(self.pitch)),
};
}
const t = (self.position[1] - (self.position[1] - self.distance)) / direction[1];
pub fn getTarget(self: *Camera) @Vector(3, f32) {
const direction = self.getDirection();
const t = self.position[1] / direction[1];
const target: @Vector(3, f32) = .{
self.position[0] + (t*direction[0]),
(self.position[1] - self.distance),
self.position[2] + (t*direction[2]),
0.0,
self.position[2] - (t*direction[2]),
};
//target[2] = 0.0;
std.debug.print("{} {} {}\n", .{direction, t, target});
return target;
}
pub fn rotateAround(self: *Camera, pivot: @Vector(3, f32), angle: f32) void {
self.yaw -= math.deg(angle);
var rotation = math.Quaternion.fromAxisAngle(.{0.0, 1.0, 0.0}, angle);
self.position = rotation.rotateVector(self.position - pivot) + pivot;
}

View file

@ -138,6 +138,8 @@ export fn sideros_init(init: api.GameInit) callconv(.c) void {
.input = &input,
};
ecs.hooks.init(allocator) catch @panic("TODO: handle this");
ecs.hooks.addHook(.scroll, systems.zoomCamera) catch @panic("TODO handle this");
pool = ecs.Pool.init(allocator, &resources) catch @panic("TODO: Gracefully handle error");
// TODO(ernesto): I think this @ptrCast are unavoidable but maybe not?
renderer = Renderer.init(allocator, @ptrCast(init.instance), @ptrCast(init.surface)) catch @panic("TODO: Gracefully handle error");
@ -178,3 +180,9 @@ export fn sideros_key_callback(key: u32, release: bool) callconv(.c) void {
}
}
}
export fn sideros_scroll_callback(up: bool) callconv(.c) void {
for (ecs.hooks.scroll.items) |hook| {
hook(&pool, if (up) .up else .down) catch @panic("TODO: actually handle this");
}
}

View file

@ -13,4 +13,5 @@ typedef struct {
void sideros_init(GameInit init);
void sideros_update(GameUpdate state);
void sideros_key_callback(unsigned int key, bool release);
void sideros_scroll_callback(bool up);
void sideros_cleanup(void);

View file

@ -1,6 +1,7 @@
const ecs = @import("ecs");
const math = @import("math");
const std = @import("std");
const Input = ecs.Input;
pub fn render(pool: *ecs.Pool) anyerror!void {
var renderer = pool.resources.renderer;
@ -32,17 +33,38 @@ pub fn moveCamera(pool: *ecs.Pool) !void {
const input = pool.resources.input;
var camera = pool.resources.camera;
const mul = @as(@Vector(3, f32), @splat(camera.speed * pool.resources.delta_time));
var forward = camera.getDirection();
forward[0] = -forward[0];
forward[1] = 0.0;
const left = math.cross(forward, camera.up);
if (input.isKeyDown(.w)) {
camera.position += @as(@Vector(3, f32), .{0.0, 0.0, 1.0}) * mul;
camera.position += forward * mul;
}
if (input.isKeyDown(.s)) {
camera.position += @as(@Vector(3, f32), .{0.0, 0.0, -1.0}) * mul;
camera.position -= forward * mul;
}
if (input.isKeyDown(.a)) {
camera.position -= @as(@Vector(3, f32), .{1.0, 0.0, 0.0}) * mul;
camera.position += left * mul;
}
if (input.isKeyDown(.d)) {
camera.position += @as(@Vector(3, f32), .{1.0, 0.0, 0.0}) * mul;
camera.position -= left * mul;
}
if (input.isKeyDown(.q)) {
camera.rotateAround(camera.getTarget(), math.rad(100.0 * pool.resources.delta_time));
}
if (input.isKeyDown(.e)) {
camera.rotateAround(camera.getTarget(), math.rad(-100.0 * pool.resources.delta_time));
}
}
pub fn zoomCamera(pool: *ecs.Pool, direction: Input.ScrollDirection) !void {
var camera = pool.resources.camera;
var camera_direction = camera.getDirection();
camera_direction[0] = -camera_direction[0];
if (direction == .up) {
camera.position += camera_direction;
} else {
camera.position -= camera_direction;
}
}

View file

@ -154,7 +154,7 @@ pub fn main() !void {
const screen = iter.data;
const mask = c.XCB_CW_EVENT_MASK;
const value = c.XCB_EVENT_MASK_EXPOSURE | c.XCB_EVENT_MASK_KEY_PRESS | c.XCB_EVENT_MASK_KEY_RELEASE;
const value = c.XCB_EVENT_MASK_EXPOSURE | c.XCB_EVENT_MASK_KEY_PRESS | c.XCB_EVENT_MASK_KEY_RELEASE | c.XCB_EVENT_MASK_BUTTON_PRESS;
const window = c.xcb_generate_id(connection);
_ = c.xcb_create_window(connection, c.XCB_COPY_FROM_PARENT, window, screen.*.root, 0, 0, 800, 600, 10, c.XCB_WINDOW_CLASS_INPUT_OUTPUT, screen.*.root_visual, mask, &value);
@ -190,6 +190,14 @@ pub fn main() !void {
const key = c.xcb_key_symbols_get_keysym(keysyms, ev.detail, 0);
sideros.sideros_key_callback(mapKeysym(key), true);
},
c.XCB_BUTTON_PRESS => {
const ev: *c.xcb_button_press_event_t = @ptrCast(e);
switch (ev.detail) {
4 => sideros.sideros_scroll_callback(true),
5 => sideros.sideros_scroll_callback(false),
else => {},
}
},
else => {},
}
std.c.free(e);