// 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/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::BONES : load_bones( source, element_header ); break; case nmd_type::STRINGS : load_strings( source ); break; case nmd_type::POSES : load_poses( source, element_header ); 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_set( e.children ); data_node_info info; load_channel_set( source, mesh, info, e ); m_infos.push_back( info ); m_meshes.push_back( mesh ); return true; } data_channel_set* nv::nmd_loader::release_mesh_data( size_t index, data_node_info& info ) { data_channel_set* result = m_meshes[ index ]; info = m_infos[ index ]; m_meshes[ index ] = nullptr; return result; } void nv::nmd_loader::reset() { for ( auto mesh : m_meshes ) if ( mesh ) delete mesh; if ( m_bone_data ) delete m_bone_data; if ( m_pose_data_set ) delete m_pose_data_set; m_meshes.clear(); m_bone_data = 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 ) { if ( !m_strings ) return true; // TODO: load strings optionally string_table* strings = new string_table( source ); m_strings->insert( strings ); delete strings; return true; } bool nv::nmd_loader::load_bones( stream& source, const nmd_element_header& e ) { NV_ASSERT( m_bone_data == nullptr, "MULTIPLE NODE ENTRIES!" ); nmd_animation_header animation_header; source.read( &animation_header, sizeof( animation_header ), 1 ); m_bone_data = new data_node_list( e.name ); 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!" ); data_channel_set* set = data_channel_set_creator::create_set( element_header.children ); data_node_info info; load_channel_set( source, set, info, element_header ); m_bone_data->append( info ); delete set; } 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, data_node_info& info, 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 ); } info.name = e.name; info.parent_id = e.parent_id; info.transform = e.transform; return true; } bool nv::nmd_loader::load_poses( stream& source, const nmd_element_header& e ) { if ( m_pose_data_set == nullptr ) { NV_ASSERT_ALWAYS( m_bone_data, "POSES WITHOUT BONES!" ); m_pose_data_set = new pose_data_set; m_pose_data_set->initialize( *m_bone_data ); } uint32 count = e.children; shash64 name = e.name; auto& set = m_pose_data_set->m_sets[ name ]; NV_ASSERT_ALWAYS( !set.name, "SET REWRITE!" ); set.name = name; set.count = count; set.start = m_pose_data_set->size(); for ( uint32 i = 0; i < count; ++i ) { uint32 length = 0; source.read( &length, sizeof( length ), 1 ); m_pose_data_set->m_data.push_back( skeleton_transforms() ); auto& data = m_pose_data_set->m_data.back().m_transforms; data.resize( length ); source.read( &data[0], length * sizeof( transform ), 1 ); } return true; } mesh_nodes_data* nv::nmd_loader::release_mesh_nodes_data( size_t ) { return nullptr; } data_node_list* nv::nmd_loader::release_data_node_list( size_t ) { data_node_list* result = m_bone_data; m_bone_data = nullptr; return result; } bool nv::nmd_loader::is_animated( size_t /*= 0 */ ) { return m_pose_data_set != nullptr; } // ---------------------------------------------------------------- // nmd format dump // HACK : TEMPORARY - will go to it's own file, probably nmd_io 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_element( stream& stream_out, const data_channel_set& data, const data_node_info& info, nmd_type type ) { uint32 size = 0; for ( auto& chan : data ) { size += sizeof( nmd_channel_header ); size += chan.raw_size(); } nmd_element_header eheader; eheader.type = type; eheader.children = static_cast( data.size() ); eheader.size = size; eheader.name = info.name; eheader.transform = info.transform; eheader.parent_id = info.parent_id; eheader.attributes = 0; stream_out.write( &eheader, sizeof( eheader ), 1 ); for ( auto& channel : data ) { nmd_channel_header cheader; cheader.format = channel.descriptor(); cheader.count = channel.size(); stream_out.write( &cheader, sizeof( cheader ), 1 ); stream_out.write( channel.raw_data(), channel.element_size(), channel.size() ); } } void nv::nmd_dump_bones( stream& stream_out, const data_node_list& nodes ) { uint32 total = sizeof( nmd_animation_header ); for ( auto node : nodes ) { total += sizeof( nmd_element_header ); } nmd_element_header header; header.type = nmd_type::BONES; header.children = static_cast( nodes.size() ); 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 = 0; aheader.frame_count = 0; aheader.unused = false; stream_out.write( &aheader, sizeof( aheader ), 1 ); for ( auto node : nodes ) { nmd_element_header eheader; eheader.type = nv::nmd_type::NODE; eheader.children = 0; eheader.size = 0; eheader.name = node.name; eheader.transform = node.transform; eheader.parent_id = node.parent_id; eheader.attributes = 0; stream_out.write( &eheader, sizeof( eheader ), 1 ); } } void nv::nmd_dump_poses( stream& stream_out, const array_view< skeleton_transforms* > poses, shash64 name ) { nmd_element_header pheader; pheader.type = nv::nmd_type::POSES; pheader.children = uint16( poses.size() ); pheader.size = sizeof( transform ) * poses.size() * ( poses.size() > 0 ? poses[0]->size() : 0 ) + sizeof( uint32 ) * poses.size(); pheader.name = name; pheader.parent_id = -1; pheader.attributes = 0; stream_out.write( &pheader, sizeof( pheader ), 1 ); for ( auto pose : poses ) { uint32 count = pose->size(); stream_out.write( &count, sizeof( count ), 1 ); stream_out.write( pose->xforms(), sizeof( transform ) * count, 1 ); } } void nv::nmd_dump_pose( stream& stream_out, const skeleton_transforms& pose, shash64 name ) { nmd_element_header pheader; pheader.type = nv::nmd_type::POSES; pheader.children = 1; pheader.size = sizeof( transform ) * pose.size(); pheader.name = name; pheader.parent_id = -1; pheader.attributes = 0; stream_out.write( &pheader, sizeof( pheader ), 1 ); uint32 count = pose.size(); stream_out.write( &count, sizeof( count ), 1 ); stream_out.write( pose.xforms(), sizeof( transform ) * count, 1 ); } 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 = shash64(); 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, array_view< data_channel_set* > meshes, array_view< data_node_info > infos, const nv::data_node_list* nodes, const string_table* strings /*= nullptr*/, uint64 name /*= 0 */ ) { uint32 elements = ( strings ? 1 : 0 ) // +1 string array + meshes.size() // meshes + ( nodes && nodes->size() > 0 ? 1 : 0 ); // nodes nmd_dump_header( stream_out, elements, name ); for ( uint32 i = 0; i < meshes.size(); ++i ) { NV_ASSERT( meshes[i], "mesh is null!" ); nmd_dump_element( stream_out, *meshes[i], infos[i], nv::nmd_type::MESH ); } if ( nodes && nodes->size() > 0 ) { nmd_dump_bones( stream_out, *nodes ); } if ( strings ) { nmd_dump_strings( stream_out, *strings ); } }