1#[derive(Debug, Clone, Copy)]
5pub enum Projection {
6 Perspective {
7 fov_y: f32,
8 aspect: f32,
9 near: f32,
10 far: f32,
11 },
12 Orthographic {
13 width: f32,
14 height: f32,
15 near: f32,
16 far: f32,
17 },
18}
19
20#[derive(Debug, Clone)]
22pub struct Camera {
23 eye: [f32; 3],
24 target: [f32; 3],
25 up: [f32; 3],
26 projection: Projection,
27}
28
29impl Camera {
30 pub fn perspective(fov_y: f32, aspect: f32, near: f32, far: f32) -> Self {
31 Self {
32 eye: [0.0, 0.0, 5.0],
33 target: [0.0, 0.0, 0.0],
34 up: [0.0, 1.0, 0.0],
35 projection: Projection::Perspective {
36 fov_y,
37 aspect,
38 near,
39 far,
40 },
41 }
42 }
43
44 pub fn orthographic(width: f32, height: f32, near: f32, far: f32) -> Self {
45 Self {
46 eye: [0.0, 0.0, 5.0],
47 target: [0.0, 0.0, 0.0],
48 up: [0.0, 1.0, 0.0],
49 projection: Projection::Orthographic {
50 width,
51 height,
52 near,
53 far,
54 },
55 }
56 }
57
58 pub fn position(mut self, eye: [f32; 3]) -> Self {
59 self.eye = eye;
60 self
61 }
62
63 pub fn look_at(mut self, target: [f32; 3]) -> Self {
64 self.target = target;
65 self
66 }
67
68 pub fn up(mut self, up: [f32; 3]) -> Self {
69 self.up = up;
70 self
71 }
72
73 pub fn eye(&self) -> [f32; 3] {
74 self.eye
75 }
76
77 pub fn target(&self) -> [f32; 3] {
78 self.target
79 }
80
81 pub fn projection(&self) -> Projection {
82 self.projection
83 }
84
85 pub fn fov_y(&self) -> f32 {
86 match self.projection {
87 Projection::Perspective { fov_y, .. } => fov_y,
88 Projection::Orthographic { .. } => 0.0,
89 }
90 }
91
92 pub fn view_matrix(&self) -> [f32; 16] {
94 let f = normalize(sub(self.target, self.eye));
95 let s = normalize(cross(f, self.up));
96 let u = cross(s, f);
97
98 [
99 s[0],
100 u[0],
101 -f[0],
102 0.0,
103 s[1],
104 u[1],
105 -f[1],
106 0.0,
107 s[2],
108 u[2],
109 -f[2],
110 0.0,
111 -dot(s, self.eye),
112 -dot(u, self.eye),
113 dot(f, self.eye),
114 1.0,
115 ]
116 }
117
118 pub fn projection_matrix(&self) -> [f32; 16] {
120 match self.projection {
121 Projection::Perspective {
122 fov_y,
123 aspect,
124 near,
125 far,
126 } => {
127 let f = 1.0 / (fov_y.to_radians() / 2.0).tan();
128 let nf = 1.0 / (near - far);
129 [
130 f / aspect,
131 0.0,
132 0.0,
133 0.0,
134 0.0,
135 f,
136 0.0,
137 0.0,
138 0.0,
139 0.0,
140 (far + near) * nf,
141 -1.0,
142 0.0,
143 0.0,
144 2.0 * far * near * nf,
145 0.0,
146 ]
147 }
148 Projection::Orthographic {
149 width,
150 height,
151 near,
152 far,
153 } => {
154 let nf = 1.0 / (near - far);
155 [
156 2.0 / width,
157 0.0,
158 0.0,
159 0.0,
160 0.0,
161 2.0 / height,
162 0.0,
163 0.0,
164 0.0,
165 0.0,
166 2.0 * nf,
167 0.0,
168 0.0,
169 0.0,
170 (far + near) * nf,
171 1.0,
172 ]
173 }
174 }
175 }
176}
177
178fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
179 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
180}
181
182fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
183 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
184}
185
186fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
187 [
188 a[1] * b[2] - a[2] * b[1],
189 a[2] * b[0] - a[0] * b[2],
190 a[0] * b[1] - a[1] * b[0],
191 ]
192}
193
194fn normalize(v: [f32; 3]) -> [f32; 3] {
195 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
196 if len < 1e-10 {
197 return [0.0, 0.0, 0.0];
198 }
199 [v[0] / len, v[1] / len, v[2] / len]
200}