1use crate::{primitives::HalfSpace, Mat4, Vec3, Vec4};
2
3#[cfg(feature = "bevy_reflect")]
4use bevy_reflect::{std_traits::ReflectDefault, Reflect};
5#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
6use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
7
8#[derive(Clone, Copy, Debug, Default, PartialEq)]
15#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
16#[cfg_attr(
17 feature = "bevy_reflect",
18 derive(Reflect),
19 reflect(Clone, Debug, Default, PartialEq)
20)]
21#[cfg_attr(
22 all(feature = "serialize", feature = "bevy_reflect"),
23 reflect(Serialize, Deserialize)
24)]
25pub struct ViewFrustum {
26 pub half_spaces: [HalfSpace; 6],
28}
29
30impl ViewFrustum {
31 pub const NEAR_PLANE_IDX: usize = 4;
33 pub const FAR_PLANE_IDX: usize = 5;
35 const INACTIVE_HALF_SPACE: Vec4 = Vec4::new(0.0, 0.0, 0.0, f32::INFINITY);
39
40 #[inline]
42 pub fn from_clip_from_world(clip_from_world: &Mat4) -> Self {
43 let mut frustum = ViewFrustum::from_clip_from_world_no_far(clip_from_world);
44 frustum.half_spaces[Self::FAR_PLANE_IDX] = HalfSpace::new(clip_from_world.row(2));
45 frustum
46 }
47
48 #[inline]
51 pub fn from_clip_from_world_custom_far(
52 clip_from_world: &Mat4,
53 view_translation: &Vec3,
54 view_backward: &Vec3,
55 far: f32,
56 ) -> Self {
57 let mut frustum = ViewFrustum::from_clip_from_world_no_far(clip_from_world);
58 let far_center = *view_translation - far * *view_backward;
59 frustum.half_spaces[Self::FAR_PLANE_IDX] =
60 HalfSpace::new(view_backward.extend(-view_backward.dot(far_center)));
61 frustum
62 }
63
64 #[inline]
72 pub fn corners(&self) -> Option<[Vec3; 8]> {
73 let [left, right, top, bottom, near, far] = self.half_spaces;
74 Some([
75 HalfSpace::intersection_point(top, left, near)?,
76 HalfSpace::intersection_point(top, right, near)?,
77 HalfSpace::intersection_point(bottom, right, near)?,
78 HalfSpace::intersection_point(bottom, left, near)?,
79 HalfSpace::intersection_point(top, left, far)?,
80 HalfSpace::intersection_point(top, right, far)?,
81 HalfSpace::intersection_point(bottom, right, far)?,
82 HalfSpace::intersection_point(bottom, left, far)?,
83 ])
84 }
85
86 fn from_clip_from_world_no_far(clip_from_world: &Mat4) -> Self {
92 let row0 = clip_from_world.row(0);
93 let row1 = clip_from_world.row(1);
94 let row2 = clip_from_world.row(2);
95 let row3 = clip_from_world.row(3);
96
97 Self {
98 half_spaces: [
99 HalfSpace::new(row3 + row0),
100 HalfSpace::new(row3 - row0),
101 HalfSpace::new(row3 + row1),
102 HalfSpace::new(row3 - row1),
103 HalfSpace::new(row3 + row2),
104 HalfSpace::new(Self::INACTIVE_HALF_SPACE),
105 ],
106 }
107 }
108}
109
110#[cfg(test)]
111mod view_frustum_tests {
112 use core::f32::consts::FRAC_1_SQRT_2;
113
114 use approx::assert_relative_eq;
115
116 use super::ViewFrustum;
117 use crate::{primitives::HalfSpace, Vec3, Vec4};
118
119 #[test]
120 fn cuboid_frustum_corners() {
121 let cuboid_frustum = ViewFrustum {
122 half_spaces: [
126 HalfSpace::new(Vec4::new(1., 0., 0., 5.)),
128 HalfSpace::new(Vec4::new(-1., 0., 0., 4.)),
130 HalfSpace::new(Vec4::new(0., 0., -1., 3.)),
132 HalfSpace::new(Vec4::new(0., 0., 1., 2.)),
134 HalfSpace::new(Vec4::new(0., 1., 0., 0.)),
136 HalfSpace::new(Vec4::new(0., -1., 0., 6.)),
138 ],
139 };
140 let corners = cuboid_frustum.corners().unwrap();
141 assert_relative_eq!(corners[0], Vec3::new(-5., 0., 3.), epsilon = 2e-7);
143 assert_relative_eq!(corners[1], Vec3::new(4., 0., 3.), epsilon = 2e-7);
145 assert_relative_eq!(corners[2], Vec3::new(4., 0., -2.), epsilon = 2e-7);
147 assert_relative_eq!(corners[3], Vec3::new(-5., 0., -2.), epsilon = 2e-7);
149 assert_relative_eq!(corners[4], Vec3::new(-5., 6., 3.), epsilon = 2e-7);
151 assert_relative_eq!(corners[5], Vec3::new(4., 6., 3.), epsilon = 2e-7);
153 assert_relative_eq!(corners[6], Vec3::new(4., 6., -2.), epsilon = 2e-7);
155 assert_relative_eq!(corners[7], Vec3::new(-5., 6., -2.), epsilon = 2e-7);
157 }
158
159 #[test]
160 fn pyramid_frustum_corners() {
161 let pyramid_frustum = ViewFrustum {
164 half_spaces: [
165 HalfSpace::new(Vec4::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
167 HalfSpace::new(Vec4::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
169 HalfSpace::new(Vec4::new(0., FRAC_1_SQRT_2, -FRAC_1_SQRT_2, FRAC_1_SQRT_2)),
171 HalfSpace::new(Vec4::new(0., FRAC_1_SQRT_2, FRAC_1_SQRT_2, FRAC_1_SQRT_2)),
173 HalfSpace::new(Vec4::new(0., 1., 0., 1.)),
175 HalfSpace::new(Vec4::new(0., -1., 0., 3.)),
177 ],
178 };
179 let corners = pyramid_frustum.corners().unwrap();
180 assert_relative_eq!(corners[0], Vec3::new(0., -1., 0.), epsilon = 2e-7);
182 assert_relative_eq!(corners[1], Vec3::new(0., -1., 0.), epsilon = 2e-7);
184 assert_relative_eq!(corners[2], Vec3::new(0., -1., 0.), epsilon = 2e-7);
186 assert_relative_eq!(corners[3], Vec3::new(0., -1., 0.), epsilon = 2e-7);
188 assert_relative_eq!(corners[4], Vec3::new(-4., 3., 4.), epsilon = 2e-7);
190 assert_relative_eq!(corners[5], Vec3::new(4., 3., 4.), epsilon = 2e-7);
192 assert_relative_eq!(corners[6], Vec3::new(4., 3., -4.), epsilon = 2e-7);
194 assert_relative_eq!(corners[7], Vec3::new(-4., 3., -4.), epsilon = 2e-7);
196 }
197
198 #[test]
199 fn frustum_with_some_nan_corners() {
200 let no_far = ViewFrustum {
202 half_spaces: [
203 HalfSpace::new(Vec4::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
205 HalfSpace::new(Vec4::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
207 HalfSpace::new(Vec4::new(0., FRAC_1_SQRT_2, -FRAC_1_SQRT_2, FRAC_1_SQRT_2)),
209 HalfSpace::new(Vec4::new(0., FRAC_1_SQRT_2, FRAC_1_SQRT_2, FRAC_1_SQRT_2)),
211 HalfSpace::new(Vec4::new(0., 1., 0., 0.)),
213 HalfSpace::new(ViewFrustum::INACTIVE_HALF_SPACE),
215 ],
216 };
217 let corners = no_far.corners().unwrap();
218 assert_relative_eq!(corners[0], Vec3::new(-1., 0., 1.), epsilon = 2e-7);
220 assert_relative_eq!(corners[1], Vec3::new(1., 0., 1.), epsilon = 2e-7);
222 assert_relative_eq!(corners[2], Vec3::new(1., 0., -1.), epsilon = 2e-7);
224 assert_relative_eq!(corners[3], Vec3::new(-1., 0., -1.), epsilon = 2e-7);
226 assert!(corners[4].is_nan());
228 assert!(corners[5].is_nan());
230 assert!(corners[6].is_nan());
232 assert!(corners[7].is_nan());
234 }
235
236 #[test]
237 fn invalid_frustum_corners() {
238 let invalid = ViewFrustum {
239 half_spaces: [
240 HalfSpace::new(Vec4::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
242 HalfSpace::new(Vec4::new(-FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., -FRAC_1_SQRT_2)),
243 HalfSpace::new(Vec4::new(FRAC_1_SQRT_2, FRAC_1_SQRT_2, 0., FRAC_1_SQRT_2)),
244 HalfSpace::new(Vec4::new(0., FRAC_1_SQRT_2, FRAC_1_SQRT_2, FRAC_1_SQRT_2)),
245 HalfSpace::new(Vec4::new(0., 1., 0., 0.)),
246 HalfSpace::new(Vec4::new(0., -1., 0., 3.)),
247 ],
248 };
249 assert!(invalid.corners().is_none());
250 }
251}