1use crate::geometry3d::{cross3 as cross, dot3 as dot, norm3 as norm};
5
6#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
8pub struct Aabb {
9 pub min: [f64; 3],
10 pub max: [f64; 3],
11}
12
13impl Aabb {
14 pub fn empty() -> Self {
15 Self {
16 min: [f64::INFINITY; 3],
17 max: [f64::NEG_INFINITY; 3],
18 }
19 }
20
21 pub fn is_empty(&self) -> bool {
22 (0..3).any(|i| self.min[i] > self.max[i])
23 }
24
25 pub fn expand(&mut self, p: [f64; 3]) {
26 for i in 0..3 {
27 self.min[i] = self.min[i].min(p[i]);
28 self.max[i] = self.max[i].max(p[i]);
29 }
30 }
31
32 pub fn union(&mut self, other: &Aabb) {
33 if other.is_empty() {
34 return;
35 }
36 self.expand(other.min);
37 self.expand(other.max);
38 }
39
40 pub fn center(&self) -> [f64; 3] {
41 [
42 (self.min[0] + self.max[0]) * 0.5,
43 (self.min[1] + self.max[1]) * 0.5,
44 (self.min[2] + self.max[2]) * 0.5,
45 ]
46 }
47
48 pub fn size(&self) -> [f64; 3] {
49 [
50 self.max[0] - self.min[0],
51 self.max[1] - self.min[1],
52 self.max[2] - self.min[2],
53 ]
54 }
55}
56
57#[derive(Debug, Clone, Copy)]
61pub struct Camera {
62 pub view_proj: [[f32; 4]; 4],
64 pub forward: [f32; 3],
66}
67
68fn look_at(eye: [f64; 3], target: [f64; 3], up: [f64; 3]) -> [[f64; 4]; 4] {
70 let f = norm([target[0] - eye[0], target[1] - eye[1], target[2] - eye[2]]);
71 let r = norm(cross(f, up));
72 let u = cross(r, f);
73 [
75 [r[0], u[0], -f[0], 0.0],
76 [r[1], u[1], -f[1], 0.0],
77 [r[2], u[2], -f[2], 0.0],
78 [-dot(r, eye), -dot(u, eye), dot(f, eye), 1.0],
79 ]
80}
81
82fn ortho(l: f64, r: f64, b: f64, t: f64, near: f64, far: f64) -> [[f64; 4]; 4] {
85 let sx = 2.0 / (r - l);
86 let sy = 2.0 / (t - b);
87 let sz = -1.0 / (far - near);
88 [
89 [sx, 0.0, 0.0, 0.0],
90 [0.0, sy, 0.0, 0.0],
91 [0.0, 0.0, sz, 0.0],
92 [
93 -(r + l) / (r - l),
94 -(t + b) / (t - b),
95 -near / (far - near),
96 1.0,
97 ],
98 ]
99}
100
101fn mul(a: [[f64; 4]; 4], b: [[f64; 4]; 4]) -> [[f64; 4]; 4] {
102 let mut out = [[0.0; 4]; 4];
103 for col in 0..4 {
104 for row in 0..4 {
105 let mut sum = 0.0;
106 for k in 0..4 {
107 sum += a[k][row] * b[col][k];
108 }
109 out[col][row] = sum;
110 }
111 }
112 out
113}
114
115fn to_f32(m: [[f64; 4]; 4]) -> [[f32; 4]; 4] {
116 let mut out = [[0.0f32; 4]; 4];
117 for c in 0..4 {
118 for r in 0..4 {
119 out[c][r] = m[c][r] as f32;
120 }
121 }
122 out
123}
124
125pub fn ortho_camera(
128 eye: [f64; 3],
129 target: [f64; 3],
130 up: [f64; 3],
131 half_width: f64,
132 half_height: f64,
133 near: f64,
134 far: f64,
135) -> Camera {
136 let view = look_at(eye, target, up);
137 let proj = ortho(-half_width, half_width, -half_height, half_height, near, far);
138 let forward = norm([
139 target[0] - eye[0],
140 target[1] - eye[1],
141 target[2] - eye[2],
142 ]);
143 Camera {
144 view_proj: to_f32(mul(proj, view)),
145 forward: [forward[0] as f32, forward[1] as f32, forward[2] as f32],
146 }
147}
148
149pub fn artifact_camera(bbox: &Aabb, width: u32, height: u32) -> Camera {
153 let bbox = if bbox.is_empty() {
154 Aabb {
155 min: [-1.0, -1.0, -1.0],
156 max: [1.0, 1.0, 1.0],
157 }
158 } else {
159 *bbox
160 };
161 let center = bbox.center();
162 let size = bbox.size();
163 let radius = size[0].max(size[1]).max(size[2]).max(1e-9) * 0.75;
164 let dir = norm([1.0, -1.2, 0.9]);
165 let eye = [
166 center[0] + dir[0] * radius * 6.0,
167 center[1] + dir[1] * radius * 6.0,
168 center[2] + dir[2] * radius * 6.0,
169 ];
170 let aspect = width.max(1) as f64 / height.max(1) as f64;
171 ortho_camera(
172 eye,
173 center,
174 [0.0, 0.0, 1.0],
175 radius * aspect * 1.15,
176 radius * 1.15,
177 0.01,
178 radius * 20.0,
179 )
180}
181
182#[derive(Debug, Clone, Copy)]
185pub struct Orbit {
186 pub azimuth: f64,
187 pub elevation: f64,
188 pub zoom: f64,
189}
190
191impl Default for Orbit {
192 fn default() -> Self {
193 let dir = norm([1.0, -1.2, 0.9]);
195 Self {
196 azimuth: dir[1].atan2(dir[0]),
197 elevation: dir[2].asin(),
198 zoom: 1.0,
199 }
200 }
201}
202
203impl Orbit {
204 pub fn camera(&self, bbox: &Aabb, width: u32, height: u32) -> Camera {
205 let bbox = if bbox.is_empty() {
206 Aabb {
207 min: [-1.0, -1.0, -1.0],
208 max: [1.0, 1.0, 1.0],
209 }
210 } else {
211 *bbox
212 };
213 let center = bbox.center();
214 let size = bbox.size();
215 let radius = size[0].max(size[1]).max(size[2]).max(1e-9) * 0.75;
216 let el = self.elevation.clamp(-1.55, 1.55);
217 let dir = [
218 el.cos() * self.azimuth.cos(),
219 el.cos() * self.azimuth.sin(),
220 el.sin(),
221 ];
222 let eye = [
223 center[0] + dir[0] * radius * 6.0,
224 center[1] + dir[1] * radius * 6.0,
225 center[2] + dir[2] * radius * 6.0,
226 ];
227 let aspect = width.max(1) as f64 / height.max(1) as f64;
228 let half_h = radius * 1.15 * self.zoom;
229 ortho_camera(
230 eye,
231 center,
232 [0.0, 0.0, 1.0],
233 half_h * aspect,
234 half_h,
235 0.01,
236 radius * 20.0,
237 )
238 }
239}
240
241