Skip to main content

goldsrc_rs/
texture.rs

1use static_assertions::assert_eq_size;
2use zerocopy::{
3    FromBytes,
4    little_endian::{F32, I32, U16, U32},
5};
6use zerocopy_derive::*;
7
8use crate::{
9    error::{ParsingError, ParsingResult},
10    util::{mip_level_size, pixel_size},
11};
12
13/// Number of mip levels in a mipmapped texture.
14pub const MIP_LEVELS: usize = 4;
15/// Number of glyphs in a bitmap font.
16pub const FONT_GLYPHS: usize = 256;
17
18/// Sprite magic.
19pub const SPRITE_MAGIC: [u8; 4] = *b"IDSP";
20/// Sprite version.
21pub const SPRITE_VERSION: u32 = 2;
22
23/// Sprite camera alignment type values.
24pub const SPRITE_TYPE_FWD_PARALLEL_UPRIGHT: u32 = 0;
25pub const SPRITE_TYPE_FACING_UPRIGHT: u32 = 1;
26pub const SPRITE_TYPE_FWD_PARALLEL: u32 = 2;
27pub const SPRITE_TYPE_ORIENTED: u32 = 3;
28pub const SPRITE_TYPE_FWD_PARALLEL_ORIENTED: u32 = 4;
29
30/// Sprite draw type values.
31pub const SPRITE_TEX_FORMAT_NORMAL: u32 = 0;
32pub const SPRITE_TEX_FORMAT_ADDITIVE: u32 = 1;
33pub const SPRITE_TEX_FORMAT_INDEX_ALPHA: u32 = 2;
34pub const SPRITE_TEX_FORMAT_ALPH_TEST: u32 = 3;
35
36/// Sprite sync type (animation timing) values.
37pub const SPRITE_SYNC_TYPE_SYNC: u32 = 0;
38pub const SPRITE_SYNC_TYPE_RAND: u32 = 1;
39
40/// Frame type that precedes each frame entry in a sprite.
41pub const SPRITE_FRAME_TYPE_SINGLE: u32 = 0;
42pub const SPRITE_FRAME_TYPE_GROUP: u32 = 1;
43
44/// RGB color as three u8 values (red, green, blue).
45pub type Rgb = [u8; 3];
46/// Index into a palette.
47pub type PaletteIndex = u8;
48
49/// Parsed mipmapped texture (header + indexed data + palette).
50pub struct MipTexture<'a> {
51    /// Miptex header.
52    pub header: &'a MipTextureHeader,
53    /// Indexed color data and palette.
54    pub data: Option<ColorData<'a, MIP_LEVELS>>,
55}
56
57/// Parsed picture texture (header + indexed data + palette).
58pub struct Picture<'a> {
59    /// Picture header.
60    pub header: &'a PictureHeader,
61    /// Indexed color data and palette.
62    pub data: ColorData<'a, 1>,
63}
64
65/// Parsed bitmap font (header + indexed data + palette).
66pub struct Font<'a> {
67    /// Font header.
68    pub header: &'a FontHeader,
69    /// Indexed color data and palette.
70    pub data: ColorData<'a, 1>,
71}
72
73/// View of indexed color data and palette.
74pub struct ColorData<'a, const N: usize> {
75    /// Indexed color data for each mip level (or single level for pictures).
76    pub indices: [&'a [PaletteIndex]; N],
77    /// Palette mapping indices to RGB colors.
78    pub palette: &'a [Rgb],
79}
80
81/// Sprite loaded from a SPR file.
82pub struct Sprite<'a> {
83    /// Sprite header.
84    pub header: &'a SpriteHeader,
85    /// Shared palette (RGB).
86    pub palette: &'a [Rgb],
87    /// Sprite frames.
88    pub frames: Vec<SpriteFrame<'a>>,
89}
90
91pub enum SpriteFrame<'a> {
92    Single(SpriteFrameSingle<'a>),
93    Group(Vec<SpriteFrameGroup<'a>>),
94}
95
96pub struct SpriteFrameGroup<'a> {
97    pub interval: F32,
98    pub subframe: SpriteFrameSingle<'a>,
99}
100
101pub struct SpriteFrameSingle<'a> {
102    /// Sprite frame header.
103    pub header: &'a SpriteFrameHeader,
104    /// Indices pointing to the palette.
105    pub indices: &'a [PaletteIndex],
106}
107
108/// Quake/GoldSrc miptex header.
109#[repr(C)]
110#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
111pub struct MipTextureHeader {
112    /// Texture name (C string, not guaranteed UTF-8).
113    pub name: [u8; 16],
114    /// Width in pixels.
115    pub width: U32,
116    /// Height in pixels.
117    pub height: U32,
118    /// Offsets to each mip level, relative to start of this header.
119    pub offsets: [U32; MIP_LEVELS],
120}
121
122/// Picture (single-level texture) header.
123#[repr(C)]
124#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
125pub struct PictureHeader {
126    /// Width in pixels.
127    pub width: U32,
128    /// Height in pixels.
129    pub height: U32,
130}
131
132/// Bitmap font header.
133#[repr(C)]
134#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
135pub struct FontHeader {
136    /// Font width in pixels.
137    pub width: U32,
138    /// Font height in pixels.
139    pub height: U32,
140    /// Number of character rows in the font.
141    pub row_count: U32,
142    /// Height of each row in pixels.
143    pub row_height: U32,
144    /// Character info table.
145    pub chars: [CharInfo; FONT_GLYPHS],
146}
147
148/// Info about a single character in a bitmap font.
149#[repr(C)]
150#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
151pub struct CharInfo {
152    /// Offset in the font data where this character starts.
153    pub offset: U16,
154    /// Width of the character in pixels.
155    pub width: U16,
156}
157
158/// Sprite header (Half-Life, version 2).
159#[repr(C)]
160#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
161pub struct SpriteHeader {
162    /// File magic ("IDSP").
163    pub magic: [u8; 4],
164    /// Sprite format version.
165    pub version: U32,
166    /// Sprite type.
167    pub ty: U32,
168    /// Texture format.
169    pub tex_format: U32,
170    /// Bounding radius (raw bits; usually float in tools).
171    pub bounding_radius: F32,
172    /// Bounds or size, depending on toolchain.
173    pub bounds: [U32; 2],
174    /// Number of frames (including groups).
175    pub frames_num: U32,
176    /// Beam length.
177    pub beam_len: F32,
178    /// Synchronisation type.
179    pub sync_type: U32,
180}
181
182/// Single frame header.
183#[repr(C)]
184#[derive(Debug, Clone, FromBytes, IntoBytes, KnownLayout, Immutable)]
185pub struct SpriteFrameHeader {
186    /// Origin in sprite space.
187    pub origin: [I32; 2],
188    /// Frame width.
189    pub width: U32,
190    /// Frame height.
191    pub height: U32,
192}
193
194pub fn mip_texture(bytes: &[u8]) -> ParsingResult<MipTexture<'_>> {
195    let (header, _) = MipTextureHeader::ref_from_prefix(bytes)
196        .map_err(|_| ParsingError::OutOfRange("miptex header"))?;
197    let width = header.width.get();
198    let height = header.height.get();
199
200    if header.offsets.iter().any(|offset| offset.get() == 0) {
201        return Ok(MipTexture { header, data: None });
202    }
203
204    let mut ptr = bytes;
205    let mut indices = [Default::default(); MIP_LEVELS];
206    for (level, slot) in indices.iter_mut().enumerate() {
207        let offset = usize::try_from(header.offsets[level].get())
208            .map_err(|_| ParsingError::NumberOverflow("miptex offset"))?;
209        let size = mip_level_size(width, height, level, "miptex")?;
210        let data = bytes
211            .get(offset..)
212            .ok_or(ParsingError::OutOfRange("miptex data"))?;
213
214        let (indices, bytes) = <[PaletteIndex]>::ref_from_prefix_with_elems(data, size)
215            .map_err(|_| ParsingError::Invalid("miptex palette indices"))?;
216        *slot = indices;
217        ptr = bytes;
218    }
219
220    let (palette, _) = palette_ref(ptr)?;
221
222    Ok(MipTexture {
223        header,
224        data: Some(ColorData { indices, palette }),
225    })
226}
227
228pub fn picture(bytes: &[u8]) -> ParsingResult<Picture<'_>> {
229    let (header, bytes) = PictureHeader::ref_from_prefix(bytes)
230        .map_err(|_| ParsingError::OutOfRange("pic header"))?;
231    let width = header.width.get();
232    let height = header.height.get();
233    let size = pixel_size(width, height, "picture")?;
234
235    let (indices, bytes) = <[PaletteIndex]>::ref_from_prefix_with_elems(bytes, size)
236        .map_err(|_| ParsingError::Invalid("pic indices"))?;
237    let (palette, _) = palette_ref(bytes)?;
238
239    Ok(Picture {
240        header,
241        data: ColorData {
242            indices: [indices],
243            palette,
244        },
245    })
246}
247
248pub fn font(bytes: &[u8]) -> ParsingResult<Font<'_>> {
249    let (header, bytes) =
250        FontHeader::ref_from_prefix(bytes).map_err(|_| ParsingError::OutOfRange("font header"))?;
251
252    let width = header.width.get();
253    let height = header.height.get();
254    let size = pixel_size(width, height, "font")?;
255
256    let (indices, bytes) = <[PaletteIndex]>::ref_from_prefix_with_elems(bytes, size)
257        .map_err(|_| ParsingError::Invalid("font indices"))?;
258    let (palette, _) = palette_ref(bytes)?;
259
260    Ok(Font {
261        header,
262        data: ColorData {
263            indices: [indices],
264            palette,
265        },
266    })
267}
268
269pub fn sprite(bytes: &[u8]) -> ParsingResult<Sprite<'_>> {
270    let (header, bytes) = SpriteHeader::ref_from_prefix(bytes)
271        .map_err(|_| ParsingError::OutOfRange("sprite header"))?;
272
273    if header.magic != SPRITE_MAGIC {
274        return Err(ParsingError::WrongFourCC {
275            got: header.magic,
276            expected: SPRITE_MAGIC,
277        });
278    }
279
280    let version = header.version.get();
281    if version != SPRITE_VERSION {
282        return Err(ParsingError::WrongVersion {
283            got: version,
284            expected: SPRITE_VERSION,
285        });
286    }
287
288    let (palette, bytes) = palette_ref(bytes)?;
289    let frames = frames_ref(bytes, header)?;
290
291    Ok(Sprite {
292        header,
293        palette,
294        frames,
295    })
296}
297
298fn palette_ref(bytes: &[u8]) -> ParsingResult<(&'_ [Rgb], &'_ [u8])> {
299    let (size, bytes) =
300        U16::ref_from_prefix(bytes).map_err(|_| ParsingError::OutOfRange("palette header"))?;
301
302    let count = usize::from(size.get()).min(256);
303    let (palette, bytes) = <[Rgb]>::ref_from_prefix_with_elems(bytes, count)
304        .map_err(|_| ParsingError::Invalid("palette"))?;
305
306    Ok((palette, bytes))
307}
308
309fn frames_ref<'a>(bytes: &'a [u8], header: &SpriteHeader) -> ParsingResult<Vec<SpriteFrame<'a>>> {
310    let count = usize::try_from(header.frames_num.get())
311        .map_err(|_| ParsingError::NumberOverflow("sprite frame count"))?;
312
313    let mut frames = Vec::with_capacity(count);
314    let mut ptr = bytes;
315    for _ in 0..count {
316        let (frame, bytes) = frame_ref(ptr)?;
317        frames.push(frame);
318        ptr = bytes;
319    }
320
321    Ok(frames)
322}
323
324fn frame_ref(bytes: &[u8]) -> ParsingResult<(SpriteFrame<'_>, &'_ [u8])> {
325    let (group, bytes) =
326        U32::ref_from_prefix(bytes).map_err(|_| ParsingError::OutOfRange("sprite frame header"))?;
327
328    match group.get() {
329        SPRITE_FRAME_TYPE_SINGLE => {
330            let (single, bytes) = frame_single_ref(bytes)?;
331            Ok((SpriteFrame::Single(single), bytes))
332        }
333        SPRITE_FRAME_TYPE_GROUP => {
334            let (count, bytes) = U32::ref_from_prefix(bytes)
335                .map_err(|_| ParsingError::OutOfRange("sprite group header"))?;
336            let count = usize::try_from(count.get())
337                .map_err(|_| ParsingError::NumberOverflow("sprite group subframes count"))?;
338            let (intervals, bytes) = <[F32]>::ref_from_prefix_with_elems(bytes, count)
339                .map_err(|_| ParsingError::OutOfRange("sprite group intervals"))?;
340
341            let mut subframes = Vec::with_capacity(count);
342            let mut ptr = bytes;
343            for interval in intervals.iter().copied() {
344                let (subframe, bytes) = frame_single_ref(ptr)?;
345                subframes.push(SpriteFrameGroup { interval, subframe });
346                ptr = bytes;
347            }
348
349            Ok((SpriteFrame::Group(subframes), ptr))
350        }
351        _ => Err(ParsingError::Invalid("sprite group type")),
352    }
353}
354
355fn frame_single_ref(bytes: &[u8]) -> ParsingResult<(SpriteFrameSingle<'_>, &'_ [u8])> {
356    let (header, bytes) = SpriteFrameHeader::ref_from_prefix(bytes)
357        .map_err(|_| ParsingError::OutOfRange("sprite single frame header"))?;
358    let width = header.width.get();
359    let height = header.height.get();
360    let size = pixel_size(width, height, "sprite single frame")?;
361    let (indices, bytes) = <[PaletteIndex]>::ref_from_prefix_with_elems(bytes, size)
362        .map_err(|_| ParsingError::Invalid("sprite single frame indices"))?;
363
364    Ok((SpriteFrameSingle { header, indices }, bytes))
365}
366
367assert_eq_size!(MipTextureHeader, [u8; 40]);
368assert_eq_size!(PictureHeader, [u8; 8]);
369assert_eq_size!(FontHeader, ([u8; 16], [u8; 4 * FONT_GLYPHS]));
370assert_eq_size!(CharInfo, [u8; 4]);
371assert_eq_size!(SpriteHeader, [u8; 40]);
372assert_eq_size!(SpriteFrameHeader, [u8; 16]);
373assert_eq_size!(Rgb, [u8; 3]);