#pragma once #include #include #include #include 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 timestamps; // Milliseconds std::vector vec3Values; // For translation/scale tracks std::vector quatValues; // For rotation tracks std::vector floatValues; // For float tracks (particle emitters) }; std::vector 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 (from submesh) uint16_t submeshId = 0; // Submesh/geoset ID (determines body part group) uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh) }; // Material / render flags (per-batch blend mode) struct M2Material { uint16_t flags; // Render flags (unlit, unfogged, two-sided, etc.) uint16_t blendMode; // 0=Opaque, 1=AlphaKey, 2=Alpha, 3=Add, 4=Mod, 5=Mod2x, 6=BlendAdd, 7=Screen }; // Texture transform (UV animation) data struct M2TextureTransform { M2AnimationTrack translation; // UV translation keyframes M2AnimationTrack rotation; // UV rotation keyframes (quat) M2AnimationTrack scale; // UV scale keyframes }; // 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 }; // FBlock: particle lifetime curve (color/alpha/scale over particle life) struct M2FBlock { std::vector timestamps; // Normalized 0..1 std::vector floatValues; // For alpha/scale std::vector vec3Values; // For color RGB }; // Particle emitter definition parsed from M2 struct M2ParticleEmitter { int32_t particleId; uint32_t flags; glm::vec3 position; uint16_t bone; uint16_t texture; uint8_t blendingType; // 0=opaque,1=alphakey,2=alpha,4=add uint8_t emitterType; // 1=plane,2=sphere,3=spline int16_t textureTileRotation = 0; uint16_t textureRows = 1; uint16_t textureCols = 1; M2AnimationTrack emissionSpeed; M2AnimationTrack speedVariation; M2AnimationTrack verticalRange; M2AnimationTrack horizontalRange; M2AnimationTrack gravity; M2AnimationTrack lifespan; M2AnimationTrack emissionRate; M2AnimationTrack emissionAreaLength; M2AnimationTrack emissionAreaWidth; M2AnimationTrack deceleration; M2FBlock particleColor; // vec3 RGB at 3 timestamps M2FBlock particleAlpha; // float (from uint16/32767) at 3 timestamps M2FBlock particleScale; // float (x component of vec2) at 3 timestamps bool enabled = true; }; // Ribbon emitter definition parsed from M2 (WotLK format) struct M2RibbonEmitter { int32_t ribbonId = 0; uint32_t bone = 0; // Bone that drives the ribbon spine glm::vec3 position{0.0f}; // Offset from bone pivot uint16_t textureIndex = 0; // First texture lookup index uint16_t materialIndex = 0; // First material lookup index (blend mode) // Animated tracks M2AnimationTrack colorTrack; // RGB 0..1 M2AnimationTrack alphaTrack; // float 0..1 (stored as fixed16 on disk) M2AnimationTrack heightAboveTrack; // Half-width above bone M2AnimationTrack heightBelowTrack; // Half-width below bone M2AnimationTrack visibilityTrack; // 0=hidden, 1=visible float edgesPerSecond = 15.0f; // How many edge points are generated per second float edgeLifetime = 0.5f; // Seconds before edges expire float gravity = 0.0f; // Downward pull on edges per s² uint16_t textureRows = 1; uint16_t textureCols = 1; }; // 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 vertices; std::vector indices; // Skeletal animation std::vector bones; std::vector sequences; std::vector globalSequenceDurations; // Per-global-sequence loop durations (ms) // Bone lookup table (vertex bone indices reference this to get global bone index) std::vector boneLookupTable; // Rendering std::vector batches; std::vector textures; std::vector textureLookup; // Batch texture index lookup std::vector materials; // Render flags / blend modes // Texture transforms (UV animation) std::vector textureTransforms; std::vector textureTransformLookup; // Texture weights (per-batch opacity, from M2Track) // Each entry is the "at-rest" opacity value (0=transparent, 1=opaque). // batch.transparencyIndex → textureTransformLookup[idx] → textureWeights[trackIdx] std::vector textureWeights; // Color animation alpha values (from M2Color.alpha M2Track) // One entry per color animation slot; batch.colorIndex indexes directly into this. // Value 0=transparent, 1=opaque. Independent from textureWeights. std::vector colorAlphas; // Attachment points (for weapon/effect anchoring) std::vector attachments; std::vector attachmentLookup; // attachment ID → index // Particle emitters std::vector particleEmitters; // Ribbon emitters std::vector ribbonEmitters; // Collision mesh (simplified geometry for physics) std::vector collisionVertices; std::vector collisionIndices; // 3 per triangle std::vector collisionNormals; // xyz=normal, w=distance; one per triangle // 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& 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& 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& m2Data, const std::vector& animData, uint32_t sequenceIndex, M2Model& model); }; } // namespace pipeline } // namespace wowee