vox_format/
writer.rs

1//! Provides functions to write VOX files. This is work-in-progress.
2
3use std::{
4    convert::TryInto,
5    fs::OpenOptions,
6    io::{
7        Cursor,
8        Seek,
9        Write,
10    },
11    path::Path,
12};
13
14use byteorder::{
15    WriteBytesExt,
16    LE,
17};
18use thiserror::Error;
19
20use crate::{
21    chunk::{
22        chunk_writer,
23        ChunkId,
24        ChunkWriter,
25    },
26    data::VoxData,
27    types::Version,
28};
29
30/// Error type returned when writing fails.
31#[derive(Debug, Error)]
32pub enum Error {
33    #[error("IO error")]
34    Io(#[from] std::io::Error),
35
36    /// An integer overflowed.
37    #[error("Integer overflow")]
38    Overflow(#[from] std::num::TryFromIntError),
39
40    /// This is a work-around,since sometimes we want to read VOX files in a
41    /// chunk-writer closure.
42    #[error("Reader error")]
43    Reader(#[from] crate::reader::Error),
44}
45
46/// Writes the file header for a VOX file.
47fn write_file_header<W: Write>(mut writer: W, version: Version) -> Result<(), Error> {
48    writer.write_all(b"VOX ")?;
49    version.write(writer)?;
50    Ok(())
51}
52
53/// Writes the `MAIN` chunk including the file signature. The closure you pass,
54/// will be called with the [`ChunkWriter`] for the `MAIN` chunk.
55pub fn main_chunk_writer<W: Write + Seek, F: FnMut(&mut ChunkWriter<W>) -> Result<(), Error>>(
56    mut writer: W,
57    version: Version,
58    f: F,
59) -> Result<(), Error> {
60    write_file_header(&mut writer, version)?;
61
62    chunk_writer(writer, ChunkId::Main, f)
63}
64
65/// Writes [`crate::data::VoxData`] to a [`std::io::Write`].
66pub fn to_writer<W: Write + Seek>(writer: W, vox: &VoxData) -> Result<(), Error> {
67    main_chunk_writer(writer, Version::default(), |chunk_writer| {
68        // Write PACK, if there is more than 1 model.
69        // FIXME: Apparently PACK is not used anymore.
70        if vox.models.len() > 1 {
71            chunk_writer.child_content_writer(ChunkId::Pack, |writer| {
72                writer.write_u32::<LE>(vox.models.len().try_into()?)?;
73                Ok(())
74            })?;
75        }
76
77        // Write models
78        for model in &vox.models {
79            // Write SIZE chunk
80            chunk_writer.child_content_writer(ChunkId::Size, |writer| {
81                model.size.write(writer)?;
82                Ok(())
83            })?;
84
85            // Write XYZI chunk
86            chunk_writer.child_content_writer(ChunkId::Xyzi, |mut writer| {
87                writer.write_u32::<LE>(model.voxels.len().try_into()?)?;
88                for voxel in &model.voxels {
89                    voxel.write(&mut writer)?;
90                }
91                Ok(())
92            })?;
93        }
94
95        // Write palette
96        if !vox.palette.is_default() {
97            chunk_writer.child_content_writer(ChunkId::Rgba, |writer| {
98                vox.palette.write(writer)?;
99                Ok(())
100            })?;
101        }
102
103        Ok(())
104    })
105}
106
107/// Encode [`VoxData`] and return bytes as `Vec<u8>`.
108pub fn to_vec(vox: &VoxData) -> Result<Vec<u8>, Error> {
109    //let mut buf = Vec::with_capacity(vox.size_hint());
110    let mut buf = Vec::with_capacity(1024);
111    to_writer(Cursor::new(&mut buf), vox)?;
112    buf.shrink_to_fit();
113    Ok(buf)
114}
115
116/// Writes VOX data to the specified path.
117pub fn to_file<P: AsRef<Path>>(path: P, vox: &VoxData) -> Result<(), Error> {
118    to_writer(OpenOptions::new().create(true).write(true).open(path)?, vox)
119}