// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. use crate::core::byteorder::{LittleEndian, ReadBytesExt}; use crate::{ core::pool::{Handle, Pool}, resource::fbx::{ document::{attribute::FbxAttribute, FbxDocument, FbxNode, FbxNodeContainer}, error::FbxError, }, }; use std::io::{Cursor, Read, Seek, SeekFrom}; fn read_attribute(type_code: u8, file: &mut R) -> Result where R: Read, { match type_code { b'f' | b'F' => Ok(FbxAttribute::Float(file.read_f32::()?)), b'd' | b'D' => Ok(FbxAttribute::Double(file.read_f64::()?)), b'l' | b'L' => Ok(FbxAttribute::Long(file.read_i64::()?)), b'i' | b'I' => Ok(FbxAttribute::Integer(file.read_i32::()?)), b'Y' => Ok(FbxAttribute::Integer(i32::from( file.read_i16::()?, ))), b'b' | b'C' => Ok(FbxAttribute::Bool(file.read_u8()? != 0)), _ => Err(FbxError::UnknownAttributeType(type_code)), } } fn read_array(type_code: u8, file: &mut R) -> Result, FbxError> where R: Read, { let length = file.read_u32::()? as usize; let encoding = file.read_u32::()?; let compressed_length = file.read_u32::()? as usize; let mut array = Vec::new(); if encoding == 0 { for _ in 0..length { array.push(read_attribute(type_code, file)?); } } else { let mut compressed = vec![Default::default(); compressed_length]; file.read_exact(compressed.as_mut_slice())?; let decompressed = inflate::inflate_bytes_zlib(&compressed)?; let mut cursor = Cursor::new(decompressed); for _ in 0..length { array.push(read_attribute(type_code, &mut cursor)?); } } Ok(array) } fn read_string(file: &mut R) -> Result where R: Read, { let length = file.read_u32::()? as usize; let mut raw_string = vec![Default::default(); length]; file.read_exact(raw_string.as_mut_slice())?; // Find null terminator. It is required because for some reason some strings // have additional data after null terminator like this: Omni004\x0\x1Model, but // length still more than position of null terminator. if let Some(null_terminator_pos) = raw_string.iter().position(|c| *c == 0) { raw_string.truncate(null_terminator_pos); } let string = String::from_utf8(raw_string)?; Ok(FbxAttribute::String(string)) } const VERSION_7500: i32 = 7500; const VERSION_7500_NULLREC_SIZE: usize = 25; // in bytes const NORMAL_NULLREC_SIZE: usize = 13; // in bytes /// Read binary FBX DOM using this specification: /// https://code.blender.org/2013/08/fbx-binary-file-format-specification/ /// In case of success returns Ok(valid_handle), in case if no more nodes /// are present returns Ok(none_handle), in case of error returns some FbxError. fn read_binary_node( file: &mut R, pool: &mut Pool, version: i32, ) -> Result, FbxError> where R: Read + Seek, { let end_offset = if version < VERSION_7500 { u64::from(file.read_u32::()?) } else { file.read_u64::()? }; if end_offset == 0 { // Footer found. We're done. return Ok(Handle::NONE); } let num_attrib = if version < VERSION_7500 { file.read_u32::()? as usize } else { file.read_u64::()? as usize }; let _attrib_list_len = if version < VERSION_7500 { file.read_u32::()? as u64 } else { file.read_u64::()? }; // Read name. let name_len = file.read_u8()? as usize; let mut raw_name = vec![Default::default(); name_len]; file.read_exact(raw_name.as_mut_slice())?; let node = FbxNode { name: String::from_utf8(raw_name)?, ..FbxNode::default() }; let node_handle = pool.spawn(node); // Read attributes. for _ in 0..num_attrib { let type_code = file.read_u8()?; match type_code { b'C' | b'Y' | b'I' | b'F' | b'D' | b'L' => { let node = pool.borrow_mut(node_handle); node.attributes.push(read_attribute(type_code, file)?); } b'f' | b'd' | b'l' | b'i' | b'b' => { let a = FbxNode { name: String::from("a"), attributes: read_array(type_code, file)?, parent: node_handle, ..FbxNode::default() }; let a_handle = pool.spawn(a); let node = pool.borrow_mut(node_handle); node.children.push(a_handle); } b'S' => pool .borrow_mut(node_handle) .attributes .push(read_string(file)?), b'R' => { let length = i64::from(file.read_u32::()?); let mut data = vec![0; length as usize]; file.read_exact(&mut data)?; pool.borrow_mut(node_handle) .attributes .push(FbxAttribute::RawData(data)); } _ => (), } } if file.stream_position()? < end_offset { let nullrec_size = if version < VERSION_7500 { NORMAL_NULLREC_SIZE } else { VERSION_7500_NULLREC_SIZE }; let null_record_position = end_offset - nullrec_size as u64; while file.stream_position()? < null_record_position { let child_handle = read_binary_node(file, pool, version)?; if child_handle.is_none() { return Ok(child_handle); } pool.borrow_mut(child_handle).parent = node_handle; pool.borrow_mut(node_handle).children.push(child_handle); } // Check if we have a null-record if version < VERSION_7500 { let mut null_record = [0; NORMAL_NULLREC_SIZE]; file.read_exact(&mut null_record)?; if !null_record.iter().all(|i| *i == 0) { return Err(FbxError::InvalidNullRecord); } } else { let mut null_record = [0; VERSION_7500_NULLREC_SIZE]; file.read_exact(&mut null_record)?; if !null_record.iter().all(|i| *i == 0) { return Err(FbxError::InvalidNullRecord); } } } Ok(node_handle) } pub fn read_binary(file: &mut R) -> Result where R: Read + Seek, { let total_length = file.seek(SeekFrom::End(0))?; file.rewind()?; // Ignore all stuff until version. let mut temp = [0; 23]; file.read_exact(&mut temp)?; // Verify version. let version = file.read_u32::()? as i32; // Anything else should be supported. if version < 7100 { return Err(FbxError::UnsupportedVersion(version)); } let mut nodes = Pool::new(); let root = FbxNode { name: String::from("__ROOT__"), ..FbxNode::default() }; let root_handle = nodes.spawn(root); // FBX document can have multiple root nodes, so we must read the file // until the end. while file.stream_position()? < total_length { let root_child = read_binary_node(file, &mut nodes, version)?; if root_child.is_none() { break; } nodes.borrow_mut(root_child).parent = root_handle; nodes.borrow_mut(root_handle).children.push(root_child); } Ok(FbxDocument { nodes: FbxNodeContainer { nodes }, root: root_handle, }) }