"""Camera — view and projection matrix management."""
from __future__ import annotations
import logging
import numpy as np
log = logging.getLogger(__name__)
__all__ = ["Camera"]
[docs]
class Camera:
"""Manages view and projection matrices."""
def __init__(
self,
position: tuple[float, float, float] = (0.0, 0.0, 5.0),
target: tuple[float, float, float] = (0.0, 0.0, 0.0),
up: tuple[float, float, float] = (0.0, 1.0, 0.0),
fov: float = 60.0,
aspect: float = 16.0 / 9.0,
near: float = 0.1,
far: float = 1000.0,
) -> None:
self.position = np.array(position, dtype=np.float32)
self.target = np.array(target, dtype=np.float32)
self.up = np.array(up, dtype=np.float32)
self.fov = fov
self.aspect = aspect
self.near = near
self.far = far
@property
def view_matrix(self) -> np.ndarray:
"""Compute a look-at view matrix (column-major for GLSL)."""
f = self.target - self.position
f = f / np.linalg.norm(f)
s = np.cross(f, self.up)
s = s / np.linalg.norm(s)
u = np.cross(s, f)
m = np.eye(4, dtype=np.float32)
m[0, 0], m[1, 0], m[2, 0] = s
m[0, 1], m[1, 1], m[2, 1] = u
m[0, 2], m[1, 2], m[2, 2] = -f
m[3, 0] = -np.dot(s, self.position)
m[3, 1] = -np.dot(u, self.position)
m[3, 2] = np.dot(f, self.position)
return m
@property
def projection_matrix(self) -> np.ndarray:
"""Compute a perspective projection matrix (Vulkan clip space, column-major)."""
fov_rad = np.radians(self.fov)
f = 1.0 / np.tan(fov_rad / 2.0)
n, fa = self.near, self.far
m = np.zeros((4, 4), dtype=np.float32)
m[0, 0] = f / self.aspect
m[1, 1] = -f # Vulkan Y-flip
m[2, 2] = fa / (n - fa)
m[2, 3] = -1.0
m[3, 2] = (n * fa) / (n - fa)
return m