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}