WoW Model Viewer
Your premiere tool for viewing, equipping and animating World of Warcraft models.
Loading...
Searching...
No Matches
animated.h
Go to the documentation of this file.
1#pragma once
2
3#include <map>
4#include <ostream>
5#include <utility>
6#include <vector>
7
8#include "glm/glm.hpp"
9#include "glm/gtc/quaternion.hpp"
10
11#include "GameFile.h"
12#include "modelheaders.h"
13#include "types.h"
14#include "Logger.h"
15
18{
19public:
20 std::map<uint, int16> animIndexToAnimId;
21 std::map<int16, std::pair<GameFile*, GameFile*>> animfiles;
22 std::vector<uint32> globalSequences;
23};
24
31template <class T>
32inline T interpolate(const float r, const T& v1, const T& v2)
33{
34 return static_cast<T>(v1 * (1.0f - r) + v2 * r);
35}
36
45template <class T>
46inline T interpolateHermite(const float r, const T& v1, const T& v2, const T& in, const T& outVal)
47{
48 // basis functions
49 float h1 = 2.0f * r * r * r - 3.0f * r * r + 1.0f;
50 float h2 = -2.0f * r * r * r + 3.0f * r * r;
51 float h3 = r * r * r - 2.0f * r * r + r;
52 float h4 = r * r * r - r * r;
53
54 // interpolation
55 return static_cast<T>(v1 * h1 + v2 * h2 + in * h3 + outVal * h4);
56}
57
66template <class T>
67inline T interpolateBezier(const float r, const T& v1, const T& v2, const T& in, const T& outVal)
68{
69 const float InverseFactor = (1.0f - r);
70 const float FactorTimesTwo = r * r;
71 const float InverseFactorTimesTwo = InverseFactor * InverseFactor;
72 // basis functions
73 float h1 = InverseFactorTimesTwo * InverseFactor;
74 float h2 = 3.0f * r * InverseFactorTimesTwo;
75 float h3 = 3.0f * FactorTimesTwo * InverseFactor;
76 float h4 = FactorTimesTwo * r;
77
78 // interpolation
79 return static_cast<T>(v1 * h1 + v2 * h2 + in * h3 + outVal * h4);
80}
81
82// "linear" interpolation for quaternions should be slerp by default
83template <>
84inline glm::fquat interpolate<glm::fquat>(const float r, const glm::fquat& v1, const glm::fquat& v2)
85{
86 return glm::slerp(v1, v2, r);
87}
88
90typedef std::pair<size_t, size_t> AnimRange;
91
93extern size_t globalTime;
94
103
106template <class T>
108{
109public:
111 static const T& conv(const T& t)
112 {
113 return t;
114 }
115};
116
119{
120 int16 x, y, z, w;
121};
122
125{
126public:
128 static const glm::fquat conv(const PACK_QUATERNION t)
129 {
130 return glm::fquat(
131 static_cast<float>(t.w < 0 ? t.w + 32768 : t.w - 32767) / 32767.0f,
132 static_cast<float>(t.x < 0 ? t.x + 32768 : t.x - 32767) / 32767.0f,
133 static_cast<float>(t.y < 0 ? t.y + 32768 : t.y - 32767) / 32767.0f,
134 static_cast<float>(t.z < 0 ? t.z + 32768 : t.z - 32767) / 32767.0f);
135 }
136};
137
140{
141public:
143 static float conv(const short t)
144 {
145 return t / 32767.0f;
146 }
147};
148
150enum
151{
152 MAX_ANIMATED = 500
154
159template <class T, class D=T, class Conv=Identity<T>>
161{
162public:
163 ssize_t type, seq;
164 std::vector<uint32> globals;
165 std::vector<size_t> times[MAX_ANIMATED];
166 std::vector<T> data[MAX_ANIMATED];
167 // for nonlinear interpolations:
168 std::vector<T> in[MAX_ANIMATED], out[MAX_ANIMATED];
169 size_t sizes; // for fix function
170
171 bool uses(ssize_t anim) const
172 {
173 if (seq > -1)
174 anim = 0;
175 return ((data[anim].size()) > 0);
176 }
177
178 T getValue(ssize_t anim, size_t time)
179 {
180 // obtain a time value and a data range
181 if (seq >= 0 && seq < static_cast<int>(globals.size()))
182 {
183 // TODO
184 if (!globals[seq])
185 return T();
186 // if (globals[seq] == 0)
187 // time = 0;
188 // else
189 time = globalTime % globals[seq];
190 anim = 0;
191 }
192 if (data[anim].size() > 1 && times[anim].size() > 1)
193 {
194 size_t pos = 0;
195 float r;
196 const size_t max_time = times[anim][times[anim].size() - 1];
197 //if (max_time > 0)
198 // time %= max_time; // I think this might not be necessary?
199 if (time > max_time)
200 {
201 pos = times[anim].size() - 1;
202 r = 1.0f;
203
205 return data[anim][pos];
206 else if (type == INTERPOLATION_LINEAR)
207 return interpolate<T>(r, data[anim][pos], data[anim][pos]);
208 else if (type == INTERPOLATION_HERMITE)
209 {
210 // INTERPOLATION_HERMITE is only used in cameras afaik?
211 return interpolateHermite<T>(r, data[anim][pos], data[anim][pos], in[anim][pos], out[anim][pos]);
212 }
213 else if (type == INTERPOLATION_BEZIER)
214 {
215 //Is this used ingame or only by custom models?
216 return interpolateBezier<T>(r, data[anim][pos], data[anim][pos], in[anim][pos], out[anim][pos]);
217 }
218 else //this shouldn't appear!
219 return data[anim][pos];
220 }
221 else
222 {
223 for (size_t i = 0; i < times[anim].size() - 1; i++)
224 {
225 if (time >= times[anim][i] && time < times[anim][i + 1])
226 {
227 pos = i;
228 break;
229 }
230 }
231 const size_t t1 = times[anim][pos];
232 const size_t t2 = times[anim][pos + 1];
233 r = (time - t1) / static_cast<float>(t2 - t1);
234
236 return data[anim][pos];
237 else if (type == INTERPOLATION_LINEAR)
238 return interpolate<T>(r, data[anim][pos], data[anim][pos + 1]);
239 else if (type == INTERPOLATION_HERMITE)
240 {
241 // INTERPOLATION_HERMITE is only used in cameras afaik?
242 return interpolateHermite<T>(r, data[anim][pos], data[anim][pos + 1], in[anim][pos],
243 out[anim][pos]);
244 }
245 else if (type == INTERPOLATION_BEZIER)
246 {
247 //Is this used ingame or only by custom models?
248 return interpolateBezier<T>(r, data[anim][pos], data[anim][pos + 1], in[anim][pos], out[anim][pos]);
249 }
250 else //this shouldn't appear!
251 return data[anim][pos];
252 }
253 }
254 else
255 {
256 // default value
257 if (data[anim].size() == 0)
258 return T();
259 else
260 return data[anim][0];
261 }
262 }
263
264 void init(AnimationBlock& b, GameFile* f, std::vector<uint32>& gs)
265 {
266 globals = gs;
267 type = b.type;
268 seq = b.seq;
269
270 // times
271 if (b.nTimes != b.nKeys)
272 return;
273 //assert(b.nTimes == b.nKeys);
274 sizes = b.nTimes;
275 if (b.nTimes == 0)
276 return;
277
278 for (size_t j = 0; j < b.nTimes; j++)
279 {
280 const AnimationBlockHeader* pHeadTimes = reinterpret_cast<AnimationBlockHeader*>(f->getBuffer() + b.ofsTimes + j * sizeof(
282
283 const unsigned int* ptimes = reinterpret_cast<unsigned int*>(f->getBuffer() + pHeadTimes->ofsEntrys);
284 for (size_t i = 0; i < pHeadTimes->nEntrys; i++)
285 times[j].push_back(ptimes[i]);
286 }
287
288 // keyframes
289 for (size_t j = 0; j < b.nKeys; j++)
290 {
291 const AnimationBlockHeader* pHeadKeys = reinterpret_cast<AnimationBlockHeader*>(f->getBuffer() + b.ofsKeys + j * sizeof(
293
294 D* keys = reinterpret_cast<D*>(f->getBuffer() + pHeadKeys->ofsEntrys);
295 switch (type)
296 {
299 for (size_t i = 0; i < pHeadKeys->nEntrys; i++)
300 data[j].push_back(Conv::conv(keys[i]));
301 break;
303 case INTERPOLATION_BEZIER: //let's use same values like hermite?!?
304 for (size_t i = 0; i < pHeadKeys->nEntrys; i++)
305 {
306 data[j].push_back(Conv::conv(keys[i * 3]));
307 in[j].push_back(Conv::conv(keys[i * 3 + 1]));
308 out[j].push_back(Conv::conv(keys[i * 3 + 2]));
309 }
310 break;
311 default: ;
312 }
313 }
314 }
315
316 void init(AnimationBlock& b, GameFile& f, const modelAnimData& modelData)
317 {
318 globals = modelData.globalSequences;
319 type = b.type;
320 seq = b.seq;
321
322 // times
323 if (b.nTimes != b.nKeys)
324 return;
325 //assert(b.nTimes == b.nKeys);
326 sizes = b.nTimes;
327 if (b.nTimes == 0)
328 return;
329
330 for (size_t j = 0; j < b.nTimes; j++)
331 {
332 uint32* ptimes;
333 AnimationBlockHeader* pHeadTimes;
334 auto animIdIt = modelData.animIndexToAnimId.find(static_cast<uint>(j));
335 if (animIdIt == modelData.animIndexToAnimId.end())
336 continue;
337 auto it = modelData.animfiles.find(animIdIt->second);
338 if (it != modelData.animfiles.end())
339 {
340 GameFile* animfile = it->second.first;
341 GameFile* skelfile = it->second.second;
342 if (!animfile || !skelfile)
343 {
344 LOG_WARNING << "Animation data loading: null file pointer for animation index" << j;
345 continue;
346 }
347 if (!skelfile->setChunk("SKB1"))
348 {
349 LOG_WARNING << "Animation data loading: setChunk(SKB1) failed for" << skelfile->fullname();
350 continue;
351 }
352 unsigned char* skelBuf = skelfile->getBuffer();
353 unsigned char* animBuf = animfile->getBuffer();
354 // Only log if files claim to be open (not EOF) but have null buffers - this is the crash case
355 if (!skelBuf || !animBuf)
356 {
357 if ((!skelBuf && !skelfile->isEof()) || (!animBuf && !animfile->isEof()))
358 {
359 LOG_WARNING << "Animation data loading: null buffer despite file not being EOF - skelBuf:" << (void*)skelBuf
360 << "animBuf:" << (void*)animBuf
361 << "skelEof:" << skelfile->isEof()
362 << "animEof:" << animfile->isEof()
363 << "for files:" << skelfile->fullname() << "/" << animfile->fullname();
364 }
365 continue;
366 }
367 const size_t headerOffset = b.ofsTimes + j * sizeof(AnimationBlockHeader);
368 if (skelfile->getSize() < headerOffset + sizeof(AnimationBlockHeader))
369 {
370 LOG_WARNING << "Animation data loading: header offset" << headerOffset << "out of bounds for" << skelfile->fullname();
371 continue;
372 }
373 pHeadTimes = reinterpret_cast<AnimationBlockHeader*>(skelBuf + headerOffset);
374 if (pHeadTimes->ofsEntrys >= animfile->getSize() || pHeadTimes->nEntrys > 10000)
375 {
376 LOG_WARNING << "Animation data loading: invalid header data - ofsEntrys:" << pHeadTimes->ofsEntrys
377 << "nEntrys:" << pHeadTimes->nEntrys
378 << "animSize:" << animfile->getSize()
379 << "for" << animfile->fullname();
380 continue;
381 }
382 const size_t dataSize = static_cast<size_t>(pHeadTimes->nEntrys) * sizeof(uint32);
383 if (dataSize > animfile->getSize() || pHeadTimes->ofsEntrys > animfile->getSize() - dataSize)
384 {
385 LOG_WARNING << "Animation data loading: data size" << dataSize << "at offset" << pHeadTimes->ofsEntrys
386 << "exceeds file size" << animfile->getSize() << "for" << animfile->fullname();
387 continue;
388 }
389 ptimes = reinterpret_cast<uint32*>(animBuf + pHeadTimes->ofsEntrys);
390 }
391 else
392 {
393 const size_t headerOffset = b.ofsTimes + j * sizeof(AnimationBlockHeader);
394 if (f.getSize() < headerOffset + sizeof(AnimationBlockHeader))
395 continue;
396 pHeadTimes = reinterpret_cast<AnimationBlockHeader*>(f.getBuffer() + headerOffset);
397 if (pHeadTimes->ofsEntrys >= f.getSize() || pHeadTimes->nEntrys > 10000)
398 continue;
399 const size_t dataSize = static_cast<size_t>(pHeadTimes->nEntrys) * sizeof(uint32);
400 if (dataSize > f.getSize() || pHeadTimes->ofsEntrys > f.getSize() - dataSize)
401 continue;
402 ptimes = reinterpret_cast<uint32*>(f.getBuffer() + pHeadTimes->ofsEntrys);
403 }
404
405 for (size_t i = 0; i < pHeadTimes->nEntrys; i++)
406 times[j].push_back(ptimes[i]);
407 }
408
409 // keyframes
410 for (size_t j = 0; j < b.nKeys; j++)
411 {
412 D* keys;
413 AnimationBlockHeader* pHeadKeys;
414 auto animIdIt = modelData.animIndexToAnimId.find(static_cast<uint>(j));
415 if (animIdIt == modelData.animIndexToAnimId.end())
416 continue;
417 auto it = modelData.animfiles.find(animIdIt->second);
418 if (it != modelData.animfiles.end())
419 {
420 GameFile* animfile = it->second.first;
421 GameFile* skelfile = it->second.second;
422 if (!animfile || !skelfile)
423 {
424 LOG_WARNING << "Keyframe data loading: null file pointer for animation index" << j;
425 continue;
426 }
427 if (!skelfile->setChunk("SKB1"))
428 {
429 LOG_WARNING << "Keyframe data loading: setChunk(SKB1) failed for" << skelfile->fullname();
430 continue;
431 }
432 unsigned char* skelBuf = skelfile->getBuffer();
433 unsigned char* animBuf = animfile->getBuffer();
434 // Only log if files claim to be open (not EOF) but have null buffers - this is the crash case
435 if (!skelBuf || !animBuf)
436 {
437 if ((!skelBuf && !skelfile->isEof()) || (!animBuf && !animfile->isEof()))
438 {
439 LOG_WARNING << "Keyframe data loading: null buffer despite file not being EOF - skelBuf:" << (void*)skelBuf
440 << "animBuf:" << (void*)animBuf
441 << "skelEof:" << skelfile->isEof()
442 << "animEof:" << animfile->isEof()
443 << "for files:" << skelfile->fullname() << "/" << animfile->fullname();
444 }
445 continue;
446 }
447 const size_t headerOffset = b.ofsKeys + j * sizeof(AnimationBlockHeader);
448 if (skelfile->getSize() < headerOffset + sizeof(AnimationBlockHeader))
449 {
450 LOG_WARNING << "Keyframe data loading: header offset" << headerOffset << "out of bounds for" << skelfile->fullname();
451 continue;
452 }
453 pHeadKeys = reinterpret_cast<AnimationBlockHeader*>(skelBuf + headerOffset);
454 if (pHeadKeys->ofsEntrys >= animfile->getSize() || pHeadKeys->nEntrys > 10000)
455 {
456 LOG_WARNING << "Keyframe data loading: invalid header data - ofsEntrys:" << pHeadKeys->ofsEntrys
457 << "nEntrys:" << pHeadKeys->nEntrys
458 << "animSize:" << animfile->getSize()
459 << "for" << animfile->fullname();
460 continue;
461 }
462 const size_t multiplier = (type == INTERPOLATION_HERMITE || type == INTERPOLATION_BEZIER) ? 3 : 1;
463 const size_t dataSize = static_cast<size_t>(pHeadKeys->nEntrys) * multiplier * sizeof(D);
464 if (dataSize > animfile->getSize() || pHeadKeys->ofsEntrys > animfile->getSize() - dataSize)
465 {
466 LOG_WARNING << "Keyframe data loading: data size" << dataSize << "at offset" << pHeadKeys->ofsEntrys
467 << "exceeds file size" << animfile->getSize() << "for" << animfile->fullname();
468 continue;
469 }
470 keys = reinterpret_cast<D*>(animBuf + pHeadKeys->ofsEntrys);
471 }
472 else
473 {
474 const size_t headerOffset = b.ofsKeys + j * sizeof(AnimationBlockHeader);
475 if (f.getSize() < headerOffset + sizeof(AnimationBlockHeader))
476 continue;
477 pHeadKeys = reinterpret_cast<AnimationBlockHeader*>(f.getBuffer() + headerOffset);
478 if (pHeadKeys->ofsEntrys >= f.getSize() || pHeadKeys->nEntrys > 10000)
479 continue;
480 const size_t multiplier = (type == INTERPOLATION_HERMITE || type == INTERPOLATION_BEZIER) ? 3 : 1;
481 const size_t dataSize = static_cast<size_t>(pHeadKeys->nEntrys) * multiplier * sizeof(D);
482 if (dataSize > f.getSize() || pHeadKeys->ofsEntrys > f.getSize() - dataSize)
483 continue;
484 keys = reinterpret_cast<D*>(f.getBuffer() + pHeadKeys->ofsEntrys);
485 }
486
487 switch (type)
488 {
491 for (size_t i = 0; i < pHeadKeys->nEntrys; i++)
492 data[j].push_back(Conv::conv(keys[i]));
493 break;
495 case INTERPOLATION_BEZIER: //let's use same values like hermite?!?
496 for (size_t i = 0; i < pHeadKeys->nEntrys; i++)
497 {
498 data[j].push_back(Conv::conv(keys[i * 3]));
499 in[j].push_back(Conv::conv(keys[i * 3 + 1]));
500 out[j].push_back(Conv::conv(keys[i * 3 + 2]));
501 }
502 break;
503 default: ;
504 }
505 }
506 }
507
508 friend std::ostream& operator<<(std::ostream& outVal, const Animated& v)
509 {
510 if (v.sizes == 0)
511 return outVal;
512 outVal << " <type>" << v.type << "</type>" << std::endl;
513 outVal << " <seq>" << v.seq << "</seq>" << std::endl;
514 outVal << " <anims>" << std::endl;
515 for (size_t j = 0; j < v.sizes; j++)
516 {
517 if (j != 0)
518 {
519 // For global sequences, only process the first animation
520 if (v.seq > -1)
521 break;
522 continue; // only output walk animation for non-global sequences
523 }
524
525 if (v.uses((unsigned int)j))
526 {
527 outVal << " <anim id=\"" << j << "\" size=\"" << v.data[j].size() << "\">" << std::endl;
528 for (size_t k = 0; k < v.data[j].size(); k++)
529 {
530 // out << " <data time=\"" << v.times[j][k] << "\">" << v.data[j][k] << "</data>" << std::endl;
531 }
532 outVal << " </anim>" << std::endl;
533 }
534 }
535 outVal << " </anims>" << std::endl;
536 return outVal;
537 }
538};
539
541
542float frand();
543
544float randfloat(float lower, float upper);
545int randint(int lower, int upper);
#define LOG_WARNING
Definition Logger.h:12
@ MAX_ANIMATED
Definition animated.h:152
T interpolateBezier(const float r, const T &v1, const T &v2, const T &in, const T &outVal)
Bezier spline interpolation between two values.
Definition animated.h:67
T interpolateHermite(const float r, const T &v1, const T &v2, const T &in, const T &outVal)
Hermite spline interpolation between two values.
Definition animated.h:46
float randfloat(float lower, float upper)
Definition animated.cpp:10
glm::fquat interpolate< glm::fquat >(const float r, const glm::fquat &v1, const glm::fquat &v2)
Definition animated.h:84
T interpolate(const float r, const T &v1, const T &v2)
Linearly interpolate between two values.
Definition animated.h:32
float frand()
Definition animated.cpp:5
size_t globalTime
Global clock for global-sequence animations.
Definition animated.cpp:3
std::pair< size_t, size_t > AnimRange
A (start, end) frame range for an animation.
Definition animated.h:90
Interpolations
Interpolation modes used by animated values in M2 models.
Definition animated.h:97
@ INTERPOLATION_LINEAR
Linear interpolation.
Definition animated.h:99
@ INTERPOLATION_NONE
No interpolation (step).
Definition animated.h:98
@ INTERPOLATION_HERMITE
Hermite spline.
Definition animated.h:100
@ INTERPOLATION_BEZIER
Bezier spline.
Definition animated.h:101
int randint(int lower, int upper)
Definition animated.cpp:15
Animated< float, short, ShortToFloat > AnimatedShort
Definition animated.h:540
Generic animated value class that reads keyframe data from M2 files.
Definition animated.h:161
std::vector< T > data[MAX_ANIMATED]
Definition animated.h:166
size_t sizes
Definition animated.h:169
friend std::ostream & operator<<(std::ostream &outVal, const Animated &v)
Definition animated.h:508
T getValue(ssize_t anim, size_t time)
Definition animated.h:178
std::vector< uint32 > globals
Definition animated.h:164
std::vector< size_t > times[MAX_ANIMATED]
Definition animated.h:165
ssize_t seq
Definition animated.h:163
void init(AnimationBlock &b, GameFile *f, std::vector< uint32 > &gs)
Definition animated.h:264
void init(AnimationBlock &b, GameFile &f, const modelAnimData &modelData)
Definition animated.h:316
ssize_t type
Definition animated.h:163
std::vector< T > out[MAX_ANIMATED]
Definition animated.h:168
std::vector< T > in[MAX_ANIMATED]
Definition animated.h:168
bool uses(ssize_t anim) const
Definition animated.h:171
Abstract base class representing a file within the game data archive.
Definition GameFile.h:12
size_t getSize()
Total size of the file in bytes.
Definition GameFile.cpp:134
unsigned char * getBuffer() const
Pointer to the start of the internal buffer.
Definition GameFile.cpp:144
const std::string & fullname() const
Definition GameFile.h:56
bool setChunk(std::string chunkName, bool resetToStart=true)
Switch the active read window to the named chunk.
Definition GameFile.cpp:100
bool isEof()
True if the read pointer has reached the end of the file.
Definition GameFile.cpp:24
Identity conversion functor — returns its argument unchanged.
Definition animated.h:108
static const T & conv(const T &t)
Passthrough conversion.
Definition animated.h:111
Converts a packed 16-bit quaternion to a 32-bit float quaternion.
Definition animated.h:125
static const glm::fquat conv(const PACK_QUATERNION t)
Convert packed 16-bit quaternion to glm::fquat.
Definition animated.h:128
Converts opacity values stored as int16 to normalised float [0, 1].
Definition animated.h:140
static float conv(const short t)
Convert a short opacity value to float.
Definition animated.h:143
Holds per-model animation metadata: index-to-id mapping, external anim files, and global sequences.
Definition animated.h:18
std::vector< uint32 > globalSequences
Global sequence durations.
Definition animated.h:22
std::map< int16, std::pair< GameFile *, GameFile * > > animfiles
Maps anim ID to (anim file, skel file) pair.
Definition animated.h:21
std::map< uint, int16 > animIndexToAnimId
Maps animation index to animation ID.
Definition animated.h:20
Sub-block header for animated values in M2 models.
Packed 16-bit quaternion as stored in WoW 2.0+ M2 files.
Definition animated.h:119
int16 w
Packed quaternion components.
Definition animated.h:120
unsigned int uint
Definition types.h:36
int16_t int16
Definition types.h:33
uint32_t uint32
Definition types.h:34