Quaternion rotation!

This commit is contained in:
duck
2025-05-15 00:08:01 +05:00
parent 12285321d2
commit 796d2bfb35
4 changed files with 165 additions and 45 deletions

View File

@@ -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 => {},
}
}

View File

@@ -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);

View File

@@ -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| {