render-zig

A 3D rendering engine written in Zig
git clone git://git.christianermann.dev/render-zig
Log | Files | Refs

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 }