mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
Implement M2 texture animation (UV scrolling) for fountain water
Parse M2TextureTransform entries and texture transform lookups from the M2 binary, then apply per-batch UV offsets in the vertex shader using the existing animation time base and global sequence durations.
This commit is contained in:
parent
4bb2828b21
commit
ad04da31c3
4 changed files with 83 additions and 1 deletions
|
|
@ -109,6 +109,13 @@ struct M2Batch {
|
|||
uint16_t submeshLevel = 0; // Submesh level (0=base, 1+=LOD/alternate mesh)
|
||||
};
|
||||
|
||||
// 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.
|
||||
|
|
@ -139,6 +146,10 @@ struct M2Model {
|
|||
std::vector<M2Texture> textures;
|
||||
std::vector<uint16_t> textureLookup; // Batch texture index lookup
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ struct M2ModelGPU {
|
|||
uint32_t indexStart = 0; // offset in indices (not bytes)
|
||||
uint32_t indexCount = 0;
|
||||
bool hasAlpha = false;
|
||||
uint16_t textureAnimIndex = 0xFFFF; // 0xFFFF = no texture animation
|
||||
};
|
||||
|
||||
GLuint vao = 0;
|
||||
|
|
@ -61,6 +62,11 @@ struct M2ModelGPU {
|
|||
bool hasAnimation = false; // True if any bone has keyframes
|
||||
bool isSmoke = false; // True for smoke models (UV scroll animation)
|
||||
bool disableAnimation = false; // Keep foliage/tree doodads visually stable
|
||||
bool hasTextureAnimation = false; // True if any batch has UV animation
|
||||
|
||||
// Texture transform data for UV animation
|
||||
std::vector<pipeline::M2TextureTransform> textureTransforms;
|
||||
std::vector<uint16_t> textureTransformLookup;
|
||||
std::vector<int> idleVariationIndices; // Sequence indices for idle variations (animId 0)
|
||||
|
||||
bool isValid() const { return vao != 0 && indexCount > 0; }
|
||||
|
|
|
|||
|
|
@ -210,6 +210,13 @@ struct CompressedQuat {
|
|||
int16_t x, y, z, w;
|
||||
};
|
||||
|
||||
// M2 texture transform (on-disk, 3 × M2TrackDisk = 60 bytes)
|
||||
struct M2TextureTransformDisk {
|
||||
M2TrackDisk translation; // 20
|
||||
M2TrackDisk rotation; // 20
|
||||
M2TrackDisk scaling; // 20
|
||||
};
|
||||
|
||||
// M2 attachment point (on-disk)
|
||||
struct M2AttachmentDisk {
|
||||
uint32_t id;
|
||||
|
|
@ -511,6 +518,35 @@ M2Model M2Loader::load(const std::vector<uint8_t>& m2Data) {
|
|||
model.textureLookup = readArray<uint16_t>(m2Data, header.ofsTexLookup, header.nTexLookup);
|
||||
}
|
||||
|
||||
// Read texture transforms (UV animation data)
|
||||
if (header.nUVAnimation > 0 && header.ofsUVAnimation > 0) {
|
||||
// Build per-sequence flags for skipping external .anim data
|
||||
std::vector<uint32_t> seqFlags;
|
||||
seqFlags.reserve(model.sequences.size());
|
||||
for (const auto& seq : model.sequences) {
|
||||
seqFlags.push_back(seq.flags);
|
||||
}
|
||||
|
||||
model.textureTransforms.reserve(header.nUVAnimation);
|
||||
for (uint32_t i = 0; i < header.nUVAnimation; i++) {
|
||||
uint32_t ofs = header.ofsUVAnimation + i * sizeof(M2TextureTransformDisk);
|
||||
if (ofs + sizeof(M2TextureTransformDisk) > m2Data.size()) break;
|
||||
|
||||
M2TextureTransformDisk dt = readValue<M2TextureTransformDisk>(m2Data, ofs);
|
||||
M2TextureTransform tt;
|
||||
parseAnimTrack(m2Data, dt.translation, tt.translation, TrackType::VEC3, seqFlags);
|
||||
parseAnimTrack(m2Data, dt.rotation, tt.rotation, TrackType::QUAT_COMPRESSED, seqFlags);
|
||||
parseAnimTrack(m2Data, dt.scaling, tt.scale, TrackType::VEC3, seqFlags);
|
||||
model.textureTransforms.push_back(std::move(tt));
|
||||
}
|
||||
core::Logger::getInstance().debug(" Texture transforms: ", model.textureTransforms.size());
|
||||
}
|
||||
|
||||
// Read texture transform lookup (nTransLookup)
|
||||
if (header.nTransLookup > 0 && header.ofsTransLookup > 0) {
|
||||
model.textureTransformLookup = readArray<uint16_t>(m2Data, header.ofsTransLookup, header.nTransLookup);
|
||||
}
|
||||
|
||||
// Read attachment points
|
||||
if (header.nAttachments > 0 && header.ofsAttachments > 0) {
|
||||
auto diskAttachments = readArray<M2AttachmentDisk>(m2Data, header.ofsAttachments, header.nAttachments);
|
||||
|
|
|
|||
|
|
@ -219,6 +219,7 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
uniform mat4 uProjection;
|
||||
uniform bool uUseBones;
|
||||
uniform mat4 uBones[128];
|
||||
uniform vec2 uUVOffset;
|
||||
out vec3 FragPos;
|
||||
out vec3 Normal;
|
||||
out vec2 TexCoord;
|
||||
|
|
@ -240,7 +241,7 @@ bool M2Renderer::initialize(pipeline::AssetManager* assets) {
|
|||
vec4 worldPos = uModel * vec4(pos, 1.0);
|
||||
FragPos = worldPos.xyz;
|
||||
Normal = mat3(uModel) * norm;
|
||||
TexCoord = aTexCoord;
|
||||
TexCoord = aTexCoord + uUVOffset;
|
||||
|
||||
gl_Position = uProjection * uView * worldPos;
|
||||
}
|
||||
|
|
@ -710,6 +711,11 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
}
|
||||
}
|
||||
|
||||
// Copy texture transform data for UV animation
|
||||
gpuModel.textureTransforms = model.textureTransforms;
|
||||
gpuModel.textureTransformLookup = model.textureTransformLookup;
|
||||
gpuModel.hasTextureAnimation = false;
|
||||
|
||||
// Build per-batch GPU entries
|
||||
if (!model.batches.empty()) {
|
||||
for (const auto& batch : model.batches) {
|
||||
|
|
@ -717,6 +723,12 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
bgpu.indexStart = batch.indexStart;
|
||||
bgpu.indexCount = batch.indexCount;
|
||||
|
||||
// Store texture animation index from batch
|
||||
bgpu.textureAnimIndex = batch.textureAnimIndex;
|
||||
if (bgpu.textureAnimIndex != 0xFFFF) {
|
||||
gpuModel.hasTextureAnimation = true;
|
||||
}
|
||||
|
||||
// Resolve texture: batch.textureIndex → textureLookup → allTextures
|
||||
GLuint tex = whiteTexture;
|
||||
if (batch.textureIndex < model.textureLookup.size()) {
|
||||
|
|
@ -1224,6 +1236,23 @@ void M2Renderer::render(const Camera& camera, const glm::mat4& view, const glm::
|
|||
for (const auto& batch : model.batches) {
|
||||
if (batch.indexCount == 0) continue;
|
||||
|
||||
// Compute UV offset for texture animation
|
||||
glm::vec2 uvOffset(0.0f, 0.0f);
|
||||
if (batch.textureAnimIndex != 0xFFFF && model.hasTextureAnimation) {
|
||||
uint16_t lookupIdx = batch.textureAnimIndex;
|
||||
if (lookupIdx < model.textureTransformLookup.size()) {
|
||||
uint16_t transformIdx = model.textureTransformLookup[lookupIdx];
|
||||
if (transformIdx < model.textureTransforms.size()) {
|
||||
const auto& tt = model.textureTransforms[transformIdx];
|
||||
glm::vec3 trans = interpVec3(tt.translation,
|
||||
instance.currentSequenceIndex, instance.animTime,
|
||||
glm::vec3(0.0f), model.globalSequenceDurations);
|
||||
uvOffset = glm::vec2(trans.x, trans.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
shader->setUniform("uUVOffset", uvOffset);
|
||||
|
||||
bool hasTexture = (batch.texture != 0);
|
||||
shader->setUniform("uHasTexture", hasTexture);
|
||||
shader->setUniform("uAlphaTest", batch.hasAlpha);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue