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}
227
228#[derive(Clone, Debug)]
232#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
233pub struct Camera {
234 viewport: Viewport,
235 projection_type: ProjectionType,
236 z_near: f32,
237 z_far: f32,
238 position: Vec3,
239 target: Vec3,
240 up: Vec3,
241 view: Mat4,
242 projection: Mat4,
243}
244
245impl Camera {
246 pub fn new_orthographic(
250 viewport: Viewport,
251 position: Vec3,
252 target: Vec3,
253 up: Vec3,
254 height: f32,
255 z_near: f32,
256 z_far: f32,
257 ) -> Self {
258 let mut camera = Camera::new(viewport);
259 camera.set_view(position, target, up);
260 camera.set_orthographic_projection(height, z_near, z_far);
261 camera
262 }
263
264 pub fn new_perspective(
268 viewport: Viewport,
269 position: Vec3,
270 target: Vec3,
271 up: Vec3,
272 field_of_view_y: impl Into<Radians>,
273 z_near: f32,
274 z_far: f32,
275 ) -> Self {
276 let mut camera = Camera::new(viewport);
277 camera.set_view(position, target, up);
278 camera.set_perspective_projection(field_of_view_y, z_near, z_far);
279 camera
280 }
281
282 pub fn set_perspective_projection(
286 &mut self,
287 field_of_view_y: impl Into<Radians>,
288 z_near: f32,
289 z_far: f32,
290 ) {
291 self.z_near = z_near;
292 self.z_far = z_far;
293 let field_of_view_y = field_of_view_y.into();
294 self.projection_type = ProjectionType::Perspective { field_of_view_y };
295 self.projection =
296 cgmath::perspective(field_of_view_y, self.viewport.aspect(), z_near, z_far);
297 }
298
299 pub fn set_orthographic_projection(&mut self, height: f32, z_near: f32, z_far: f32) {
307 self.projection_type = ProjectionType::Orthographic { height };
308 self.z_near = z_near;
309 self.z_far = z_far;
310 let zoom = self.position.distance(self.target);
311 let height = zoom * height;
312 let width = height * self.viewport.aspect();
313 self.projection = cgmath::ortho(
314 -0.5 * width,
315 0.5 * width,
316 -0.5 * height,
317 0.5 * height,
318 z_near,
319 z_far,
320 );
321 }
322
323 pub fn set_viewport(&mut self, viewport: Viewport) -> bool {
328 if self.viewport != viewport {
329 self.viewport = viewport;
330 match self.projection_type {
331 ProjectionType::Orthographic { height } => {
332 self.set_orthographic_projection(height, self.z_near, self.z_far);
333 }
334 ProjectionType::Perspective { field_of_view_y } => {
335 self.set_perspective_projection(field_of_view_y, self.z_near, self.z_far);
336 }
337 }
338 true
339 } else {
340 false
341 }
342 }
343
344 pub fn set_view(&mut self, position: Vec3, target: Vec3, up: Vec3) {
349 self.position = position;
350 self.target = target;
351 self.up = up.normalize();
352 self.view = Mat4::look_at_rh(
353 Point3::from_vec(self.position),
354 Point3::from_vec(self.target),
355 self.up,
356 );
357 if let ProjectionType::Orthographic { height } = self.projection_type {
358 self.set_orthographic_projection(height, self.z_near, self.z_far);
359 }
360 }
361
362 pub fn frustum(&self) -> Frustum {
364 Frustum::new(self.projection() * self.view())
365 }
366
367 pub fn position_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
371 match self.projection_type() {
372 ProjectionType::Orthographic { .. } => {
373 let coords = self.uv_coordinates_at_pixel(pixel);
374 self.position_at_uv_coordinates(coords)
375 }
376 ProjectionType::Perspective { .. } => self.position,
377 }
378 }
379
380 pub fn position_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
384 match self.projection_type() {
385 ProjectionType::Orthographic { .. } => {
386 let coords = coords.into();
387 let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0.0, 1.);
388 let p = (self.screen2ray() * screen_pos).truncate();
389 p + (self.position - p).project_on(self.view_direction()) }
391 ProjectionType::Perspective { .. } => self.position,
392 }
393 }
394
395 pub fn view_direction_at_pixel(&self, pixel: impl Into<PixelPoint>) -> Vec3 {
399 match self.projection_type() {
400 ProjectionType::Orthographic { .. } => self.view_direction(),
401 ProjectionType::Perspective { .. } => {
402 let coords = self.uv_coordinates_at_pixel(pixel);
403 self.view_direction_at_uv_coordinates(coords)
404 }
405 }
406 }
407
408 pub fn view_direction_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> Vec3 {
412 match self.projection_type() {
413 ProjectionType::Orthographic { .. } => self.view_direction(),
414 ProjectionType::Perspective { .. } => {
415 let coords = coords.into();
416 let screen_pos = vec4(2. * coords.u - 1., 2. * coords.v - 1.0, 0., 1.);
417 (self.screen2ray() * screen_pos).truncate().normalize()
418 }
419 }
420 }
421
422 pub fn uv_coordinates_at_pixel(&self, pixel: impl Into<PixelPoint>) -> UvCoordinate {
426 let pixel = pixel.into();
427 (
428 (pixel.x - self.viewport.x as f32) / self.viewport.width as f32,
429 (pixel.y - self.viewport.y as f32) / self.viewport.height as f32,
430 )
431 .into()
432 }
433
434 pub fn uv_coordinates_at_position(&self, position: Vec3) -> UvCoordinate {
438 let proj = self.projection() * self.view() * position.extend(1.0);
439 (
440 0.5 * (proj.x / proj.w.abs() + 1.0),
441 0.5 * (proj.y / proj.w.abs() + 1.0),
442 )
443 .into()
444 }
445
446 pub fn pixel_at_uv_coordinates(&self, coords: impl Into<UvCoordinate>) -> PixelPoint {
450 let coords = coords.into();
451 (
452 coords.u * self.viewport.width as f32 + self.viewport.x as f32,
453 coords.v * self.viewport.height as f32 + self.viewport.y as f32,
454 )
455 .into()
456 }
457
458 pub fn pixel_at_position(&self, position: Vec3) -> PixelPoint {
462 self.pixel_at_uv_coordinates(self.uv_coordinates_at_position(position))
463 }
464
465 pub fn projection_type(&self) -> &ProjectionType {
469 &self.projection_type
470 }
471
472 pub fn view(&self) -> Mat4 {
476 self.view
477 }
478
479 pub fn projection(&self) -> Mat4 {
483 self.projection
484 }
485
486 pub fn viewport(&self) -> Viewport {
490 self.viewport
491 }
492
493 pub fn z_near(&self) -> f32 {
497 self.z_near
498 }
499
500 pub fn z_far(&self) -> f32 {
504 self.z_far
505 }
506
507 pub fn position(&self) -> Vec3 {
511 self.position
512 }
513
514 pub fn target(&self) -> Vec3 {
518 self.target
519 }
520
521 pub fn up(&self) -> Vec3 {
526 self.up
527 }
528
529 pub fn up_orthogonal(&self) -> Vec3 {
533 self.right_direction().cross(self.view_direction())
534 }
535
536 pub fn view_direction(&self) -> Vec3 {
540 (self.target - self.position).normalize()
541 }
542
543 pub fn right_direction(&self) -> Vec3 {
547 self.view_direction().cross(self.up)
548 }
549
550 fn new(viewport: Viewport) -> Camera {
551 Camera {
552 viewport,
553 projection_type: ProjectionType::Orthographic { height: 1.0 },
554 z_near: 0.0,
555 z_far: 0.0,
556 position: vec3(0.0, 0.0, 5.0),
557 target: vec3(0.0, 0.0, 0.0),
558 up: vec3(0.0, 1.0, 0.0),
559 view: Mat4::identity(),
560 projection: Mat4::identity(),
561 }
562 }
563
564 fn screen2ray(&self) -> Mat4 {
565 let mut v = self.view;
566 if let ProjectionType::Perspective { .. } = self.projection_type {
567 v[3] = vec4(0.0, 0.0, 0.0, 1.0);
568 }
569 (self.projection * v)
570 .invert()
571 .unwrap_or_else(|| Mat4::identity())
572 }
573
574 pub fn translate(&mut self, change: Vec3) {
578 self.set_view(self.position + change, self.target + change, self.up);
579 }
580
581 pub fn pitch(&mut self, delta: impl Into<Radians>) {
585 let target = (self.view.invert().unwrap()
586 * Mat4::from_angle_x(delta)
587 * self.view
588 * self.target.extend(1.0))
589 .truncate();
590 if (target - self.position).normalize().dot(self.up).abs() < 0.999 {
591 self.set_view(self.position, target, self.up);
592 }
593 }
594
595 pub fn yaw(&mut self, delta: impl Into<Radians>) {
599 let target = (self.view.invert().unwrap()
600 * Mat4::from_angle_y(delta)
601 * self.view
602 * self.target.extend(1.0))
603 .truncate();
604 self.set_view(self.position, target, self.up);
605 }
606
607 pub fn roll(&mut self, delta: impl Into<Radians>) {
611 let up = (self.view.invert().unwrap()
612 * Mat4::from_angle_z(delta)
613 * self.view
614 * (self.up + self.position).extend(1.0))
615 .truncate()
616 - self.position;
617 self.set_view(self.position, self.target, up.normalize());
618 }
619
620 pub fn rotate_around(&mut self, point: Vec3, x: f32, y: f32) {
626 let dir = (point - self.position()).normalize();
627 let right = dir.cross(self.up);
628 let up = right.cross(dir);
629 let new_dir = (point - self.position() + right * x - up * y).normalize();
630 let rotation = rotation_matrix_from_dir_to_dir(dir, new_dir);
631 let new_position = (rotation * (self.position() - point).extend(1.0)).truncate() + point;
632 let new_target = (rotation * (self.target() - point).extend(1.0)).truncate() + point;
633 self.set_view(new_position, new_target, up);
634 }
635
636 pub fn rotate_around_with_fixed_up(&mut self, point: Vec3, x: f32, y: f32) {
641 let position = self.position() - point;
644 let target = self.target() - point;
645 let up = self.up.normalize();
646 let k_x = up;
650 let k_y = (target - position).cross(up).normalize();
651 let cos_x = (-x).cos();
654 let sin_x = (-x).sin();
655 let cos_y = (-y).cos();
656 let sin_y = (-y).sin();
657 let rodrigues =
659 |v, k: Vec3, cos, sin| v * cos + k.cross(v) * sin + k * k.dot(v) * (1.0 - cos);
660 let position_x = rodrigues(position, k_x, cos_x, sin_x);
661 let target_x = rodrigues(target, k_x, cos_x, sin_x);
662 let position_y = rodrigues(position_x, k_y, cos_y, sin_y);
663 let target_y = rodrigues(target_x, k_y, cos_y, sin_y);
664 let new_dir = (target_y - position_y).normalize();
666 if new_dir.dot(up).abs() < 0.999 {
667 self.set_view(position_y + point, target_y + point, self.up);
668 } else {
669 self.set_view(position_x + point, target_x + point, self.up);
670 }
671 }
672
673 pub fn zoom(&mut self, delta: f32, minimum_distance: f32, maximum_distance: f32) {
677 self.zoom_towards(self.target, delta, minimum_distance, maximum_distance);
678 }
679
680 pub fn zoom_towards(
685 &mut self,
686 point: Vec3,
687 delta: f32,
688 minimum_distance: f32,
689 maximum_distance: f32,
690 ) {
691 let view = self.view_direction();
692 let towards = (point - self.position).normalize();
693 let cos_angle = view.dot(towards);
694 if cos_angle.abs() > std::f32::EPSILON {
695 let distance = self.target.distance(self.position);
696 let minimum_distance = minimum_distance.max(std::f32::EPSILON);
697 let maximum_distance = maximum_distance.max(minimum_distance);
698 let delta_clamped =
699 distance - (distance - delta).clamp(minimum_distance, maximum_distance);
700 let a = view * delta_clamped;
701 let b = towards * delta_clamped / cos_angle;
702 self.set_view(self.position + b, self.target + b - a, self.up);
703 }
704 }
705
706 pub fn set_zoom_factor(&mut self, zoom_factor: f32) {
710 let zoom_factor = zoom_factor.max(std::f32::EPSILON);
711 let position = self.target - self.view_direction() / zoom_factor;
712 self.set_view(position, self.target, self.up);
713 }
714
715 pub fn zoom_factor(&self) -> f32 {
719 let distance = self.target.distance(self.position);
720 if distance > f32::EPSILON {
721 1.0 / distance
722 } else {
723 0.0
724 }
725 }
726}