Kelsidavis-WoWee/include/pipeline/m2_loader.hpp
Kelsi 45048757ef Fix geoset filtering using correct geosetId from M2 batch
M2 batches have a geosetIndex field that determines the geoset group for
filtering (0-18=body, 1xx=hair, etc.). We were incorrectly using submeshId
from the submesh struct instead. Now properly stores and uses geosetId.
2026-02-05 23:26:30 -08:00

189 lines
6.3 KiB
C++

#pragma once
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/quaternion.hpp>
namespace wowee {
namespace pipeline {
/**
* M2 Model Format (WoW Character/Creature Models)
*
* M2 files contain:
* - Skeletal animated meshes
* - Multiple texture units and materials
* - Animation sequences
* - Bone hierarchy
* - Particle emitters, ribbon emitters, etc.
*
* Reference: https://wowdev.wiki/M2
*/
// Animation sequence data
struct M2Sequence {
uint32_t id; // Animation ID
uint32_t variationIndex; // Sub-animation index
uint32_t duration; // Length in milliseconds
float movingSpeed; // Speed during animation
uint32_t flags; // Animation flags
int16_t frequency; // Probability weight
uint32_t replayMin; // Minimum replay delay
uint32_t replayMax; // Maximum replay delay
uint32_t blendTime; // Blend time in ms
glm::vec3 boundMin; // Bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere radius
int16_t nextAnimation; // Next animation in chain
uint16_t aliasNext; // Alias for next animation
};
// Animation track with per-sequence keyframe data
struct M2AnimationTrack {
uint16_t interpolationType = 0; // 0=none, 1=linear, 2=hermite, 3=bezier
int16_t globalSequence = -1; // -1 if not a global sequence
struct SequenceKeys {
std::vector<uint32_t> timestamps; // Milliseconds
std::vector<glm::vec3> vec3Values; // For translation/scale tracks
std::vector<glm::quat> quatValues; // For rotation tracks
};
std::vector<SequenceKeys> sequences; // One per animation sequence
bool hasData() const { return !sequences.empty(); }
};
// Bone data for skeletal animation
struct M2Bone {
int32_t keyBoneId; // Bone ID (-1 = not key bone)
uint32_t flags; // Bone flags
int16_t parentBone; // Parent bone index (-1 = root)
uint16_t submeshId; // Submesh ID
glm::vec3 pivot; // Pivot point
M2AnimationTrack translation; // Position keyframes per sequence
M2AnimationTrack rotation; // Rotation keyframes per sequence
M2AnimationTrack scale; // Scale keyframes per sequence
};
// Vertex with skinning data
struct M2Vertex {
glm::vec3 position;
uint8_t boneWeights[4]; // Bone weights (0-255)
uint8_t boneIndices[4]; // Bone indices
glm::vec3 normal;
glm::vec2 texCoords[2]; // Two UV sets
};
// Texture unit
struct M2Texture {
uint32_t type; // Texture type
uint32_t flags; // Texture flags
std::string filename; // Texture filename (from FileData or embedded)
};
// Render batch (submesh)
struct M2Batch {
uint8_t flags;
int8_t priorityPlane;
uint16_t shader; // Shader ID
uint16_t skinSectionIndex; // Submesh index
uint16_t colorIndex; // Color animation index
uint16_t materialIndex; // Material index
uint16_t materialLayer; // Material layer
uint16_t textureCount; // Number of textures
uint16_t textureIndex; // First texture lookup index
uint16_t textureUnit; // Texture unit
uint16_t transparencyIndex; // Transparency animation index
uint16_t textureAnimIndex; // Texture animation index
// Render data
uint32_t indexStart; // First index
uint32_t indexCount; // Number of indices
uint32_t vertexStart; // First vertex
uint32_t vertexCount; // Number of vertices
// Geoset info
uint16_t submeshId = 0; // Submesh ID from skin file
uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh)
uint16_t geosetId = 0; // Geoset ID for filtering (0-18=body, 1xx=hair, etc.)
};
// Attachment point (bone-anchored position for weapons, effects, etc.)
struct M2Attachment {
uint32_t id; // 0=Head, 1=RightHand, 2=LeftHand, etc.
uint16_t bone; // Bone index
glm::vec3 position; // Offset from bone pivot
};
// Complete M2 model structure
struct M2Model {
// Model metadata
std::string name;
uint32_t version;
glm::vec3 boundMin; // Model bounding box
glm::vec3 boundMax;
float boundRadius; // Bounding sphere
// Geometry data
std::vector<M2Vertex> vertices;
std::vector<uint16_t> indices;
// Skeletal animation
std::vector<M2Bone> bones;
std::vector<M2Sequence> sequences;
std::vector<uint32_t> globalSequenceDurations; // Per-global-sequence loop durations (ms)
// Rendering
std::vector<M2Batch> batches;
std::vector<M2Texture> textures;
std::vector<uint16_t> textureLookup; // Batch texture index lookup
// Attachment points (for weapon/effect anchoring)
std::vector<M2Attachment> attachments;
std::vector<uint16_t> attachmentLookup; // attachment ID → index
// Flags
uint32_t globalFlags;
bool isValid() const {
return !vertices.empty() && !indices.empty();
}
};
class M2Loader {
public:
/**
* Load M2 model from raw file data
*
* @param m2Data Raw M2 file bytes
* @return Parsed M2 model
*/
static M2Model load(const std::vector<uint8_t>& m2Data);
/**
* Load M2 skin file (contains submesh/batch data)
*
* @param skinData Raw M2 skin file bytes
* @param model Model to populate with skin data
* @return True if successful
*/
static bool loadSkin(const std::vector<uint8_t>& skinData, M2Model& model);
/**
* Load external .anim file data into model bone tracks
*
* @param m2Data Original M2 file bytes (contains track headers)
* @param animData Raw .anim file bytes
* @param sequenceIndex Which sequence index this .anim file provides data for
* @param model Model to patch with animation data
*/
static void loadAnimFile(const std::vector<uint8_t>& m2Data,
const std::vector<uint8_t>& animData,
uint32_t sequenceIndex,
M2Model& model);
};
} // namespace pipeline
} // namespace wowee