From 796d2bfb35389a38daaff1e936b6a0977aa8dbbd Mon Sep 17 00:00:00 2001 From: duck Date: Thu, 15 May 2025 00:08:01 +0500 Subject: [PATCH] Quaternion rotation! --- data/shaders/basic.frag | 4 +- src/game.zig | 13 ++++- src/graphics.zig | 82 ++++++++++++++++++--------- src/graphics/transform.zig | 111 +++++++++++++++++++++++++++++++------ 4 files changed, 165 insertions(+), 45 deletions(-) diff --git a/data/shaders/basic.frag b/data/shaders/basic.frag index c67c5d6..65a1710 100644 --- a/data/shaders/basic.frag +++ b/data/shaders/basic.frag @@ -7,8 +7,8 @@ layout(location = 0) out vec4 fragColor; void main() { fragColor = vec4( depth, - 0.0, - 0.0, + cos(vertexIndex * 0.5) * 0.25 + 0.75, + sin(vertexIndex * 0.5) * 0.25 + 0.75, 1.0 ); } diff --git a/src/game.zig b/src/game.zig index 534ee7d..2b8e3e6 100644 --- a/src/game.zig +++ b/src/game.zig @@ -80,9 +80,20 @@ fn processEvents(graphics: *Graphics, run_info: *RunInfo) GameError!void { run_info.running = false; }, sdl.EVENT_WINDOW_RESIZED => { - if (event.window.windowID != sdl.GetWindowID(graphics.window)) return; + if (event.window.windowID != sdl.GetWindowID(graphics.window)) continue; graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2)); }, + sdl.EVENT_MOUSE_MOTION => { + if (event.motion.windowID != sdl.GetWindowID(graphics.window)) continue; + if (@abs(event.motion.xrel) < 0.01 and @abs(event.motion.yrel) < 0.01) continue; + const Transform = @import("graphics/transform.zig"); + const delta, const length = Transform.extractNormal(.{ -event.motion.yrel, -event.motion.xrel, 0.0 }); + const rot = Transform.rotationByAxis( + delta, + length * std.math.pi / @as(f32, @floatFromInt(graphics.window_size[1])) * 2.0, + ); + graphics.mesh_transform.rotate(rot); + }, else => {}, } } diff --git a/src/graphics.zig b/src/graphics.zig index bbe865f..1011415 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -19,23 +19,52 @@ depth_texture: *sdl.GPUTexture, msaa_resolve: *sdl.GPUTexture, pipeline: *sdl.GPUGraphicsPipeline, -to_resize: ?struct { u32, u32 } = null, +window_size: [2]u32, + +camera: Camera, +mesh_transform: Transform, + +to_resize: ?[2]u32 = null, const MESH_BYTES = MESH.len * 4; const MESH_VERTS = @divExact(MESH.len, 3); const MESH = [_]f32{ - -1, 0, 4, - 0, 1, 4, - 1, -1, 6, - 1, 0, 4, - 0, -1, 4, - -1, 1, 6, - -1, 1, 6, - 1, 1, 6, - -1, -1, 6, - 1, -1, 6, - -1, -1, 6, - 1, 1, 6, + -1, 1, -1, + 1, 1, -1, + -1, -1, -1, + 1, -1, -1, + -1, -1, -1, + 1, 1, -1, + 1, 1, -1, + 1, 1, 1, + 1, -1, -1, + 1, -1, 1, + 1, -1, -1, + 1, 1, 1, + 1, 1, 1, + -1, 1, 1, + 1, -1, 1, + -1, -1, 1, + 1, -1, 1, + -1, 1, 1, + -1, 1, 1, + -1, 1, -1, + -1, -1, 1, + -1, -1, -1, + -1, -1, 1, + -1, 1, -1, + -1, 1, 1, + 1, 1, 1, + -1, 1, -1, + 1, 1, -1, + -1, 1, -1, + 1, 1, 1, + -1, -1, -1, + 1, -1, -1, + -1, -1, 1, + 1, -1, 1, + -1, -1, 1, + 1, -1, -1, }; const Self = @This(); @@ -189,6 +218,16 @@ pub fn create() GameError!Self { .depth_texture = depth_texture, .msaa_resolve = msaa_resolve, .pipeline = pipeline, + .window_size = .{ 1600, 900 }, + .camera = Camera{ + .transform = Transform{ + .position = .{ 0.0, 0.0, -6.0 }, + }, + .near = 1.0, + .far = 1024.0, + .lens = .{ 0.5 * 16.0 / 9.0, 0.5 }, + }, + .mesh_transform = Transform{}, }; } @@ -216,6 +255,8 @@ pub fn beginDraw(self: *Self) GameError!void { self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError; if (self.to_resize) |new_size| { try self.resetTextures(new_size[0], new_size[1]); + self.camera.lens[0] = self.camera.lens[1] * @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1])); + self.window_size = new_size; self.to_resize = null; } } @@ -229,7 +270,7 @@ pub fn drawDebug(self: *Self) GameError!void { if (render_target == null) return; const render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{ - .clear_color = .{ .r = 0.0, .g = 0.0, .b = 1.0, .a = 1.0 }, + .clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }, .cycle = false, .load_op = sdl.GPU_LOADOP_CLEAR, .store_op = sdl.GPU_STOREOP_RESOLVE, @@ -248,17 +289,8 @@ pub fn drawDebug(self: *Self) GameError!void { sdl.BindGPUGraphicsPipeline(render_pass, self.pipeline); sdl.BindGPUVertexBuffers(render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1); - const transform = Transform{}; - const camera = Camera{ - .transform = Transform{ - .position = .{ 0.0, 0.0, -1.0 }, - }, - .near = 1.0, - .far = 1024.0, - .lens = .{ 0.25 * 16.0 / 9.0, 0.25 }, - }; - sdl.PushGPUVertexUniformData(self.command_buffer, 0, &camera.matrix(), 16 * 4); - sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4); + sdl.PushGPUVertexUniformData(self.command_buffer, 0, &self.camera.matrix(), 16 * 4); + sdl.PushGPUVertexUniformData(self.command_buffer, 1, &self.mesh_transform.matrix(), 16 * 4); sdl.DrawGPUPrimitives(render_pass, MESH_VERTS, 1, 0, 0); sdl.EndGPURenderPass(render_pass); diff --git a/src/graphics/transform.zig b/src/graphics/transform.zig index 64157aa..6bce338 100644 --- a/src/graphics/transform.zig +++ b/src/graphics/transform.zig @@ -1,47 +1,124 @@ const std = @import("std"); const Transform = @This(); -// TODO: Rotation +// TODO: Scale -position: @Vector(3, f32) = @splat(0.0), -rotation: @Vector(3, f32) = @splat(0.0), -scale: @Vector(3, f32) = @splat(1.0), +pub const TMatrix = @Vector(16, f32); -pub fn matrix(transform: Transform) @Vector(16, f32) { +pub const Position = @Vector(3, f32); +pub const Rotation = @Vector(4, f32); +pub const Scale = @Vector(3, f32); + +position: Position = @splat(0.0), +rotation: Rotation = .{ 1.0, 0.0, 0.0, 0.0 }, +scale: Scale = @splat(1.0), + +pub fn matrix(transform: Transform) TMatrix { + const r = rotationMatrix(transform.rotation); return .{ - 1.0, 0.0, 0.0, transform.position[0], - 0.0, 1.0, 0.0, transform.position[1], - 0.0, 0.0, 1.0, transform.position[2], - 0.0, 0.0, 0.0, 1.0, + r[0], r[1], r[2], transform.position[0], + r[3], r[4], r[5], transform.position[1], + r[6], r[7], r[8], transform.position[2], + 0.0, 0.0, 0.0, 1.0, }; } -pub fn inverse(transform: Transform) @Vector(16, f32) { +pub fn inverse(transform: Transform) TMatrix { // TODO: Could we just translate, rotate and scale back instead of relying on matrix math? return invertMatrix(transform.matrix()); } -fn invertMatrix(a: @Vector(16, f32)) @Vector(16, f32) { +pub fn rotate(transform: *Transform, rotation: Rotation) void { + transform.rotation = normalizeRotation(combineRotations(transform.rotation, rotation)); +} + +pub fn normalizeRotation(r: Rotation) Rotation { + @setFloatMode(.optimized); + + const length = @sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]); + return r / @as(Rotation, @splat(length)); +} + +pub fn combineRotations(a: Rotation, b: Rotation) Rotation { + @setFloatMode(.optimized); + + return .{ + a[0] * b[0] - a[1] * b[1] - a[2] * b[2] - a[3] * b[3], + a[1] * b[0] + a[0] * b[1] + a[3] * b[2] - a[2] * b[3], + a[2] * b[0] + a[0] * b[2] + a[1] * b[3] - a[3] * b[1], + a[3] * b[0] + a[0] * b[3] + a[2] * b[1] - a[1] * b[2], + }; +} + +pub fn rotationByAxis(axis: Position, rotation: f32) Rotation { + @setFloatMode(.optimized); + + const cos = std.math.cos(rotation * 0.5); + const sin = std.math.sin(rotation * 0.5); + + return .{ cos, sin * axis[0], sin * axis[1], sin * axis[2] }; +} + +pub fn extractNormal(vector: Position) struct { Position, f32 } { + @setFloatMode(.optimized); + + const length = vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]; + return .{ vector / @as(Position, @splat(length)), length }; +} + +fn rotationMatrix(quaternion: Rotation) @Vector(9, f32) { + @setFloatMode(.optimized); + + const a = quaternion[0]; + const b = quaternion[1]; + const c = quaternion[2]; + const d = quaternion[3]; + + const s = 2.0 / (a * a + b * b + c * c + d * d); + const bs = b * s; + const cs = c * s; + const ds = d * s; + + const ab = a * bs; + const ac = a * cs; + const ad = a * ds; + const bb = b * bs; + const bc = b * cs; + const bd = b * ds; + const cc = c * cs; + const cd = c * ds; + const dd = d * ds; + + return .{ + 1 - cc - dd, bc - ad, bd + ac, + bc + ad, 1 - bb - dd, cd - ab, + bd - ac, cd + ab, 1 - bb - cc, + }; +} + +fn invertMatrix(a: TMatrix) TMatrix { + @setFloatMode(.optimized); + const MOD: f32 = 1.0 / 16.0; - const ID = @Vector(16, f32){ + const ID = TMatrix{ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, }; - var p = ID - @as(@Vector(16, f32), @splat(MOD)) * a; + var p = ID - @as(TMatrix, @splat(MOD)) * a; var output = ID + p; inline for (0..8) |_| { p = multiplyMatrix(p, p); output = multiplyMatrix(output, ID + p); } - return output * @as(@Vector(16, f32), @splat(MOD)); + return output * @as(TMatrix, @splat(MOD)); } -pub fn multiplyMatrix(a: @Vector(16, f32), b: @Vector(16, f32)) @Vector(16, f32) { - var output: @Vector(16, f32) = [1]f32{0.0} ** 16; - +pub fn multiplyMatrix(a: TMatrix, b: TMatrix) TMatrix { @setFloatMode(.optimized); + + var output: TMatrix = [1]f32{0.0} ** 16; for (0..4) |row| { for (0..4) |col| { for (0..4) |i| {