dot_vox/
lib.rs

1//! Load [MagicaVoxel](https://ephtracy.github.io/) `.vox` files from Rust.
2
3use parser::parse_vox_file;
4use std::{fs::File, io::Read};
5
6#[macro_use]
7extern crate lazy_static;
8#[macro_use]
9extern crate log;
10
11#[cfg(test)]
12extern crate avow;
13
14mod dot_vox_data;
15mod model;
16mod palette;
17mod parser;
18mod scene;
19mod types;
20
21pub use types::Rotation;
22
23pub use dot_vox_data::DotVoxData;
24
25pub use parser::{Dict, Material};
26
27pub use model::Model;
28pub use model::Size;
29pub use model::Voxel;
30
31pub use scene::*;
32
33pub use palette::Color;
34pub use palette::DEFAULT_PALETTE;
35
36/// Loads the supplied [MagicaVoxel](https://ephtracy.github.io/) `.vox` file
37///
38/// Loads the supplied file, parses it, and returns a [`DotVoxData`] containing
39/// the version of the MagicaVoxel file, a `Vec<`[`Model`]`>` containing all
40/// [`Model`]s contained within the file, a `Vec<u32>` containing the palette
41/// information (RGBA), and a `Vec<`[`Material`]`>` containing all the
42/// specialized materials.
43///
44/// # Panics
45///
46/// No panics should occur with this library -- if you find one, please raise a
47/// [GitHub issue](https://github.com/dust-engine/dot_vox/issues) for it.
48///
49/// # Errors
50///
51/// All errors are strings, and should describe the issue that caused them to
52/// occur.
53///
54/// # Examples
55///
56/// Loading a file:
57///
58/// ```
59/// use dot_vox::*;
60///
61/// let result = load("src/resources/placeholder.vox");
62/// assert_eq!(
63///     result.unwrap(),
64///     DotVoxData {
65///         version: 150,
66///         models: vec!(Model {
67///             size: Size { x: 2, y: 2, z: 2 },
68///             voxels: vec!(
69///                 Voxel {
70///                     x: 0,
71///                     y: 0,
72///                     z: 0,
73///                     i: 225
74///                 },
75///                 Voxel {
76///                     x: 0,
77///                     y: 1,
78///                     z: 1,
79///                     i: 215
80///                 },
81///                 Voxel {
82///                     x: 1,
83///                     y: 0,
84///                     z: 1,
85///                     i: 235
86///                 },
87///                 Voxel {
88///                     x: 1,
89///                     y: 1,
90///                     z: 0,
91///                     i: 5
92///                 }
93///             )
94///         }),
95///         palette: DEFAULT_PALETTE.to_vec(),
96///         materials: (0..256)
97///             .into_iter()
98///             .map(|i| Material {
99///                 id: i,
100///                 properties: {
101///                     let mut map = Dict::new();
102///                     map.insert("_ior".to_owned(), "0.3".to_owned());
103///                     map.insert("_spec".to_owned(), "0.5".to_owned());
104///                     map.insert("_rough".to_owned(), "0.1".to_owned());
105///                     map.insert("_type".to_owned(), "_diffuse".to_owned());
106///                     map.insert("_weight".to_owned(), "1".to_owned());
107///                     map
108///                 }
109///             })
110///             .collect(),
111///         scenes: placeholder::SCENES.to_vec(),
112///         layers: placeholder::LAYERS.to_vec(),
113///     }
114/// );
115/// ```
116pub fn load(filename: &str) -> Result<DotVoxData, &'static str> {
117    match File::open(filename) {
118        Ok(mut f) => {
119            let mut buffer = Vec::new();
120            f.read_to_end(&mut buffer).expect("Unable to read file");
121            load_bytes(&buffer)
122        }
123        Err(_) => Err("Unable to load file"),
124    }
125}
126
127/// Parses the byte array as a .vox file.
128///
129/// Parses the byte array and returns a [`DotVoxData`] containing  the version
130/// of the MagicaVoxel file, a `Vec<`[`Model`]`>` containing all `Model`s
131/// contained within the file, a `Vec<u32>` containing the palette information
132/// (RGBA), and a `Vec<`[`Material`]`>` containing all the specialized
133/// materials.
134///
135/// # Panics
136///
137/// No panics should occur with this library -- if you find one, please raise a
138/// [GitHub issue](https://github.com/dust-engine/dot_vox/issues) for it.
139///
140/// # Errors
141///
142/// All errors are strings, and should describe the issue that caused them to
143/// occur.
144///
145/// # Examples
146///
147/// Reading a byte array:
148///
149/// ```
150/// use dot_vox::*;
151///
152/// let result = load_bytes(include_bytes!("resources/placeholder.vox"));
153/// assert_eq!(
154///     result.unwrap(),
155///     DotVoxData {
156///         version: 150,
157///         models: vec!(Model {
158///             size: Size { x: 2, y: 2, z: 2 },
159///             voxels: vec!(
160///                 Voxel {
161///                     x: 0,
162///                     y: 0,
163///                     z: 0,
164///                     i: 225
165///                 },
166///                 Voxel {
167///                     x: 0,
168///                     y: 1,
169///                     z: 1,
170///                     i: 215
171///                 },
172///                 Voxel {
173///                     x: 1,
174///                     y: 0,
175///                     z: 1,
176///                     i: 235
177///                 },
178///                 Voxel {
179///                     x: 1,
180///                     y: 1,
181///                     z: 0,
182///                     i: 5
183///                 }
184///             )
185///         }),
186///         palette: DEFAULT_PALETTE.to_vec(),
187///         materials: (0..256)
188///             .into_iter()
189///             .map(|i| Material {
190///                 id: i,
191///                 properties: {
192///                     let mut map = Dict::new();
193///                     map.insert("_ior".to_owned(), "0.3".to_owned());
194///                     map.insert("_spec".to_owned(), "0.5".to_owned());
195///                     map.insert("_rough".to_owned(), "0.1".to_owned());
196///                     map.insert("_type".to_owned(), "_diffuse".to_owned());
197///                     map.insert("_weight".to_owned(), "1".to_owned());
198///                     map
199///                 }
200///             })
201///             .collect(),
202///         scenes: placeholder::SCENES.to_vec(),
203///         layers: placeholder::LAYERS.to_vec(),
204///     }
205/// );
206/// ```
207pub fn load_bytes(bytes: &[u8]) -> Result<DotVoxData, &'static str> {
208    match parse_vox_file(bytes) {
209        Ok((_, parsed)) => Ok(parsed),
210        Err(_) => Err("Not a valid MagicaVoxel .vox file"),
211    }
212}
213
214/// Data extracted from placeholder.vox for example and testing purposes
215pub mod placeholder {
216    use super::*;
217
218    lazy_static! {
219        /// Scenes extracted from placeholder.vox
220        pub static ref SCENES: Vec<SceneNode> = vec![
221            SceneNode::Transform {
222                attributes: Dict::new(),
223                frames: vec![Frame::default()], // Is this true??  Why empty dict? FIXME
224                child: 1,
225                layer_id: 4294967295
226            },
227            SceneNode::Group {
228                attributes: Dict::new(),
229                children: vec![2]
230            },
231            SceneNode::Transform {
232                attributes: Dict::new(),
233                frames: {
234                    let mut map = Dict::new();
235                    map.insert("_t".to_owned(), "0 0 1".to_owned());
236
237                    vec![Frame::new(map)]
238                },
239                child: 3,
240                layer_id: 0
241            },
242            SceneNode::Shape {
243                attributes: Dict::new(),
244                models: vec![ShapeModel{
245                    model_id: 0,
246                    attributes: Dict::new()
247                }],
248            },
249        ];
250
251        /// Layers extracted from placeholder.vox
252        pub static ref LAYERS: Vec<Layer> = (0..8)
253            .into_iter()
254            .map(|layer| Layer {
255                attributes: {
256                    let mut map = Dict::new();
257                    map.insert("_name".to_string(), layer.to_string());
258
259                    map
260                },
261            })
262            .collect();
263    }
264}
265
266#[cfg(test)]
267mod tests {
268    use super::*;
269    use avow::vec;
270
271    lazy_static! {
272        static ref DEFAULT_MATERIALS: Vec<Material> = (0..256)
273            .into_iter()
274            .map(|i| Material {
275                id: i,
276                properties: {
277                    let mut map = Dict::new();
278                    map.insert("_ior".to_owned(), "0.3".to_owned());
279                    map.insert("_spec".to_owned(), "0.5".to_owned());
280                    map.insert("_rough".to_owned(), "0.1".to_owned());
281                    map.insert("_type".to_owned(), "_diffuse".to_owned());
282                    map.insert("_weight".to_owned(), "1".to_owned());
283                    map
284                }
285            })
286            .collect();
287    }
288
289    fn placeholder(
290        palette: Vec<Color>,
291        materials: Vec<Material>,
292        scenes: Vec<SceneNode>,
293        layers: Vec<Layer>,
294    ) -> DotVoxData {
295        DotVoxData {
296            version: 150,
297            models: vec![Model {
298                size: Size { x: 2, y: 2, z: 2 },
299                voxels: vec![
300                    Voxel {
301                        x: 0,
302                        y: 0,
303                        z: 0,
304                        i: 225,
305                    },
306                    Voxel {
307                        x: 0,
308                        y: 1,
309                        z: 1,
310                        i: 215,
311                    },
312                    Voxel {
313                        x: 1,
314                        y: 0,
315                        z: 1,
316                        i: 235,
317                    },
318                    Voxel {
319                        x: 1,
320                        y: 1,
321                        z: 0,
322                        i: 5,
323                    },
324                ],
325            }],
326            palette,
327            materials,
328            scenes,
329            layers,
330        }
331    }
332
333    fn compare_data(actual: DotVoxData, expected: DotVoxData) {
334        assert_eq!(actual.version, expected.version);
335        assert_eq!(actual.models.len(), expected.models.len());
336        actual
337            .models
338            .into_iter()
339            .zip(expected.models.into_iter())
340            .for_each(|(actual, expected)| {
341                assert_eq!(actual.size, expected.size);
342                vec::are_eq(actual.voxels, expected.voxels);
343            });
344        vec::are_eq(actual.palette, expected.palette);
345        vec::are_eq(actual.materials, expected.materials);
346        vec::are_eq(actual.scenes, expected.scenes);
347        vec::are_eq(actual.layers, expected.layers)
348    }
349
350    #[test]
351    fn valid_file_with_palette_is_read_successfully() {
352        let result = load("src/resources/placeholder.vox");
353        assert!(result.is_ok());
354        compare_data(
355            result.unwrap(),
356            placeholder(
357                DEFAULT_PALETTE.to_vec(),
358                DEFAULT_MATERIALS.to_vec(),
359                placeholder::SCENES.to_vec(),
360                placeholder::LAYERS.to_vec(),
361            ),
362        );
363    }
364
365    #[test]
366    fn not_present_file_causes_error() {
367        let result = load("src/resources/not_here.vox");
368        assert!(result.is_err());
369        assert_eq!(result.unwrap_err(), "Unable to load file");
370    }
371
372    #[test]
373    fn non_vox_file_causes_error() {
374        let result = load("src/resources/not_a.vox");
375        assert!(result.is_err());
376        assert_eq!(result.unwrap_err(), "Not a valid MagicaVoxel .vox file");
377    }
378
379    #[test]
380    fn can_parse_vox_file_with_palette() {
381        let bytes = include_bytes!("resources/placeholder.vox").to_vec();
382        let result = super::parse_vox_file(&bytes);
383        assert!(result.is_ok());
384        let (_, models) = result.unwrap();
385        compare_data(
386            models,
387            placeholder(
388                DEFAULT_PALETTE.to_vec(),
389                DEFAULT_MATERIALS.to_vec(),
390                placeholder::SCENES.to_vec(),
391                placeholder::LAYERS.to_vec(),
392            ),
393        );
394    }
395
396    #[test]
397    fn can_parse_vox_file_with_materials() {
398        let bytes = include_bytes!("resources/placeholder-with-materials.vox").to_vec();
399        let result = super::parse_vox_file(&bytes);
400        assert!(result.is_ok());
401        let (_, voxel_data) = result.unwrap();
402        let mut materials: Vec<Material> = DEFAULT_MATERIALS.to_vec();
403        materials[216] = Material {
404            id: 216,
405            properties: {
406                let mut map = Dict::new();
407                map.insert("_ior".to_owned(), "0.3".to_owned());
408                map.insert("_spec".to_owned(), "0.821053".to_owned());
409                map.insert("_rough".to_owned(), "0.389474".to_owned());
410                map.insert("_type".to_owned(), "_metal".to_owned());
411                map.insert("_plastic".to_owned(), "1".to_owned());
412                map.insert("_weight".to_owned(), "0.694737".to_owned());
413                map
414            },
415        };
416        compare_data(
417            voxel_data,
418            placeholder(
419                DEFAULT_PALETTE.to_vec(),
420                materials,
421                placeholder::SCENES.to_vec(),
422                placeholder::LAYERS.to_vec(),
423            ),
424        );
425    }
426
427    fn write_and_load(data: DotVoxData) {
428        let mut buffer = Vec::new();
429        let write_result = data.write_vox(&mut buffer);
430        assert!(write_result.is_ok());
431        let load_result = load_bytes(&buffer);
432        assert!(load_result.is_ok());
433        compare_data(load_result.unwrap(), data);
434    }
435
436    #[test]
437    fn can_write_vox_format_without_palette_nor_materials() {
438        write_and_load(placeholder(Vec::new(), Vec::new(), Vec::new(), Vec::new()));
439    }
440
441    #[test]
442    fn can_write_vox_format_without_materials() {
443        write_and_load(placeholder(
444            DEFAULT_PALETTE.to_vec(),
445            Vec::new(),
446            Vec::new(),
447            Vec::new(),
448        ));
449    }
450}