1use glam::{Vec3, Vec4, Mat4, Quat};
2use crate::math::{Vec4H, Mat5, Ray3};
3
4#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
7pub enum Projection {
8 Perspective { fov_y: f32, near: f32, far: f32 },
9 Orthographic { half_width: f32, near: f32, far: f32 },
10}
11
12#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
13pub struct Camera3D {
14 pub position: Vec3,
15 pub rotation: Quat,
16 pub projection: Projection,
17 pub aspect: f32,
18}
19
20impl Camera3D {
21 pub fn perspective(fov_y_deg: f32, aspect: f32, near: f32, far: f32) -> Self {
22 Self {
23 position: Vec3::ZERO,
24 rotation: Quat::IDENTITY,
25 projection: Projection::Perspective {
26 fov_y: fov_y_deg.to_radians(),
27 near,
28 far,
29 },
30 aspect,
31 }
32 }
33
34 pub fn orthographic(half_width: f32, aspect: f32, near: f32, far: f32) -> Self {
35 Self {
36 position: Vec3::ZERO,
37 rotation: Quat::IDENTITY,
38 projection: Projection::Orthographic { half_width, near, far },
39 aspect,
40 }
41 }
42
43 pub fn forward(&self) -> Vec3 { self.rotation * -Vec3::Z }
44 pub fn right(&self) -> Vec3 { self.rotation * Vec3::X }
45 pub fn up(&self) -> Vec3 { self.rotation * Vec3::Y }
46
47 pub fn look_at(&mut self, target: Vec3, world_up: Vec3) {
48 let dir = (target - self.position).normalize();
49 if dir.length_squared() < 1e-8 { return; }
50 let mat = Mat4::look_at_rh(self.position, target, world_up);
51 let (_, rot, _) = mat.inverse().to_scale_rotation_translation();
52 self.rotation = rot;
53 }
54
55 pub fn view_matrix(&self) -> Mat4 {
56 Mat4::from_rotation_translation(self.rotation, self.position).inverse()
57 }
58
59 pub fn projection_matrix(&self) -> Mat4 {
60 match self.projection {
61 Projection::Perspective { fov_y, near, far } =>
62 Mat4::perspective_rh(fov_y, self.aspect, near, far),
63 Projection::Orthographic { half_width, near, far } => {
64 let h = half_width / self.aspect;
65 Mat4::orthographic_rh(-half_width, half_width, -h, h, near, far)
66 }
67 }
68 }
69
70 pub fn view_proj(&self) -> Mat4 {
71 self.projection_matrix() * self.view_matrix()
72 }
73
74 pub fn unproject_ray(&self, ndc_x: f32, ndc_y: f32) -> Ray3 {
76 let inv_vp = self.view_proj().inverse();
77 let near = inv_vp * Vec4::new(ndc_x, ndc_y, -1.0, 1.0);
78 let far = inv_vp * Vec4::new(ndc_x, ndc_y, 1.0, 1.0);
79 let near = near.truncate() / near.w;
80 let far = far.truncate() / far.w;
81 Ray3::new(near, (far - near).normalize())
82 }
83
84 pub fn move_forward(&mut self, dist: f32) { self.position += self.forward() * dist; }
85 pub fn move_right(&mut self, dist: f32) { self.position += self.right() * dist; }
86 pub fn move_up(&mut self, dist: f32) { self.position += self.up() * dist; }
87
88 pub fn orbit(&mut self, target: Vec3, yaw: f32, pitch: f32) {
89 let rot = Quat::from_rotation_y(yaw) * Quat::from_rotation_x(pitch);
90 let offset = self.position - target;
91 self.position = target + rot * offset;
92 self.look_at(target, Vec3::Y);
93 }
94}
95
96impl Default for Camera3D {
97 fn default() -> Self { Self::perspective(60.0, 16.0 / 9.0, 0.1, 1000.0) }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
104pub enum HyperModel {
105 Klein,
107 Poincare,
109 CrossSection { w_slice: f32 },
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
117pub struct HyperPoint4D {
118 pub x0: f32,
119 pub x1: f32,
120 pub x2: f32,
121 pub x3: f32,
122 pub x4: f32,
123}
124
125impl HyperPoint4D {
126 pub const ORIGIN: Self = Self { x0: 1.0, x1: 0.0, x2: 0.0, x3: 0.0, x4: 0.0 };
128
129 pub fn new(x0: f32, x1: f32, x2: f32, x3: f32, x4: f32) -> Self {
130 Self { x0, x1, x2, x3, x4 }
131 }
132
133 pub fn minkowski_dot(&self, other: &Self) -> f32 {
134 -self.x0 * other.x0
135 + self.x1 * other.x1
136 + self.x2 * other.x2
137 + self.x3 * other.x3
138 + self.x4 * other.x4
139 }
140
141 pub fn distance(&self, other: &Self) -> f32 {
143 (-self.minkowski_dot(other)).max(1.0).acosh()
144 }
145
146 pub fn normalize(&self) -> Self {
148 let sq = self.x0 * self.x0
149 - self.x1 * self.x1
150 - self.x2 * self.x2
151 - self.x3 * self.x3
152 - self.x4 * self.x4;
153 if sq <= 0.0 { return *self; }
154 let s = sq.sqrt();
155 Self::new(self.x0 / s, self.x1 / s, self.x2 / s, self.x3 / s, self.x4 / s)
156 }
157
158 pub fn to_klein(&self) -> Vec4H {
159 Vec4H::new(
160 self.x1 / self.x0,
161 self.x2 / self.x0,
162 self.x3 / self.x0,
163 self.x4 / self.x0,
164 )
165 }
166
167 pub fn to_poincare(&self) -> Vec4H {
168 let d = 1.0 + self.x0;
169 Vec4H::new(
170 self.x1 / d,
171 self.x2 / d,
172 self.x3 / d,
173 self.x4 / d,
174 )
175 }
176}
177
178#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
181pub struct Camera4D {
182 pub position: HyperPoint4D,
184 pub frame: Mat5,
186 pub model: HyperModel,
187 pub cam3d: Camera3D,
189}
190
191impl Camera4D {
192 pub fn new(model: HyperModel) -> Self {
193 Self {
194 position: HyperPoint4D::ORIGIN,
195 frame: Mat5::identity(),
196 model,
197 cam3d: Camera3D::perspective(60.0, 16.0 / 9.0, 0.01, 100.0),
198 }
199 }
200
201 pub fn project(&self, point: &HyperPoint4D) -> Option<Vec3> {
203 match self.model {
204 HyperModel::Klein => {
205 let k = point.to_klein();
206 Some(Vec3::new(k.x, k.y, k.z))
208 }
209 HyperModel::Poincare => {
210 let p = point.to_poincare();
211 Some(Vec3::new(p.x, p.y, p.z))
212 }
213 HyperModel::CrossSection { w_slice } => {
214 let k = point.to_klein();
216 if (k.w - w_slice).abs() > 0.5 { return None; }
217 Some(Vec3::new(k.x, k.y, k.z))
218 }
219 }
220 }
221
222 pub fn move_by(&mut self, direction: Vec4H, dist: f32) {
225 let len = direction.length();
226 if len < 1e-8 { return; }
227 let d = direction * (1.0 / len);
228 let ch = dist.cosh();
229 let sh = dist.sinh();
230 let p = &self.position;
232 self.position = HyperPoint4D::new(
233 ch * p.x0 + sh * (d.x * p.x1 + d.y * p.x2 + d.z * p.x3 + d.w * p.x4),
234 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,
235 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,
236 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,
237 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,
238 ).normalize();
239 }
240}
241
242impl Default for Camera4D {
243 fn default() -> Self { Self::new(HyperModel::Klein) }
244}