1use 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#[derive(Debug, Error)]
32pub enum Error {
33 #[error("IO error")]
34 Io(#[from] std::io::Error),
35
36 #[error("Integer overflow")]
38 Overflow(#[from] std::num::TryFromIntError),
39
40 #[error("Reader error")]
43 Reader(#[from] crate::reader::Error),
44}
45
46fn 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
53pub 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
65pub fn to_writer<W: Write + Seek>(writer: W, vox: &VoxData) -> Result<(), Error> {
67 main_chunk_writer(writer, Version::default(), |chunk_writer| {
68 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 for model in &vox.models {
79 chunk_writer.child_content_writer(ChunkId::Size, |writer| {
81 model.size.write(writer)?;
82 Ok(())
83 })?;
84
85 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 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
107pub fn to_vec(vox: &VoxData) -> Result<Vec<u8>, Error> {
109 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
116pub 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}