1#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
7pub struct Aabb {
8 pub min: [f64; 3],
9 pub max: [f64; 3],
10}
11
12impl Aabb {
13 pub fn empty() -> Self {
14 Self {
15 min: [f64::INFINITY; 3],
16 max: [f64::NEG_INFINITY; 3],
17 }
18 }
19
20 pub fn is_empty(&self) -> bool {
21 (0..3).any(|i| self.min[i] > self.max[i])
22 }
23
24 pub fn expand(&mut self, p: [f64; 3]) {
25 for i in 0..3 {
26 self.min[i] = self.min[i].min(p[i]);
27 self.max[i] = self.max[i].max(p[i]);
28 }
29 }
30
31 pub fn union(&mut self, other: &Aabb) {
32 if other.is_empty() {
33 return;
34 }
35 self.expand(other.min);
36 self.expand(other.max);
37 }
38
39 pub fn center(&self) -> [f64; 3] {
40 [
41 (self.min[0] + self.max[0]) * 0.5,
42 (self.min[1] + self.max[1]) * 0.5,
43 (self.min[2] + self.max[2]) * 0.5,
44 ]
45 }
46
47 pub fn size(&self) -> [f64; 3] {
48 [
49 self.max[0] - self.min[0],
50 self.max[1] - self.min[1],
51 self.max[2] - self.min[2],
52 ]
53 }
54}
55
56fn norm(v: [f64; 3]) -> [f64; 3] {
57 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
58 if len <= 0.0 {
59 return [0.0, 0.0, 1.0];
60 }
61 [v[0] / len, v[1] / len, v[2] / len]
62}
63
64fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
65 [
66 a[1] * b[2] - a[2] * b[1],
67 a[2] * b[0] - a[0] * b[2],
68 a[0] * b[1] - a[1] * b[0],
69 ]
70}
71
72fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
73 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
74}
75
76#[derive(Debug, Clone, Copy)]
80pub struct Camera {
81 pub view_proj: [[f32; 4]; 4],
83 pub forward: [f32; 3],
85}
86
87fn look_at(eye: [f64; 3], target: [f64; 3], up: [f64; 3]) -> [[f64; 4]; 4] {
89 let f = norm([target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]]);
90 let r = norm(cross(f, up));
91 let u = cross(r, f);
92 [
94 [r[0], u[0], -f[0], 0.0],
95 [r[1], u[1], -f[1], 0.0],
96 [r[2], u[2], -f[2], 0.0],
97 [-dot(r, eye), -dot(u, eye), dot(f, eye), 1.0],
98 ]
99}
100
101fn ortho(l: f64, r: f64, b: f64, t: f64, near: f64, far: f64) -> [[f64; 4]; 4] {
104 let sx = 2.0 / (r - l);
105 let sy = 2.0 / (t - b);
106 let sz = -1.0 / (far - near);
107 [
108 [sx, 0.0, 0.0, 0.0],
109 [0.0, sy, 0.0, 0.0],
110 [0.0, 0.0, sz, 0.0],
111 [
112 -(r + l) / (r - l),
113 -(t + b) / (t - b),
114 -near / (far - near),
115 1.0,
116 ],
117 ]
118}
119
120fn mul(a: [[f64; 4]; 4], b: [[f64; 4]; 4]) -> [[f64; 4]; 4] {
121 let mut out = [[0.0; 4]; 4];
122 for col in 0..4 {
123 for row in 0..4 {
124 let mut sum = 0.0;
125 for k in 0..4 {
126 sum += a[k][row] * b[col][k];
127 }
128 out[col][row] = sum;
129 }
130 }
131 out
132}
133
134fn to_f32(m: [[f64; 4]; 4]) -> [[f32; 4]; 4] {
135 let mut out = [[0.0f32; 4]; 4];
136 for c in 0..4 {
137 for r in 0..4 {
138 out[c][r] = m[c][r] as f32;
139 }
140 }
141 out
142}
143
144pub fn ortho_camera(
147 eye: [f64; 3],
148 target: [f64; 3],
149 up: [f64; 3],
150 half_width: f64,
151 half_height: f64,
152 near: f64,
153 far: f64,
154) -> Camera {
155 let view = look_at(eye, target, up);
156 let proj = ortho(-half_width, half_width, -half_height, half_height, near, far);
157 let forward = norm([
158 target[0] - eye[0],
159 target[1] - eye[1],
160 target[2] - eye[2],
161 ]);
162 Camera {
163 view_proj: to_f32(mul(proj, view)),
164 forward: [forward[0] as f32, forward[1] as f32, forward[2] as f32],
165 }
166}
167
168pub fn artifact_camera(bbox: &Aabb, width: u32, height: u32) -> Camera {
172 let bbox = if bbox.is_empty() {
173 Aabb {
174 min: [-1.0, -1.0, -1.0],
175 max: [1.0, 1.0, 1.0],
176 }
177 } else {
178 *bbox
179 };
180 let center = bbox.center();
181 let size = bbox.size();
182 let radius = size[0].max(size[1]).max(size[2]).max(1e-9) * 0.75;
183 let dir = norm([1.0, -1.2, 0.9]);
184 let eye = [
185 center[0] + dir[0] * radius * 6.0,
186 center[1] + dir[1] * radius * 6.0,
187 center[2] + dir[2] * radius * 6.0,
188 ];
189 let aspect = width.max(1) as f64 / height.max(1) as f64;
190 ortho_camera(
191 eye,
192 center,
193 [0.0, 0.0, 1.0],
194 radius * aspect * 1.15,
195 radius * 1.15,
196 0.01,
197 radius * 20.0,
198 )
199}
200
201#[derive(Debug, Clone, Copy)]
204pub struct Orbit {
205 pub azimuth: f64,
206 pub elevation: f64,
207 pub zoom: f64,
208}
209
210impl Default for Orbit {
211 fn default() -> Self {
212 let dir = norm([1.0, -1.2, 0.9]);
214 Self {
215 azimuth: dir[1].atan2(dir[0]),
216 elevation: dir[2].asin(),
217 zoom: 1.0,
218 }
219 }
220}
221
222impl Orbit {
223 pub fn camera(&self, bbox: &Aabb, width: u32, height: u32) -> Camera {
224 let bbox = if bbox.is_empty() {
225 Aabb {
226 min: [-1.0, -1.0, -1.0],
227 max: [1.0, 1.0, 1.0],
228 }
229 } else {
230 *bbox
231 };
232 let center = bbox.center();
233 let size = bbox.size();
234 let radius = size[0].max(size[1]).max(size[2]).max(1e-9) * 0.75;
235 let el = self.elevation.clamp(-1.55, 1.55);
236 let dir = [
237 el.cos() * self.azimuth.cos(),
238 el.cos() * self.azimuth.sin(),
239 el.sin(),
240 ];
241 let eye = [
242 center[0] + dir[0] * radius * 6.0,
243 center[1] + dir[1] * radius * 6.0,
244 center[2] + dir[2] * radius * 6.0,
245 ];
246 let aspect = width.max(1) as f64 / height.max(1) as f64;
247 let half_h = radius * 1.15 * self.zoom;
248 ortho_camera(
249 eye,
250 center,
251 [0.0, 0.0, 1.0],
252 half_h * aspect,
253 half_h,
254 0.01,
255 radius * 20.0,
256 )
257 }
258}
259
260#[cfg(test)]
261mod tests {
262 use super::*;
263
264 #[test]
265 fn artifact_camera_centers_bbox() {
266 let bbox = Aabb {
267 min: [0.0, 0.0, 0.0],
268 max: [10.0, 10.0, 10.0],
269 };
270 let cam = artifact_camera(&bbox, 640, 480);
271 let c = [5.0f32, 5.0, 5.0, 1.0];
273 let m = cam.view_proj;
274 let mut clip = [0.0f32; 4];
275 for row in 0..4 {
276 clip[row] = m[0][row] * c[0] + m[1][row] * c[1] + m[2][row] * c[2] + m[3][row] * c[3];
277 }
278 assert!((clip[0] / clip[3]).abs() < 1e-5);
279 assert!((clip[1] / clip[3]).abs() < 1e-5);
280 let z = clip[2] / clip[3];
281 assert!((0.0..=1.0).contains(&z), "z {z}");
282 for corner in [[0.0f32, 0.0, 0.0, 1.0], [10.0, 10.0, 10.0, 1.0]] {
284 let mut clip = [0.0f32; 4];
285 for row in 0..4 {
286 clip[row] = m[0][row] * corner[0]
287 + m[1][row] * corner[1]
288 + m[2][row] * corner[2]
289 + m[3][row] * corner[3];
290 }
291 assert!((clip[0] / clip[3]).abs() <= 1.0);
292 assert!((clip[1] / clip[3]).abs() <= 1.0);
293 }
294 }
295}