436 lines
12 KiB
C++
436 lines
12 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This file is based on WME.
|
|
* http://dead-code.org/redir.php?target=wme
|
|
* Copyright (c) 2003-2013 Jan Nedoma and contributors
|
|
*/
|
|
|
|
#include "engines/wintermute/base/base_game.h"
|
|
#include "engines/wintermute/base/base_engine.h"
|
|
#include "engines/wintermute/base/gfx/3dutils.h"
|
|
#include "engines/wintermute/base/gfx/xanimation.h"
|
|
#include "engines/wintermute/base/gfx/xanimation_set.h"
|
|
#include "engines/wintermute/base/gfx/xframe_node.h"
|
|
#include "engines/wintermute/base/gfx/xmodel.h"
|
|
#include "engines/wintermute/base/gfx/xfile_loader.h"
|
|
#include "engines/wintermute/dcgf.h"
|
|
|
|
namespace Wintermute {
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Animation::Animation(BaseGame *inGame) : BaseClass(inGame) {
|
|
_targetFrame = nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Animation::~Animation() {
|
|
for (int32 i = 0; i < _posKeys.getSize(); i++) {
|
|
delete _posKeys[i];
|
|
}
|
|
_posKeys.removeAll();
|
|
|
|
for (int32 i = 0; i < _rotKeys.getSize(); i++) {
|
|
delete _rotKeys[i];
|
|
}
|
|
_rotKeys.removeAll();
|
|
|
|
for (int32 i = 0; i < _scaleKeys.getSize(); i++) {
|
|
delete _scaleKeys[i];
|
|
}
|
|
_scaleKeys.removeAll();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool Animation::findBone(FrameNode *rootFrame) {
|
|
if (!_targetName.empty()) {
|
|
_targetFrame = rootFrame->findFrame(_targetName.c_str());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool Animation::load(XFileData *xobj, AnimationSet *parentAnimSet) {
|
|
bool result;
|
|
XClassType objectType;
|
|
|
|
// Query the child for it's FileDataReference
|
|
if (xobj->isReference()) {
|
|
// The original data is found
|
|
result = xobj->getType(objectType);
|
|
if (!result) {
|
|
BaseEngine::LOG(0, "Couldn't retrieve object type while loading animation");
|
|
return result;
|
|
}
|
|
|
|
// The object must be a frame
|
|
if (objectType == kXClassFrame) {
|
|
// The frame is found, get its name
|
|
// The name will be used later by the findBone function to get
|
|
// a pointer to the target frame
|
|
if (_targetFrame) {
|
|
BaseEngine::LOG(0, "Animation frame name reference duplicated");
|
|
return false;
|
|
}
|
|
|
|
// get name
|
|
result = XModel::loadName(_targetName, xobj);
|
|
if (!result) {
|
|
BaseEngine::LOG(0, "Error retrieving frame name while loading animation");
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
// a data object is found, get its type
|
|
result = xobj->getType(objectType);
|
|
if (!result)
|
|
return false;
|
|
|
|
if (objectType == kXClassAnimationKey) {
|
|
// an animation key is found, load the data
|
|
XAnimationKeyObject *animationKey = xobj->getXAnimationKeyObject();
|
|
if (!animationKey)
|
|
return false;
|
|
result = loadAnimationKeyData(animationKey);
|
|
if (!result)
|
|
return false;
|
|
} else if (objectType == kXClassAnimationOptions) {
|
|
XAnimationOptionsObject *animationOptions = xobj->getXAnimationOptionsObject();
|
|
if (!animationOptions)
|
|
return false;
|
|
result = loadAnimationOptionData(animationOptions, parentAnimSet);
|
|
if (!result)
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool Animation::loadAnimationOptionData(XAnimationOptionsObject *animationOptionData, AnimationSet *parentAnimSet) {
|
|
if (animationOptionData->_openclosed && parentAnimSet)
|
|
parentAnimSet->_looping = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool Animation::loadAnimationKeyData(XAnimationKeyObject *animationKey) {
|
|
// get the type and count of the key
|
|
uint32 keyType = animationKey->_keyType;
|
|
uint32 numKeys = animationKey->_numKeys;
|
|
|
|
if (keyType == 0) { // rotation key
|
|
if (_rotKeys.getSize() != 0) {
|
|
BaseEngine::LOG(0, "Rotation key duplicated");
|
|
return false;
|
|
}
|
|
|
|
for (uint32 key = 0; key < numKeys; key++) {
|
|
const XTimedFloatKeys *fileRotKey = &animationKey->_keys[key];
|
|
assert(fileRotKey->_numTfkeys == 4);
|
|
|
|
BoneRotationKey *rotKey = new BoneRotationKey;
|
|
rotKey->_time = fileRotKey->_time;
|
|
// NOTE x files are w x y z and QUATERNIONS are x y z w
|
|
rotKey->_rotation._w = fileRotKey->_tfkeys[0];
|
|
rotKey->_rotation._x = fileRotKey->_tfkeys[1];
|
|
rotKey->_rotation._y = fileRotKey->_tfkeys[2];
|
|
rotKey->_rotation._z = fileRotKey->_tfkeys[3];
|
|
|
|
_rotKeys.add(rotKey);
|
|
}
|
|
} else if (keyType == 1) { // scale key
|
|
if (_scaleKeys.getSize() != 0) {
|
|
BaseEngine::LOG(0, "Scale key duplicated");
|
|
return false;
|
|
}
|
|
|
|
for (uint32 key = 0; key < numKeys; key++) {
|
|
const XTimedFloatKeys *fileScaleKey = &animationKey->_keys[key];
|
|
assert(fileScaleKey->_numTfkeys == 3);
|
|
|
|
BoneScaleKey *scaleKey = new BoneScaleKey;
|
|
scaleKey->_time = fileScaleKey->_time;
|
|
scaleKey->_scale._x = fileScaleKey->_tfkeys[0];
|
|
scaleKey->_scale._y = fileScaleKey->_tfkeys[1];
|
|
scaleKey->_scale._z = fileScaleKey->_tfkeys[2];
|
|
|
|
_scaleKeys.add(scaleKey);
|
|
}
|
|
} else if (keyType == 2) { // position key
|
|
if (_posKeys.getSize() != 0) {
|
|
BaseEngine::LOG(0, "Position key duplicated");
|
|
return false;
|
|
}
|
|
|
|
for (uint32 key = 0; key < numKeys; key++) {
|
|
const XTimedFloatKeys *filePosKey = &animationKey->_keys[key];
|
|
assert(filePosKey->_numTfkeys == 3);
|
|
|
|
BonePositionKey *posKey = new BonePositionKey;
|
|
posKey->_time = filePosKey->_time;
|
|
posKey->_pos._x = filePosKey->_tfkeys[0];
|
|
posKey->_pos._y = filePosKey->_tfkeys[1];
|
|
posKey->_pos._z = filePosKey->_tfkeys[2];
|
|
|
|
_posKeys.add(posKey);
|
|
}
|
|
} else if (keyType == 4) { // matrix key
|
|
if (_rotKeys.getSize() != 0 || _scaleKeys.getSize() != 0 || _posKeys.getSize() != 0) {
|
|
BaseEngine::LOG(0, "Matrix key duplicated");
|
|
return false;
|
|
}
|
|
|
|
DXQuaternion qRot;
|
|
DXVector3 transVec;
|
|
DXVector3 scaleVec;
|
|
|
|
for (uint32 key = 0; key < numKeys; key++) {
|
|
const XTimedFloatKeys *fileMatrixKey = &animationKey->_keys[key];
|
|
uint32 time = fileMatrixKey->_time;
|
|
assert(fileMatrixKey->_numTfkeys == 16);
|
|
|
|
DXMatrix keyData;
|
|
for (uint32 i = 0; i < 16; ++i) {
|
|
keyData._m4x4[i] = fileMatrixKey->_tfkeys[i];
|
|
}
|
|
|
|
// we always convert matrix keys to T-R-S
|
|
C3DUtils::decomposeMatrixSimple(&keyData, &transVec, &scaleVec, &qRot);
|
|
|
|
BonePositionKey *positionKey = new BonePositionKey;
|
|
positionKey->_time = time;
|
|
positionKey->_pos = transVec;
|
|
_posKeys.add(positionKey);
|
|
|
|
BoneScaleKey *scaleKey = new BoneScaleKey;
|
|
scaleKey->_time = time;
|
|
scaleKey->_scale = scaleVec;
|
|
_scaleKeys.add(scaleKey);
|
|
|
|
BoneRotationKey *rotationKey = new BoneRotationKey;
|
|
rotationKey->_time = time;
|
|
rotationKey->_rotation = qRot;
|
|
|
|
rotationKey->_rotation._x = -rotationKey->_rotation._x;
|
|
rotationKey->_rotation._y = -rotationKey->_rotation._y;
|
|
rotationKey->_rotation._z = -rotationKey->_rotation._z;
|
|
_rotKeys.add(rotationKey);
|
|
}
|
|
} else {
|
|
// the type is unknown, report the error
|
|
BaseEngine::LOG(0, "Unexpected animation key type (%d)", keyType);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool Animation::update(int slot, uint32 localTime, float animLerpValue) {
|
|
// no target frame = no animation keys
|
|
if (!_targetFrame) {
|
|
return true;
|
|
}
|
|
|
|
DXVector3 resultPos(0.0f, 0.0f, 0.0f);
|
|
DXVector3 resultScale(1.0f, 1.0f, 1.0f);
|
|
DXQuaternion resultRot(0.0f, 0.0f, 0.0f, 1.0f);
|
|
|
|
int keyIndex1, keyIndex2;
|
|
uint32 time1, time2;
|
|
float lerpValue;
|
|
|
|
bool animate = false;
|
|
|
|
// scale keys
|
|
if (_scaleKeys.getSize() > 0) {
|
|
keyIndex1 = keyIndex2 = 0;
|
|
|
|
// get the two keys between which the time is currently in
|
|
for (int32 key = 0; key < _scaleKeys.getSize(); key++) {
|
|
if (_scaleKeys[key]->_time > localTime) {
|
|
keyIndex2 = key;
|
|
|
|
if (key > 0) {
|
|
keyIndex1 = key - 1;
|
|
} else { // when ikey == 0, then dwp2 == 0
|
|
keyIndex1 = key;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
time1 = _scaleKeys[keyIndex1]->_time;
|
|
time2 = _scaleKeys[keyIndex2]->_time;
|
|
|
|
// get the lerp value
|
|
if ((time2 - time1) == 0) {
|
|
lerpValue = 0;
|
|
} else {
|
|
lerpValue = float(localTime - time1) / float(time2 - time1);
|
|
}
|
|
|
|
// apply the lerp function on the scale vector
|
|
DXVec3Lerp(&resultScale, &_scaleKeys[keyIndex1]->_scale, &_scaleKeys[keyIndex2]->_scale, lerpValue);
|
|
|
|
animate = true;
|
|
}
|
|
|
|
// rotation keys
|
|
if (_rotKeys.getSize() > 0) {
|
|
keyIndex1 = keyIndex2 = 0;
|
|
|
|
// get the two keys surrounding the current time value
|
|
for (int32 key = 0; key < _rotKeys.getSize(); key++) {
|
|
if (_rotKeys[key]->_time > localTime) {
|
|
keyIndex2 = key;
|
|
if (key > 0) {
|
|
keyIndex1 = key - 1;
|
|
} else { // when ikey == 0, then dwp2 == 0
|
|
keyIndex1 = key;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
time1 = _rotKeys[keyIndex1]->_time;
|
|
time2 = _rotKeys[keyIndex2]->_time;
|
|
|
|
// get the lerp value
|
|
if ((time2 - time1) == 0) {
|
|
lerpValue = 0;
|
|
} else {
|
|
lerpValue = float(localTime - time1) / float(time2 - time1);
|
|
}
|
|
|
|
// apply spherical lerp function
|
|
DXQuaternion q1, q2;
|
|
|
|
q1._x = -_rotKeys[keyIndex1]->_rotation._x;
|
|
q1._y = -_rotKeys[keyIndex1]->_rotation._y;
|
|
q1._z = -_rotKeys[keyIndex1]->_rotation._z;
|
|
q1._w = _rotKeys[keyIndex1]->_rotation._w;
|
|
|
|
q2._x = -_rotKeys[keyIndex2]->_rotation._x;
|
|
q2._y = -_rotKeys[keyIndex2]->_rotation._y;
|
|
q2._z = -_rotKeys[keyIndex2]->_rotation._z;
|
|
q2._w = _rotKeys[keyIndex2]->_rotation._w;
|
|
|
|
DXQuaternionSlerp(&resultRot, &q1, &q2, lerpValue);
|
|
|
|
animate = true;
|
|
}
|
|
|
|
// position keys
|
|
if (_posKeys.getSize() > 0) {
|
|
keyIndex1 = keyIndex2 = 0;
|
|
|
|
// get the two keys surrounding the time value
|
|
for (int32 key = 0; key < _posKeys.getSize(); key++) {
|
|
if (_posKeys[key]->_time > localTime) {
|
|
keyIndex2 = key;
|
|
if (key > 0) {
|
|
keyIndex1 = key - 1;
|
|
} else { // when ikey == 0, then dwp2 == 0
|
|
keyIndex1 = key;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
time1 = _posKeys[keyIndex1]->_time;
|
|
time2 = _posKeys[keyIndex2]->_time;
|
|
|
|
// get the lerp value
|
|
if (time2 - time1 == 0)
|
|
lerpValue = 0;
|
|
else
|
|
lerpValue = float(localTime - time1) / float(time2 - time1);
|
|
|
|
// apply the lerp function
|
|
DXVec3Lerp(&resultPos, &_posKeys[keyIndex1]->_pos, &_posKeys[keyIndex2]->_pos, lerpValue);
|
|
|
|
animate = true;
|
|
}
|
|
|
|
if (animate) {
|
|
_targetFrame->setTransformation(slot, resultPos, resultScale, resultRot, animLerpValue);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int Animation::getFrameTime() {
|
|
uint32 frameTime = 0;
|
|
uint32 prevTime;
|
|
|
|
// get the shortest frame time
|
|
prevTime = 0;
|
|
for (int32 i = 0; i < _rotKeys.getSize(); i++) {
|
|
if (frameTime == 0 || _rotKeys[i]->_time - prevTime < frameTime)
|
|
frameTime = _rotKeys[i]->_time - prevTime;
|
|
|
|
prevTime = _rotKeys[i]->_time;
|
|
}
|
|
|
|
prevTime = 0;
|
|
for (int32 i = 0; i < _posKeys.getSize(); i++) {
|
|
if (frameTime == 0 || _posKeys[i]->_time - prevTime < frameTime)
|
|
frameTime = _posKeys[i]->_time - prevTime;
|
|
|
|
prevTime = _posKeys[i]->_time;
|
|
}
|
|
|
|
prevTime = 0;
|
|
for (int32 i = 0; i < _scaleKeys.getSize(); i++) {
|
|
if (frameTime == 0 || _scaleKeys[i]->_time - prevTime < frameTime)
|
|
frameTime = _scaleKeys[i]->_time - prevTime;
|
|
|
|
prevTime = _scaleKeys[i]->_time;
|
|
}
|
|
|
|
return frameTime;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
uint32 Animation::getTotalTime() {
|
|
uint32 totalTime = 0;
|
|
if (_rotKeys.getSize() > 0) {
|
|
totalTime = MAX(totalTime, _rotKeys[_rotKeys.getSize() - 1]->_time);
|
|
}
|
|
|
|
if (_posKeys.getSize() > 0) {
|
|
totalTime = MAX(totalTime, _posKeys[_posKeys.getSize() - 1]->_time);
|
|
}
|
|
|
|
if (_scaleKeys.getSize() > 0) {
|
|
totalTime = MAX(totalTime, _scaleKeys[_scaleKeys.getSize() - 1]->_time);
|
|
}
|
|
|
|
return totalTime;
|
|
}
|
|
|
|
} // namespace Wintermute
|