# ============================================================================ # camera.ludic — a first-person fly camera (yaw / pitch, metres) and its view / # projection matrices. Float bits throughout; see fmath.ludic. # ============================================================================ var cam_pos: words = null # x, y, z var cam_yaw: int = 0 # radians, 0 = looking down -z var cam_pitch: int = 0 var cam_fov: int = 0 # vertical, radians var cam_near: int = 0 var cam_far: int = 0 var cam_aspect: int = 0 var cam_view: words = null var cam_proj: words = null var cam_vp: words = null var cam_inv_vp: words = null var cam_inv_proj: words = null var cam_vp_clean: words = null # view-projection, kept for depth reconstruction var cam_inv_vp_clean: words = null var cam_fwd: words = null var cam_right: words = null # the view frustum's four side planes (a, b, c, d), float bits: left, right, bottom, top; # from the clean (unjittered) view-projection, column-major m[col * 4 + row] var cam_planes: words = null function cam_init(aspect: int) -> void { cam_pos = v3_new(F_ZERO, fi(2), F_ZERO) cam_view = m4_new(); cam_proj = m4_new(); cam_vp = m4_new(); cam_inv_vp = m4_new(); cam_inv_proj = m4_new() cam_vp_clean = m4_new(); cam_inv_vp_clean = m4_new() cam_fwd = v3_new(F_ZERO, F_ZERO, f_neg1()) cam_right = v3_new(F_ONE, F_ZERO, F_ZERO) cam_fov = f_rad(fi(42)) cam_near = fl(0.3) cam_far = fi(14000) cam_aspect = aspect cam_update() } function cam_begin_frame(n: int, w: int, h: int) -> void { cam_update() } function cam_set(x: int, y: int, z: int, yaw_deg: int, pitch_deg: int) -> void { v3_set(cam_pos, x, y, z) cam_yaw = f_rad(yaw_deg) cam_pitch = f_rad(pitch_deg) cam_update() } function cam_update() -> void { let cy = f_cos(cam_yaw); let sy = f_sin(cam_yaw) let cp = f_cos(cam_pitch); let sp = f_sin(cam_pitch) v3_set(cam_fwd, f_neg(f_mul(sy, cp)), sp, f_neg(f_mul(cy, cp))) v3_set(cam_right, cy, F_ZERO, f_neg(sy)) let at = words(3) v3_add(at, cam_pos, cam_fwd) let up = v3_new(F_ZERO, F_ONE, F_ZERO) m4_look_at(cam_view, cam_pos, at, up) m4_perspective(cam_proj, cam_fov, cam_aspect, cam_near, cam_far) m4_mul(cam_vp_clean, cam_proj, cam_view) m4_inverse(cam_inv_vp_clean, cam_vp_clean) m4_mul(cam_vp, cam_proj, cam_view) m4_inverse(cam_inv_vp, cam_vp) m4_inverse(cam_inv_proj, cam_proj) free(at); free(up) cam_planes_update() } function cam_planes_update() -> void { if cam_planes == null { cam_planes = words(16) } let m = cam_vp_clean for p in 0 .. 4 { var r = 0 if p >= 2 { r = 1 } var sg = F_ONE if p == 1 or p == 3 { sg = f_neg(F_ONE) } var a = f_add(m[3], f_mul(sg, m[r])) var b = f_add(m[7], f_mul(sg, m[4 + r])) var c = f_add(m[11], f_mul(sg, m[8 + r])) var d = f_add(m[15], f_mul(sg, m[12 + r])) let inv = f_div(F_ONE, f_sqrt(f_add(f_add(f_mul(a, a), f_mul(b, b)), f_mul(c, c)))) cam_planes[p * 4] = f_mul(a, inv); cam_planes[p * 4 + 1] = f_mul(b, inv) cam_planes[p * 4 + 2] = f_mul(c, inv); cam_planes[p * 4 + 3] = f_mul(d, inv) } } # is a sphere (float bits) at least partly inside the side planes of the view? function cam_sphere_visible(x: int, y: int, z: int, r: int) -> bool { if cam_planes == null { return true } let nr = f_neg(r) for p in 0 .. 4 { let o = p * 4 let dist = f_add(f_add(f_add(f_mul(cam_planes[o], x), f_mul(cam_planes[o + 1], y)), f_mul(cam_planes[o + 2], z)), cam_planes[o + 3]) if f_ls(dist, nr) { return false } } return true } # fly: forward/strafe in metres, turn in radians function cam_move(fwd: int, strafe: int, up: int, dyaw: int, dpitch: int) -> void { cam_yaw = f_add(cam_yaw, dyaw) cam_pitch = f_clamp(f_add(cam_pitch, dpitch), f_neg(fl(1.5)), fl(1.5)) v3_madd(cam_pos, cam_pos, cam_fwd, fwd) v3_madd(cam_pos, cam_pos, cam_right, strafe) cam_pos[1] = f_add(cam_pos[1], up) cam_update() }