use glam::{Vec3, Vec4, Mat4, Quat};
use crate::math::{Vec4H, Mat5, Ray3};
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Projection {
Perspective { fov_y: f32, near: f32, far: f32 },
Orthographic { half_width: f32, near: f32, far: f32 },
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Camera3D {
pub position: Vec3,
pub rotation: Quat,
pub projection: Projection,
pub aspect: f32,
}
impl Camera3D {
pub fn perspective(fov_y_deg: f32, aspect: f32, near: f32, far: f32) -> Self {
Self {
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
projection: Projection::Perspective {
fov_y: fov_y_deg.to_radians(),
near,
far,
},
aspect,
}
}
pub fn orthographic(half_width: f32, aspect: f32, near: f32, far: f32) -> Self {
Self {
position: Vec3::ZERO,
rotation: Quat::IDENTITY,
projection: Projection::Orthographic { half_width, near, far },
aspect,
}
}
pub fn forward(&self) -> Vec3 { self.rotation * -Vec3::Z }
pub fn right(&self) -> Vec3 { self.rotation * Vec3::X }
pub fn up(&self) -> Vec3 { self.rotation * Vec3::Y }
pub fn look_at(&mut self, target: Vec3, world_up: Vec3) {
let dir = (target - self.position).normalize();
if dir.length_squared() < 1e-8 { return; }
let mat = Mat4::look_at_rh(self.position, target, world_up);
let (_, rot, _) = mat.inverse().to_scale_rotation_translation();
self.rotation = rot;
}
pub fn view_matrix(&self) -> Mat4 {
Mat4::from_rotation_translation(self.rotation, self.position).inverse()
}
pub fn projection_matrix(&self) -> Mat4 {
match self.projection {
Projection::Perspective { fov_y, near, far } =>
Mat4::perspective_rh(fov_y, self.aspect, near, far),
Projection::Orthographic { half_width, near, far } => {
let h = half_width / self.aspect;
Mat4::orthographic_rh(-half_width, half_width, -h, h, near, far)
}
}
}
pub fn view_proj(&self) -> Mat4 {
self.projection_matrix() * self.view_matrix()
}
pub fn unproject_ray(&self, ndc_x: f32, ndc_y: f32) -> Ray3 {
let inv_vp = self.view_proj().inverse();
let near = inv_vp * Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
let far = inv_vp * Vec4::new(ndc_x, ndc_y, 1.0, 1.0);
let near = near.truncate() / near.w;
let far = far.truncate() / far.w;
Ray3::new(near, (far - near).normalize())
}
pub fn move_forward(&mut self, dist: f32) { self.position += self.forward() * dist; }
pub fn move_right(&mut self, dist: f32) { self.position += self.right() * dist; }
pub fn move_up(&mut self, dist: f32) { self.position += self.up() * dist; }
pub fn orbit(&mut self, target: Vec3, yaw: f32, pitch: f32) {
let rot = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
let offset = self.position - target;
self.position = target + rot * offset;
self.look_at(target, Vec3::Y);
}
}
impl Default for Camera3D {
fn default() -> Self { Self::perspective(60.0, 16.0 / 9.0, 0.1, 1000.0) }
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum HyperModel {
Klein,
Poincare,
CrossSection { w_slice: f32 },
}
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct HyperPoint4D {
pub x0: f32,
pub x1: f32,
pub x2: f32,
pub x3: f32,
pub x4: f32,
}
impl HyperPoint4D {
pub const ORIGIN: Self = Self { x0: 1.0, x1: 0.0, x2: 0.0, x3: 0.0, x4: 0.0 };
pub fn new(x0: f32, x1: f32, x2: f32, x3: f32, x4: f32) -> Self {
Self { x0, x1, x2, x3, x4 }
}
pub fn minkowski_dot(&self, other: &Self) -> f32 {
-self.x0 * other.x0
+ self.x1 * other.x1
+ self.x2 * other.x2
+ self.x3 * other.x3
+ self.x4 * other.x4
}
pub fn distance(&self, other: &Self) -> f32 {
(-self.minkowski_dot(other)).max(1.0).acosh()
}
pub fn normalize(&self) -> Self {
let sq = self.x0 * self.x0
- self.x1 * self.x1
- self.x2 * self.x2
- self.x3 * self.x3
- self.x4 * self.x4;
if sq <= 0.0 { return *self; }
let s = sq.sqrt();
Self::new(self.x0 / s, self.x1 / s, self.x2 / s, self.x3 / s, self.x4 / s)
}
pub fn to_klein(&self) -> Vec4H {
Vec4H::new(
self.x1 / self.x0,
self.x2 / self.x0,
self.x3 / self.x0,
self.x4 / self.x0,
)
}
pub fn to_poincare(&self) -> Vec4H {
let d = 1.0 + self.x0;
Vec4H::new(
self.x1 / d,
self.x2 / d,
self.x3 / d,
self.x4 / d,
)
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Camera4D {
pub position: HyperPoint4D,
pub frame: Mat5,
pub model: HyperModel,
pub cam3d: Camera3D,
}
impl Camera4D {
pub fn new(model: HyperModel) -> Self {
Self {
position: HyperPoint4D::ORIGIN,
frame: Mat5::identity(),
model,
cam3d: Camera3D::perspective(60.0, 16.0 / 9.0, 0.01, 100.0),
}
}
pub fn project(&self, point: &HyperPoint4D) -> Option<Vec3> {
match self.model {
HyperModel::Klein => {
let k = point.to_klein();
Some(Vec3::new(k.x, k.y, k.z))
}
HyperModel::Poincare => {
let p = point.to_poincare();
Some(Vec3::new(p.x, p.y, p.z))
}
HyperModel::CrossSection { w_slice } => {
let k = point.to_klein();
if (k.w - w_slice).abs() > 0.5 { return None; }
Some(Vec3::new(k.x, k.y, k.z))
}
}
}
pub fn move_by(&mut self, direction: Vec4H, dist: f32) {
let len = direction.length();
if len < 1e-8 { return; }
let d = direction * (1.0 / len);
let ch = dist.cosh();
let sh = dist.sinh();
let p = &self.position;
self.position = HyperPoint4D::new(
ch * p.x0 + sh * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4),
p.x1 + (sh * p.x0 + (ch - 1.0) * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4)) * d.x,
p.x2 + (sh * p.x0 + (ch - 1.0) * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4)) * d.y,
p.x3 + (sh * p.x0 + (ch - 1.0) * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4)) * d.z,
p.x4 + (sh * p.x0 + (ch - 1.0) * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4)) * d.w,
).normalize();
}
}
impl Default for Camera4D {
fn default() -> Self { Self::new(HyperModel::Klein) }
}