import { Mat4 } from "../math/Mat4" import { Vec3 } from "../math/Vec3" /** First-person camera. Orientation is Euler yaw/pitch (no roll), which is all * an FPS needs and avoids gimbal bookkeeping. */ export type Camera = { position: Vec3 /** Rotation around +Y, radians. 0 looks toward -Z; increasing turns right. */ yaw: number /** Look up/down, radians. Positive looks up. Clamp near +-pi/2 to avoid flip. */ pitch: number /** Vertical field of view, radians. */ fov: number } export namespace Camera { /** Unit forward direction implied by yaw/pitch. */ export function forward(cam: Camera): Vec3 { const cp = Math.cos(cam.pitch) return { x: cp * Math.sin(cam.yaw), y: Math.sin(cam.pitch), z: -cp * Math.cos(cam.yaw), } } /** Combined projection * view matrix for the given viewport aspect ratio. * Near/far are fixed; far only needs to exceed the fog distance, and is set * wide enough to reach the outdoor world's distant peaks. */ export function viewProjection(cam: Camera, aspect: number): Mat4 { const eye = cam.position const view = Mat4.lookAt(eye, Vec3.add(eye, forward(cam)), { x: 0, y: 1, z: 0 }) const proj = Mat4.perspective(cam.fov, aspect, 0.05, 260) return Mat4.multiply(proj, view) } }