scummvm/engines/grim/emi/skeleton.cpp
Paweł Kołodziejski 06902574b4
GRIM: Janitorial
2022-06-08 01:12:00 +02:00

228 lines
6.8 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/vector4d.h"
#include "math/quat.h"
#include "engines/grim/debug.h"
#include "engines/grim/emi/animationemi.h"
#include "engines/grim/emi/skeleton.h"
namespace Grim {
#define ROTATE_OP 4
#define TRANSLATE_OP 3
Skeleton::Skeleton(const Common::String &filename, Common::SeekableReadStream *data) :
_numJoints(0), _joints(nullptr), _animLayers(nullptr) {
loadSkeleton(data);
}
Skeleton::~Skeleton() {
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
delete[] _animLayers[i]._jointAnims;
}
delete[] _animLayers;
delete[] _joints;
}
void Skeleton::loadSkeleton(Common::SeekableReadStream *data) {
_numJoints = data->readUint32LE();
_joints = new Joint[_numJoints];
char inString[32];
for (int i = 0; i < _numJoints; i++) {
data->read(inString, 32);
_joints[i]._name = inString;
data->read(inString, 32);
_joints[i]._parent = inString;
_joints[i]._trans.readFromStream(data);
_joints[i]._quat.readFromStream(data);
_joints[i]._parentIndex = findJointIndex(_joints[i]._parent);
_jointsMap[_joints[i]._name] = i;
}
initBones();
resetAnim();
}
void Skeleton::initBone(int index) {
// The matrix should have identity at this point.
_joints[index]._quat.toMatrix(_joints[index]._relMatrix);
// Might need to be translate instead.
_joints[index]._relMatrix.setPosition(_joints[index]._trans);
if (_joints[index]._parentIndex == -1) {
_joints[index]._absMatrix = _joints[index]._relMatrix;
} else {
_joints[index]._absMatrix = _joints[_joints[index]._parentIndex]._absMatrix;
// Might be the other way around.
_joints[index]._absMatrix = _joints[index]._absMatrix * _joints[index]._relMatrix;
}
}
void Skeleton::initBones() {
for (int i = 0; i < _numJoints; i++) {
initBone(i);
}
_animLayers = new AnimationLayer[MAX_ANIMATION_LAYERS];
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
_animLayers[i]._jointAnims = new JointAnimation[_numJoints];
}
}
void Skeleton::resetAnim() {
for (int i = 0; i < MAX_ANIMATION_LAYERS; ++i) {
AnimationLayer &layer = _animLayers[i];
for (int j = 0; j < _numJoints; ++j) {
JointAnimation &jointAnim = layer._jointAnims[j];
jointAnim._pos.set(0.f, 0.f, 0.f);
jointAnim._quat.set(0.f, 0.f, 0.f, 1.f);
jointAnim._transWeight = 0.0f;
jointAnim._rotWeight = 0.0f;
}
}
for (int i = 0; i < _numJoints; ++i) {
_joints[i]._animMatrix = _joints[i]._relMatrix;
_joints[i]._animQuat = _joints[i]._quat;
}
}
void Skeleton::animate() {
resetAnim();
// This first pass over the animations calculates bone-specific sums of blend weights for all
// animation layers. The sums must be pre-computed in order to be able to normalize the blend
// weights properly in the next step.
for (Common::List<AnimationStateEmi*>::iterator j = _activeAnims.begin(); j != _activeAnims.end(); ++j) {
(*j)->computeWeights();
}
// Now make a second pass over the animations to actually accumulate animation to layers.
for (Common::List<AnimationStateEmi*>::iterator j = _activeAnims.begin(); j != _activeAnims.end(); ++j) {
(*j)->animate();
}
// Blend the layers together in priority order to produce the final result. Highest priority
// layer will get as much weight as it wants, while the next highest priority will get the
// amount that remains and so on.
for (int i = 0; i < _numJoints; ++i) {
float remainingTransWeight = 1.0f;
float remainingRotWeight = 1.0f;
for (int j = MAX_ANIMATION_LAYERS - 1; j >= 0; --j) {
AnimationLayer &layer = _animLayers[j];
JointAnimation &jointAnim = layer._jointAnims[i];
if (remainingRotWeight > 0.0f && jointAnim._rotWeight != 0.0f) {
Math::Vector3d pos = _joints[i]._animMatrix.getPosition();
_joints[i]._animQuat = _joints[i]._animQuat.slerpQuat(_joints[i]._animQuat * jointAnim._quat, remainingRotWeight);
_joints[i]._animQuat.toMatrix(_joints[i]._animMatrix);
_joints[i]._animMatrix.setPosition(pos);
remainingRotWeight *= 1.0f - jointAnim._rotWeight;
}
if (remainingTransWeight > 0.0f && jointAnim._transWeight != 0.0f) {
Math::Vector3d pos = _joints[i]._animMatrix.getPosition();
Math::Vector3d delta = jointAnim._pos;
_joints[i]._animMatrix.setPosition(pos + delta * remainingTransWeight);
remainingTransWeight *= 1.0f - jointAnim._transWeight;
}
if (remainingRotWeight <= 0.0f && remainingTransWeight <= 0.0f)
break;
}
}
commitAnim();
}
void Skeleton::addAnimation(AnimationStateEmi *anim) {
_activeAnims.push_back(anim);
}
void Skeleton::removeAnimation(AnimationStateEmi *anim) {
_activeAnims.remove(anim);
}
void Skeleton::commitAnim() {
for (int m = 0; m < _numJoints; ++m) {
const Joint *parent = getParentJoint(&_joints[m]);
if (parent) {
_joints[m]._finalMatrix = parent->_finalMatrix * _joints[m]._animMatrix;
_joints[m]._finalQuat = parent->_finalQuat * _joints[m]._animQuat;
} else {
_joints[m]._finalMatrix = _joints[m]._animMatrix;
_joints[m]._finalQuat = _joints[m]._animQuat;
}
}
}
int Skeleton::findJointIndex(const Common::String &name) const {
JointMap::const_iterator it = _jointsMap.find(name);
if (it != _jointsMap.end())
return it->_value;
return -1;
}
bool Skeleton::hasJoint(const Common::String &name) const {
return name.empty() || findJointIndex(name) >= 0;
}
Joint *Skeleton::getJointNamed(const Common::String &name) const {
int idx = findJointIndex(name);
if (name.empty()) {
return & _joints[0];
} else if (idx == -1) {
warning("Skeleton has no joint named '%s'!", name.c_str());
return nullptr;
} else {
return & _joints[idx];
}
}
Joint *Skeleton::getParentJoint(const Joint *j) const {
assert(j);
if (j->_parentIndex == -1)
return nullptr;
return &_joints[j->_parentIndex];
}
int Skeleton::getJointIndex(const Joint *j) const {
int idx = j - _joints;
assert(idx >= 0 && idx < _numJoints);
return idx;
}
AnimationLayer* Skeleton::getLayer(int priority) const {
assert(priority >= 0 && priority < MAX_ANIMATION_LAYERS);
return &_animLayers[priority];
}
} // end of namespace Grim