vox_format/
chunk.rs

1//! This modules provides utilities to read and write VOX files as chunks, but
2//! is agnostic to the content of these chunks.
3//!
4//! VOX files use a binary format that is similar to that of RIFF files.
5//! Unfortunately it's different enough that the `riff` crate can't be used.
6//!
7//! The file format consists of so-called chunks, which contain specific data
8//! (e.g. palette data). Chunks have IDs consisting of 4 bytes. A file
9//! starts with a root-chunk `MAIN`. The `MAIN` chunk then contains other chunks
10//! that contain the voxel data. The format is specified [here](https://github.com/ephtracy/voxel-model/blob/master/MagicaVoxel-file-format-vox.txt), but not all chunk IDs are described.
11
12use std::{
13    convert::TryInto,
14    io::{
15        Error as IoError,
16        ErrorKind,
17        Read,
18        Seek,
19        SeekFrom,
20        Write,
21    },
22    str::FromStr,
23    u64,
24};
25
26use byteorder::{
27    ReadBytesExt,
28    WriteBytesExt,
29    LE,
30};
31use thiserror::Error;
32
33use crate::{
34    reader::Error as ReadError,
35    types::Version,
36    writer::Error as WriteError,
37};
38
39#[derive(Debug, Error)]
40#[error("Failed to parse chunk ID: {0}")]
41pub struct ChunkIdParseError(String);
42
43/// A chunk ID. It's either a pre-defined ID (that is used for VOX), or
44/// `Unsupported`.
45///
46/// The best way to find out which chunk IDs MagicaVoxel uses, is by looking at
47/// its [source](https://github.com/aiekick/MagicaVoxel_File_Writer/blob/master/VoxWriter.cpp)
48#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
49pub enum ChunkId {
50    // These are actually defined in the spec.
51    Main,
52    Pack,
53    Size,
54    Xyzi,
55    Rgba,
56    Matt, // There's also a `MATL`?
57
58    /// TODO: What format does this have?
59    Note,
60
61    // From source `VoxWriter::VoxWriter` (line 482)
62    Vox,
63    NTrn,
64    NGrp,
65    NShp,
66    Layr,
67    Matl,
68    RObj,
69    RCam,
70
71    /// Unsupported chunk ID
72    Unsupported([u8; 4]),
73}
74
75impl ChunkId {
76    pub fn read<R: Read>(mut reader: R) -> Result<Self, ReadError> {
77        let mut id = [0u8; 4];
78        reader.read_exact(&mut id)?;
79        Ok(ChunkId::from(id))
80    }
81
82    pub fn write<W: Write>(&self, mut writer: W) -> Result<(), WriteError> {
83        let id: [u8; 4] = (*self).into();
84        Ok(writer.write_all(&id)?)
85    }
86
87    /// Returns whether this chunk ID is currently supported by this crate.
88    pub fn is_supported(&self) -> bool {
89        !matches!(self, ChunkId::Unsupported(_))
90    }
91}
92
93impl From<[u8; 4]> for ChunkId {
94    fn from(value: [u8; 4]) -> Self {
95        match &value {
96            b"MAIN" => Self::Main,
97            b"PACK" => Self::Pack,
98            b"SIZE" => Self::Size,
99            b"XYZI" => Self::Xyzi,
100            b"RGBA" => Self::Rgba,
101            b"MATT" => Self::Matt,
102            b"NOTE" => Self::Note,
103            b"VOX " => Self::Vox,
104            b"nTRN" => Self::NTrn,
105            b"nGRP" => Self::NGrp,
106            b"nSHP" => Self::NShp,
107            b"LAYR" => Self::Layr,
108            b"MATL" => Self::Matl,
109            b"rOBJ" => Self::RObj,
110            b"rCAM" => Self::RCam,
111            _ => Self::Unsupported(value),
112        }
113    }
114}
115
116impl From<ChunkId> for [u8; 4] {
117    fn from(chunk_id: ChunkId) -> Self {
118        match chunk_id {
119            ChunkId::Main => *b"MAIN",
120            ChunkId::Pack => *b"PACK",
121            ChunkId::Size => *b"SIZE",
122            ChunkId::Xyzi => *b"XYZI",
123            ChunkId::Rgba => *b"RGBA",
124            ChunkId::Matt => *b"MATT",
125            ChunkId::Note => *b"NOTE",
126            ChunkId::Vox => *b"VOX ",
127            ChunkId::NTrn => *b"nTRN",
128            ChunkId::NGrp => *b"nGRP",
129            ChunkId::NShp => *b"nSHP",
130            ChunkId::Layr => *b"LAYR",
131            ChunkId::Matl => *b"MATL",
132            ChunkId::RObj => *b"rOBJ",
133            ChunkId::RCam => *b"rCAM",
134            ChunkId::Unsupported(value) => value,
135        }
136    }
137}
138
139impl FromStr for ChunkId {
140    type Err = ChunkIdParseError;
141
142    fn from_str(s: &str) -> Result<Self, Self::Err> {
143        if s.as_bytes().len() != 4 {
144            return Err(ChunkIdParseError(s.to_owned()));
145        }
146
147        let mut id = [0u8; 4];
148        id.copy_from_slice(s.as_bytes());
149        Ok(ChunkId::from(id))
150    }
151}
152
153/// Chunk meta-data. This doesn't contain contents or children, but information
154/// needed to read the chunk from a file. You will still need a reader to read
155/// the contents though.
156#[derive(Clone, Debug)]
157pub struct Chunk {
158    offset: u32,
159    id: ChunkId,
160    content_len: u32,
161    children_len: u32,
162}
163
164impl Chunk {
165    /// Reads the chunk header and returns the information needed to read its
166    /// contents or children.
167    pub fn read<R: Read + Seek>(mut reader: R) -> Result<Self, ReadError> {
168        let offset = reader.stream_position()? as u32;
169
170        let id = ChunkId::read(&mut reader)?;
171        log::trace!("read chunk at {}: {:?}", offset, id);
172
173        let content_len = reader.read_u32::<LE>()?;
174        let children_len = reader.read_u32::<LE>()?;
175        log::trace!(
176            "content_len = {}, children_len = {}",
177            content_len,
178            children_len
179        );
180
181        Ok(Chunk {
182            offset,
183            id,
184            content_len,
185            children_len,
186        })
187    }
188
189    /// Creates a reader for its contents.
190    pub fn content<R: Read + Seek>(&self, mut reader: R) -> Result<ContentReader<R>, ReadError> {
191        let offset = self.content_offset();
192        log::trace!("content reader: id={:?}, offset={}", self.id, offset);
193        reader.seek(SeekFrom::Start(offset as u64))?;
194        Ok(ContentReader {
195            reader,
196            start: offset,
197            offset,
198            end: offset + self.content_len,
199        })
200    }
201
202    pub fn read_content_to_vec<R: Read + Seek>(&self, reader: R) -> Result<Vec<u8>, ReadError> {
203        let mut buf = vec![];
204        self.content(reader)?.read_to_end(&mut buf)?;
205        Ok(buf)
206    }
207
208    /// Creates an iterator over its children. The iterator yields
209    /// `Result<Chunk, _>`, so you'll need to handle the error first.
210    /// Each child then is another `Chunk` struct that can be used to read
211    /// contents or children.
212    pub fn children<R: Read + Seek>(&self, reader: R) -> ChildrenReader<R> {
213        let offset = self.children_offset();
214
215        log::trace!(
216            "children reader: offset={}, end={}",
217            offset,
218            offset + self.children_len
219        );
220
221        ChildrenReader {
222            reader,
223            offset,
224            end: offset + self.children_len,
225        }
226    }
227
228    /// Returns the offset at which the chunk starts. This is relative to the
229    /// start of the reader. Note that for children chunks, this is relative
230    /// to the start of the child data, since they basically use a
231    /// `ContentReader` to read children chunks.
232    pub fn offset(&self) -> u32 {
233        self.offset
234    }
235
236    /// Returns the chunk IDs
237    pub fn id(&self) -> ChunkId {
238        self.id
239    }
240
241    /// Returns the offset to the content data. See [`Self::offset`] for further
242    /// information.
243    pub fn content_offset(&self) -> u32 {
244        self.offset + 12
245    }
246
247    /// Returns the length of the content data.
248    pub fn content_len(&self) -> u32 {
249        self.content_len
250    }
251
252    /// Returns the offset to the children data. See [`Self::offset`] for
253    /// further information.
254    pub fn children_offset(&self) -> u32 {
255        self.offset + 12 + self.content_len
256    }
257
258    /// Returns the length of the children data.
259    pub fn children_len(&self) -> u32 {
260        self.children_len
261    }
262
263    /// Returns the length of this chunk. That is the length of its contents,
264    /// children and header.
265    pub fn len(&self) -> u32 {
266        self.content_len + self.children_len + 12
267    }
268
269    /// Returns `true` if the chunks has neither content nor children, `false`
270    /// otherwise.
271    pub fn is_empty(&self) -> bool {
272        self.content_len == 0 && self.children_len == 0
273    }
274}
275
276/// A reader for a chunk's contents.
277pub struct ContentReader<R> {
278    reader: R,
279    offset: u32,
280    start: u32,
281    end: u32,
282}
283
284impl<R: Read> Read for ContentReader<R> {
285    fn read(&mut self, buf: &mut [u8]) -> Result<usize, IoError> {
286        log::trace!(
287            "read: offset={}, start={}, end={}",
288            self.offset,
289            self.start,
290            self.end
291        );
292        if self.offset < self.end {
293            let n_at_most = ((self.end - self.offset) as usize).min(buf.len());
294            log::trace!(
295                "read: offset={}, end={}, n_at_most={}",
296                self.offset,
297                self.end,
298                n_at_most
299            );
300            let n_read = self.reader.read(&mut buf[..n_at_most])?;
301            log::trace!("read: n_read={}", n_read);
302            self.offset += n_read as u32;
303            Ok(n_read)
304        }
305        else {
306            Ok(0)
307        }
308    }
309}
310
311impl<R: Seek> Seek for ContentReader<R> {
312    fn seek(&mut self, pos: SeekFrom) -> Result<u64, IoError> {
313        let new_offset = seek_to(self.offset, self.start, self.end, pos)?;
314
315        if new_offset != self.offset {
316            log::trace!(
317                "seek: offset={}, start={}, end={}, pos={:?}, new_offset={}",
318                self.offset,
319                self.start,
320                self.end,
321                pos,
322                new_offset
323            );
324
325            self.offset = new_offset;
326
327            self.reader.seek(SeekFrom::Start(self.offset.into()))?;
328        }
329
330        Ok((self.offset - self.start).into())
331    }
332}
333
334/// An iterator over a chunk's children.
335pub struct ChildrenReader<R> {
336    reader: R,
337    offset: u32,
338    end: u32,
339}
340
341impl<R: Read + Seek> Iterator for ChildrenReader<R> {
342    type Item = Result<Chunk, ReadError>;
343
344    fn next(&mut self) -> Option<Self::Item> {
345        log::trace!("read next child: offset={}, end={}", self.offset, self.end);
346        if self.offset < self.end {
347            Some(read_chunk_at(&mut self.reader, &mut self.offset))
348        }
349        else {
350            None
351        }
352    }
353}
354
355/// Reads a chunk from `reader` at the specified offset.
356pub fn read_chunk_at<R: Read + Seek>(mut reader: R, offset: &mut u32) -> Result<Chunk, ReadError> {
357    log::trace!("reading chunk at {}", offset);
358    reader.seek(SeekFrom::Start((*offset).into()))?;
359    let chunk = Chunk::read(reader)?;
360    *offset += chunk.len();
361    Ok(chunk)
362}
363
364/// Reads the VOX file's header, verifies it, and then reads the MAIN chunk.
365pub fn read_main_chunk<R: Read + Seek>(mut reader: R) -> Result<(Chunk, Version), ReadError> {
366    let mut buf = [0u8; 4];
367    reader.read_exact(&mut buf)?;
368    log::trace!("magic = {:?}", buf);
369    if &buf != b"VOX " {
370        return Err(ReadError::InvalidMagic { got: buf });
371    }
372
373    let version = Version::read(&mut reader)?;
374    log::trace!("version = {:?}", version);
375    if !version.is_supported() {
376        return Err(ReadError::UnsupportedFileVersion { version });
377    }
378
379    let main_chunk = Chunk::read(reader)?;
380
381    if main_chunk.id() != ChunkId::Main {
382        return Err(ReadError::ExpectedMainChunk { got: main_chunk });
383    }
384
385    Ok((main_chunk, version))
386}
387
388/// This struct is used to write out chunks to a file.
389#[derive(Debug)]
390pub struct ChunkWriter<W> {
391    chunk_id: ChunkId,
392
393    writer: W,
394
395    /// Offset for the chunk. Points at the Chunk ID.
396    offset: u64,
397
398    content_len: u32,
399
400    children_len: u32,
401}
402
403impl<W: Write + Seek> ChunkWriter<W> {
404    fn new(mut writer: W, chunk_id: ChunkId) -> Result<Self, WriteError> {
405        chunk_id.write(&mut writer)?;
406
407        // Leave 8 bytes for `content_len` and `children_len`. Remember offset to write
408        // values later.
409        let offset = writer.seek(SeekFrom::Current(8))? - 12;
410
411        Ok(Self {
412            chunk_id,
413            writer,
414            offset,
415            content_len: 0,
416            children_len: 0,
417        })
418    }
419
420    /// Returns the chunk ID
421    pub fn id(&self) -> ChunkId {
422        self.chunk_id
423    }
424
425    /// Returns the offset at which this chunk is written in the underlying
426    /// writer. Similar to [`Chunk::offset`], this is is usually 0 for child
427    /// chunks, since the use a [`ContentWriter`], which only sees the children
428    /// data.
429    pub fn offset(&self) -> u64 {
430        self.offset
431    }
432
433    /// Returns the current length of the contents.
434    pub fn content_len(&self) -> u32 {
435        self.content_len
436    }
437
438    /// Returns the current length of the children data. This is the total
439    /// number of bytes written for children chunks.
440    pub fn children_len(&self) -> u32 {
441        self.children_len
442    }
443
444    /// Writes data to the chunks content.
445    ///
446    /// Note, that this must be called before any calls to `child_writer`.
447    ///
448    /// # Example
449    ///
450    /// ```
451    /// # use std::io::Cursor;
452    /// # use vox_format::chunk::*;
453    /// # vox_format::writer::main_chunk_writer(Cursor::new(vec![]), Default::default(), |chunk_writer| {
454    /// use std::io::Write;
455    ///
456    /// chunk_writer.content_writer(|writer| {
457    ///     // `writer` implements `std::io::Write + std::io::Seek`.
458    ///     writer.write_all(b"Hello World")?;
459    ///     Ok(())
460    /// })
461    /// # }).unwrap();
462    /// ```
463    ///
464    /// # Panics
465    ///
466    /// Panics, if children have been written already.
467    pub fn content_writer<'w, F: FnMut(&mut ContentWriter<&'w mut W>) -> Result<(), WriteError>>(
468        &'w mut self,
469        mut f: F,
470    ) -> Result<(), WriteError> {
471        /*if self.content_len != 0 {
472            panic!("Chunk content already written: content_len = {}", self.content_len);
473        }*/
474        if self.children_len != 0 {
475            panic!(
476                "Chunk children already written: children_len = {}",
477                self.children_len
478            );
479        }
480
481        let mut content_writer = ContentWriter::new(&mut self.writer)?;
482
483        f(&mut content_writer)?;
484
485        self.content_len += content_writer.len();
486
487        Ok(())
488    }
489
490    /// Writes the given slice to the chunk's data.
491    pub fn write_content(&mut self, data: &[u8]) -> Result<(), WriteError> {
492        self.content_writer(|content_writer| {
493            content_writer.write_all(data)?;
494            Ok(())
495        })
496    }
497
498    /// Writes children chunks to this chunk. Note, that after a child has been
499    /// written to a chunk, you can't write any more data to its contents.
500    ///
501    /// # Example
502    ///
503    /// ```
504    /// # use std::io::Cursor;
505    /// # use vox_format::chunk::*;
506    /// # vox_format::writer::main_chunk_writer(Cursor::new(vec![]), Default::default(), |chunk_writer| {
507    /// chunk_writer.child_writer(ChunkId::Main, |child_writer| {
508    ///     // `child_writer` is just another `ChunkWriter`.
509    ///     child_writer.write_content(b"Hello World")?;
510    ///     Ok(())
511    /// })
512    /// # }).unwrap();
513    /// ```
514    pub fn child_writer<'w, F: FnMut(&mut ChildWriter<'w, W>) -> Result<(), WriteError>>(
515        &'w mut self,
516        chunk_id: ChunkId,
517        mut f: F,
518    ) -> Result<(), WriteError> {
519        let mut child_writer = ChildWriter::new(ContentWriter::new(&mut self.writer)?, chunk_id)?;
520
521        f(&mut child_writer)?;
522
523        self.children_len += child_writer.writer.len();
524
525        child_writer.write_header()?;
526
527        Ok(())
528    }
529
530    /// Short-hand to opening and child-writer and then a content-writer to that
531    /// child. Useful, if you want to write a child with only content data.
532    pub fn child_content_writer<
533        'w,
534        F: FnMut(&mut ContentWriter<&mut ContentWriter<&'w mut W>>) -> Result<(), WriteError>,
535    >(
536        &'w mut self,
537        chunk_id: ChunkId,
538        mut f: F,
539    ) -> Result<(), WriteError> {
540        self.child_writer(chunk_id, |child_writer| {
541            child_writer.content_writer(|content_writer| f(content_writer))
542        })
543    }
544
545    fn write_header(&mut self) -> Result<(), WriteError> {
546        log::trace!(
547            "Write header for chunk {:?} to offset {}: content_len = {}, children_len = {}",
548            self.chunk_id,
549            self.offset,
550            self.content_len,
551            self.children_len
552        );
553
554        let old_pos = self.writer.seek(SeekFrom::Current(0))?;
555        self.writer.seek(SeekFrom::Start(self.offset + 4))?;
556
557        self.writer.write_u32::<LE>(self.content_len)?;
558        self.writer.write_u32::<LE>(self.children_len)?;
559
560        self.writer.seek(SeekFrom::Start(old_pos))?;
561
562        Ok(())
563    }
564}
565
566/// This implements `Write` and `Seek` on a restricted range of offsets in the
567/// underlying reader of the chunk you're writing to.
568#[derive(Debug)]
569pub struct ContentWriter<W> {
570    writer: W,
571    offset: u32,
572    start: u32,
573    end: u32,
574}
575
576impl<W: Seek> ContentWriter<W> {
577    fn new(mut writer: W) -> Result<Self, WriteError> {
578        let offset = writer.stream_position()?.try_into()?;
579        Ok(Self {
580            writer,
581            offset,
582            start: offset,
583            end: offset,
584        })
585    }
586
587    fn len(&self) -> u32 {
588        self.end - self.start
589    }
590}
591
592impl<W: Write> Write for ContentWriter<W> {
593    fn write(&mut self, buf: &[u8]) -> Result<usize, IoError> {
594        let n_written = self.writer.write(buf)?;
595
596        self.offset = n_written
597            .try_into()
598            .ok()
599            .and_then(|n| self.offset.checked_add(n))
600            .ok_or_else(|| IoError::from(ErrorKind::Other))?;
601
602        if self.offset > self.end {
603            log::trace!(
604                "write {} bytes: offset={}, end={}",
605                n_written,
606                self.offset,
607                self.end
608            );
609            self.end = self.offset;
610        }
611
612        Ok(n_written)
613    }
614
615    fn flush(&mut self) -> std::io::Result<()> {
616        self.writer.flush()
617    }
618}
619
620impl<W: Seek> Seek for ContentWriter<W> {
621    fn seek(&mut self, pos: SeekFrom) -> Result<u64, IoError> {
622        let new_offset = seek_to(self.offset, self.start, self.end, pos)?;
623
624        if new_offset != self.offset {
625            log::trace!(
626                "seek: offset={}, start={}, end={}, pos={:?}, new_offset={}",
627                self.offset,
628                self.start,
629                self.end,
630                pos,
631                new_offset
632            );
633            self.writer.seek(SeekFrom::Start(new_offset.into()))?;
634            self.offset = new_offset;
635        }
636
637        Ok((self.offset - self.start).into())
638    }
639}
640
641/// Short-hand for a [`ChunkWriter`] wrapping a [`ContentWriter`]. The
642/// underlying content-writer writes to the chunk's children data blob.
643pub type ChildWriter<'w, W> = ChunkWriter<ContentWriter<&'w mut W>>;
644
645/// This creates a `ChunkWriter` and will the closure you passed in with it.
646/// This allows you to write contents and children to the `ChunkWriter`. After
647/// you return from the closure this function will make sure that the chunk
648/// headers are upates.
649pub fn chunk_writer<W: Write + Seek, F: FnMut(&mut ChunkWriter<W>) -> Result<(), WriteError>>(
650    writer: W,
651    chunk_id: ChunkId,
652    mut f: F,
653) -> Result<(), WriteError> {
654    let mut chunk_writer = ChunkWriter::new(writer, chunk_id)?;
655
656    f(&mut chunk_writer)?;
657
658    chunk_writer.write_header()?;
659
660    Ok(())
661}
662
663#[derive(Debug, Error)]
664#[error("The argument {pos:?} to seek is invalid.")]
665struct InvalidSeek {
666    current: u32,
667    start: u32,
668    end: u32,
669    pos: SeekFrom,
670}
671
672fn seek_to(current: u32, start: u32, end: u32, pos: SeekFrom) -> Result<u32, IoError> {
673    let (offset, delta) = match pos {
674        SeekFrom::Current(pos) => (current, Some(pos)),
675        SeekFrom::Start(pos) => (start, pos.try_into().ok()),
676        SeekFrom::End(pos) => (end, Some(pos)),
677    };
678
679    let offset = i64::from(offset);
680    let new_pos: Option<u32> = delta
681        .and_then(|d| offset.checked_add(d))
682        .and_then(|p| p.try_into().ok());
683
684    new_pos.ok_or_else(|| {
685        IoError::new(
686            ErrorKind::Other,
687            InvalidSeek {
688                current,
689                start,
690                end,
691                pos,
692            },
693        )
694    })
695}