Kelsidavis-WoWee/include/pipeline/m2_loader.hpp
Kelsi d7aabc0caa Add M2 collision mesh parsing and mesh-based wall/floor collision
Parse bounding vertices, triangles, and normals from M2 files and use
them for proper triangle-level collision instead of AABB heuristics.
Spatial grid bucketing for efficient queries, closest-point wall push
with soft clamping, and ray-triangle floor detection alongside existing
AABB fallback.
2026-02-08 19:56:17 -08:00

250 lines
8.6 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<float> floatValues; // For float tracks (particle emitters)
};
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
};
// FBlock: particle lifetime curve (color/alpha/scale over particle life)
struct M2FBlock {
std::vector<float> timestamps; // Normalized 0..1
std::vector<float> floatValues; // For alpha/scale
std::vector<glm::vec3> 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;
};
// 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
// Particle emitters
std::vector<M2ParticleEmitter> particleEmitters;
// Collision mesh (simplified geometry for physics)
std::vector<glm::vec3> collisionVertices;
std::vector<uint16_t> collisionIndices; // 3 per triangle
std::vector<glm::vec4> 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<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