1pub mod camera;
10pub mod renderer;
11pub mod slice;
12pub mod volume;
13
14pub use camera::{Camera3D, CameraController, MouseButton};
15pub use renderer::{RenderConfig, Renderer3D, VisualizationMode};
16pub use slice::{SliceAxis, SliceConfig, SliceRenderer};
17pub use volume::{VolumeParams, VolumeRenderer};
18
19use crate::simulation::physics::Position3D;
20use glam::Mat4;
21
22#[derive(Debug, Clone, Copy)]
24pub enum ColorMap {
25 BlueWhiteRed,
27 Hot,
29 Diverging,
31 Grayscale,
33 CoolWarm,
35}
36
37impl ColorMap {
38 pub fn map(&self, value: f32, max_abs: f32) -> [f32; 4] {
40 let normalized = (value / max_abs.max(0.001)).clamp(-1.0, 1.0);
41
42 match self {
43 ColorMap::BlueWhiteRed => {
44 if normalized >= 0.0 {
45 [1.0, 1.0 - normalized, 1.0 - normalized, 1.0]
47 } else {
48 [1.0 + normalized, 1.0 + normalized, 1.0, 1.0]
50 }
51 }
52 ColorMap::Hot => {
53 let abs_val = normalized.abs();
54 if abs_val < 0.33 {
55 [abs_val * 3.0, 0.0, 0.0, 1.0]
56 } else if abs_val < 0.66 {
57 [1.0, (abs_val - 0.33) * 3.0, 0.0, 1.0]
58 } else {
59 [1.0, 1.0, (abs_val - 0.66) * 3.0, 1.0]
60 }
61 }
62 ColorMap::Diverging => {
63 if normalized >= 0.0 {
64 [1.0 - normalized * 0.5, 1.0, 1.0 - normalized, 1.0]
65 } else {
66 [1.0, 1.0 + normalized * 0.5, 1.0 + normalized, 1.0]
67 }
68 }
69 ColorMap::Grayscale => {
70 let gray = (normalized + 1.0) * 0.5;
71 [gray, gray, gray, 1.0]
72 }
73 ColorMap::CoolWarm => {
74 let t = (normalized + 1.0) * 0.5;
76 let r = 0.230 + t * (0.706 - 0.230);
77 let g = 0.299 + t * (0.016 - 0.299);
78 let b = 0.754 + t * (0.150 - 0.754);
79 [r, g, b, 1.0]
80 }
81 }
82 }
83
84 pub fn map_with_alpha(&self, value: f32, max_abs: f32, alpha_scale: f32) -> [f32; 4] {
86 let mut color = self.map(value, max_abs);
87 color[3] = (value.abs() / max_abs.max(0.001)).clamp(0.0, 1.0) * alpha_scale;
88 color
89 }
90}
91
92#[repr(C)]
94#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
95pub struct Vertex3D {
96 pub position: [f32; 3],
97 pub color: [f32; 4],
98 pub normal: [f32; 3],
99 pub tex_coord: [f32; 2],
100}
101
102impl Vertex3D {
103 pub fn new(position: [f32; 3], color: [f32; 4]) -> Self {
104 Self {
105 position,
106 color,
107 normal: [0.0, 1.0, 0.0],
108 tex_coord: [0.0, 0.0],
109 }
110 }
111
112 pub fn with_normal(mut self, normal: [f32; 3]) -> Self {
113 self.normal = normal;
114 self
115 }
116
117 pub fn with_tex_coord(mut self, tex_coord: [f32; 2]) -> Self {
118 self.tex_coord = tex_coord;
119 self
120 }
121
122 pub fn desc() -> wgpu::VertexBufferLayout<'static> {
123 wgpu::VertexBufferLayout {
124 array_stride: std::mem::size_of::<Vertex3D>() as wgpu::BufferAddress,
125 step_mode: wgpu::VertexStepMode::Vertex,
126 attributes: &[
127 wgpu::VertexAttribute {
128 offset: 0,
129 shader_location: 0,
130 format: wgpu::VertexFormat::Float32x3,
131 },
132 wgpu::VertexAttribute {
133 offset: 12,
134 shader_location: 1,
135 format: wgpu::VertexFormat::Float32x4,
136 },
137 wgpu::VertexAttribute {
138 offset: 28,
139 shader_location: 2,
140 format: wgpu::VertexFormat::Float32x3,
141 },
142 wgpu::VertexAttribute {
143 offset: 40,
144 shader_location: 3,
145 format: wgpu::VertexFormat::Float32x2,
146 },
147 ],
148 }
149 }
150}
151
152#[repr(C)]
154#[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)]
155pub struct CameraUniform {
156 pub view_proj: [[f32; 4]; 4],
157 pub view: [[f32; 4]; 4],
158 pub camera_pos: [f32; 4],
159 pub grid_size: [f32; 4],
160}
161
162impl CameraUniform {
163 pub fn new() -> Self {
164 Self {
165 view_proj: Mat4::IDENTITY.to_cols_array_2d(),
166 view: Mat4::IDENTITY.to_cols_array_2d(),
167 camera_pos: [0.0, 0.0, 0.0, 1.0],
168 grid_size: [1.0, 1.0, 1.0, 1.0],
169 }
170 }
171
172 pub fn update(&mut self, camera: &Camera3D, grid_size: (f32, f32, f32)) {
173 self.view_proj = camera.view_projection_matrix().to_cols_array_2d();
174 self.view = camera.view_matrix().to_cols_array_2d();
175 let pos = camera.position();
176 self.camera_pos = [pos.x, pos.y, pos.z, 1.0];
177 self.grid_size = [grid_size.0, grid_size.1, grid_size.2, 1.0];
178 }
179}
180
181impl Default for CameraUniform {
182 fn default() -> Self {
183 Self::new()
184 }
185}
186
187pub struct MarkerSphere {
189 pub position: Position3D,
190 pub radius: f32,
191 pub color: [f32; 4],
192}
193
194impl MarkerSphere {
195 pub fn new(position: Position3D, radius: f32, color: [f32; 4]) -> Self {
196 Self {
197 position,
198 radius,
199 color,
200 }
201 }
202
203 pub fn generate_vertices(&self, segments: u32) -> Vec<Vertex3D> {
205 let mut vertices = Vec::new();
206
207 for i in 0..=segments {
208 let lat = std::f32::consts::PI * i as f32 / segments as f32;
209 let sin_lat = lat.sin();
210 let cos_lat = lat.cos();
211
212 for j in 0..=segments {
213 let lon = 2.0 * std::f32::consts::PI * j as f32 / segments as f32;
214 let sin_lon = lon.sin();
215 let cos_lon = lon.cos();
216
217 let x = self.position.x + self.radius * sin_lat * cos_lon;
218 let y = self.position.y + self.radius * cos_lat;
219 let z = self.position.z + self.radius * sin_lat * sin_lon;
220
221 let nx = sin_lat * cos_lon;
222 let ny = cos_lat;
223 let nz = sin_lat * sin_lon;
224
225 vertices.push(
226 Vertex3D::new([x, y, z], self.color)
227 .with_normal([nx, ny, nz])
228 .with_tex_coord([j as f32 / segments as f32, i as f32 / segments as f32]),
229 );
230 }
231 }
232
233 vertices
234 }
235
236 pub fn generate_indices(&self, segments: u32) -> Vec<u32> {
238 let mut indices = Vec::new();
239 let row_length = segments + 1;
240
241 for i in 0..segments {
242 for j in 0..segments {
243 let a = i * row_length + j;
244 let b = a + row_length;
245
246 indices.push(a);
247 indices.push(b);
248 indices.push(a + 1);
249
250 indices.push(a + 1);
251 indices.push(b);
252 indices.push(b + 1);
253 }
254 }
255
256 indices
257 }
258}
259
260pub struct GridLines {
262 pub size: (f32, f32, f32),
263 pub divisions: (u32, u32, u32),
264 pub color: [f32; 4],
265}
266
267impl GridLines {
268 pub fn new(size: (f32, f32, f32)) -> Self {
269 Self {
270 size,
271 divisions: (10, 10, 10),
272 color: [0.3, 0.3, 0.3, 1.0],
273 }
274 }
275
276 pub fn generate_floor_grid(&self) -> Vec<Vertex3D> {
278 let mut vertices = Vec::new();
279 let (sx, _, sz) = self.size;
280 let (dx, _, dz) = self.divisions;
281
282 for i in 0..=dz {
284 let z = i as f32 * sz / dz as f32;
285 vertices.push(Vertex3D::new([0.0, 0.0, z], self.color));
286 vertices.push(Vertex3D::new([sx, 0.0, z], self.color));
287 }
288
289 for i in 0..=dx {
291 let x = i as f32 * sx / dx as f32;
292 vertices.push(Vertex3D::new([x, 0.0, 0.0], self.color));
293 vertices.push(Vertex3D::new([x, 0.0, sz], self.color));
294 }
295
296 vertices
297 }
298
299 pub fn generate_box(&self) -> Vec<Vertex3D> {
301 let mut vertices = Vec::new();
302 let (sx, sy, sz) = self.size;
303
304 vertices.extend([
306 Vertex3D::new([0.0, 0.0, 0.0], self.color),
307 Vertex3D::new([sx, 0.0, 0.0], self.color),
308 Vertex3D::new([sx, 0.0, 0.0], self.color),
309 Vertex3D::new([sx, 0.0, sz], self.color),
310 Vertex3D::new([sx, 0.0, sz], self.color),
311 Vertex3D::new([0.0, 0.0, sz], self.color),
312 Vertex3D::new([0.0, 0.0, sz], self.color),
313 Vertex3D::new([0.0, 0.0, 0.0], self.color),
314 ]);
315
316 vertices.extend([
318 Vertex3D::new([0.0, sy, 0.0], self.color),
319 Vertex3D::new([sx, sy, 0.0], self.color),
320 Vertex3D::new([sx, sy, 0.0], self.color),
321 Vertex3D::new([sx, sy, sz], self.color),
322 Vertex3D::new([sx, sy, sz], self.color),
323 Vertex3D::new([0.0, sy, sz], self.color),
324 Vertex3D::new([0.0, sy, sz], self.color),
325 Vertex3D::new([0.0, sy, 0.0], self.color),
326 ]);
327
328 vertices.extend([
330 Vertex3D::new([0.0, 0.0, 0.0], self.color),
331 Vertex3D::new([0.0, sy, 0.0], self.color),
332 Vertex3D::new([sx, 0.0, 0.0], self.color),
333 Vertex3D::new([sx, sy, 0.0], self.color),
334 Vertex3D::new([sx, 0.0, sz], self.color),
335 Vertex3D::new([sx, sy, sz], self.color),
336 Vertex3D::new([0.0, 0.0, sz], self.color),
337 Vertex3D::new([0.0, sy, sz], self.color),
338 ]);
339
340 vertices
341 }
342}
343
344pub struct HeadWireframe {
346 pub position: Position3D,
347 pub scale: f32,
348 pub yaw: f32,
349}
350
351impl HeadWireframe {
352 pub fn new(position: Position3D, scale: f32) -> Self {
353 Self {
354 position,
355 scale,
356 yaw: 0.0,
357 }
358 }
359
360 pub fn generate_vertices(&self) -> Vec<Vertex3D> {
362 let mut vertices = Vec::new();
363 let head_color = [0.2, 0.8, 0.2, 1.0];
364 let ear_color = [0.8, 0.2, 0.2, 1.0];
365
366 let segments = 16u32;
368 let head_width = 0.15 * self.scale;
369 let head_height = 0.22 * self.scale;
370 let head_depth = 0.18 * self.scale;
371
372 for i in 0..=segments {
373 let lat = std::f32::consts::PI * i as f32 / segments as f32;
374
375 for j in 0..segments {
376 let lon1 = 2.0 * std::f32::consts::PI * j as f32 / segments as f32;
377 let lon2 = 2.0 * std::f32::consts::PI * (j + 1) as f32 / segments as f32;
378
379 let y1 = self.position.y + head_height * lat.cos();
380 let x1 = self.position.x + head_width * lat.sin() * lon1.cos();
381 let z1 = self.position.z + head_depth * lat.sin() * lon1.sin();
382
383 let x2 = self.position.x + head_width * lat.sin() * lon2.cos();
384 let z2 = self.position.z + head_depth * lat.sin() * lon2.sin();
385
386 vertices.push(Vertex3D::new([x1, y1, z1], head_color));
387 vertices.push(Vertex3D::new([x2, y1, z2], head_color));
388 }
389 }
390
391 let ear_offset = 0.085 * self.scale;
393 let ear_radius = 0.03 * self.scale;
394
395 for i in 0..=8 {
397 let angle = 2.0 * std::f32::consts::PI * i as f32 / 8.0;
398 let x = self.position.x - ear_offset;
399 let y = self.position.y + ear_radius * angle.cos();
400 let z = self.position.z + ear_radius * angle.sin();
401 vertices.push(Vertex3D::new([x, y, z], ear_color));
402 }
403
404 for i in 0..=8 {
406 let angle = 2.0 * std::f32::consts::PI * i as f32 / 8.0;
407 let x = self.position.x + ear_offset;
408 let y = self.position.y + ear_radius * angle.cos();
409 let z = self.position.z + ear_radius * angle.sin();
410 vertices.push(Vertex3D::new([x, y, z], ear_color));
411 }
412
413 let nose_length = 0.15 * self.scale;
415 vertices.push(Vertex3D::new(
416 [self.position.x, self.position.y, self.position.z],
417 [1.0, 1.0, 0.0, 1.0],
418 ));
419 vertices.push(Vertex3D::new(
420 [
421 self.position.x,
422 self.position.y,
423 self.position.z + nose_length,
424 ],
425 [1.0, 1.0, 0.0, 1.0],
426 ));
427
428 vertices
429 }
430}
431
432#[cfg(test)]
433mod tests {
434 use super::*;
435
436 #[test]
437 fn test_color_map() {
438 let map = ColorMap::BlueWhiteRed;
439
440 let white = map.map(0.0, 1.0);
442 assert!((white[0] - 1.0).abs() < 0.01);
443 assert!((white[1] - 1.0).abs() < 0.01);
444 assert!((white[2] - 1.0).abs() < 0.01);
445
446 let red = map.map(1.0, 1.0);
448 assert!(red[0] > red[1]);
449 assert!(red[0] > red[2]);
450
451 let blue = map.map(-1.0, 1.0);
453 assert!(blue[2] > blue[0]);
454 assert!(blue[2] > blue[1]);
455 }
456
457 #[test]
458 fn test_sphere_generation() {
459 let sphere = MarkerSphere::new(Position3D::origin(), 1.0, [1.0, 0.0, 0.0, 1.0]);
460
461 let vertices = sphere.generate_vertices(8);
462 let indices = sphere.generate_indices(8);
463
464 assert!(!vertices.is_empty());
465 assert!(!indices.is_empty());
466 }
467
468 #[test]
469 fn test_grid_lines() {
470 let grid = GridLines::new((10.0, 5.0, 10.0));
471
472 let floor = grid.generate_floor_grid();
473 let bbox = grid.generate_box();
474
475 assert!(!floor.is_empty());
476 assert!(!bbox.is_empty());
477 assert_eq!(bbox.len(), 24);
479 }
480}