camera.zig (7905B)
1 const std = @import("std"); 2 const glfw = @import("mach_glfw"); 3 4 const UserInput = @import("input.zig").UserInput; 5 const KeyCallbackArgs = @import("input.zig").KeyCallbackArgs; 6 7 const Camera = @This(); 8 9 const f32x3 = @Vector(3, f32); 10 const f32x4 = @Vector(4, f32); 11 const mat4 = [4]f32x4; 12 13 fovy: f32 = 60, 14 aspect: f32 = 1, 15 near: f32 = 0.1, 16 far: f32 = 100, 17 18 yaw: f32 = 90.0, 19 pitch: f32 = 0.0, 20 21 position: f32x3 = .{ 0, 0, -1 }, 22 23 input: CameraInput = CameraInput{}, 24 25 pub const CameraInput = struct { 26 w: bool = false, 27 a: bool = false, 28 s: bool = false, 29 d: bool = false, 30 31 shift: bool = false, 32 space: bool = false, 33 34 left: bool = false, 35 right: bool = false, 36 up: bool = false, 37 down: bool = false, 38 39 fn keyCallback(ptr: *anyopaque, args: KeyCallbackArgs) void { 40 const self: *CameraInput = @ptrCast(@alignCast(ptr)); 41 if ((args.action != glfw.Action.press) and (args.action != glfw.Action.release)) { 42 return; 43 } 44 switch (args.key) { 45 .w => self.w = !self.w, 46 .a => self.a = !self.a, 47 .s => self.s = !self.s, 48 .d => self.d = !self.d, 49 .left_shift => self.shift = !self.shift, 50 .space => self.space = !self.space, 51 .left => self.left = !self.left, 52 .right => self.right = !self.right, 53 .up => self.up = !self.up, 54 .down => self.down = !self.down, 55 else => {}, 56 } 57 } 58 59 pub fn userInput(self: *CameraInput) UserInput { 60 return .{ 61 .ptr = self, 62 .keyCallbackFn = CameraInput.keyCallback, 63 }; 64 } 65 }; 66 67 pub fn logParams(self: *const Camera) void { 68 std.log.info("camera fovy: {}", .{self.fovy}); 69 std.log.info("camera aspect: {}", .{self.aspect}); 70 std.log.info("camera near: {}", .{self.near}); 71 std.log.info("camera far: {}", .{self.far}); 72 } 73 74 fn f32x3_dot(u: f32x3, v: f32x3) f32 { 75 return @reduce(.Add, u * v); 76 } 77 78 pub fn f32x3_normalize(v: f32x3) f32x3 { 79 return v / @as(f32x3, @splat(@sqrt(f32x3_dot(v, v)))); 80 } 81 82 /// Modified from https://geometrian.com/programming/tutorials/cross-product/index.php 83 pub fn f32x3_mul(u: f32x3, v: f32x3) f32x3 { 84 const mask = @Vector(3, i32){ 1, 2, 0 }; 85 const t1 = u * @shuffle(f32, v, undefined, mask); 86 const t2 = v * @shuffle(f32, u, undefined, mask); 87 return @shuffle(f32, t1 - t2, undefined, mask); 88 } 89 90 test "f32x3_mul works" { 91 const u = f32x3{ 1, 2, 3 }; 92 const v = f32x3{ 4, 5, 6 }; 93 const result = f32x3_mul(u, v); 94 try std.testing.expectApproxEqAbs(-3, result[0], 0.00001); 95 } 96 97 fn f32x4_scale(v: f32x4, s: f32) f32x4 { 98 return v * @as(f32x4, @splat(s)); 99 } 100 101 fn mat4_mul(l: mat4, r: mat4, result: *mat4) void { 102 result.* = .{ 103 f32x4_scale(l[0], r[0][0]) + f32x4_scale(l[1], r[0][1]) + f32x4_scale(l[2], r[0][2]) + f32x4_scale(l[3], r[0][3]), 104 f32x4_scale(l[0], r[1][0]) + f32x4_scale(l[1], r[1][1]) + f32x4_scale(l[2], r[1][2]) + f32x4_scale(l[3], r[1][3]), 105 f32x4_scale(l[0], r[2][0]) + f32x4_scale(l[1], r[2][1]) + f32x4_scale(l[2], r[2][2]) + f32x4_scale(l[3], r[2][3]), 106 f32x4_scale(l[0], r[3][0]) + f32x4_scale(l[1], r[3][1]) + f32x4_scale(l[2], r[3][2]) + f32x4_scale(l[3], r[3][3]), 107 }; 108 } 109 110 fn lookAt(eye: f32x3, target: f32x3, up: f32x3, mat: *mat4) void { 111 const f = f32x3_normalize(target - eye); 112 const s = f32x3_normalize(f32x3_mul(f, up)); 113 const u = f32x3_normalize(f32x3_mul(s, f)); 114 mat.* = .{ 115 .{ s[0], u[0], -f[0], 0 }, 116 .{ s[1], u[1], -f[1], 0 }, 117 .{ s[2], u[2], -f[2], 0 }, 118 .{ -f32x3_dot(s, eye), -f32x3_dot(u, eye), f32x3_dot(f, eye), 1.0 }, 119 }; 120 } 121 122 fn invLookAt(eye: f32x3, target: f32x3, up: f32x3, mat: *mat4) void { 123 const f = f32x3_normalize(target - eye); 124 const s = f32x3_normalize(f32x3_mul(f, up)); 125 const u = f32x3_normalize(f32x3_mul(s, f)); 126 mat.* = .{ 127 .{ s[0], s[1], s[2], 0 }, 128 .{ u[0], u[1], u[2], 0 }, 129 .{ -f[0], -f[1], -f[2], 0 }, 130 .{ f32x3_dot(s, eye), f32x3_dot(u, eye), f32x3_dot(f, eye), 1.0 }, 131 }; 132 } 133 134 fn perspective(fovy: f32, aspect: f32, near: f32, far: f32, mat: *mat4) void { 135 const tan_half_fov = @tan(std.math.degreesToRadians(fovy * 0.5)); 136 const a = 1.0 / (aspect * tan_half_fov); 137 const b = 1.0 / tan_half_fov; 138 const c = -(far + near) / (far - near); 139 const d = -(2 * far * near) / (far - near); 140 mat.* = .{ 141 .{ a, 0, 0, 0 }, 142 .{ 0, b, 0, 0 }, 143 .{ 0, 0, c, -1 }, 144 .{ 0, 0, d, 0 }, 145 }; 146 } 147 148 fn invPerspective(fovy: f32, aspect: f32, near: f32, far: f32, mat: *mat4) void { 149 const tan_half_fov = @tan(std.math.degreesToRadians(fovy * 0.5)); 150 const a = aspect * tan_half_fov; 151 const b = tan_half_fov; 152 const c = (far + near) / (2 * far * near); 153 const d = (near - far) / (2 * far * near); 154 mat.* = .{ 155 .{ a, 0, 0, 0 }, 156 .{ 0, b, 0, 0 }, 157 .{ 0, 0, 0, d }, 158 .{ 0, 0, -1, c }, 159 }; 160 } 161 162 pub fn view(self: *const Camera, mat: *mat4) void { 163 const cos_yaw = @cos(std.math.degreesToRadians(self.yaw)); 164 const sin_yaw = @sin(std.math.degreesToRadians(self.yaw)); 165 const cos_pitch = @cos(std.math.degreesToRadians(self.pitch)); 166 const sin_pitch = @sin(std.math.degreesToRadians(self.pitch)); 167 const forward = f32x3_normalize( 168 f32x3{ cos_yaw * cos_pitch, sin_pitch, sin_yaw * cos_pitch }, 169 ); 170 171 const target = self.position + forward; 172 lookAt(self.position, target, f32x3{ 0, 1, 0 }, mat); 173 } 174 175 pub fn invView(self: *const Camera, mat: *mat4) void { 176 const cos_yaw = @cos(std.math.degreesToRadians(self.yaw)); 177 const sin_yaw = @sin(std.math.degreesToRadians(self.yaw)); 178 const cos_pitch = @cos(std.math.degreesToRadians(self.pitch)); 179 const sin_pitch = @sin(std.math.degreesToRadians(self.pitch)); 180 const forward = f32x3_normalize( 181 f32x3{ cos_yaw * cos_pitch, sin_pitch, sin_yaw * cos_pitch }, 182 ); 183 184 const target = self.position + forward; 185 invLookAt(self.position, target, f32x3{ 0, 1, 0 }, mat); 186 } 187 188 pub fn proj(self: *const Camera, mat: *mat4) void { 189 perspective(self.fovy, self.aspect, self.near, self.far, mat); 190 } 191 192 pub fn invProj(self: *const Camera, mat: *mat4) void { 193 invPerspective(self.fovy, self.aspect, self.near, self.far, mat); 194 } 195 196 pub fn update(self: *Camera, _: f32) void { 197 var yaw: f32 = 0; 198 if (self.input.right) { 199 yaw += 1; 200 } 201 if (self.input.left) { 202 yaw -= 1; 203 } 204 self.yaw += yaw; 205 if (self.yaw > 180) { 206 self.yaw -= 360; 207 } 208 if (self.yaw < -180) { 209 self.yaw += 360; 210 } 211 212 var pitch: f32 = 0; 213 if (self.input.up) { 214 pitch += 1; 215 } 216 if (self.input.down) { 217 pitch -= 1; 218 } 219 self.pitch += pitch; 220 if (self.pitch > 89) { 221 self.pitch = 89; 222 } 223 if (self.pitch < -89) { 224 self.pitch = -89; 225 } 226 227 const cos_yaw = @cos(std.math.degreesToRadians(self.yaw)); 228 const sin_yaw = @sin(std.math.degreesToRadians(self.yaw)); 229 const cos_pitch = @cos(std.math.degreesToRadians(self.pitch)); 230 const sin_pitch = @sin(std.math.degreesToRadians(self.pitch)); 231 const f = f32x3_normalize( 232 f32x3{ cos_yaw * cos_pitch, sin_pitch, sin_yaw * cos_pitch }, 233 ); 234 const s = f32x3_normalize(f32x3_mul(f, f32x3{ 0, 1, 0 })); 235 const u = f32x3_normalize(f32x3_mul(s, f)); 236 237 var forward: f32 = 0; 238 var right: f32 = 0; 239 var up: f32 = 0; 240 if (self.input.w) { 241 forward += 1; 242 } 243 if (self.input.s) { 244 forward -= 1; 245 } 246 if (self.input.d) { 247 right += 1; 248 } 249 if (self.input.a) { 250 right -= 1; 251 } 252 if (self.input.space) { 253 up += 1; 254 } 255 if (self.input.shift) { 256 up -= 1; 257 } 258 259 self.position += f * @as(f32x3, @splat(forward * 0.01)); 260 self.position += s * @as(f32x3, @splat(right * 0.01)); 261 self.position += u * @as(f32x3, @splat(up * 0.01)); 262 }