diff --git a/.gitignore b/.gitignore index 887f9b1..bff2fac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ target Cargo.lock .vscode .idea/ -*.iml \ No newline at end of file +*.iml +.DS_Store diff --git a/Cargo.toml b/Cargo.toml index caa13d0..469ca21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,5 @@ nom = { version = "^7", default-features = false, features = ["alloc"] } [dev-dependencies] avow = "0.2.0" env_logger = "^0.9" +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/src/dot_vox_data.rs b/src/dot_vox_data.rs index 23fe48c..651794b 100644 --- a/src/dot_vox_data.rs +++ b/src/dot_vox_data.rs @@ -1,5 +1,9 @@ -use crate::{Layer, Material, Model, SceneNode}; -use std::io::{self, Write}; +use crate::{parser::RenderCamera, Layer, Material, Model, SceneNode}; +use std::{ + collections::HashMap, + io::{self, Write}, +}; +pub type Dict = HashMap; /// Container for .vox file data #[derive(Debug, PartialEq, Eq)] @@ -12,6 +16,10 @@ pub struct DotVoxData { pub palette: Vec, /// A Vec containing all the Materials set pub materials: Vec, + /// A Vec containing a collection Render Object dicts + pub render_objects: Vec, + // A Vec containing a collection of Render Cameras + pub render_cameras: Vec, /// Scene. The first node in this list is always the root node. pub scenes: Vec, /// Layers. Used by scene transform nodes. diff --git a/src/lib.rs b/src/lib.rs index fbab7d9..ccefb9e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -87,6 +87,8 @@ use std::io::Read; /// } /// }) /// .collect(), +/// render_objects: vec![], +/// render_cameras: vec![], /// scenes: placeholder::SCENES.to_vec(), /// layers: placeholder::LAYERS.to_vec(), /// }); @@ -153,6 +155,8 @@ pub fn load(filename: &str) -> Result { /// } /// }) /// .collect(), +/// render_objects: vec![], +/// render_cameras: vec![], /// scenes: placeholder::SCENES.to_vec(), /// layers: placeholder::LAYERS.to_vec(), /// }); @@ -218,6 +222,8 @@ pub mod placeholder { mod tests { use std::collections::HashMap; + use crate::parser::RenderCamera; + use super::*; use avow::vec; @@ -239,6 +245,27 @@ mod tests { .collect(); } + lazy_static! { + static ref DEFAULT_RENDER_OBJECTS: Vec = serde_json::from_slice::>( + include_bytes!("resources/default_render_objects.json") + ) + .unwrap(); + } + + lazy_static! { + static ref DEFAULT_RENDER_CAMERAS: Vec = + serde_json::from_slice::>(include_bytes!( + "resources/default_render_cameras.json" + )) + .unwrap() + .iter() + .map(|(id, properties)| RenderCamera { + id: *id, + properties: properties.to_owned() + }) + .collect(); + } + fn placeholder( palette: Vec, materials: Vec, @@ -278,6 +305,8 @@ mod tests { }], palette, materials, + render_objects: vec![], + render_cameras: vec![], scenes, layers, } @@ -378,6 +407,26 @@ mod tests { ); } + #[test] + fn can_parse_vox_file_with_render_objects() { + let bytes = include_bytes!("resources/placeholder-with-render-objects.vox").to_vec(); + let result = super::parse_vox_file(&bytes); + assert!(result.is_ok()); + let (_, voxel_data) = result.unwrap(); + + vec::are_eq(voxel_data.render_objects, DEFAULT_RENDER_OBJECTS.to_vec()); + } + + #[test] + fn can_parse_vox_file_with_render_cameras() { + let bytes = include_bytes!("resources/placeholder-with-render-objects.vox").to_vec(); + let result = super::parse_vox_file(&bytes); + assert!(result.is_ok()); + let (_, voxel_data) = result.unwrap(); + + vec::are_eq(voxel_data.render_cameras, DEFAULT_RENDER_CAMERAS.to_vec()); + } + fn write_and_load(data: DotVoxData) { let mut buffer = Vec::new(); let write_result = data.write_vox(&mut buffer); diff --git a/src/parser.rs b/src/parser.rs index 6ee9491..fd4ade5 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -24,6 +24,8 @@ pub enum Chunk { Pack(Model), Palette(Vec), Material(Material), + RenderObjects(Dict), + RenderCamera(RenderCamera), TransformNode(SceneTransform), GroupNode(SceneGroup), ShapeNode(SceneShape), @@ -41,6 +43,15 @@ pub struct Material { pub properties: Dict, } +/// A material used to render this model. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct RenderCamera { + /// The Cameras's ID. + pub id: u32, + /// Properties of the camera, mapped by property name. + pub properties: Dict, +} + // TODO: maybe material schemas? impl Material { /// The '_type' field, if present @@ -56,9 +67,10 @@ impl Material { pub fn weight(&self) -> Option { let w = self.get_f32("_weight"); - if let Some(w) = w && (w < 0.0 || w > 1.0) - { - debug!("_weight observed outside of range of [0..1]: {}", w); + if let Some(w) = w { + if w < 0.0 || w > 1.0 { + debug!("_weight observed outside of range of [0..1]: {}", w); + } } w @@ -183,6 +195,8 @@ fn map_chunk_to_data(version: u32, main: Chunk) -> DotVoxData { let mut models: Vec = vec![]; let mut palette_holder: Vec = DEFAULT_PALETTE.to_vec(); let mut materials: Vec = vec![]; + let mut render_objects: Vec = vec![]; + let mut render_cameras: Vec = vec![]; let mut scene: Vec = vec![]; let mut layers: Vec = Vec::new(); @@ -197,6 +211,8 @@ fn map_chunk_to_data(version: u32, main: Chunk) -> DotVoxData { Chunk::Pack(model) => models.push(model), Chunk::Palette(palette) => palette_holder = palette, Chunk::Material(material) => materials.push(material), + Chunk::RenderObjects(dict) => render_objects.push(dict), + Chunk::RenderCamera(camera) => render_cameras.push(camera), Chunk::TransformNode(scene_transform) => { scene.push(SceneNode::Transform { attributes: scene_transform.header.attributes, @@ -238,6 +254,8 @@ fn map_chunk_to_data(version: u32, main: Chunk) -> DotVoxData { models, palette: palette_holder, materials, + render_objects, + render_cameras, scenes: scene, layers, } @@ -247,6 +265,8 @@ fn map_chunk_to_data(version: u32, main: Chunk) -> DotVoxData { models: vec![], palette: vec![], materials: vec![], + render_objects: vec![], + render_cameras: vec![], scenes: vec![], layers: vec![], }, @@ -270,6 +290,8 @@ fn build_chunk(id: &str, chunk_content: &[u8], children_size: u32, child_content "PACK" => build_pack_chunk(chunk_content), "RGBA" => build_palette_chunk(chunk_content), "MATL" => build_material_chunk(chunk_content), + "rOBJ" => build_render_objects_chunk(chunk_content), + "rCAM" => build_render_camera_chunk(chunk_content), "nTRN" => build_scene_transform_chunk(chunk_content), "nGRP" => build_scene_group_chunk(chunk_content), "nSHP" => build_scene_shape_chunk(chunk_content), @@ -305,6 +327,19 @@ fn build_material_chunk(chunk_content: &[u8]) -> Chunk { } Chunk::Invalid(chunk_content.to_vec()) } +fn build_render_camera_chunk(chunk_content: &[u8]) -> Chunk { + if let Ok((_, camera)) = parse_render_camera(chunk_content) { + return Chunk::RenderCamera(camera); + } + Chunk::Invalid(chunk_content.to_vec()) +} + +fn build_render_objects_chunk(chunk_content: &[u8]) -> Chunk { + if let Ok((_, dict)) = parse_dict(chunk_content) { + return Chunk::RenderObjects(dict); + } + Chunk::Invalid(chunk_content.to_vec()) +} fn build_palette_chunk(chunk_content: &[u8]) -> Chunk { if let Ok((_, palette)) = palette::extract_palette(chunk_content) { @@ -374,6 +409,11 @@ pub fn parse_material(i: &[u8]) -> IResult<&[u8], Material> { Ok((i, Material { id, properties })) } +pub fn parse_render_camera(i: &[u8]) -> IResult<&[u8], RenderCamera> { + let (i, (id, properties)) = pair(le_u32, parse_dict)(i)?; + Ok((i, RenderCamera { id, properties })) +} + pub(crate) fn parse_dict(i: &[u8]) -> IResult<&[u8], Dict> { let (i, n) = le_u32(i)?; diff --git a/src/resources/default_render_cameras.json b/src/resources/default_render_cameras.json new file mode 100644 index 0000000..dafc88c --- /dev/null +++ b/src/resources/default_render_cameras.json @@ -0,0 +1,112 @@ +[ + [ + 0, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 1, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 2, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 3, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 4, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 5, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 6, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 7, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 8, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ], + [ + 9, + { + "_angle": "0 0 0", + "_focus": "0 0 0", + "_fov": "45", + "_frustum": "0.414214", + "_mode": "pers", + "_radius": "0" + } + ] +] \ No newline at end of file diff --git a/src/resources/default_render_objects.json b/src/resources/default_render_objects.json new file mode 100644 index 0000000..35f6585 --- /dev/null +++ b/src/resources/default_render_objects.json @@ -0,0 +1,101 @@ +[ + { + "_type": "_bounce", + "_diffuse": "2", + "_energy": "3", + "_scatter": "5", + "_specular": "5" + }, + { + "_type": "_env", + "_mode": "0" + }, + { + "_type": "_inf", + "_angle": "50 50", + "_area": "0.07", + "_disk": "0", + "_i": "0.7", + "_k": "255 255 255" + }, + { + "_type": "_uni", + "_i": "0.7", + "_k": "255 255 255" + }, + { + "_type": "_ibl", + "_i": "1", + "_path": "HDR_041_Path_Env.hdr", + "_rot": "0" + }, + { + "_type": "_atm", + "_mie_d": "0.4", + "_mie_g": "0.85", + "_mie_k": "255 255 255", + "_o3_d": "0", + "_o3_k": "105 255 110", + "_ray_d": "0.4", + "_ray_k": "45 104 255" + }, + { + "_type": "_fog_uni", + "_d": "0", + "_g": "0", + "_k": "255 255 255" + }, + { + "_type": "_lens", + "_aperture": "0.25", + "_blade_n": "0", + "_blade_r": "0", + "_fov": "45", + "_proj": "0" + }, + { + "_type": "_film", + "_aces": "1", + "_expo": "1", + "_gam": "2.2", + "_vig": "0" + }, + { + "_type": "_bloom", + "_aspect": "0", + "_mix": "0.5", + "_scale": "0", + "_threshold": "1" + }, + { + "_type": "_ground", + "_color": "80 80 80", + "_hor": "0.1" + }, + { + "_type": "_bg", + "_color": "0 0 0" + }, + { + "_type": "_edge", + "_color": "0 0 0", + "_width": "0.2" + }, + { + "_type": "_grid", + "_color": "0 0 0", + "_display": "0", + "_spacing": "1", + "_width": "0.02" + }, + { + "_type": "_setting", + "_bg_a": "0", + "_bg_c": "0", + "_cell": "1", + "_edge": "0", + "_grid": "0", + "_ground": "1", + "_scale": "1 1 1" + } +] \ No newline at end of file diff --git a/src/resources/placeholder-with-render-objects.vox b/src/resources/placeholder-with-render-objects.vox new file mode 100644 index 0000000..cf14a10 Binary files /dev/null and b/src/resources/placeholder-with-render-objects.vox differ