Skip to main content

plasma_prp/resource/
vert_coder.rs

1//! plVertCoder — decode compressed vertex data from .prp files
2//!
3//! Plasma uses a custom compression scheme:
4//! - Floats: quantized to uint16 with offset + quantum scaling
5//! - Normals: encoded as 3 bytes (per-component byte mapping to [-1, 1])
6//! - Colors: run-length encoded per channel (4 channels × 1 byte each)
7
8use anyhow::Result;
9use std::io::Read;
10use super::prp::PlasmaRead;
11
12const POS_QUANTUM: f32 = 1.0 / 1024.0;   // kPosQuantum
13const WEIGHT_QUANTUM: f32 = 1.0 / 32768.0; // kWeightQuantum
14const UVW_QUANTUM: f32 = 1.0 / 65536.0;    // kUVWQuantum (actually same precision)
15
16// Quantum values per field type
17const QUANTA: [f32; 10] = [
18    POS_QUANTUM,    // kPosition
19    WEIGHT_QUANTUM, // kWeight
20    UVW_QUANTUM, UVW_QUANTUM, UVW_QUANTUM, UVW_QUANTUM, // kUVW 0-3
21    UVW_QUANTUM, UVW_QUANTUM, UVW_QUANTUM, UVW_QUANTUM, // kUVW 4-7
22];
23
24const FIELD_POSITION: usize = 0;
25const FIELD_WEIGHT: usize = 1;
26const FIELD_UVW: usize = 2;
27
28const SAME_MASK: u16 = 0x8000;
29
30struct FloatState {
31    offset: f32,
32    all_same: bool,
33    count: u16,
34}
35
36struct ByteState {
37    count: u16,
38    val: u8,
39    same: bool,
40}
41
42pub struct VertCoder {
43    floats: [[FloatState; 3]; 10],
44    colors: [ByteState; 4],
45}
46
47impl VertCoder {
48    pub fn new() -> Self {
49        Self {
50            floats: std::array::from_fn(|_| std::array::from_fn(|_| FloatState {
51                offset: 0.0, all_same: false, count: 0,
52            })),
53            colors: std::array::from_fn(|_| ByteState {
54                count: 0, val: 0, same: false,
55            }),
56        }
57    }
58
59    fn decode_float(&mut self, reader: &mut impl Read, field: usize, chan: usize,
60                     dst: &mut [u8], offset: &mut usize) -> Result<()> {
61        if self.floats[field][chan].count == 0 {
62            // Read header: offset (float) + allSame (bool) + count (uint16)
63            self.floats[field][chan].offset = reader.read_f32()?;
64
65            let all_same = reader.read_u8()?;
66            self.floats[field][chan].all_same = all_same != 0;
67
68            self.floats[field][chan].count = reader.read_u16()?;
69        }
70
71        if self.floats[field][chan].all_same {
72            // All values are the same — use offset directly
73            let bytes = self.floats[field][chan].offset.to_le_bytes();
74            dst[*offset..*offset + 4].copy_from_slice(&bytes);
75        } else {
76            // Read quantized uint16 and reconstruct float
77            let ival = reader.read_u16()?;
78            let fval = ival as f32 * QUANTA[field] + self.floats[field][chan].offset;
79            let bytes = fval.to_le_bytes();
80            dst[*offset..*offset + 4].copy_from_slice(&bytes);
81        }
82
83        *offset += 4;
84        self.floats[field][chan].count = self.floats[field][chan].count.saturating_sub(1);
85        Ok(())
86    }
87
88    fn decode_normal(&mut self, reader: &mut impl Read, dst: &mut [u8], offset: &mut usize) -> Result<()> {
89        // Normal encoded as 3 bytes: each byte maps to float in [-1, 1]
90        // C++ IDecodeNormal: (byte / 255.9f - 0.5f) * 2.0f
91        for _ in 0..3 {
92            let byte = reader.read_u8()?;
93            let val = (byte as f32 / 255.9 - 0.5) * 2.0;
94            dst[*offset..*offset + 4].copy_from_slice(&val.to_le_bytes());
95            *offset += 4;
96        }
97        Ok(())
98    }
99
100    fn decode_byte(&mut self, reader: &mut impl Read, chan: usize, dst: &mut [u8], offset: &mut usize) -> Result<()> {
101        if self.colors[chan].count == 0 {
102            let cnt = reader.read_u16()?;
103            if cnt & SAME_MASK != 0 {
104                self.colors[chan].same = true;
105                self.colors[chan].val = reader.read_u8()?;
106                self.colors[chan].count = cnt & !SAME_MASK;
107            } else {
108                self.colors[chan].same = false;
109                self.colors[chan].count = cnt;
110            }
111        }
112
113        if !self.colors[chan].same {
114            dst[*offset] = reader.read_u8()?;
115        } else {
116            dst[*offset] = self.colors[chan].val;
117        }
118
119        *offset += 1;
120        self.colors[chan].count = self.colors[chan].count.saturating_sub(1);
121        Ok(())
122    }
123
124    fn decode_color(&mut self, reader: &mut impl Read, dst: &mut [u8], offset: &mut usize) -> Result<()> {
125        self.decode_byte(reader, 0, dst, offset)?;
126        self.decode_byte(reader, 1, dst, offset)?;
127        self.decode_byte(reader, 2, dst, offset)?;
128        self.decode_byte(reader, 3, dst, offset)?;
129        Ok(())
130    }
131
132    fn decode_vertex(&mut self, reader: &mut impl Read, dst: &mut [u8], offset: &mut usize,
133                      format: u8) -> Result<()> {
134        // Position (3 floats)
135        self.decode_float(reader, FIELD_POSITION, 0, dst, offset)?;
136        self.decode_float(reader, FIELD_POSITION, 1, dst, offset)?;
137        self.decode_float(reader, FIELD_POSITION, 2, dst, offset)?;
138
139        // Weights
140        let num_weights = ((format & 0x30) >> 4) as usize;
141        for j in 0..num_weights {
142            self.decode_float(reader, FIELD_WEIGHT, j, dst, offset)?;
143        }
144        // Skin indices
145        if format & 0x40 != 0 {
146            let idx = reader.read_u32()?;
147            dst[*offset..*offset + 4].copy_from_slice(&idx.to_le_bytes());
148            *offset += 4;
149        }
150
151        // Normal (encoded as 2 bytes → float3)
152        self.decode_normal(reader, dst, offset)?;
153
154        // Color (4 bytes RLE)
155        self.decode_color(reader, dst, offset)?;
156
157        // Color2 (specular — zeroed out)
158        dst[*offset..*offset + 4].copy_from_slice(&[0u8; 4]);
159        *offset += 4;
160
161        // UVWs
162        let num_uvws = (format & 0x0F) as usize;
163        for i in 0..num_uvws {
164            self.decode_float(reader, FIELD_UVW + i, 0, dst, offset)?;
165            self.decode_float(reader, FIELD_UVW + i, 1, dst, offset)?;
166            self.decode_float(reader, FIELD_UVW + i, 2, dst, offset)?;
167        }
168
169        Ok(())
170    }
171
172    /// Decode compressed vertex data from stream.
173    /// Returns partial data on failure instead of bailing entirely.
174    pub fn read(&mut self, reader: &mut impl Read, format: u8, stride: usize,
175                 num_verts: u16) -> Result<Vec<u8>> {
176        let total_size = num_verts as usize * stride;
177        let mut data = vec![0u8; total_size];
178
179        for i in 0..num_verts as usize {
180            let mut offset = i * stride;
181            if let Err(e) = self.decode_vertex(reader, &mut data, &mut offset, format) {
182                if i == 0 {
183                    // No vertices decoded at all — propagate the error
184                    return Err(e);
185                }
186                log::warn!("Vertex decode failed at vertex {}/{}: {}, returning partial data",
187                           i, num_verts, e);
188                // Truncate to successfully decoded vertices
189                data.truncate(i * stride);
190                return Ok(data);
191            }
192        }
193
194        Ok(data)
195    }
196}