@@ -6,10 +6,17 @@
# include <godot_cpp/variant/packed_vector3_array.hpp>
# include <godot_cpp/variant/packed_vector2_array.hpp>
# include <godot_cpp/variant/packed_int32_array.hpp>
# include <godot_cpp/variant/packed_float32_array.hpp>
# include <godot_cpp/variant/packed_vector4_array.hpp>
# include <godot_cpp/variant/color.hpp>
# include <fstream>
# include <cstring>
# include <cstdio>
# include <cmath>
# include <algorithm>
# include <cstdint>
# include <cctype>
using namespace godot ;
@@ -54,6 +61,38 @@ struct M2Header {
uint32_t ofsBoneCombos ;
uint32_t nTextureCombos ; // offset 128
uint32_t ofsTextureCombos ; // offset 132
uint32_t nTextureCoordCombos ;
uint32_t ofsTextureCoordCombos ;
uint32_t nTextureWeightCombos ;
uint32_t ofsTextureWeightCombos ;
uint32_t nTextureTransformCombos ;
uint32_t ofsTextureTransformCombos ;
float bounds [ 7 ] ;
float collisionBounds [ 7 ] ;
uint32_t nCollisionIndices ;
uint32_t ofsCollisionIndices ;
uint32_t nCollisionPositions ;
uint32_t ofsCollisionPositions ;
uint32_t nCollisionFaceNormals ;
uint32_t ofsCollisionFaceNormals ;
uint32_t nAttachments ;
uint32_t ofsAttachments ;
uint32_t nAttachmentLookup ;
uint32_t ofsAttachmentLookup ;
uint32_t nEvents ;
uint32_t ofsEvents ;
uint32_t nLights ;
uint32_t ofsLights ;
uint32_t nCameras ;
uint32_t ofsCameras ;
uint32_t nCameraLookup ;
uint32_t ofsCameraLookup ;
uint32_t nRibbonEmitters ;
uint32_t ofsRibbonEmitters ;
uint32_t nParticleEmitters ;
uint32_t ofsParticleEmitters ;
uint32_t nTextureCombinerCombos ;
uint32_t ofsTextureCombinerCombos ;
} ;
// Total: 34 × 4 = 136 bytes
@@ -79,6 +118,70 @@ struct M2Material {
uint16_t blendingMode ; // 0=opaque 1=alpha_key 2=alpha_blend
} ;
struct M2ArrayRef {
uint32_t count ;
uint32_t offset ;
} ;
struct M2Track {
uint16_t interpolationType ;
uint16_t globalSequence ;
M2ArrayRef timestamps ;
M2ArrayRef values ;
} ;
struct M2Color {
M2Track colorTrack ;
M2Track alphaTrack ;
} ;
struct M2TextureTransform {
M2Track translationTrack ;
M2Track rotationTrack ;
M2Track scaleTrack ;
} ;
struct M2TextureWeight {
M2Track weightTrack ;
} ;
struct M2CompBone {
uint32_t boneId ;
uint32_t flags ;
uint16_t parentIndex ;
uint16_t submeshId ;
uint32_t unknown ;
M2Track translation ;
M2Track rotation ;
M2Track scale ;
float pivot [ 3 ] ;
} ;
struct M2Sequence {
uint16_t id ;
uint16_t variationIndex ;
uint32_t duration ;
float moveSpeed ;
uint32_t flags ;
uint32_t frequency ;
uint32_t replayMin ;
uint32_t replayMax ;
uint32_t blendTime ;
float minBounds [ 3 ] ;
float maxBounds [ 3 ] ;
float boundsRadius ;
uint16_t nextAnimation ;
uint16_t aliasNext ;
} ;
struct M2Float3 {
float v [ 3 ] ;
} ;
struct M2CompQuat {
uint16_t v [ 4 ] ;
} ;
struct SkinHeader {
uint32_t magic ; // 'SKIN' LE = 0x4E494B53
uint32_t nIndices ;
@@ -164,10 +267,203 @@ static const T *safe_array(const std::vector<uint8_t> &buf, uint32_t ofs, uint32
return reinterpret_cast < const T * > ( buf . data ( ) + ofs ) ;
}
static Vector3 wow_vec3_to_godot ( const float v [ 3 ] ) {
return Vector3 ( v [ 0 ] , v [ 2 ] , - v [ 1 ] ) ;
}
static Vector3 wow_vec3_to_godot ( float x , float y , float z ) {
return Vector3 ( x , z , - y ) ;
}
static Vector4 normalize_quat ( float x , float y , float z , float w ) {
float len = std : : sqrt ( x * x + y * y + z * z + w * w ) ;
if ( len < = 0.000001f ) {
return Vector4 ( 0.0f , 0.0f , 0.0f , 1.0f ) ;
}
float inv = 1.0f / len ;
return Vector4 ( x * inv , y * inv , z * inv , w * inv ) ;
}
static Vector4 wow_quat_to_godot ( float x , float y , float z , float w ) {
// WoW model space is converted to Godot with Z/Y axis swap and Y handedness flip.
return normalize_quat ( x , z , - y , w ) ;
}
static Vector4 decode_comp_quat ( const uint16_t q [ 4 ] ) {
float x = ( ( float ) q [ 0 ] - 32768.0f ) / 32767.0f ;
float y = ( ( float ) q [ 1 ] - 32768.0f ) / 32767.0f ;
float z = ( ( float ) q [ 2 ] - 32768.0f ) / 32767.0f ;
float w = ( ( float ) q [ 3 ] - 32768.0f ) / 32767.0f ;
return wow_quat_to_godot ( x , y , z , w ) ;
}
template < typename T >
static const T * track_sequence_array ( const std : : vector < uint8_t > & buf , const M2ArrayRef & outer , uint32_t sequence_index , uint32_t & count_out ) {
count_out = 0 ;
if ( sequence_index > = outer . count ) return nullptr ;
const M2ArrayRef * inner_arrays = safe_array < M2ArrayRef > ( buf , outer . offset , outer . count ) ;
if ( ! inner_arrays ) return nullptr ;
const M2ArrayRef & inner = inner_arrays [ sequence_index ] ;
const T * items = safe_array < T > ( buf , inner . offset , inner . count ) ;
if ( ! items ) return nullptr ;
count_out = inner . count ;
return items ;
}
static uint32_t track_sequence_key_count ( const std : : vector < uint8_t > & buf , const M2Track & track , uint32_t sequence_index ) {
if ( sequence_index > = track . values . count ) return 0 ;
const M2ArrayRef * inner_arrays = safe_array < M2ArrayRef > ( buf , track . values . offset , track . values . count ) ;
if ( ! inner_arrays ) return 0 ;
return inner_arrays [ sequence_index ] . count ;
}
template < typename T >
static const T * first_track_value ( const std : : vector < uint8_t > & buf , const M2Track & track ) {
for ( uint32_t i = 0 ; i < track . values . count ; + + i ) {
uint32_t value_count = 0 ;
const T * values = track_sequence_array < T > ( buf , track . values , i , value_count ) ;
if ( values & & value_count > 0 ) {
return values ;
}
}
return nullptr ;
}
static float fixed16_to_float ( int16_t value ) {
return std : : clamp ( ( float ) value / 32767.0f , 0.0f , 1.0f ) ;
}
static Vector2 first_vec3_track_xy_speed ( const std : : vector < uint8_t > & buf , const M2Track & track ) {
for ( uint32_t i = 0 ; i < track . values . count & & i < track . timestamps . count ; + + i ) {
uint32_t time_count = 0 ;
uint32_t value_count = 0 ;
const uint32_t * times = track_sequence_array < uint32_t > ( buf , track . timestamps , i , time_count ) ;
const M2Float3 * values = track_sequence_array < M2Float3 > ( buf , track . values , i , value_count ) ;
uint32_t key_count = std : : min ( time_count , value_count ) ;
if ( ! times | | ! values | | key_count < 2 ) {
continue ;
}
uint32_t first = 0 ;
uint32_t last = key_count - 1 ;
float duration = ( float ) ( times [ last ] - times [ first ] ) / 1000.0f ;
if ( duration < = 0.0001f ) {
continue ;
}
return Vector2 (
( values [ last ] . v [ 0 ] - values [ first ] . v [ 0 ] ) / duration ,
( values [ last ] . v [ 1 ] - values [ first ] . v [ 1 ] ) / duration ) ;
}
return Vector2 ( 0.0f , 0.0f ) ;
}
static uint32_t sequence_activity_score ( const std : : vector < uint8_t > & buf , const M2CompBone * bones , uint32_t bone_count , uint32_t sequence_index ) {
if ( ! bones ) return 0 ;
uint32_t score = 0 ;
for ( uint32_t i = 0 ; i < bone_count ; + + i ) {
uint32_t t = track_sequence_key_count ( buf , bones [ i ] . translation , sequence_index ) ;
uint32_t r = track_sequence_key_count ( buf , bones [ i ] . rotation , sequence_index ) ;
uint32_t s = track_sequence_key_count ( buf , bones [ i ] . scale , sequence_index ) ;
if ( t > 1 ) score + = t ;
if ( r > 1 ) score + = r ;
if ( s > 1 ) score + = s ;
}
return score ;
}
static uint32_t choose_animated_sequence ( const std : : vector < uint8_t > & buf , const M2Sequence * seqs , uint32_t seq_count , const M2CompBone * bones , uint32_t bone_count , bool prefer_calm_stand ) {
if ( ! seqs | | seq_count = = 0 ) return 0 ;
uint32_t best_stand = UINT32_MAX ;
uint32_t best_stand_score = 0 ;
uint32_t calm_stand = UINT32_MAX ;
uint32_t calm_stand_score = UINT32_MAX ;
uint32_t best_any = UINT32_MAX ;
uint32_t best_any_score = 0 ;
for ( uint32_t i = 0 ; i < seq_count ; + + i ) {
if ( seqs [ i ] . duration = = 0 ) continue ;
uint32_t score = sequence_activity_score ( buf , bones , bone_count , i ) ;
if ( seqs [ i ] . id = = 0 & & score > best_stand_score ) {
best_stand = i ;
best_stand_score = score ;
}
if ( seqs [ i ] . id = = 0 & & score > 0 & & score < calm_stand_score ) {
calm_stand = i ;
calm_stand_score = score ;
}
if ( score > best_any_score ) {
best_any = i ;
best_any_score = score ;
}
}
if ( prefer_calm_stand & & calm_stand ! = UINT32_MAX ) return calm_stand ;
if ( best_stand ! = UINT32_MAX ) return best_stand ;
if ( best_any ! = UINT32_MAX ) return best_any ;
for ( uint32_t i = 0 ; i < seq_count ; + + i ) {
if ( seqs [ i ] . id = = 0 & & seqs [ i ] . duration > 0 ) return i ;
}
for ( uint32_t i = 0 ; i < seq_count ; + + i ) {
if ( seqs [ i ] . duration > 0 ) return i ;
}
return 0 ;
}
static Dictionary make_vec3_track ( const std : : vector < uint8_t > & buf , const M2Track & track , uint32_t sequence_index , bool scale_track ) {
Dictionary result ;
uint32_t time_count = 0 ;
uint32_t value_count = 0 ;
const uint32_t * times = track_sequence_array < uint32_t > ( buf , track . timestamps , sequence_index , time_count ) ;
const M2Float3 * values = track_sequence_array < M2Float3 > ( buf , track . values , sequence_index , value_count ) ;
if ( ! times | | ! values | | time_count = = 0 | | value_count = = 0 ) {
return result ;
}
uint32_t key_count = std : : min ( time_count , value_count ) ;
PackedFloat32Array out_times ;
PackedVector3Array out_values ;
out_times . resize ( key_count ) ;
out_values . resize ( key_count ) ;
for ( uint32_t i = 0 ; i < key_count ; + + i ) {
out_times [ i ] = ( float ) times [ i ] / 1000.0f ;
if ( scale_track ) {
out_values [ i ] = Vector3 ( values [ i ] . v [ 0 ] , values [ i ] . v [ 1 ] , values [ i ] . v [ 2 ] ) ;
} else {
out_values [ i ] = wow_vec3_to_godot ( values [ i ] . v [ 0 ] , values [ i ] . v [ 1 ] , values [ i ] . v [ 2 ] ) ;
}
}
result [ " times " ] = out_times ;
result [ " values " ] = out_values ;
return result ;
}
static Dictionary make_quat_track ( const std : : vector < uint8_t > & buf , const M2Track & track , uint32_t sequence_index ) {
Dictionary result ;
uint32_t time_count = 0 ;
uint32_t value_count = 0 ;
const uint32_t * times = track_sequence_array < uint32_t > ( buf , track . timestamps , sequence_index , time_count ) ;
const M2CompQuat * values = track_sequence_array < M2CompQuat > ( buf , track . values , sequence_index , value_count ) ;
if ( ! times | | ! values | | time_count = = 0 | | value_count = = 0 ) {
return result ;
}
uint32_t key_count = std : : min ( time_count , value_count ) ;
PackedFloat32Array out_times ;
PackedVector4Array out_values ;
out_times . resize ( key_count ) ;
out_values . resize ( key_count ) ;
for ( uint32_t i = 0 ; i < key_count ; + + i ) {
out_times [ i ] = ( float ) times [ i ] / 1000.0f ;
out_values [ i ] = decode_comp_quat ( values [ i ] . v ) ;
}
result [ " times " ] = out_times ;
result [ " values " ] = out_values ;
return result ;
}
// ─────────────────────────────────────────────────────────────────────────────
// Core parser
// ─────────────────────────────────────────────────────────────────────────────
Dictionary M2Loader : : parse_m2 ( const std : : vector < uint8_t > & buf , const std : : string & path ) {
Dictionary M2Loader : : parse_m2 ( const std : : vector < uint8_t > & buf , const std : : string & path , bool include_animation ) {
if ( buf . size ( ) < sizeof ( M2Header ) ) return Dictionary ( ) ;
const auto & hdr = * reinterpret_cast < const M2Header * > ( buf . data ( ) ) ;
@@ -178,13 +474,14 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
// ── Vertices ─────────────────────────────────────────────────────────────
PackedVector3Array vertices , normals ;
PackedVector2Array uvs ;
PackedVector2Array uvs , uvs2 ;
const auto * verts = safe_array < M2Vertex > ( buf , hdr . ofsVertices , hdr . nVertices ) ;
if ( verts & & hdr . nVertices > 0 ) {
vertices . resize ( hdr . nVertices ) ;
normals . resize ( hdr . nVertices ) ;
uvs . resize ( hdr . nVertices ) ;
uvs2 . resize ( hdr . nVertices ) ;
for ( uint32_t i = 0 ; i < hdr . nVertices ; + + i ) {
const auto & v = verts [ i ] ;
// WoW model space (X right, Y forward, Z up) → Godot (X right, Y up, Z back)
@@ -194,6 +491,7 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
// (D3D/Vulkan convention) — pass UVs through unchanged. Same as
// wmo_loader's MOTV handling.
uvs [ i ] = Vector2 ( v . texCoords [ 0 ] , v . texCoords [ 1 ] ) ;
uvs2 [ i ] = Vector2 ( v . texCoords2 [ 0 ] , v . texCoords2 [ 1 ] ) ;
}
}
@@ -244,7 +542,92 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
texture_combos . push_back ( ( int ) tc_arr [ i ] ) ;
}
PackedInt32Array texture_coord_combos ;
const auto * tcc_arr = safe_array < uint16_t > ( buf , hdr . ofsTextureCoordCombos , hdr . nTextureCoordCombos ) ;
if ( tcc_arr ) {
for ( uint32_t i = 0 ; i < hdr . nTextureCoordCombos ; + + i )
texture_coord_combos . push_back ( ( int ) tcc_arr [ i ] ) ;
}
PackedInt32Array texture_weight_combos ;
const auto * twc_arr = safe_array < uint16_t > ( buf , hdr . ofsTextureWeightCombos , hdr . nTextureWeightCombos ) ;
if ( twc_arr ) {
for ( uint32_t i = 0 ; i < hdr . nTextureWeightCombos ; + + i )
texture_weight_combos . push_back ( ( int ) twc_arr [ i ] ) ;
}
PackedInt32Array texture_transform_combos ;
const auto * ttc_arr = safe_array < uint16_t > ( buf , hdr . ofsTextureTransformCombos , hdr . nTextureTransformCombos ) ;
if ( ttc_arr ) {
for ( uint32_t i = 0 ; i < hdr . nTextureTransformCombos ; + + i )
texture_transform_combos . push_back ( ( int ) ttc_arr [ i ] ) ;
}
PackedInt32Array texture_combiner_combos ;
const auto * tcomb_arr = safe_array < uint16_t > ( buf , hdr . ofsTextureCombinerCombos , hdr . nTextureCombinerCombos ) ;
if ( tcomb_arr ) {
for ( uint32_t i = 0 ; i < hdr . nTextureCombinerCombos ; + + i )
texture_combiner_combos . push_back ( ( int ) tcomb_arr [ i ] ) ;
}
// ── Skin file ────────────────────────────────────────────────────────────
Array m2_colors ;
const auto * color_arr = safe_array < M2Color > ( buf , hdr . ofsColors , hdr . nColors ) ;
if ( color_arr ) {
for ( uint32_t i = 0 ; i < hdr . nColors ; + + i ) {
Color color ( 1.0f , 1.0f , 1.0f , 1.0f ) ;
const M2Float3 * rgb = first_track_value < M2Float3 > ( buf , color_arr [ i ] . colorTrack ) ;
if ( rgb ) {
color . r = rgb - > v [ 0 ] ;
color . g = rgb - > v [ 1 ] ;
color . b = rgb - > v [ 2 ] ;
}
const int16_t * alpha = first_track_value < int16_t > ( buf , color_arr [ i ] . alphaTrack ) ;
if ( alpha ) {
color . a = fixed16_to_float ( * alpha ) ;
}
Dictionary c ;
c [ " color " ] = color ;
m2_colors . push_back ( c ) ;
}
}
PackedFloat32Array texture_weights ;
const auto * weight_arr = safe_array < M2TextureWeight > ( buf , hdr . ofsTexWeights , hdr . nTexWeights ) ;
if ( weight_arr ) {
texture_weights . resize ( hdr . nTexWeights ) ;
for ( uint32_t i = 0 ; i < hdr . nTexWeights ; + + i ) {
float weight = 1.0f ;
const int16_t * value = first_track_value < int16_t > ( buf , weight_arr [ i ] . weightTrack ) ;
if ( value ) {
weight = fixed16_to_float ( * value ) ;
}
texture_weights [ i ] = weight ;
}
}
Array texture_transforms ;
const auto * transform_arr = safe_array < M2TextureTransform > ( buf , hdr . ofsTexTransforms , hdr . nTexTransforms ) ;
if ( transform_arr ) {
for ( uint32_t i = 0 ; i < hdr . nTexTransforms ; + + i ) {
Vector2 translation ( 0.0f , 0.0f ) ;
Vector2 scale ( 1.0f , 1.0f ) ;
const M2Float3 * trans = first_track_value < M2Float3 > ( buf , transform_arr [ i ] . translationTrack ) ;
if ( trans ) {
translation = Vector2 ( trans - > v [ 0 ] , trans - > v [ 1 ] ) ;
}
const M2Float3 * scl = first_track_value < M2Float3 > ( buf , transform_arr [ i ] . scaleTrack ) ;
if ( scl ) {
scale = Vector2 ( scl - > v [ 0 ] , scl - > v [ 1 ] ) ;
}
Dictionary t ;
t [ " translation " ] = translation ;
t [ " scale " ] = scale ;
t [ " translation_speed " ] = first_vec3_track_xy_speed ( buf , transform_arr [ i ] . translationTrack ) ;
texture_transforms . push_back ( t ) ;
}
}
// Find <basename>00.skin in the same directory as the .m2
std : : string skin_path = path ;
{
@@ -257,7 +640,16 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
auto skin_buf = read_file ( skin_path ) ;
PackedInt32Array indices ;
PackedInt32Array bone_lookup_table ;
Array batches ;
Array animated_surfaces ;
const uint16_t * bone_combo_arr = safe_array < uint16_t > ( buf , hdr . ofsBoneCombos , hdr . nBoneCombos ) ;
if ( bone_combo_arr ) {
bone_lookup_table . resize ( hdr . nBoneCombos ) ;
for ( uint32_t i = 0 ; i < hdr . nBoneCombos ; + + i ) {
bone_lookup_table [ i ] = ( int ) bone_combo_arr [ i ] ;
}
}
if ( skin_buf . size ( ) > = sizeof ( SkinHeader ) ) {
const auto & skin = * reinterpret_cast < const SkinHeader * > ( skin_buf . data ( ) ) ;
@@ -265,6 +657,7 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
if ( skin . magic = = MAGIC_SKIN ) {
const uint16_t * skin_idx = safe_array < uint16_t > ( skin_buf , skin . ofsIndices , skin . nIndices ) ;
const uint16_t * skin_tri = safe_array < uint16_t > ( skin_buf , skin . ofsTriangles , skin . nTriangles ) ;
const uint8_t * skin_bones = safe_array < uint8_t > ( skin_buf , skin . ofsProperties , skin . nProperties * 4 ) ;
const SkinSubMesh * sms = safe_array < SkinSubMesh > ( skin_buf , skin . ofsSubMeshes , skin . nSubMeshes ) ;
const SkinTextureUnit * tu = safe_array < SkinTextureUnit > ( skin_buf , skin . ofsTextureUnits , skin . nTextureUnits ) ;
@@ -303,7 +696,113 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
batch [ " index_count " ] = idx_count ;
batch [ " material_id " ] = ( int ) tu [ u ] . materialIndex ;
batch [ " texture_combo_index " ] = ( int ) tu [ u ] . textureComboIndex ;
batch [ " texture_count " ] = ( int ) tu [ u ] . textureCount ;
batch [ " shader_id " ] = ( int ) tu [ u ] . shaderID ;
batch [ " texture_coord_combo_index " ] = ( int ) tu [ u ] . textureCoordComboIndex ;
batch [ " texture_weight_combo_index " ] = ( int ) tu [ u ] . textureWeightComboIndex ;
batch [ " texture_transform_combo_index " ] = ( int ) tu [ u ] . textureTransformComboIndex ;
batch [ " material_layer " ] = ( int ) tu [ u ] . materialLayer ;
batch [ " color_index " ] = ( int ) tu [ u ] . colorIndex ;
batch [ " priority_plane " ] = ( int ) tu [ u ] . priority ;
batch [ " skin_flags " ] = ( int ) tu [ u ] . flags ;
batch [ " skin_flags2 " ] = ( int ) tu [ u ] . flags2 ;
batch [ " bone_count " ] = ( int ) sm . boneCount ;
batch [ " bone_combo_index " ] = ( int ) sm . boneStart ;
batches . push_back ( batch ) ;
if ( include_animation & & verts ) {
PackedVector3Array surface_vertices ;
PackedVector3Array surface_normals ;
PackedVector2Array surface_uvs , surface_uvs2 ;
PackedInt32Array surface_bones ;
PackedFloat32Array surface_weights ;
PackedInt32Array surface_indices ;
uint32_t expanded_count = 0 ;
surface_vertices . resize ( tri_count ) ;
surface_normals . resize ( tri_count ) ;
surface_uvs . resize ( tri_count ) ;
surface_uvs2 . resize ( tri_count ) ;
surface_bones . resize ( tri_count * 4 ) ;
surface_weights . resize ( tri_count * 4 ) ;
surface_indices . resize ( tri_count ) ;
for ( uint32_t t = 0 ; t + 2 < tri_count ; t + = 3 ) {
uint16_t tri_local [ 3 ] = {
skin_tri [ tri_start + t ] ,
skin_tri [ tri_start + t + 2 ] ,
skin_tri [ tri_start + t + 1 ] ,
} ;
for ( uint32_t corner = 0 ; corner < 3 ; + + corner ) {
uint16_t vertex_lookup = tri_local [ corner ] ;
if ( vertex_lookup > = skin . nIndices ) {
continue ;
}
uint16_t global_vertex_index = skin_idx [ vertex_lookup ] ;
if ( global_vertex_index > = hdr . nVertices ) {
continue ;
}
const auto & v = verts [ global_vertex_index ] ;
surface_vertices [ expanded_count ] = Vector3 ( v . pos [ 0 ] , v . pos [ 2 ] , - v . pos [ 1 ] ) ;
surface_normals [ expanded_count ] = Vector3 ( v . normal [ 0 ] , v . normal [ 2 ] , - v . normal [ 1 ] ) ;
surface_uvs [ expanded_count ] = Vector2 ( v . texCoords [ 0 ] , v . texCoords [ 1 ] ) ;
surface_uvs2 [ expanded_count ] = Vector2 ( v . texCoords2 [ 0 ] , v . texCoords2 [ 1 ] ) ;
surface_indices [ expanded_count ] = ( int ) expanded_count ;
uint32_t weight_sum = 0 ;
for ( uint32_t j = 0 ; j < 4 ; + + j ) {
weight_sum + = ( uint32_t ) v . boneWeights [ j ] ;
}
if ( weight_sum = = 0 ) {
weight_sum = 1 ;
}
for ( uint32_t j = 0 ; j < 4 ; + + j ) {
int local_bone = ( int ) v . boneIndices [ j ] ;
if ( skin_bones & & vertex_lookup < skin . nProperties ) {
local_bone = ( int ) skin_bones [ vertex_lookup * 4 + j ] ;
}
int global_bone = local_bone ;
uint32_t lookup_index = ( uint32_t ) sm . boneStart + ( uint32_t ) std : : max ( local_bone , 0 ) ;
if ( bone_combo_arr & & lookup_index < hdr . nBoneCombos ) {
global_bone = ( int ) bone_combo_arr [ lookup_index ] ;
}
surface_bones [ expanded_count * 4 + j ] = global_bone ;
surface_weights [ expanded_count * 4 + j ] = ( float ) v . boneWeights [ j ] / ( float ) weight_sum ;
}
expanded_count + + ;
}
}
if ( expanded_count > 0 ) {
surface_vertices . resize ( expanded_count ) ;
surface_normals . resize ( expanded_count ) ;
surface_uvs . resize ( expanded_count ) ;
surface_uvs2 . resize ( expanded_count ) ;
surface_bones . resize ( expanded_count * 4 ) ;
surface_weights . resize ( expanded_count * 4 ) ;
surface_indices . resize ( expanded_count ) ;
Dictionary surface ;
surface [ " vertices " ] = surface_vertices ;
surface [ " normals " ] = surface_normals ;
surface [ " uvs " ] = surface_uvs ;
surface [ " uvs2 " ] = surface_uvs2 ;
surface [ " bones " ] = surface_bones ;
surface [ " weights " ] = surface_weights ;
surface [ " indices " ] = surface_indices ;
surface [ " material_id " ] = ( int ) tu [ u ] . materialIndex ;
surface [ " texture_combo_index " ] = ( int ) tu [ u ] . textureComboIndex ;
surface [ " texture_count " ] = ( int ) tu [ u ] . textureCount ;
surface [ " shader_id " ] = ( int ) tu [ u ] . shaderID ;
surface [ " texture_coord_combo_index " ] = ( int ) tu [ u ] . textureCoordComboIndex ;
surface [ " texture_weight_combo_index " ] = ( int ) tu [ u ] . textureWeightComboIndex ;
surface [ " texture_transform_combo_index " ] = ( int ) tu [ u ] . textureTransformComboIndex ;
surface [ " material_layer " ] = ( int ) tu [ u ] . materialLayer ;
surface [ " color_index " ] = ( int ) tu [ u ] . colorIndex ;
surface [ " bone_count " ] = ( int ) sm . boneCount ;
surface [ " bone_combo_index " ] = ( int ) sm . boneStart ;
animated_surfaces . push_back ( surface ) ;
}
}
}
}
} else {
@@ -315,18 +814,81 @@ Dictionary M2Loader::parse_m2(const std::vector<uint8_t> &buf, const std::string
to_godot ( skin_path ) ) ;
}
Array animated_bones ;
Array animated_sequences ;
int animation_sequence_index = - 1 ;
float animation_length = 0.0f ;
int animation_id = - 1 ;
uint32_t animation_activity_score = 0 ;
if ( include_animation ) {
const M2Sequence * seqs = safe_array < M2Sequence > ( buf , hdr . ofsAnimations , hdr . nAnimations ) ;
const M2CompBone * bones = safe_array < M2CompBone > ( buf , hdr . ofsBones , hdr . nBones ) ;
if ( seqs & & hdr . nAnimations > 0 ) {
std : : string lower_path = path ;
std : : transform ( lower_path . begin ( ) , lower_path . end ( ) , lower_path . begin ( ) , [ ] ( unsigned char c ) { return ( char ) std : : tolower ( c ) ; } ) ;
bool prefer_calm_stand = lower_path . find ( " gryphonroost " ) ! = std : : string : : npos ;
uint32_t chosen = choose_animated_sequence ( buf , seqs , hdr . nAnimations , bones , hdr . nBones , prefer_calm_stand ) ;
animation_sequence_index = ( int ) chosen ;
animation_length = ( float ) seqs [ chosen ] . duration / 1000.0f ;
animation_id = ( int ) seqs [ chosen ] . id ;
animation_activity_score = sequence_activity_score ( buf , bones , hdr . nBones , chosen ) ;
for ( uint32_t i = 0 ; i < hdr . nAnimations ; + + i ) {
Dictionary seq ;
seq [ " id " ] = ( int ) seqs [ i ] . id ;
seq [ " variation " ] = ( int ) seqs [ i ] . variationIndex ;
seq [ " duration " ] = ( int ) seqs [ i ] . duration ;
seq [ " alias_next " ] = ( int ) seqs [ i ] . aliasNext ;
seq [ " activity_score " ] = ( int ) sequence_activity_score ( buf , bones , hdr . nBones , i ) ;
animated_sequences . push_back ( seq ) ;
}
}
if ( bones & & hdr . nBones > 0 & & animation_sequence_index > = 0 ) {
for ( uint32_t i = 0 ; i < hdr . nBones ; + + i ) {
Dictionary bone ;
int parent = ( bones [ i ] . parentIndex = = 0xFFFF ) ? - 1 : ( int ) bones [ i ] . parentIndex ;
bone [ " id " ] = ( int ) bones [ i ] . boneId ;
bone [ " flags " ] = ( int ) bones [ i ] . flags ;
bone [ " parent " ] = parent ;
bone [ " pivot " ] = wow_vec3_to_godot ( bones [ i ] . pivot ) ;
bone [ " translation " ] = make_vec3_track ( buf , bones [ i ] . translation , ( uint32_t ) animation_sequence_index , false ) ;
bone [ " rotation " ] = make_quat_track ( buf , bones [ i ] . rotation , ( uint32_t ) animation_sequence_index ) ;
bone [ " scale " ] = make_vec3_track ( buf , bones [ i ] . scale , ( uint32_t ) animation_sequence_index , true ) ;
animated_bones . push_back ( bone ) ;
}
}
}
Dictionary result ;
result [ " textures " ] = textures ;
result [ " texture_types " ] = texture_types ;
result [ " texture_flags " ] = texture_flags ;
result [ " materials " ] = materials ;
result [ " texture_combos " ] = texture_combos ;
result [ " texture_coord_combos " ] = texture_coord_combos ;
result [ " texture_weight_combos " ] = texture_weight_combos ;
result [ " texture_transform_combos " ] = texture_transform_combos ;
result [ " texture_combiner_combos " ] = texture_combiner_combos ;
result [ " m2_colors " ] = m2_colors ;
result [ " texture_weights " ] = texture_weights ;
result [ " texture_transforms " ] = texture_transforms ;
result [ " m2_flags " ] = ( int ) hdr . flags ;
result [ " model_path " ] = to_godot ( path ) ;
result [ " vertices " ] = vertices ;
result [ " normals " ] = normals ;
result [ " uvs " ] = uvs ;
result [ " uvs2 " ] = uvs2 ;
result [ " indices " ] = indices ;
result [ " batches " ] = batches ;
if ( include_animation ) {
result [ " animated_surfaces " ] = animated_surfaces ;
result [ " bone_lookup_table " ] = bone_lookup_table ;
result [ " bones " ] = animated_bones ;
result [ " sequences " ] = animated_sequences ;
result [ " animation_sequence_index " ] = animation_sequence_index ;
result [ " animation_id " ] = animation_id ;
result [ " animation_length " ] = animation_length ;
result [ " animation_activity_score " ] = ( int ) animation_activity_score ;
}
return result ;
}
@@ -340,9 +902,20 @@ Dictionary M2Loader::load_m2(const String &path) {
UtilityFunctions : : push_error ( " M2Loader: cannot read " , path ) ;
return Dictionary ( ) ;
}
return parse_m2 ( buf , spath ) ;
return parse_m2 ( buf , spath , false ) ;
}
Dictionary M2Loader : : load_m2_animated ( const String & path ) {
std : : string spath = to_std ( path ) ;
auto buf = read_file ( spath ) ;
if ( buf . empty ( ) ) {
UtilityFunctions : : push_error ( " M2Loader: cannot read " , path ) ;
return Dictionary ( ) ;
}
return parse_m2 ( buf , spath , true ) ;
}
void M2Loader : : _bind_methods ( ) {
ClassDB : : bind_method ( D_METHOD ( " load_m2 " , " path " ) , & M2Loader : : load_m2 ) ;
ClassDB : : bind_method ( D_METHOD ( " load_m2_animated " , " path " ) , & M2Loader : : load_m2_animated ) ;
}