mirror of
https://github.com/libretro/scummvm.git
synced 2025-01-10 03:40:25 +00:00
379 lines
9.6 KiB
C++
379 lines
9.6 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/>.
|
|
*
|
|
*/
|
|
|
|
#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;
|
|
|
|
_duration = 1000 * data->readFloatLE();
|
|
_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();
|
|
|
|
if (_operation == 3) { // Translation
|
|
_translations = new AnimTranslation[_count];
|
|
for (int j = 0; j < _count; j++) {
|
|
_translations[j]._vec.readFromStream(data);
|
|
_translations[j]._time = 1000 * data->readFloatLE();
|
|
}
|
|
} else if (_operation == 4) { // Rotation
|
|
_rotations = new AnimRotation[_count];
|
|
for (int j = 0; j < _count; j++) {
|
|
_rotations[j]._quat.readFromStream(data);
|
|
_rotations[j]._time = 1000 * data->readFloatLE();
|
|
}
|
|
} 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
|