1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//! Code to manage the camera.
use core::f32::consts::TAU;
use lin_alg::f32::{Mat4, Quaternion, Vec3, Vec4};
use crate::{
copy_ne,
types::{F32_SIZE, MAT4_SIZE, VEC3_UNIFORM_SIZE},
};
// cam size is only the parts we pass to the shader.
// For each of the 4 matrices in the camera, plus a padded vec3 for position.
pub const CAMERA_SIZE: usize = MAT4_SIZE + 3 * VEC3_UNIFORM_SIZE + 16; // Final 16 is an alignment pad.
#[derive(Clone, Debug)]
pub struct Camera {
pub fov_y: f32, // Vertical field of view in radians.
pub aspect: f32, // width / height.
pub near: f32,
pub far: f32,
/// Position shifts all points prior to the camera transform; this is what
/// we adjust with move keys.
pub position: Vec3,
pub orientation: Quaternion,
/// We store the projection matrix here since it only changes when we change the camera cfg.
pub proj_mat: Mat4, // todo: Make provide, and provide a constructor?
/// These are in distances from the camera.
/// E scale within band. 1.0 is a good baseline.
pub fog_density: f32,
/// Curve steepness, e.g. 4–8. Higher means more of a near "wall" with heavy far fade.
pub fog_power: f32,
/// distance where fog begins
pub fog_start: f32,
/// Distance where fog reaches full strength
pub fog_end: f32,
pub fog_color: [f32; 3],
/// Strength of edge cueing (silhouette darkening). 0.0 = off, 1.0 = full effect.
/// Controlled at startup via GraphicsSettings::edge_cueing.
pub edge_cueing: f32,
/// World-space expansion along normals used in the depth-aware halo prepass.
/// 0.0 = disabled. Set from GraphicsSettings::depth_aware_halos.
pub halo_expansion: f32,
}
impl Camera {
pub fn to_bytes(&self) -> [u8; CAMERA_SIZE] {
let mut result = [0; CAMERA_SIZE];
let mut i = 0;
let proj_view = self.proj_mat.clone() * self.view_mat();
result[i..i + MAT4_SIZE].clone_from_slice(&proj_view.to_bytes());
i += MAT4_SIZE;
result[i..i + VEC3_UNIFORM_SIZE].clone_from_slice(&self.position.to_bytes_uniform());
i += VEC3_UNIFORM_SIZE;
copy_ne!(result, self.fog_density, i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_power, i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_start, i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_end, i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_color[0], i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_color[1], i..i + F32_SIZE);
i += F32_SIZE;
copy_ne!(result, self.fog_color[2], i..i + F32_SIZE);
// WGSL layout: fog_color: vec3<f32> at 96..108 (12 bytes).
// After it, edge_cueing at 108, near at 112, far at 116.
// These fit within CAMERA_SIZE = 128 (previously unused padding).
copy_ne!(result, self.edge_cueing, 108..112);
copy_ne!(result, self.near, 112..116);
copy_ne!(result, self.far, 116..120);
copy_ne!(result, self.halo_expansion, 120..124);
result
}
/// Updates the projection matrix based on the projection parameters.
/// Run this after updating the parameters.
pub fn update_proj_mat(&mut self) {
self.proj_mat = Mat4::new_perspective_lh(self.fov_y, self.aspect, self.near, self.far);
}
/// Calculate the view matrix: This is a translation of the negative coordinates of the camera's
/// position, applied before the camera's rotation.
pub fn view_mat(&self) -> Mat4 {
self.orientation.inverse().to_matrix() * Mat4::new_translation(-self.position)
}
pub fn view_size(&self, far: bool) -> (f32, f32) {
// Calculate the projected window width and height, using basic trig.
let dist = if far { self.far } else { self.near };
let width = 2. * dist * (self.fov_y * self.aspect / 2.).tan();
let height = 2. * dist * (self.fov_y / 2.).tan();
(width, height)
}
/// Determines if an object is in view. Also returns NDC coordinates, for use
/// in some applications.
pub fn in_view(&self, point: Vec3) -> (bool, (f32, f32, f32)) {
let pv = self.proj_mat.clone() * self.view_mat();
let p4 = pv * Vec4::new(point.x, point.y, point.z, 1.0);
if p4.w <= 0.0 {
return Default::default();
}
let inv_w = 1.0 / p4.w;
let x = p4.x * inv_w;
let y = p4.y * inv_w;
let z = p4.z * inv_w;
let iv =
(-1.0..=1.0).contains(&x) && (-1.0..=1.0).contains(&y) && (-1.0..=1.0).contains(&z);
(iv, (x, y, z))
}
}
impl Default for Camera {
fn default() -> Self {
let mut result = Self {
position: Vec3::new(0., 0., 0.),
orientation: Quaternion::new_identity(),
fov_y: TAU / 6., // Vertical field of view in radians.
aspect: 4. / 3., // width / height.
near: 0.5,
far: 60.,
proj_mat: Mat4::new_identity(),
fog_density: 1.,
fog_power: 6.,
fog_start: 0.,
fog_end: 0.,
fog_color: [0., 0., 0.],
edge_cueing: 0.,
halo_expansion: 0.,
};
result.update_proj_mat();
result
}
}