// Copyright (C) 2014 ChaosForge / Kornel Kisielewicz // http://chaosforge.org/ // // This file is part of NV Libraries. // For conditions of distribution and use, see copyright notice in nv.hh #include "nv/formats/nmd_loader.hh" #include "nv/io/std_stream.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 ); for ( uint32 i = 0; i < root_header.elements; ++i ) { nmd_element_header element_header; source.read( &element_header, sizeof( element_header ), 1 ); switch ( element_header.type ) { case nmd_type::MESH : load_mesh( source, element_header.children ); break; case nmd_type::ANIMATION : load_animation( source, element_header.children ); break; case nmd_type::BONE_ARRAY : load_bones( source, element_header.children ); break; case nmd_type::STRING_TABLE : load_strings( source ); break; default: NV_ASSERT( false, "UNKNOWN NMD ELEMENT!" ); break; } } return true; } bool nv::nmd_loader::load_mesh( stream& source, uint32 children ) { mesh_data* mesh = new mesh_data(); for ( uint32 s = 0; s < children; ++s ) { nmd_element_header element_header; source.read( &element_header, sizeof( element_header ), 1 ); NV_ASSERT( element_header.type == nmd_type::STREAM, "STREAM expected!" ); nmd_stream_header stream_header; source.read( &stream_header, sizeof( stream_header ), 1 ); mesh_raw_channel* channel = mesh_raw_channel::create( stream_header.format, stream_header.count ); source.read( channel->data, stream_header.format.size, stream_header.count ); mesh->add_channel( channel ); } m_meshes.push_back( mesh ); return true; } mesh_data* nv::nmd_loader::release_mesh_data( size_t index ) { mesh_data* result = m_meshes[ index ]; m_meshes[ index ] = nullptr; return result; } void nv::nmd_loader::reset() { for ( auto mesh : m_meshes ) if ( mesh ) delete mesh; if ( m_animation ) delete m_animation; if ( m_strings ) delete m_strings; if ( m_bone_data ) delete m_bone_data; m_meshes.clear(); m_animation = nullptr; m_bone_data = nullptr; m_strings = nullptr; } nv::nmd_loader::~nmd_loader() { reset(); } bool nv::nmd_loader::load_strings( stream& source ) { NV_ASSERT( m_strings == nullptr, "MULTIPLE STRING ENTRIES!" ); m_strings = new string_table( &source ); return true; } bool nv::nmd_loader::load_bones( stream& source, uint32 children ) { NV_ASSERT( m_bone_data == nullptr, "MULTIPLE BONE ENTRIES!" ); m_bone_data = new nmd_bone_data; m_bone_data->bones = new nmd_bone[ children ]; m_bone_data->count = (uint16)children; source.read( m_bone_data->bones, sizeof( nmd_bone ), children ); return true; } nmd_animation* nv::nmd_loader::release_animation() { nmd_animation* result = m_animation; m_animation = nullptr; return result; } nmd_bone_data* nv::nmd_loader::release_bone_data() { nmd_bone_data* result = m_bone_data; m_bone_data = nullptr; return result; } string_table* nv::nmd_loader::release_string_table() { string_table* result = m_strings; m_strings = nullptr; return result; } bool nv::nmd_loader::load_animation( stream& source, uint32 children ) { NV_ASSERT( m_animation == nullptr, "MULTIPLE ANIMATION ENTRIES!" ); nmd_animation_header header; source.read( &header, sizeof( header ), 1 ); m_animation = new nmd_animation; m_animation->fps = header.fps; m_animation->duration = header.duration; m_animation->flat = header.flat; m_animation->node_count = (uint16)children; m_animation->nodes = new nmd_node[ children ]; for ( uint32 i = 0; i < children; ++i ) { nmd_element_header element_header; source.read( &element_header, sizeof( element_header ), 1 ); NV_ASSERT( element_header.type == nmd_type::ANIMATION_NODE, "ANIMATION_NODE expected!" ); uint16 ch_count = element_header.children; nmd_animation_node_header node_header; source.read( &node_header, sizeof( node_header ), 1 ); m_animation->nodes[i].name = node_header.name; m_animation->nodes[i].parent_id = node_header.parent_id; m_animation->nodes[i].transform = node_header.transform; m_animation->nodes[i].channel_count = ch_count; m_animation->nodes[i].channels = nullptr; if ( ch_count > 0 ) { m_animation->nodes[i].channels = new key_raw_channel* [ch_count]; for ( uint32 c = 0; c < ch_count; ++c ) { source.read( &element_header, sizeof( element_header ), 1 ); NV_ASSERT( element_header.type == nmd_type::ANIMATION_CHANNEL, "ANIMATION_CHANNEL expected!" ); nv::nmd_animation_channel_header cheader; source.read( &cheader, sizeof( cheader ), 1 ); key_raw_channel* channel = key_raw_channel::create( cheader.format, cheader.count ); source.read( channel->data, channel->desc.size, channel->count ); m_animation->nodes[i].channels[c] = channel; } } } return true; } // TEMPORARY nv::nmd_temp_animation::nmd_temp_animation( nmd_loader* loader ) { m_animation = loader->release_animation(); m_strings = loader->release_string_table(); for ( uint32 n = 0; n < m_animation->node_count; ++n ) { nmd_node& node = m_animation->nodes[n]; key_animation_data* keys = nullptr; if ( node.channel_count > 1 ) { keys = new nv::key_vectors_prs( node.channels[0], node.channels[1], node.channels[2] ); } else if ( node.channel_count == 1 ) { keys = new nv::transform_vector( node.channels[0] ); node.channels[0] = nullptr; node.channel_count = 0; } m_data.push_back( keys ); } if ( !m_animation->flat ) { m_children.resize( m_animation->node_count ); for ( nv::uint32 n = 0; n < m_animation->node_count; ++n ) { const nmd_node& node = m_animation->nodes[n]; if ( node.parent_id != -1 ) { m_children[ node.parent_id ].push_back( n ); } } } m_bone_ids.resize( m_animation->node_count ); } nv::nmd_temp_animation::~nmd_temp_animation() { for ( auto node : m_data ) delete node; delete m_animation; delete m_strings; } void nv::nmd_temp_animation::prepare( const nmd_temp_model* model ) { m_offsets = model->m_bone_offsets.data(); for ( uint32 n = 0; n < m_animation->node_count; ++n ) { const nmd_node& node = m_animation->nodes[n]; sint16 bone_id = -1; auto bi = model->m_bone_names.find( m_strings->get( node.name ) ); if ( bi != model->m_bone_names.end() ) { bone_id = bi->second; } m_bone_ids[n] = bone_id; } } void nv::nmd_temp_animation::animate( mat4* data, uint32 time ) { float tick_time = ( time / 1000.0f ) * m_animation->fps; float anim_time = fmodf( tick_time, m_animation->duration ); if ( !m_animation->flat ) { animate_rec( data, anim_time, 0, mat4() ); return; } for ( uint32 n = 0; n < m_animation->node_count; ++n ) if ( m_bone_ids[n] >= 0 ) { const nmd_node* node = &m_animation->nodes[ n ]; nv::mat4 node_mat( node->transform ); if ( m_data[n] && !m_data[n]->empty() ) { node_mat = m_data[n]->get_matrix( anim_time ); } sint16 bone_id = m_bone_ids[n]; data[ bone_id ] = node_mat * m_offsets[ bone_id ]; } } void nv::nmd_temp_animation::animate_rec( mat4* data, float time, uint32 node_id, const mat4& parent_mat ) { // TODO: fix transforms, which are now embedded, // see note in assimp_loader.cc:load_node const nmd_node* node = &m_animation->nodes[ node_id ]; mat4 node_mat( node->transform ); if ( m_data[ node_id ] && !m_data[ node_id ]->empty() ) { node_mat = m_data[ node_id ]->get_matrix( time ); } mat4 global_mat = parent_mat * node_mat; sint16 bone_id = m_bone_ids[ node_id ]; if ( bone_id >= 0 ) { data[ bone_id ] = global_mat * m_offsets[ bone_id ]; } for ( auto child : m_children[ node_id ] ) { animate_rec( data, time, child, global_mat ); } } nv::nmd_temp_model::nmd_temp_model( nmd_loader* loader ) { for ( unsigned m = 0; m < loader->get_mesh_count(); ++m ) { m_mesh_data.push_back(loader->release_mesh_data(m)); } nmd_bone_data* bone_data = loader->release_bone_data(); string_table* strings = loader->release_string_table(); for ( nv::uint16 bi = 0; bi < bone_data->count; ++bi ) { m_bone_names[ strings->get( bone_data->bones[bi].name ) ] = bi; m_bone_offsets.push_back( bone_data->bones[bi].offset ); } delete bone_data; delete strings; } nv::nmd_temp_model::~nmd_temp_model() { for ( unsigned m = 0; m < m_mesh_data.size(); ++m ) { delete m_mesh_data[m]; } }