mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 15:50:20 +00:00
Water/lava batches in fountain and Ironforge M2 models use non-opaque blend modes (alpha, additive) defined in the M2 material table. Without parsing these, they rendered as solid surfaces extending visibly beyond their containers. Now each batch looks up its blend mode from the material array and sets the appropriate GL blend function.
206 lines
7 KiB
C++
206 lines
7 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 (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
|
|
};
|
|
|
|
// 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
|
|
std::vector<M2Material> materials; // Render flags / blend modes
|
|
|
|
// Texture transforms (UV animation)
|
|
std::vector<M2TextureTransform> textureTransforms;
|
|
std::vector<uint16_t> textureTransformLookup;
|
|
|
|
// 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
|