Skip to main content

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