1pub use crate::prelude::*;
2
3#[derive(Debug, Copy, Clone, PartialEq)]
6pub struct UvCoordinate {
7 pub u: f32,
9 pub v: f32,
11}
12
13impl From<(f32, f32)> for UvCoordinate {
14 fn from(value: (f32, f32)) -> Self {
15 Self {
16 u: value.0,
17 v: value.1,
18 }
19 }
20}
21
22impl From<UvCoordinate> for (f32, f32) {
23 fn from(value: UvCoordinate) -> Self {
24 (value.u, value.v)
25 }
26}
27
28impl From<Vec2> for UvCoordinate {
29 fn from(value: Vec2) -> Self {
30 Self {
31 u: value.x,
32 v: value.y,
33 }
34 }
35}
36
37impl From<UvCoordinate> for Vec2 {
38 fn from(value: UvCoordinate) -> Self {
39 Self {
40 x: value.u,
41 y: value.v,
42 }
43 }
44}
45
46#[derive(Debug, Copy, Clone, PartialEq)]
49pub struct PixelPoint {
50 pub x: f32,
52 pub y: f32,
54}
55
56impl From<(f32, f32)> for PixelPoint {
57 fn from(value: (f32, f32)) -> Self {
58 Self {
59 x: value.0,
60 y: value.1,
61 }
62 }
63}
64
65impl From<PixelPoint> for (f32, f32) {
66 fn from(value: PixelPoint) -> Self {
67 (value.x, value.y)
68 }
69}
70
71impl From<Vec2> for PixelPoint {
72 fn from(value: Vec2) -> Self {
73 Self {
74 x: value.x,
75 y: value.y,
76 }
77 }
78}
79
80impl From<PixelPoint> for Vec2 {
81 fn from(value: PixelPoint) -> Self {
82 Self {
83 x: value.x,
84 y: value.y,
85 }
86 }
87}
88
89#[derive(Debug, Copy, Clone, PartialEq)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
95pub struct Viewport {
96 pub x: i32,
98 pub y: i32,
100 pub width: u32,
102 pub height: u32,
104}
105
106impl Viewport {
107 pub fn new_at_origo(width: u32, height: u32) -> Self {
111 Self {
112 x: 0,
113 y: 0,
114 width,
115 height,
116 }
117 }
118
119 pub fn aspect(&self) -> f32 {
123 self.width as f32 / self.height as f32
124 }
125
126 pub fn intersection(&self, other: impl Into<Self>) -> Self {
130 let other = other.into();
131 let x = self.x.max(other.x);
132 let y = self.y.max(other.y);
133 let width =
134 (self.x + self.width as i32 - x).clamp(0, other.x + other.width as i32 - x) as u32;
135 let height =
136 (self.y + self.height as i32 - y).clamp(0, other.y + other.height as i32 - y) as u32;
137 Self {
138 x,
139 y,
140 width,
141 height,
142 }
143 }
144}
145
146pub struct Frustum([Vec4; 6]);
150
151impl Frustum {
152 pub fn new(view_projection: Mat4) -> Self {
154 let m = view_projection;
155 Self([
156 vec4(m.x.w + m.x.x, m.y.w + m.y.x, m.z.w + m.z.x, m.w.w + m.w.x),
157 vec4(m.x.w - m.x.x, m.y.w - m.y.x, m.z.w - m.z.x, m.w.w - m.w.x),
158 vec4(m.x.w + m.x.y, m.y.w + m.y.y, m.z.w + m.z.y, m.w.w + m.w.y),
159 vec4(m.x.w - m.x.y, m.y.w - m.y.y, m.z.w - m.z.y, m.w.w - m.w.y),
160 vec4(m.x.w + m.x.z, m.y.w + m.y.z, m.z.w + m.z.z, m.w.w + m.w.z),
161 vec4(m.x.w - m.x.z, m.y.w - m.y.z, m.z.w - m.z.z, m.w.w - m.w.z),
162 ])
163 }
164
165 pub fn contains(&self, aabb: AxisAlignedBoundingBox) -> bool {
167 if aabb.is_infinite() {
168 return true;
169 }
170 if aabb.is_empty() {
171 return false;
172 }
173 for i in 0..6 {
175 let mut out = 0;
176 if self.0[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
177 out += 1
178 };
179 if self.0[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.min().z, 1.0)) < 0.0 {
180 out += 1
181 };
182 if self.0[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
183 out += 1
184 };
185 if self.0[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.min().z, 1.0)) < 0.0 {
186 out += 1
187 };
188 if self.0[i].dot(vec4(aabb.min().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
189 out += 1
190 };
191 if self.0[i].dot(vec4(aabb.max().x, aabb.min().y, aabb.max().z, 1.0)) < 0.0 {
192 out += 1
193 };
194 if self.0[i].dot(vec4(aabb.min().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
195 out += 1
196 };
197 if self.0[i].dot(vec4(aabb.max().x, aabb.max().y, aabb.max().z, 1.0)) < 0.0 {
198 out += 1
199 };
200 if out == 8 {
201 return false;
202 }
203 }
204 true
207 }
208}
209
210#[derive(Clone, Debug)]
214#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
215pub enum ProjectionType {
216 Orthographic {
218 height: f32,
220 },
221 Perspective {
223 field_of_view_y: Radians,
225 },
226 Planar {
228 field_of_view_y: Radians,
230 },
231}
232
233#[derive(Clone, Debug)]
237#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
238pub struct Camera {
239 viewport: Viewport,
240 projection_type: ProjectionType,
241 z_near: f32,
242 z_far: f32,
243 position: Vec3,
244 target: Vec3,
245 up: Vec3,
246 view: Mat4,
247 projection: Mat4,
248}
249
250impl Camera {
251 pub fn new_orthographic(
255 viewport: Viewport,
256 position: Vec3,
257 target: Vec3,
258 up: Vec3,
259 height: f32,
260 z_near: f32,
261 z_far: f32,
262 ) -> Self {
263 let mut camera = Camera::new(viewport);
264 camera.set_view(position, target, up);
265 camera.set_orthographic_projection(height, z_near, z_far);
266 camera
267 }
268
269 pub fn new_perspective(
273 viewport: Viewport,
274 position: Vec3,
275 target: Vec3,
276 up: Vec3,
277 field_of_view_y: impl Into<Radians>,
278 z_near: f32,
279 z_far: f32,
280 ) -> Self {
281 let mut camera = Camera::new(viewport);
282 camera.set_view(position, target, up);
283 camera.set_perspective_projection(field_of_view_y, z_near, z_far);
284 camera
285 }
286
287 pub fn new_planar(
291 viewport: Viewport,
292 position: Vec3,
293 target: Vec3,
294 up: Vec3,
295 field_of_view_y: impl Into<Radians>,
296 z_near: f32,
297 z_far: f32,
298 ) -> Self {
299 let mut camera = Camera::new(viewport);
300 camera.set_view(position, target, up);
301 camera.set_planar_projection(field_of_view_y, z_near, z_far);
302 camera
303 }
304
305 pub fn set_perspective_projection(
309 &mut self,
310 field_of_view_y: impl Into<Radians>,
311 z_near: f32,
312 z_far: f32,
313 ) {
314 self.z_near = z_near;
315 self.z_far = z_far;
316 let field_of_view_y = field_of_view_y.into();
317 self.projection_type = ProjectionType::Perspective { field_of_view_y };
318 self.projection =
319 cgmath::perspective(field_of_view_y, self.viewport.aspect(), z_near, z_far);
320 }
321
322 pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
330 self.projection_type = ProjectionType::Orthographic { height };
331 self.z_near = z_near;
332 self.z_far = z_far;
333 let zoom = self.position.distance(self.target);
334 let height = zoom * height;
335 let width = height * self.viewport.aspect();
336 self.projection = cgmath::ortho(
337 -0.5 * width,
338 0.5 * width,
339 -0.5 * height,
340 0.5 * height,
341 z_near,
342 z_far,
343 );
344 }
345
346 pub fn set_planar_projection(
351 &mut self,
352 field_of_view_y: impl Into<Radians>,
353 mut z_near: f32,
354 mut z_far: f32,
355 ) {
356 self.z_near = z_near;
357 self.z_far = z_far;
358 let field_of_view_y = field_of_view_y.into();
359 self.projection_type = ProjectionType::Planar { field_of_view_y };
360 let depth = self.position.distance(self.target);
361 let height = 2.0 * depth;
362 let focal = -Rad::cot(field_of_view_y / 2.0) * depth;
363 z_near -= depth;
364 z_far -= depth;
365 if focal < 0.0 && z_near < focal {
367 z_near = focal + 0.001;
368 } else if focal > 0.0 && z_far > focal {
369 z_far = focal - 0.001;
370 }
371 self.projection = planar(
372 field_of_view_y,
373 self.viewport.aspect(),
374 height,
375 z_near,
376 z_far,
377 ) * Mat4::from_translation(vec3(0.0, 0.0, depth));
378 }
379
380 pub fn set_viewport(&mut self, viewport: Viewport) -> bool {
385 if self.viewport != viewport {
386 self.viewport = viewport;
387 match self.projection_type {
388 ProjectionType::Orthographic { height } => {
389 self.set_orthographic_projection(height, self.z_near, self.z_far);
390 }
391 ProjectionType::Perspective { field_of_view_y } => {
392 self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
393 }
394 ProjectionType::Planar { field_of_view_y } => {
395 self.set_planar_projection(field_of_view_y, self.z_near, self.z_far);
396 }
397 }
398 true
399 } else {
400 false
401 }
402 }
403
404 pub fn set_view(&mut self, position: Vec3, target: Vec3, up: Vec3) {
409 self.position = position;
410 self.target = target;
411 self.up = up.normalize();
412 self.view = Mat4::look_at_rh(
413 Point3::from_vec(self.position),
414 Point3::from_vec(self.target),
415 self.up,
416 );
417 match self.projection_type {
418 ProjectionType::Orthographic { height } => {
419 self.set_orthographic_projection(height, self.z_near, self.z_far)
420 }
421 ProjectionType::Planar { field_of_view_y } => {
422 self.set_planar_projection(field_of_view_y, self.z_near, self.z_far)
423 }
424 _ => {}
425 };
426 }
427
428 pub fn frustum(&self) -> Frustum {
430 Frustum::new(self.projection() * self.view())
431 }
432
433 pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
437 match self.projection_type() {
438 ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
439 let coords = self.uv_coordinates_at_pixel(pixel);
440 self.position_at_uv_coordinates(coords)
441 }
442 ProjectionType::Perspective { .. } => self.position,
443 }
444 }
445
446 pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
450 match self.projection_type() {
451 ProjectionType::Orthographic { .. } | ProjectionType::Planar { .. } => {
452 let coords = coords.into();
453 let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0.0, 1.);
454 let p = (self.screen2ray() * screen_pos).truncate();
455 p + (self.position - p).project_on(self.view_direction()) }
457 ProjectionType::Perspective { .. } => self.position,
458 }
459 }
460
461 pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
465 match self.projection_type() {
466 ProjectionType::Orthographic { .. } => self.view_direction(),
467 ProjectionType::Perspective { .. } | ProjectionType::Planar { .. } => {
468 let coords = self.uv_coordinates_at_pixel(pixel);
469 self.view_direction_at_uv_coordinates(coords)
470 }
471 }
472 }
473
474 pub fn view_direction_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
478 match self.projection_type() {
479 ProjectionType::Orthographic { .. } => self.view_direction(),
480 ProjectionType::Perspective { .. } => {
481 let coords = coords.into();
482 let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
483 (self.screen2ray() * screen_pos).truncate().normalize()
484 }
485 ProjectionType::Planar { .. } => {
486 let coords = coords.into();
487 let start_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, -0.5);
488 let end_pos = Point3::new(2. * coords.u - 1., 2. * coords.v - 1.0, 0.5);
489 (self.screen2ray().transform_point(end_pos)
490 - self.screen2ray().transform_point(start_pos))
491 .normalize()
492 }
493 }
494 }
495
496 pub fn uv_coordinates_at_pixel(&self, pixel: impl Into<PixelPoint>) -> UvCoordinate {
500 let pixel = pixel.into();
501 (
502 (pixel.x - self.viewport.x as f32) / self.viewport.width as f32,
503 (pixel.y - self.viewport.y as f32) / self.viewport.height as f32,
504 )
505 .into()
506 }
507
508 pub fn uv_coordinates_at_position(&self, position: Vec3) -> UvCoordinate {
512 let proj = self.projection() * self.view() * position.extend(1.0);
513 (
514 0.5 * (proj.x / proj.w.abs() + 1.0),
515 0.5 * (proj.y / proj.w.abs() + 1.0),
516 )
517 .into()
518 }
519
520 pub fn pixel_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> PixelPoint {
524 let coords = coords.into();
525 (
526 coords.u * self.viewport.width as f32 + self.viewport.x as f32,
527 coords.v * self.viewport.height as f32 + self.viewport.y as f32,
528 )
529 .into()
530 }
531
532 pub fn pixel_at_position(&self, position: Vec3) -> PixelPoint {
536 self.pixel_at_uv_coordinates(self.uv_coordinates_at_position(position))
537 }
538
539 pub fn projection_type(&self) -> &ProjectionType {
543 &self.projection_type
544 }
545
546 pub fn view(&self) -> Mat4 {
550 self.view
551 }
552
553 pub fn projection(&self) -> Mat4 {
557 self.projection
558 }
559
560 pub fn viewport(&self) -> Viewport {
564 self.viewport
565 }
566
567 pub fn z_near(&self) -> f32 {
571 self.z_near
572 }
573
574 pub fn z_far(&self) -> f32 {
578 self.z_far
579 }
580
581 pub fn position(&self) -> Vec3 {
585 self.position
586 }
587
588 pub fn target(&self) -> Vec3 {
592 self.target
593 }
594
595 pub fn up(&self) -> Vec3 {
600 self.up
601 }
602
603 pub fn up_orthogonal(&self) -> Vec3 {
607 self.right_direction().cross(self.view_direction())
608 }
609
610 pub fn view_direction(&self) -> Vec3 {
614 (self.target - self.position).normalize()
615 }
616
617 pub fn right_direction(&self) -> Vec3 {
621 self.view_direction().cross(self.up)
622 }
623
624 fn new(viewport: Viewport) -> Camera {
625 Camera {
626 viewport,
627 projection_type: ProjectionType::Orthographic { height: 1.0 },
628 z_near: 0.0,
629 z_far: 0.0,
630 position: vec3(0.0, 0.0, 5.0),
631 target: vec3(0.0, 0.0, 0.0),
632 up: vec3(0.0, 1.0, 0.0),
633 view: Mat4::identity(),
634 projection: Mat4::identity(),
635 }
636 }
637
638 fn screen2ray(&self) -> Mat4 {
639 let mut v = self.view;
640 if let ProjectionType::Perspective { .. } = self.projection_type {
641 v[3] = vec4(0.0, 0.0, 0.0, 1.0);
642 }
643 (self.projection * v).invert().unwrap_or(Mat4::identity())
644 }
645
646 pub fn translate(&mut self, change: Vec3) {
650 self.set_view(self.position + change, self.target + change, self.up);
651 }
652
653 pub fn pitch(&mut self, delta: impl Into<Radians>) {
657 let target = (self.view.invert().unwrap()
658 * Mat4::from_angle_x(delta)
659 * self.view
660 * self.target.extend(1.0))
661 .truncate();
662 if (target - self.position).normalize().dot(self.up).abs() < 0.999 {
663 self.set_view(self.position, target, self.up);
664 }
665 }
666
667 pub fn yaw(&mut self, delta: impl Into<Radians>) {
671 let target = (self.view.invert().unwrap()
672 * Mat4::from_angle_y(delta)
673 * self.view
674 * self.target.extend(1.0))
675 .truncate();
676 self.set_view(self.position, target, self.up);
677 }
678
679 pub fn roll(&mut self, delta: impl Into<Radians>) {
683 let up = (self.view.invert().unwrap()
684 * Mat4::from_angle_z(delta)
685 * self.view
686 * (self.up + self.position).extend(1.0))
687 .truncate()
688 - self.position;
689 self.set_view(self.position, self.target, up.normalize());
690 }
691
692 pub fn rotate_around(&mut self, point: Vec3, x: f32, y: f32) {
698 let dir = (point - self.position()).normalize();
699 let right = dir.cross(self.up);
700 let up = right.cross(dir);
701 let new_dir = (point - self.position() + right * x - up * y).normalize();
702 let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
703 let new_position = (rotation * (self.position() - point).extend(1.0)).truncate() + point;
704 let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
705 self.set_view(new_position, new_target, up);
706 }
707
708 pub fn rotate_around_with_fixed_up(&mut self, point: Vec3, x: f32, y: f32) {
713 let position = self.position() - point;
716 let target = self.target() - point;
717 let up = self.up.normalize();
718 let k_x = up;
722 let k_y = (target - position).cross(up).normalize();
723 let cos_x = (-x).cos();
726 let sin_x = (-x).sin();
727 let cos_y = (-y).cos();
728 let sin_y = (-y).sin();
729 let rodrigues =
731 |v, k: Vec3, cos, sin| v * cos + k.cross(v) * sin + k * k.dot(v) * (1.0 - cos);
732 let position_x = rodrigues(position, k_x, cos_x, sin_x);
733 let target_x = rodrigues(target, k_x, cos_x, sin_x);
734 let position_y = rodrigues(position_x, k_y, cos_y, sin_y);
735 let target_y = rodrigues(target_x, k_y, cos_y, sin_y);
736 let new_dir = (target_y - position_y).normalize();
738 if new_dir.dot(up).abs() < 0.999 {
739 self.set_view(position_y + point, target_y + point, self.up);
740 } else {
741 self.set_view(position_x + point, target_x + point, self.up);
742 }
743 }
744
745 pub fn zoom(&mut self, delta: f32, minimum_distance: f32, maximum_distance: f32) {
749 self.zoom_towards(self.target, delta, minimum_distance, maximum_distance);
750 }
751
752 pub fn zoom_towards(
757 &mut self,
758 point: Vec3,
759 delta: f32,
760 minimum_distance: f32,
761 maximum_distance: f32,
762 ) {
763 let view = self.view_direction();
764 let towards = (point - self.position).normalize();
765 let cos_angle = view.dot(towards);
766 if cos_angle.abs() > f32::EPSILON {
767 let distance = self.target.distance(self.position);
768 let minimum_distance = minimum_distance.max(f32::EPSILON);
769 let maximum_distance = maximum_distance.max(minimum_distance);
770 let delta_clamped =
771 distance - (distance - delta).clamp(minimum_distance, maximum_distance);
772 let a = view * delta_clamped;
773 let b = towards * delta_clamped / cos_angle;
774 self.set_view(self.position + b, self.target + b - a, self.up);
775 }
776 }
777
778 pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
782 let zoom_factor = zoom_factor.max(f32::EPSILON);
783 let position = self.target - self.view_direction() / zoom_factor;
784 self.set_view(position, self.target, self.up);
785 }
786
787 pub fn zoom_factor(&self) -> f32 {
791 let distance = self.target.distance(self.position);
792 if distance > f32::EPSILON {
793 1.0 / distance
794 } else {
795 0.0
796 }
797 }
798}