rotateTowards, key store, camera faces Z-, CCW frontface

This commit is contained in:
duck
2025-05-28 01:22:18 +05:00
parent 1e454ba428
commit 5fc8358aa7
9 changed files with 183 additions and 101 deletions

68
src/data/keystore.zig Normal file
View File

@@ -0,0 +1,68 @@
pub fn KeyStore(comptime key_type: type, comptime buf_size: usize, comptime zero: key_type) type {
return struct {
just_pressed: [buf_size]key_type = .{zero} ** buf_size,
pressed: [buf_size]key_type = .{zero} ** buf_size,
just_released: [buf_size]key_type = .{zero} ** buf_size,
const Self = @This();
pub fn reset(self: *Self) void {
self.just_pressed = .{zero} ** buf_size;
self.just_released = .{zero} ** buf_size;
}
pub fn press(self: *Self, code: key_type) void {
for (self.pressed) |pressed| {
if (pressed == code) return;
}
for (&self.pressed) |*pressed| {
if (pressed.* == zero) {
pressed.* = code;
break;
}
}
for (self.just_pressed) |just_pressed| {
if (just_pressed == code) return;
}
for (&self.just_pressed) |*just_pressed| {
if (just_pressed.* == zero) {
just_pressed.* = code;
break;
}
}
}
pub fn release(self: *Self, code: key_type) void {
for (&self.pressed) |*pressed| {
if (pressed.* == code) {
pressed.* = zero;
break;
}
}
for (self.just_released) |just_released| {
if (just_released == code) return;
}
for (&self.just_released) |*just_released| {
if (just_released.* == zero) {
just_released.* = code;
break;
}
}
}
pub fn is_just_pressed(self: *Self, code: key_type) bool {
for (self.just_pressed) |just_pressed| {
if (just_pressed == code) return true;
}
return false;
}
pub fn is_pressed(self: *Self, code: key_type) bool {
for (self.pressed) |pressed| {
if (pressed == code) return true;
}
return false;
}
pub fn is_just_released(self: *Self, code: key_type) bool {
for (self.just_released) |just_released| {
if (just_released == code) return true;
}
return false;
}
};
}

View File

@@ -1,5 +1,6 @@
const std = @import("std"); const std = @import("std");
const sdl = @import("sdl"); const sdl = @import("sdl");
const Transform = @import("graphics/transform.zig");
const Controller = @import("graph/controller.zig"); const Controller = @import("graph/controller.zig");
const Graphics = @import("graphics.zig"); const Graphics = @import("graphics.zig");
const Game = @import("game.zig"); const Game = @import("game.zig");
@@ -70,35 +71,48 @@ pub fn init(controller: *Controller, graphics: *Graphics) !void {
pub fn deinit() void {} pub fn deinit() void {}
pub fn update( pub fn update(
cube: *Cube,
mouse: *Game.Mouse, mouse: *Game.Mouse,
keyboard: *Game.Keyboard, keyboard: *Game.Keyboard,
graphics: *Graphics, graphics: *Graphics,
time: *Game.Time, time: *Game.Time,
) void { ) void {
if (keyboard.is_pressed(sdl.SCANCODE_W)) { if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) {
graphics.camera.transform.translateLocal(.{ 0.0, 0.0, 5.0 * time.delta });
}
if (keyboard.is_pressed(sdl.SCANCODE_S)) {
graphics.camera.transform.translateLocal(.{ 0.0, 0.0, -5.0 * time.delta }); graphics.camera.transform.translateLocal(.{ 0.0, 0.0, -5.0 * time.delta });
} }
if (keyboard.is_pressed(sdl.SCANCODE_D)) { if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) {
graphics.camera.transform.translateLocal(.{ 0.0, 0.0, 5.0 * time.delta });
}
if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) {
graphics.camera.transform.translateLocal(.{ 5.0 * time.delta, 0.0, 0.0 }); graphics.camera.transform.translateLocal(.{ 5.0 * time.delta, 0.0, 0.0 });
} }
if (keyboard.is_pressed(sdl.SCANCODE_A)) { if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) {
graphics.camera.transform.translateLocal(.{ -5.0 * time.delta, 0.0, 0.0 }); graphics.camera.transform.translateLocal(.{ -5.0 * time.delta, 0.0, 0.0 });
} }
if (@abs(mouse.dx) < 0.01 and @abs(mouse.dy) < 0.01) return; if (mouse.buttons.is_pressed(sdl.BUTTON_LEFT)) {
const scale = 1.0 / @as(f32, @floatFromInt(graphics.window_size[1]));
cube.transform.position[0] += mouse.dx * scale * 4.0;
cube.transform.position[1] -= mouse.dy * scale * 4.0;
const delta, const length = Graphics.Transform.extractNormal(.{ mouse.dy, mouse.dx, 0.0 }); const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
const rot = Graphics.Transform.rotationByAxis( const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
delta,
length * std.math.pi / @as(f32, @floatFromInt(graphics.window_size[1])) * 2.0, const ROTATED_DIR = Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
graphics.camera.transform.rotation = Transform.combineRotations(
INIT_ROTATION,
Transform.rotationToward(ROTATED_DIR, cube.transform.position - graphics.camera.transform.position, .{ .normalize_to = true }),
); );
graphics.camera.transform.rotateLocal(rot); }
} }
pub fn draw(cube: *Cube, graphics: *Graphics) !void { pub fn draw(cube: *Cube, graphics: *Graphics) !void {
try graphics.drawMesh(cube.mesh, Graphics.Transform{
.position = .{ 0.0, 0.0, 0.0 },
.rotation = cube.transform.rotation,
.scale = cube.transform.scale,
});
for (OFFSETS) |offset| { for (OFFSETS) |offset| {
try graphics.drawMesh(cube.mesh, Graphics.Transform{ try graphics.drawMesh(cube.mesh, Graphics.Transform{
.position = cube.transform.position + offset, .position = cube.transform.position + offset,

View File

@@ -27,6 +27,7 @@ pub fn init(alloc: std.mem.Allocator) GameError!Self {
var controller = try graph.getController(); var controller = try graph.getController();
controller.addResource(graphics); controller.addResource(graphics);
controller.addResource(Mouse{ controller.addResource(Mouse{
.buttons = .{},
.x = 0.0, .x = 0.0,
.y = 0.0, .y = 0.0,
.dx = 0.0, .dx = 0.0,
@@ -106,7 +107,7 @@ fn processEvents(
) GameError!void { ) GameError!void {
mouse.dx = 0.0; mouse.dx = 0.0;
mouse.dy = 0.0; mouse.dy = 0.0;
keyboard.reset(); keyboard.keys.reset();
sdl.PumpEvents(); sdl.PumpEvents();
while (true) { while (true) {
@@ -131,11 +132,19 @@ fn processEvents(
}, },
sdl.EVENT_KEY_DOWN => { sdl.EVENT_KEY_DOWN => {
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue; if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
keyboard.press(event.key.scancode); keyboard.keys.press(event.key.scancode);
}, },
sdl.EVENT_KEY_UP => { sdl.EVENT_KEY_UP => {
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue; if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
keyboard.release(event.key.scancode); keyboard.keys.release(event.key.scancode);
},
sdl.EVENT_MOUSE_BUTTON_DOWN => {
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
mouse.buttons.press(event.button.button);
},
sdl.EVENT_MOUSE_BUTTON_UP => {
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
mouse.buttons.release(event.button.button);
}, },
else => {}, else => {},
} }

View File

@@ -189,7 +189,7 @@ pub fn create() GameError!Self {
.camera = Camera{ .camera = Camera{
.transform = Transform{ .transform = Transform{
.position = .{ 0.0, 0.0, -4.0 }, .position = .{ 0.0, 0.0, 4.0 },
}, },
.near = 1.0, .near = 1.0,
.far = 1024.0, .far = 1024.0,

View File

@@ -17,8 +17,8 @@ pub fn matrix(camera: Camera) @Vector(16, f32) {
const projection = @Vector(16, f32){ const projection = @Vector(16, f32){
xx, 0, 0, 0, xx, 0, 0, 0,
0, yy, 0, 0, 0, yy, 0, 0,
0, 0, zz, wz, 0, 0, -zz, wz,
0, 0, 1, 0, 0, 0, -1, 0,
}; };
return Transform.multiplyMatrix(projection, camera.transform.inverseMatrix()); return Transform.multiplyMatrix(projection, camera.transform.inverseMatrix());
} }

View File

@@ -20,5 +20,10 @@ pub const DEPTH_ENABLED = sdl.GPUDepthStencilState{
pub const RASTERIZER_CULL = sdl.GPURasterizerState{ pub const RASTERIZER_CULL = sdl.GPURasterizerState{
.cull_mode = sdl.GPU_CULLMODE_BACK, .cull_mode = sdl.GPU_CULLMODE_BACK,
.fill_mode = sdl.GPU_FILLMODE_FILL, .fill_mode = sdl.GPU_FILLMODE_FILL,
.front_face = sdl.GPU_FRONTFACE_CLOCKWISE, .front_face = sdl.GPU_FRONTFACE_COUNTER_CLOCKWISE,
};
pub const RASTERIZER_NO_CULL = sdl.GPURasterizerState{
.cull_mode = sdl.GPU_CULLMODE_NONE,
.fill_mode = sdl.GPU_FILLMODE_FILL,
}; };

View File

@@ -58,10 +58,16 @@ pub fn translate(transform: *Transform, translation: Position) void {
pub fn translateLocal(transform: *Transform, translation: Position) void { pub fn translateLocal(transform: *Transform, translation: Position) void {
@setFloatMode(.optimized); @setFloatMode(.optimized);
const a = transform.rotation[0]; transform.position += rotateVector(translation, transform.rotation);
const b = transform.rotation[1]; }
const c = transform.rotation[2];
const d = transform.rotation[3]; pub fn rotateVector(vector: Position, rotation: Rotation) Position {
@setFloatMode(.optimized);
const a = rotation[0];
const b = rotation[1];
const c = rotation[2];
const d = rotation[3];
const s = 2.0 / (a * a + b * b + c * c + d * d); const s = 2.0 / (a * a + b * b + c * c + d * d);
const bs = b * s; const bs = b * s;
@@ -78,9 +84,11 @@ pub fn translateLocal(transform: *Transform, translation: Position) void {
const cd = c * ds; const cd = c * ds;
const dd = d * ds; const dd = d * ds;
transform.position[0] += translation[0] * (1 - cc - dd) + translation[1] * (bc - ad) + translation[2] * (bd + ac); return .{
transform.position[1] += translation[0] * (bc + ad) + translation[1] * (1 - bb - dd) + translation[2] * (cd - ab); vector[0] * (1 - cc - dd) + vector[1] * (bc - ad) + vector[2] * (bd + ac),
transform.position[2] += translation[0] * (bd - ac) + translation[1] * (cd + ab) + translation[2] * (1 - bb - cc); vector[0] * (bc + ad) + vector[1] * (1 - bb - dd) + vector[2] * (cd - ab),
vector[0] * (bd - ac) + vector[1] * (cd + ab) + vector[2] * (1 - bb - cc),
};
} }
pub fn rotate(transform: *Transform, rotation: Rotation) void { pub fn rotate(transform: *Transform, rotation: Rotation) void {
@@ -95,12 +103,50 @@ pub fn rotateLocal(transform: *Transform, rotation: Rotation) void {
transform.rotation = normalizeRotation(combineRotations(rotation, transform.rotation)); transform.rotation = normalizeRotation(combineRotations(rotation, transform.rotation));
} }
pub fn rotateByAxis(transform: *Transform, axis: Position, angle: f32) void {
transform.rotate(rotationByAxis(axis, angle));
}
pub fn rotateToward(transform: *Transform, target: Position, origin_norm: Position) void {
@setFloatMode(.optimized);
transform.rotation = rotationToward(origin_norm, target - transform.position, .{ .normalize_to = true });
}
const RotationTowardOptions = struct {
normalize_from: bool = false,
normalize_to: bool = false,
};
pub fn rotationToward(from: Position, to: Position, comptime options: RotationTowardOptions) Rotation {
@setFloatMode(.optimized);
const from_norm = if (options.normalize_from) extractNormal(from)[0] else from;
const to_norm = if (options.normalize_to) extractNormal(to)[0] else to;
const combined = combineRotations(.{
0.0, to_norm[0], to_norm[1], to_norm[2],
}, .{
0.0, from_norm[0], from_norm[1], from_norm[2],
});
return normalizeRotation(.{
1 - combined[0],
combined[1],
combined[2],
combined[3],
});
}
pub fn normalizeRotation(r: Rotation) Rotation { pub fn normalizeRotation(r: Rotation) Rotation {
@setFloatMode(.optimized); @setFloatMode(.optimized);
const length_inverse = 1.0 / @sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]); const length = @sqrt(r[0] * r[0] + r[1] * r[1] + r[2] * r[2] + r[3] * r[3]);
if (length <= 1e-15) {
return .{ 1.0, 0.0, 0.0, 0.0 };
} else {
const length_inverse = 1.0 / length;
return r * @as(Rotation, @splat(length_inverse)); return r * @as(Rotation, @splat(length_inverse));
} }
}
pub fn combineRotations(a: Rotation, b: Rotation) Rotation { pub fn combineRotations(a: Rotation, b: Rotation) Rotation {
@setFloatMode(.optimized); @setFloatMode(.optimized);
@@ -113,11 +159,11 @@ pub fn combineRotations(a: Rotation, b: Rotation) Rotation {
}; };
} }
pub fn rotationByAxis(axis: Position, rotation: f32) Rotation { pub fn rotationByAxis(axis: Position, angle: f32) Rotation {
@setFloatMode(.optimized); @setFloatMode(.optimized);
const cos = std.math.cos(rotation * 0.5); const cos = std.math.cos(angle * 0.5);
const sin = std.math.sin(rotation * 0.5); const sin = std.math.sin(angle * 0.5);
return .{ cos, sin * axis[0], sin * axis[1], sin * axis[2] }; return .{ cos, sin * axis[0], sin * axis[1], sin * axis[2] };
} }

View File

@@ -1,69 +1,4 @@
const sdl = @import("sdl"); const sdl = @import("sdl");
const key_store = @import("data/keystore.zig");
const BUFFER_SIZE = 16; keys: key_store.KeyStore(sdl.Scancode, 16, sdl.SCANCODE_UNKNOWN) = .{},
const ZERO = sdl.SCANCODE_UNKNOWN;
just_pressed: [BUFFER_SIZE]sdl.Scancode = .{ZERO} ** BUFFER_SIZE,
pressed: [BUFFER_SIZE]sdl.Scancode = .{ZERO} ** BUFFER_SIZE,
just_released: [BUFFER_SIZE]sdl.Scancode = .{ZERO} ** BUFFER_SIZE,
const Self = @This();
pub fn reset(self: *Self) void {
self.just_pressed = .{ZERO} ** BUFFER_SIZE;
self.just_released = .{ZERO} ** BUFFER_SIZE;
}
pub fn press(self: *Self, code: sdl.Scancode) void {
for (self.pressed) |pressed| {
if (pressed == code) return;
}
for (&self.pressed) |*pressed| {
if (pressed.* == ZERO) {
pressed.* = code;
break;
}
}
for (self.just_pressed) |just_pressed| {
if (just_pressed == code) return;
}
for (&self.just_pressed) |*just_pressed| {
if (just_pressed.* == ZERO) {
just_pressed.* = code;
break;
}
}
}
pub fn release(self: *Self, code: sdl.Scancode) void {
for (&self.pressed) |*pressed| {
if (pressed.* == code) {
pressed.* = ZERO;
break;
}
}
for (self.just_released) |just_released| {
if (just_released == code) return;
}
for (&self.just_released) |*just_released| {
if (just_released.* == ZERO) {
just_released.* = code;
break;
}
}
}
pub fn is_just_pressed(self: *Self, code: sdl.Scancode) bool {
for (self.just_pressed) |just_pressed| {
if (just_pressed == code) return true;
}
return false;
}
pub fn is_pressed(self: *Self, code: sdl.Scancode) bool {
for (self.pressed) |pressed| {
if (pressed == code) return true;
}
return false;
}
pub fn is_just_released(self: *Self, code: sdl.Scancode) bool {
for (self.just_released) |just_released| {
if (just_released == code) return true;
}
return false;
}

View File

@@ -1,3 +1,8 @@
const sdl = @import("sdl");
const key_store = @import("data/keystore.zig");
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0),
x: f32, x: f32,
y: f32, y: f32,
dx: f32, dx: f32,