fyrox-impl 1.0.1

Feature-rich, easy-to-use, 2D/3D game engine with a scene editor. Like Godot, but in Rust.
Documentation
// 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<R>(type_code: u8, file: &mut R) -> Result<FbxAttribute, FbxError>
where
    R: Read,
{
    match type_code {
        b'f' | b'F' => Ok(FbxAttribute::Float(file.read_f32::<LittleEndian>()?)),
        b'd' | b'D' => Ok(FbxAttribute::Double(file.read_f64::<LittleEndian>()?)),
        b'l' | b'L' => Ok(FbxAttribute::Long(file.read_i64::<LittleEndian>()?)),
        b'i' | b'I' => Ok(FbxAttribute::Integer(file.read_i32::<LittleEndian>()?)),
        b'Y' => Ok(FbxAttribute::Integer(i32::from(
            file.read_i16::<LittleEndian>()?,
        ))),
        b'b' | b'C' => Ok(FbxAttribute::Bool(file.read_u8()? != 0)),
        _ => Err(FbxError::UnknownAttributeType(type_code)),
    }
}

fn read_array<R>(type_code: u8, file: &mut R) -> Result<Vec<FbxAttribute>, FbxError>
where
    R: Read,
{
    let length = file.read_u32::<LittleEndian>()? as usize;
    let encoding = file.read_u32::<LittleEndian>()?;
    let compressed_length = file.read_u32::<LittleEndian>()? 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<R>(file: &mut R) -> Result<FbxAttribute, FbxError>
where
    R: Read,
{
    let length = file.read_u32::<LittleEndian>()? 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<R>(
    file: &mut R,
    pool: &mut Pool<FbxNode>,
    version: i32,
) -> Result<Handle<FbxNode>, FbxError>
where
    R: Read + Seek,
{
    let end_offset = if version < VERSION_7500 {
        u64::from(file.read_u32::<LittleEndian>()?)
    } else {
        file.read_u64::<LittleEndian>()?
    };
    if end_offset == 0 {
        // Footer found. We're done.
        return Ok(Handle::NONE);
    }

    let num_attrib = if version < VERSION_7500 {
        file.read_u32::<LittleEndian>()? as usize
    } else {
        file.read_u64::<LittleEndian>()? as usize
    };

    let _attrib_list_len = if version < VERSION_7500 {
        file.read_u32::<LittleEndian>()? as u64
    } else {
        file.read_u64::<LittleEndian>()?
    };

    // 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::<LittleEndian>()?);
                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<R>(file: &mut R) -> Result<FbxDocument, FbxError>
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::<LittleEndian>()? 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,
    })
}