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