1use std::collections::HashMap;
4
5use glam::{uvec2, UVec2, Vec2};
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8use smallvec::{smallvec, SmallVec};
9use thiserror::Error;
10
11mod packer;
12
13pub use packer::{DefaultLightmapPacker, LightmapPacker, LightmapPackerFaceView, PerSlotLightmapPacker, PerStyleLightmapPacker};
14
15use crate::{
16 data::{lighting::LightmapStyle, texture::BspTexFlags},
17 mesh::FaceExtents,
18 BspData, BspParseError,
19};
20
21#[derive(Debug, Clone, Copy)]
22#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
23pub struct ComputeLightmapSettings {
24 pub default_color: [u8; 3],
26 pub no_lighting_color: [u8; 3],
28 pub special_lighting_color: [u8; 3],
30 pub max_width: u32,
31 pub max_height: u32,
32 pub extrusion: u32,
34}
35impl Default for ComputeLightmapSettings {
36 fn default() -> Self {
37 Self {
38 default_color: [0; 3],
39 no_lighting_color: [0; 3],
40 special_lighting_color: [255; 3],
41 max_width: 2048,
42 max_height: u32::MAX,
43 extrusion: 0,
44 }
45 }
46}
47
48#[derive(Error, Debug, Clone)]
49pub enum ComputeLightmapAtlasError {
50 #[error(
51 "Failed to pack lightmap of size {lightmap_size}, {images_packed} lightmaps have already been packed. Max atlas size: {max_lightmap_size}"
52 )]
53 PackFailure {
54 lightmap_size: UVec2,
55 images_packed: usize,
56 max_lightmap_size: UVec2,
57 },
58 #[error("No lightmaps")]
59 NoLightmaps,
60 #[error("DECOUPLED_LM BSPX lump is present, but failed to parse: {0}")]
61 InvalidDecoupledLM(BspParseError),
62}
63
64struct ReservedLightmapPixel {
65 position: Option<UVec2>,
66 color: [u8; 3],
67}
68impl ReservedLightmapPixel {
69 pub fn new(color: [u8; 3]) -> Self {
70 Self { position: None, color }
71 }
72
73 pub fn get_uvs<P: LightmapPacker>(
74 &mut self,
75 lightmap_packer: &mut P,
76 view: LightmapPackerFaceView,
77 ) -> Result<FaceUvs, ComputeLightmapAtlasError> {
78 let position = match self.position {
79 Some(v) => v,
80 None => {
81 let rect = lightmap_packer.pack(
83 view,
84 P::create_single_color_input(UVec2::ONE + lightmap_packer.settings().extrusion * 2, self.color),
85 )?;
86 self.position = Some(rect.min);
87 rect.min
88 }
89 };
90
91 Ok(smallvec![position.as_vec2() + Vec2::splat(0.5); view.face.num_edges.0 as usize])
92 }
93}
94
95impl BspData {
96 pub fn compute_lightmap_atlas<P: LightmapPacker>(&self, mut packer: P) -> Result<LightmapAtlasOutput<P>, ComputeLightmapAtlasError> {
98 let Some(lighting) = &self.lighting else { return Err(ComputeLightmapAtlasError::NoLightmaps) };
99 let mut decoupled_lm = match self.bspx.parse_decoupled_lm(&self.parse_ctx) {
100 Some(x) => Some(x.map_err(ComputeLightmapAtlasError::InvalidDecoupledLM)?),
101 None => None,
102 };
103
104 if let Some(lm_infos) = &mut decoupled_lm {
106 for lm_info in lm_infos {
107 lm_info.projection.u_offset += 0.5;
108 lm_info.projection.v_offset += 0.5;
109 }
110 }
111
112 let settings = packer.settings();
113
114 let mut lightmap_uvs: HashMap<u32, FaceUvs> = HashMap::new();
115
116 let mut empty_reserved_pixel = ReservedLightmapPixel::new(settings.no_lighting_color);
117 let mut special_reserved_pixel = ReservedLightmapPixel::new(settings.special_lighting_color);
118
119 for (face_idx, face) in self.faces.iter().enumerate() {
120 let tex_info = &self.tex_info[face.texture_info_idx.0 as usize];
121
122 let decoupled_lightmap = decoupled_lm.as_ref().map(|lm_infos| lm_infos[face_idx]);
123
124 let lm_info = match &decoupled_lightmap {
125 Some(lm_info) => {
126 let uvs: FaceUvs = face.vertices(self).map(|pos| lm_info.projection.project(pos)).collect();
127 let extents = FaceExtents::new_decoupled(uvs.iter().copied(), lm_info);
128
129 LightmapInfo {
130 uvs,
131 extents,
132 lightmap_offset: lm_info.offset,
133 }
134 }
135 None => {
136 let uvs: FaceUvs = face.vertices(self).map(|pos| tex_info.projection.project(pos)).collect();
137 let extents = FaceExtents::new(uvs.iter().copied());
138
139 LightmapInfo {
140 uvs,
141 extents,
142 lightmap_offset: face.lightmap_offset.pixels,
143 }
144 }
145 };
146
147 let view = LightmapPackerFaceView {
148 lm_info: &lm_info,
149
150 bsp: self,
151
152 face_idx,
153 face,
154 tex_info,
155 lighting,
156 };
157
158 if lm_info.lightmap_offset.is_negative() || lm_info.extents.lightmap_size() == UVec2::ZERO {
159 lightmap_uvs.insert(
160 face_idx as u32,
161 if tex_info.flags.texture_flags.unwrap_or_default() == BspTexFlags::Normal {
162 empty_reserved_pixel.get_uvs(&mut packer, view)?
165 } else {
166 special_reserved_pixel.get_uvs(&mut packer, view)?
167 },
168 );
169 continue;
170 }
171
172 let input = packer.read_from_face(view);
173
174 let frame = packer.pack(view, input)?;
175
176 lightmap_uvs.insert(
177 face_idx as u32,
178 lm_info
179 .extents
180 .compute_lightmap_uvs(lm_info.uvs, (frame.min + settings.extrusion).as_vec2())
181 .collect(),
182 );
183 }
184
185 let atlas = packer.export();
186
187 for uvs in lightmap_uvs.values_mut() {
189 for uv in uvs {
190 *uv /= atlas.size().as_vec2();
191 }
192 }
193
194 Ok(LightmapAtlasOutput {
195 uvs: lightmap_uvs,
196 data: atlas,
197 })
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct LightmapInfo {
204 pub uvs: FaceUvs,
206 pub extents: FaceExtents,
207 pub lightmap_offset: i32,
209}
210impl LightmapInfo {
211 #[inline]
213 pub fn compute_lighting_index(&self, light_style_idx: usize, x: u32, y: u32) -> usize {
214 self.lightmap_offset as usize
215 + (self.extents.lightmap_pixels() as usize * light_style_idx)
216 + (y * self.extents.lightmap_size().x + x) as usize
217 }
218}
219
220pub trait LightmapAtlas {
222 fn size(&self) -> UVec2;
223}
224
225pub struct PerSlotLightmapData {
226 pub slots: [image::RgbImage; 4],
227 pub styles: image::RgbaImage,
228}
229impl LightmapAtlas for PerSlotLightmapData {
230 fn size(&self) -> UVec2 {
231 self.styles.dimensions().into()
232 }
233}
234
235#[derive(Debug, Clone)]
239pub struct PerStyleLightmapData {
240 size: UVec2,
241 inner: HashMap<LightmapStyle, image::RgbImage>,
242}
243impl PerStyleLightmapData {
244 #[inline]
245 pub fn new(size: impl Into<UVec2>) -> Self {
246 Self {
247 size: size.into(),
248 inner: HashMap::new(),
249 }
250 }
251
252 #[inline]
253 pub fn inner(&self) -> &HashMap<LightmapStyle, image::RgbImage> {
254 &self.inner
255 }
256
257 #[inline]
258 pub fn into_inner(self) -> HashMap<LightmapStyle, image::RgbImage> {
259 self.inner
260 }
261
262 pub fn modify_inner<O, F: FnOnce(&mut HashMap<LightmapStyle, image::RgbImage>) -> O>(
264 &mut self,
265 modifier: F,
266 ) -> Result<O, LightmapsInvalidSizeError> {
267 let out = modifier(&mut self.inner);
268
269 for (style, image) in &self.inner {
270 let image_size = uvec2(image.width(), image.height());
271 if self.size != image_size {
272 return Err(LightmapsInvalidSizeError {
273 style: *style,
274 image_size,
275 expected_size: self.size,
276 });
277 }
278 }
279
280 Ok(out)
281 }
282
283 pub fn insert(&mut self, style: LightmapStyle, image: image::RgbImage) -> Result<Option<image::RgbImage>, LightmapsInvalidSizeError> {
285 let image_size = uvec2(image.width(), image.height());
286 if self.size != image_size {
287 return Err(LightmapsInvalidSizeError {
288 style,
289 image_size,
290 expected_size: self.size,
291 });
292 }
293
294 Ok(self.inner.insert(style, image))
295 }
296}
297impl LightmapAtlas for PerStyleLightmapData {
298 fn size(&self) -> UVec2 {
299 self.size
300 }
301}
302
303#[derive(Debug, Error)]
304#[error("Lightmap image of style {style} is size {image_size}, when the lightmap collection's expected size is {expected_size}")]
305pub struct LightmapsInvalidSizeError {
306 pub style: LightmapStyle,
307 pub image_size: UVec2,
308 pub expected_size: UVec2,
309}
310
311pub struct LightmapAtlasOutput<P: LightmapPacker> {
313 pub uvs: LightmapUvMap,
315 pub data: P::Output,
316}
317
318pub type LightmapUvMap = HashMap<u32, FaceUvs>;
320
321pub type FaceUvs = SmallVec<[Vec2; 5]>;