// Copyright (C) 2014-2015 ChaosForge Ltd // http://chaosforge.org/ // // This file is part of Nova libraries. // For conditions of distribution and use, see copying.txt file in root folder. #include "nv/formats/nmd_loader.hh" #include "nv/io/std_stream.hh" #include "nv/stl/string.hh" #include "nv/interface/data_channel_access.hh" using namespace nv; bool nv::nmd_loader::load( stream& source ) { // TODO: proper error handling reset(); nmd_header root_header; source.read( &root_header, sizeof( root_header ), 1 ); skip_attributes( source, root_header.attributes ); for ( uint32 i = 0; i < root_header.elements; ++i ) { nmd_element_header element_header; source.read( &element_header, sizeof( element_header ), 1 ); skip_attributes( source, element_header.attributes ); switch ( element_header.type ) { case nmd_type::MESH : load_mesh( source, element_header ); break; case nmd_type::ANIMATION : load_animation( source, element_header ); break; case nmd_type::STRINGS : load_strings( source ); break; default: NV_ASSERT( false, "UNKNOWN NMD ELEMENT!" ); break; } } return true; } bool nv::nmd_loader::load_mesh( stream& source, const nmd_element_header& e ) { data_channel_set* mesh = data_channel_set_creator::create( e.children ); load_channel_set( source, mesh, e ); // m_mesh_names.push_back( e.name ); m_meshes.push_back( mesh ); return true; } data_channel_set* nv::nmd_loader::release_mesh_data( size_t index ) { data_channel_set* result = m_meshes[ index ]; // if ( m_strings ) data_channel_set_creator( result ).set_name( m_strings->get( m_mesh_names[ index ] ) ); m_meshes[ index ] = nullptr; return result; } mesh_data_pack* nv::nmd_loader::release_mesh_data_pack() { uint32 size = m_meshes.size(); data_channel_set* meshes = data_channel_set_creator::create_array( size, 0 ); for ( uint32 i = 0; i < size; ++i ) { meshes[i] = move( *m_meshes[i] ); delete m_meshes[i]; } m_meshes.clear(); return new mesh_data_pack( size, meshes, release_mesh_nodes_data() ); } void nv::nmd_loader::reset() { for ( auto mesh : m_meshes ) if ( mesh ) delete mesh; if ( m_strings ) delete m_strings; if ( m_node_data ) delete m_node_data; m_meshes.clear(); // m_mesh_names.clear(); // m_node_names.clear(); m_node_data = nullptr; m_node_array = nullptr; m_strings = nullptr; } void nv::nmd_loader::skip_attributes( stream& source, uint32 count ) { if ( count == 0 ) return; source.seek( count * sizeof( nmd_attribute ), origin::CUR ); } nv::nmd_loader::~nmd_loader() { reset(); } bool nv::nmd_loader::load_strings( stream& source ) { NV_ASSERT( m_strings == nullptr, "MULTIPLE STRING ENTRIES!" ); // TODO: load strings optionally m_strings = new string_table( source ); return true; } bool nv::nmd_loader::load_animation( stream& source, const nmd_element_header& e ) { NV_ASSERT( m_node_data == nullptr, "MULTIPLE NODE ENTRIES!" ); nmd_animation_header animation_header; source.read( &animation_header, sizeof( animation_header ), 1 ); m_node_array = new mesh_node_data[ e.children ]; for ( uint32 i = 0; i < e.children; ++i ) { nmd_element_header element_header; source.read( &element_header, sizeof( element_header ), 1 ); skip_attributes( source, element_header.attributes ); NV_ASSERT( element_header.type == nmd_type::NODE, "NODE expected!" ); // m_node_names.push_back( element_header.name ); load_node( source, &m_node_array[i], element_header ); } m_node_data = new mesh_nodes_data( e.name, e.children, m_node_array, animation_header.frame_rate, animation_header.duration, animation_header.flat ); return true; } bool nv::nmd_loader::load_node( stream& source, mesh_node_data* data, const nmd_element_header& e ) { data->name = e.name; data->parent_id = e.parent_id; data->transform = e.transform; data->data = nullptr; if ( e.children > 0 ) { data->data = data_channel_set_creator::create( e.children ); load_channel_set( source, data->data, e ); } return true; } bool nv::nmd_loader::load_channel( stream& source, data_channel_set* channel_set ) { data_channel_set_creator kaccess( channel_set ); nmd_channel_header cheader; source.read( &cheader, sizeof( cheader ), 1 ); raw_data_channel_access channel( kaccess.add_channel( cheader.format, cheader.count ) ); source.read( channel.raw_data(), channel.element_size(), channel.size() ); return true; } bool nv::nmd_loader::load_channel_set( stream& source, data_channel_set* channel_set, const nmd_element_header& e ) { data_channel_set_creator kaccess( channel_set ); for ( uint32 c = 0; c < e.children; ++c ) { load_channel( source, channel_set ); } return true; } mesh_nodes_data* nv::nmd_loader::release_mesh_nodes_data( size_t ) { if ( m_node_data ) { // if ( m_strings ) // { // for ( uint32 i = 0; i < m_node_data->get_count(); ++i ) // { // m_node_array[i].name = m_strings->get( m_node_names[i] ); // m_node_array[i].name_hash = hash_string< uint64 >( m_strings->get( m_node_names[i] ) ); // } // } mesh_nodes_data* result = m_node_data; m_node_data = nullptr; m_node_array = nullptr; return result; } return nullptr; } // ---------------------------------------------------------------- // nmd format dump // HACK : TEMPORARY - will go to it's own file, probably nmd_io static void nmd_dump_channel( const raw_data_channel* channel , stream& stream_out ) { nmd_channel_header sheader; sheader.format = channel->descriptor(); sheader.count = channel->size(); stream_out.write( &sheader, sizeof( sheader ), 1 ); stream_out.write( channel->raw_data(), channel->element_size(), channel->size() ); } static void nmd_dump_channel_set( const data_channel_set* channel_set, stream& stream_out ) { for ( auto& channel : *channel_set ) { nmd_dump_channel( &channel, stream_out ); } } static void nmd_dump_node( const mesh_node_data* node, stream& stream_out ) { uint32 chan_size = 0; uint32 chan_count = ( node->data ? node->data->size() : 0 ); for ( uint32 c = 0; c < chan_count; ++c ) { chan_size += sizeof( nmd_channel_header ); chan_size += node->data->get_channel( c )->raw_size(); } nmd_element_header eheader; eheader.type = nmd_type::NODE; // eheader.name = strings->insert( node->name ); eheader.children = static_cast( chan_count ); eheader.size = chan_size; eheader.name = node->name; eheader.parent_id = node->parent_id; eheader.transform = node->transform; eheader.attributes = 0; stream_out.write( &eheader, sizeof( eheader ), 1 ); if ( node->data ) nmd_dump_channel_set( node->data, stream_out ); } void nv::nmd_dump_header( stream& stream_out, uint32 elements, uint64 name ) { nmd_header header; header.id = four_cc<'n', 'm', 'f', '1'>::value; header.elements = elements; // +1 string array header.name = name; header.version = 1; header.attributes = 0; stream_out.write( &header, sizeof( header ), 1 ); } void nv::nmd_dump_mesh( stream& stream_out, const data_channel_set& mesh ) { uint32 size = 0; for ( auto& chan : mesh ) { size += sizeof( nmd_channel_header ); size += chan.raw_size(); } nmd_element_header eheader; eheader.type = nmd_type::MESH; eheader.children = static_cast( mesh.size() ); eheader.size = size; int uncomment; // eheader.name = mesh.get_name(); // eheader.transform = mesh.get_transform(); // eheader.parent_id = mesh.get_parent_id(); eheader.name = 0; eheader.transform = mat4(); eheader.parent_id = -1; eheader.attributes = 0; stream_out.write( &eheader, sizeof( eheader ), 1 ); nmd_dump_channel_set( &mesh, stream_out ); } void nv::nmd_dump_nodes( stream& stream_out, const mesh_nodes_data& nodes ) { uint32 total = sizeof( nmd_animation_header ); for ( uint32 i = 0; i < nodes.get_count(); ++i ) { const mesh_node_data* node = nodes.get_node( i ); total += sizeof( nmd_element_header ); if ( node->data ) for ( uint32 c = 0; c < node->data->size(); ++c ) { total += sizeof( nmd_channel_header ); total += node->data->get_channel( c )->raw_size(); } } nmd_element_header header; header.type = nmd_type::ANIMATION; header.children = static_cast( nodes.get_count() ); header.size = total; header.name = nodes.get_name(); header.transform = mat4(); header.parent_id = -1; header.attributes = 0; stream_out.write( &header, sizeof( header ), 1 ); nmd_animation_header aheader; aheader.frame_rate = nodes.get_frame_rate(); aheader.duration = nodes.get_duration(); aheader.flat = nodes.is_flat(); stream_out.write( &aheader, sizeof( aheader ), 1 ); for ( uint32 i = 0; i < nodes.get_count(); ++i ) { nmd_dump_node( nodes.get_node( i ), stream_out ); } } void nv::nmd_dump_strings( stream& stream_out, const string_table& strings ) { nmd_element_header sheader; sheader.type = nv::nmd_type::STRINGS; sheader.children = 0; sheader.size = strings.dump_size(); sheader.name = 0; sheader.parent_id = -1; sheader.attributes = 0; stream_out.write( &sheader, sizeof( sheader ), 1 ); strings.dump( stream_out ); } void nv::nmd_dump( stream& stream_out, const mesh_data_pack* model, const string_table* strings, uint64 name ) { uint32 elements = ( strings ? 1 : 0 ) // +1 string array + model->get_count() // meshes + ( model->get_node_count() > 0 ? 1 : 0 ); // nodes nmd_dump_header( stream_out, elements, name ); for ( uint32 i = 0; i < model->get_count(); ++i ) { nmd_dump_mesh( stream_out, *model->get_mesh( i ) ); } if ( model->get_node_count() > 0 ) { nmd_dump_nodes( stream_out, *model->get_nodes() ); } if ( strings ) { nmd_dump_strings( stream_out, *strings ); } }