победа над водопадами, шейдеры m2

This commit is contained in:
2026-07-07 10:45:43 +04:00
parent 8738c2495d
commit 44614a79e4
38 changed files with 4820 additions and 192 deletions
+576 -3
View File
@@ -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);
}
+9 -1
View File
@@ -28,10 +28,14 @@ namespace godot {
// "texture_flags": PackedInt32Array, # M2Texture.flags per texture slot
// "materials": Array[Dictionary], # [{flags, blend_mode}]
// "texture_combos": PackedInt32Array, # textureCombos[i] = index into textures
// "texture_coord_combos": PackedInt32Array,
// "texture_combiner_combos": PackedInt32Array,
// "m2_flags": int,
// "model_path": String, # absolute source .m2 path
// "vertices": PackedVector3Array, # Godot-space positions
// "normals": PackedVector3Array,
// "uvs": PackedVector2Array,
// "uvs2": PackedVector2Array,
// "indices": PackedInt32Array, # flat triangle list into vertex array
// "batches": Array[Dictionary], # render batches
// }
@@ -41,6 +45,9 @@ namespace godot {
// "index_start": int, # offset into indices array
// "index_count": int, # number of indices for this batch
// "material_id": int, # index into materials
// "texture_count": int,
// "shader_id": int,
// "texture_coord_combo_index": int,
// "texture_combo_index": int, # index into texture_combos → texture
// }
// ─────────────────────────────────────────────────────────────────────────────
@@ -49,12 +56,13 @@ class M2Loader : public RefCounted {
public:
Dictionary load_m2(const String &path);
Dictionary load_m2_animated(const String &path);
protected:
static void _bind_methods();
private:
Dictionary parse_m2(const std::vector<uint8_t> &buf, const std::string &path);
Dictionary parse_m2(const std::vector<uint8_t> &buf, const std::string &path, bool include_animation);
static std::vector<uint8_t> read_file(const std::string &path);
static std::string to_std(const String &s);
+22
View File
@@ -2,6 +2,7 @@
#include "wow_chunk_reader.h"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/variant/color.hpp>
#include <godot_cpp/variant/utility_functions.hpp>
#include <godot_cpp/variant/packed_string_array.hpp>
#include <godot_cpp/variant/quaternion.hpp>
@@ -141,6 +142,22 @@ String WMOLoader::to_godot(const std::string &s) {
return String(s.c_str());
}
static Color color_from_bgra_u32(uint32_t value) {
const float b = (value & 0xFFu) / 255.0f;
const float g = ((value >> 8) & 0xFFu) / 255.0f;
const float r = ((value >> 16) & 0xFFu) / 255.0f;
const float a = ((value >> 24) & 0xFFu) / 255.0f;
return Color(r, g, b, a);
}
static Color color_from_rgba_u32(uint32_t value) {
const float r = (value & 0xFFu) / 255.0f;
const float g = ((value >> 8) & 0xFFu) / 255.0f;
const float b = ((value >> 16) & 0xFFu) / 255.0f;
const float a = ((value >> 24) & 0xFFu) / 255.0f;
return Color(r, g, b, a);
}
// ─────────────────────────────────────────────────────────────────────────────
// Root parser
// ─────────────────────────────────────────────────────────────────────────────
@@ -180,6 +197,10 @@ Dictionary WMOLoader::parse_root(const std::vector<uint8_t> &buf) {
mat["flags"] = (int)mt[i].flags;
mat["shader"] = (int)mt[i].shader;
mat["blend_mode"] = (int)mt[i].blendMode;
mat["emissive_color"] = color_from_bgra_u32(mt[i].sidnColor);
mat["diffuse_color"] = color_from_rgba_u32(mt[i].diffColor);
mat["color2"] = color_from_rgba_u32(mt[i].color2);
mat["flags2"] = (int)mt[i].flags2;
// texUnit offsets → find index in tex_strings
auto tex_idx = [&](uint32_t ofs) -> int {
uint32_t cur = 0;
@@ -191,6 +212,7 @@ Dictionary WMOLoader::parse_root(const std::vector<uint8_t> &buf) {
};
mat["texture0"] = tex_idx(mt[i].texUnit0);
mat["texture1"] = tex_idx(mt[i].texUnit1);
mat["texture2"] = tex_idx(mt[i].texUnit2);
materials.push_back(mat);
}
} else if (chunk.is("MODS")) {
+4
View File
@@ -57,8 +57,12 @@ namespace godot {
// {
// "texture0": int, # index into textures array (-1 = none)
// "texture1": int,
// "texture2": int,
// "blend_mode": int, # 0=opaque 1=alpha key 2=alpha blend
// "flags": int,
// "flags2": int,
// "diffuse_color": Color,
// "emissive_color": Color,
// }
// ─────────────────────────────────────────────────────────────────────────────
class WMOLoader : public RefCounted {