1use core::ops::Range;
4
5use crate::geom::{Tri, Vertex};
6use crate::math::{
7 angle::{spherical, turns, SphericalVec},
8 mat::{orthographic, perspective, viewport, Mat4x4, RealToReal},
9 space::Linear,
10 vary::Vary,
11 vec::{vec2, Vec3},
12};
13use crate::util::{rect::Rect, Dims};
14
15#[cfg(feature = "fp")]
16use crate::math::{
17 angle::Angle,
18 mat::{orient_z, translate},
19 vec::vec3,
20};
21
22use super::{
23 clip::ClipVec,
24 ctx::Context,
25 shader::{FragmentShader, VertexShader},
26 target::Target,
27 NdcToScreen, RealToProj, ViewToProj, World, WorldToView,
28};
29
30pub trait Mode {
34 fn world_to_view(&self) -> Mat4x4<WorldToView>;
36}
37
38#[derive(Copy, Clone, Debug, Default)]
40pub struct Camera<M> {
41 pub mode: M,
43 pub dims: Dims,
45 pub project: Mat4x4<ViewToProj>,
47 pub viewport: Mat4x4<NdcToScreen>,
49}
50
51#[derive(Copy, Clone, Debug)]
56pub struct FirstPerson {
57 pub pos: Vec3,
59 pub heading: SphericalVec,
61}
62
63impl Camera<()> {
68 pub fn new(dims: Dims) -> Self {
70 Self {
71 dims,
72 viewport: viewport(vec2(0, 0)..vec2(dims.0, dims.1)),
73 ..Default::default()
74 }
75 }
76
77 pub fn mode<M: Mode>(self, mode: M) -> Camera<M> {
78 let Self { dims, project, viewport, .. } = self;
79 Camera { mode, dims, project, viewport }
80 }
81}
82
83impl<M> Camera<M> {
84 pub fn viewport(self, bounds: impl Into<Rect<u32>>) -> Self {
86 let (w, h) = self.dims;
87
88 let Rect {
89 left: Some(l),
90 top: Some(t),
91 right: Some(r),
92 bottom: Some(b),
93 } = bounds.into().intersect(&(0..w, 0..h).into())
94 else {
95 unreachable!("bounded ∩ bounded should be bounded")
96 };
97
98 Self {
99 dims: (r.abs_diff(l), b.abs_diff(t)),
100 viewport: viewport(vec2(l, t)..vec2(r, b)),
101 ..self
102 }
103 }
104
105 pub fn perspective(
107 mut self,
108 focal_ratio: f32,
109 near_far: Range<f32>,
110 ) -> Self {
111 let aspect_ratio = self.dims.0 as f32 / self.dims.1 as f32;
112 self.project = perspective(focal_ratio, aspect_ratio, near_far);
113 self
114 }
115
116 pub fn orthographic(mut self, bounds: Range<Vec3>) -> Self {
118 self.project = orthographic(bounds.start, bounds.end);
119 self
120 }
121}
122
123impl<M: Mode> Camera<M> {
124 pub fn world_to_project(&self) -> Mat4x4<RealToProj<World>> {
126 self.mode.world_to_view().then(&self.project)
127 }
128
129 pub fn render<B, Vtx: Clone, Var: Vary, Uni: Copy, Shd>(
131 &self,
132 tris: impl AsRef<[Tri<usize>]>,
133 verts: impl AsRef<[Vtx]>,
134 to_world: &Mat4x4<RealToReal<3, B, World>>,
135 shader: &Shd,
136 uniform: Uni,
137 target: &mut impl Target,
138 ctx: &Context,
139 ) where
140 Shd: for<'a> VertexShader<
141 Vtx,
142 (&'a Mat4x4<RealToProj<B>>, Uni),
143 Output = Vertex<ClipVec, Var>,
144 > + FragmentShader<Var>,
145 {
146 let tf = to_world.then(&self.world_to_project());
147
148 super::render(
149 tris.as_ref(),
150 verts.as_ref(),
151 shader,
152 (&tf, uniform),
153 self.viewport,
154 target,
155 ctx,
156 );
157 }
158}
159
160impl FirstPerson {
161 pub fn new() -> Self {
164 Self {
165 pos: Vec3::zero(),
166 heading: spherical(1.0, turns(0.0), turns(0.0)),
167 }
168 }
169}
170
171#[cfg(feature = "fp")]
172impl FirstPerson {
173 pub fn look_at(&mut self, pt: Vec3) {
174 self.heading = (pt - self.pos).into();
175 self.heading[0] = 1.0;
176 }
177
178 pub fn rotate(&mut self, az: Angle, alt: Angle) {
179 self.rotate_to(self.heading.az() + az, self.heading.alt() + alt);
180 }
181
182 pub fn rotate_to(&mut self, az: Angle, alt: Angle) {
183 self.heading = spherical(
184 1.0,
185 az.wrap(turns(-0.5), turns(0.5)),
186 alt.clamp(turns(-0.25), turns(0.25)),
187 );
188 }
189
190 pub fn translate(&mut self, delta: Vec3) {
191 let fwd = spherical(1.0, self.heading.az(), turns(0.0)).to_cart();
193 let up = vec3(0.0, 1.0, 0.0);
194 let right = up.cross(&fwd);
195
196 self.pos += Mat4x4::<RealToReal<3>>::from_basis(right, up, fwd)
201 .transpose()
202 .apply(&delta);
203 }
204}
205
206#[cfg(feature = "fp")]
211impl Mode for FirstPerson {
212 fn world_to_view(&self) -> Mat4x4<WorldToView> {
213 let &Self { pos, heading: dir, .. } = self;
214 let fwd_move = spherical(1.0, dir.az(), turns(0.0));
215 let fwd = self.heading;
216 let right = vec3(0.0, 1.0, 0.0).cross(&fwd_move.to_cart());
217
218 let transl = translate(-pos);
219 let orient = orient_z(fwd.into(), right);
220
221 transl.then(&orient).to()
222 }
223}
224
225impl Mode for Mat4x4<WorldToView> {
226 fn world_to_view(&self) -> Mat4x4<WorldToView> {
227 *self
228 }
229}
230
231#[cfg(feature = "fp")]
236impl Default for FirstPerson {
237 fn default() -> Self {
239 Self::new()
240 }
241}