scummvm/engines/grim/emi/animationemi.cpp
Christian Krause d8ef15acc2 EMI: Keep time of chores and animations in sync
Both the animation components/tracks and the chores itself keep track of
the current time independently:

animation component:
- AnimationStateEmi::_time
- initialized with 0
- after the first update() call, _time is advanced by the frametime

chores:
- Costume::_currTime
- initialized with -1
- during the first update() call, _currTime is set first to 0
- during the next update() call, _currTime is advanced by the frametime

EMICostume::update() updates first the chore and then all components
with the same frame time. This has the effect, that the internal time
base of the chore and the animation has always an initial offset (the
value of the frametime of the first update() call).

For some costumes, the chore will pause the animation by setting the
according key for that animation at the end of the animation. However,
since the animation component has a different time base, it has already
reached that point and may have already disabled itself entirely
(instead of just being paused by the chore).

This bug fix let the animation set _time to 0 in the first update() call, too.
Any consecutive update() call will advance the time by the frametime.
This will keep the two time bases of the chore and the animation component
in sync.
2015-05-15 21:26:01 +02:00

385 lines
9.7 KiB
C++

/* ResidualVM - A 3D game interpreter
*
* ResidualVM 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*/
#include "common/stream.h"
#include "math/vector3d.h"
#include "math/quat.h"
#include "engines/grim/resource.h"
#include "engines/grim/emi/animationemi.h"
#include "common/textconsole.h"
namespace Grim {
AnimationEmi::AnimationEmi(const Common::String &filename, Common::SeekableReadStream *data) :
_name(""), _duration(0.0f), _numBones(0), _bones(nullptr) {
_fname = filename;
loadAnimation(data);
}
// Use modelemi's solution for the LA-strings.
void AnimationEmi::loadAnimation(Common::SeekableReadStream *data) {
int len = data->readUint32LE();
char *inString = new char[len];
data->read(inString, len);
_name = inString;
delete[] inString;
char temp[4];
data->read(temp, 4);
_duration = 1000 * get_float(temp);
_numBones = data->readUint32LE();
_bones = new Bone[_numBones];
for (int i = 0; i < _numBones; i++) {
_bones[i].loadBinary(data);
}
}
AnimationEmi::~AnimationEmi() {
g_resourceloader->uncacheAnimationEmi(this);
delete[] _bones;
}
void Bone::loadBinary(Common::SeekableReadStream *data) {
uint32 len = data->readUint32LE();
char *inString = new char[len];
data->read(inString, len);
_boneName = inString;
delete[] inString;
_operation = data->readUint32LE();
_priority = data->readUint32LE();
_c = data->readUint32LE();
_count = data->readUint32LE();
char temp[4];
if (_operation == 3) { // Translation
_translations = new AnimTranslation[_count];
for (int j = 0; j < _count; j++) {
_translations[j]._vec.readFromStream(data);
data->read(temp, 4);
_translations[j]._time = 1000 * get_float(temp);
}
} else if (_operation == 4) { // Rotation
_rotations = new AnimRotation[_count];
for (int j = 0; j < _count; j++) {
_rotations[j]._quat.readFromStream(data);
data->read(temp, 4);
_rotations[j]._time = 1000 * get_float(temp);
}
} else {
error("Unknown animation-operation %d", _operation);
}
}
Bone::~Bone() {
if (_operation == 3) {
delete[] _translations;
} else if (_operation == 4) {
delete[] _rotations;
}
}
AnimationStateEmi::AnimationStateEmi(const Common::String &anim) :
_skel(nullptr), _looping(false), _active(false), _paused(false),
_fadeMode(Animation::None), _fade(1.0f), _fadeLength(0), _time(-1), _startFade(1.0f),
_boneJoints(nullptr) {
_anim = g_resourceloader->getAnimationEmi(anim);
if (_anim)
_boneJoints = new int[_anim->_numBones];
}
AnimationStateEmi::~AnimationStateEmi() {
deactivate();
delete[] _boneJoints;
}
void AnimationStateEmi::activate() {
if (!_active) {
_active = true;
if (_skel)
_skel->addAnimation(this);
}
}
void AnimationStateEmi::deactivate() {
if (_active) {
_active = false;
if (_skel)
_skel->removeAnimation(this);
}
}
void AnimationStateEmi::update(uint time) {
if (!_active)
return;
if (!_anim) {
deactivate();
return;
}
if (!_paused) {
int durationMs = (int)_anim->_duration;
if (_time >= durationMs) {
if (_looping) {
_time = _time % durationMs;
} else {
if (_fadeMode != Animation::FadeOut)
deactivate();
}
}
if (_time < 0) {
_time = 0;
} else {
_time += time;
}
}
if (_fadeMode != Animation::None) {
if (_fadeMode == Animation::FadeIn) {
_fade += (float)time * (1.0f - _startFade) / _fadeLength;
if (_fade >= 1.f) {
_fade = 1.f;
_fadeMode = Animation::None;
}
} else {
_fade -= (float)time * _startFade / _fadeLength;
if (_fade <= 0.f) {
_fade = 0.f;
// Don't reset the _fadeMode here. This way if fadeOut() was called
// on a looping chore its keyframe animations will remain faded out
// when it calls play() again.
deactivate();
return;
}
}
}
}
void AnimationStateEmi::computeWeights() {
if (_fade <= 0.0f)
return;
for (int bone = 0; bone < _anim->_numBones; ++bone) {
Bone &curBone = _anim->_bones[bone];
int jointIndex = _boneJoints[bone];
if (jointIndex == -1)
continue;
AnimationLayer *layer = _skel->getLayer(curBone._priority);
JointAnimation &jointAnim = layer->_jointAnims[jointIndex];
if (curBone._rotations) {
jointAnim._rotWeight += _fade;
}
if (curBone._translations) {
jointAnim._transWeight += _fade;
}
}
}
void AnimationStateEmi::animate() {
if (_fade <= 0.0f)
return;
if (_time < 0)
return;
for (int bone = 0; bone < _anim->_numBones; ++bone) {
Bone &curBone = _anim->_bones[bone];
int jointIndex = _boneJoints[bone];
if (jointIndex == -1)
continue;
Joint *target = &_skel->_joints[jointIndex];
AnimationLayer *layer = _skel->getLayer(curBone._priority);
JointAnimation &jointAnim = layer->_jointAnims[jointIndex];
if (curBone._rotations) {
int keyfIdx = -1;
Math::Quaternion quat;
// Normalize the weight so that the sum of applied weights will equal 1.
float normalizedRotWeight = _fade;
if (jointAnim._rotWeight > 1.0f) {
// Note: Division by unnormalized sum of weights.
normalizedRotWeight = _fade / jointAnim._rotWeight;
}
for (int curKeyFrame = 0; curKeyFrame < curBone._count; curKeyFrame++) {
if (curBone._rotations[curKeyFrame]._time >= _time) {
keyfIdx = curKeyFrame;
break;
}
}
if (keyfIdx == 0) {
quat = curBone._rotations[0]._quat;
}
else if (keyfIdx != -1) {
float timeDelta = curBone._rotations[keyfIdx]._time - curBone._rotations[keyfIdx - 1]._time;
float interpVal = (_time - curBone._rotations[keyfIdx - 1]._time) / timeDelta;
quat = curBone._rotations[keyfIdx - 1]._quat.slerpQuat(curBone._rotations[keyfIdx]._quat, interpVal);
}
else {
quat = curBone._rotations[curBone._count - 1]._quat;
}
Math::Quaternion &quatFinal = jointAnim._quat;
quat = target->_quat.inverse() * quat;
quat = quatFinal * quat;
quatFinal = quatFinal.slerpQuat(quat, normalizedRotWeight);
}
if (curBone._translations) {
int keyfIdx = -1;
Math::Vector3d vec;
// Normalize the weight so that the sum of applied weights will equal 1.
float normalizedTransWeight = _fade;
if (jointAnim._transWeight > 1.0f) {
// Note: Division by unnormalized sum of weights.
normalizedTransWeight = _fade / jointAnim._transWeight;
}
for (int curKeyFrame = 0; curKeyFrame < curBone._count; curKeyFrame++) {
if (curBone._translations[curKeyFrame]._time >= _time) {
keyfIdx = curKeyFrame;
break;
}
}
if (keyfIdx == 0) {
vec = curBone._translations[0]._vec;
}
else if (keyfIdx != -1) {
float timeDelta = curBone._translations[keyfIdx]._time - curBone._translations[keyfIdx - 1]._time;
float interpVal = (_time - curBone._translations[keyfIdx - 1]._time) / timeDelta;
vec = curBone._translations[keyfIdx - 1]._vec +
(curBone._translations[keyfIdx]._vec - curBone._translations[keyfIdx - 1]._vec) * interpVal;
}
else {
vec = curBone._translations[curBone._count - 1]._vec;
}
Math::Vector3d &posFinal = jointAnim._pos;
vec = vec - target->_relMatrix.getPosition();
posFinal = posFinal + vec * normalizedTransWeight;
}
}
}
void AnimationStateEmi::play() {
if (!_active) {
_time = -1;
if (_fadeMode == Animation::FadeOut)
_fadeMode = Animation::None;
if (_fadeMode == Animation::FadeIn || _fade > 0.f)
activate();
}
_paused = false;
}
void AnimationStateEmi::stop() {
_fadeMode = Animation::None;
_time = -1;
deactivate();
}
void AnimationStateEmi::setPaused(bool paused) {
_paused = paused;
}
void AnimationStateEmi::setLooping(bool loop) {
_looping = loop;
}
void AnimationStateEmi::setSkeleton(Skeleton *skel) {
if (skel != _skel) {
if (_skel)
_skel->removeAnimation(this);
_skel = skel;
if (_active)
skel->addAnimation(this);
if (_anim) {
for (int i = 0; i < _anim->_numBones; ++i) {
_boneJoints[i] = skel->findJointIndex(_anim->_bones[i]._boneName);
}
}
}
}
void AnimationStateEmi::fade(Animation::FadeMode mode, int fadeLength) {
if (mode == Animation::None) {
_fade = 1.f;
} else if (_fadeMode != Animation::FadeOut && mode == Animation::FadeIn) {
_fade = 0.f;
}
_startFade = _fade;
_fadeMode = mode;
_fadeLength = fadeLength;
}
void AnimationStateEmi::advance(uint msecs) {
if (_time >= 0) {
_time += msecs;
} else {
_time = msecs;
}
}
void AnimationStateEmi::saveState(SaveGame *state) {
state->writeBool(_looping);
state->writeBool(_active);
state->writeBool(_paused);
state->writeLESint32(_time);
state->writeFloat(_fade);
state->writeFloat(_startFade);
state->writeLESint32((int)_fadeMode);
state->writeLESint32(_fadeLength);
}
void AnimationStateEmi::restoreState(SaveGame *state) {
if (state->saveMinorVersion() >= 10) {
_looping = state->readBool();
bool active = state->readBool();
_paused = state->readBool();
if (state->saveMinorVersion() < 22) {
_time = (uint)state->readFloat();
} else {
_time = state->readLESint32();
}
_fade = state->readFloat();
_startFade = state->readFloat();
_fadeMode = (Animation::FadeMode)state->readLESint32();
_fadeLength = state->readLESint32();
if (active)
activate();
}
}
} // end of namespace Grim