Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
/* 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 "engines/stark/model/animhandler.h"
#include "engines/stark/model/model.h"
#include "engines/stark/model/skeleton_anim.h"
namespace Stark {
AnimHandler::AnimHandler() :
_model(nullptr),
_anim(nullptr),
_animTime(-1),
_framesBeforeCandidateReady(0),
_candidateAnim(nullptr),
_candidateAnimTime(-1),
_blendAnim(nullptr),
_blendAnimTime(-1),
_blendTimeRemaining(0) {
}
AnimHandler::~AnimHandler() {
}
void AnimHandler::setAnim(SkeletonAnim *anim) {
if (_candidateAnim == anim) {
return;
}
if (_anim == anim) {
// If we already have the correct anim, clean any candidates
// that may have been set but not yet enacted as the active anim.
_candidateAnim = nullptr;
_candidateAnimTime = -1;
_framesBeforeCandidateReady = 0;
return;
}
// Don't use new animations the first frame they are set.
// Scripts may change animation the very next frame,
// causing animations to blend with animations that
// were only visible for one frame, leading to animation
// jumps. Instead store them as candidates.
_framesBeforeCandidateReady = 2; // 2 because we are at the end of the frame
_candidateAnim = anim;
_candidateAnimTime = 0;
}
void AnimHandler::setModel(Model *model) {
_model = model;
}
void AnimHandler::setNode(uint32 time, BoneNode *bone, const BoneNode *parent) {
const Common::Array<BoneNode *> &bones = _model->getBones();
if (_blendTimeRemaining <= 0) {
_anim->getCoordForBone(time, bone->_idx, bone->_animPos, bone->_animRot);
} else {
// Blend the coordinates of the previous and the current animation
Math::Vector3d previousAnimPos, animPos;
Math::Quaternion previousAnimRot, animRot;
_blendAnim->getCoordForBone(_blendAnimTime, bone->_idx, previousAnimPos, previousAnimRot);
_anim->getCoordForBone(time, bone->_idx, animPos, animRot);
float blendingRatio = 1.0 - _blendTimeRemaining / (float)_blendDuration;
bone->_animPos = previousAnimPos + (animPos - previousAnimPos) * blendingRatio;
bone->_animRot = previousAnimRot.slerpQuat(animRot, blendingRatio);
}
if (parent) {
parent->_animRot.transform(bone->_animPos);
bone->_animPos = parent->_animPos + bone->_animPos;
bone->_animRot = parent->_animRot * bone->_animRot;
}
for (uint i = 0; i < bone->_children.size(); ++i) {
setNode(time, bones[bone->_children[i]], bone);
}
}
void AnimHandler::animate(uint32 time) {
if (!_anim && _candidateAnim) {
// This is the first time we animate this item.
enactCandidate();
}
if (_candidateAnim && _anim && _anim->getBoneCount() != _model->getBones().size()) {
// We changed to an incompatible model
enactCandidate();
// And the anim we were previously blending with is incompatible as well
if (_blendAnim && _blendAnim->getBoneCount() != _model->getBones().size()) {
stopBlending();
}
}
if (_candidateAnim && _framesBeforeCandidateReady > 0) {
_candidateAnimTime = time;
_framesBeforeCandidateReady--;
// We need to animate here, because the model may have
// changed from under us.
const Common::Array<BoneNode *> &bones = _model->getBones();
setNode(_animTime, bones[0], nullptr);
return;
}
if (_candidateAnim && _framesBeforeCandidateReady <= 0) {
if (_anim) {
startBlending();
}
enactCandidate();
}
int32 deltaTime = time - _animTime;
if (deltaTime < 0 || time > _blendDuration / 2) {
deltaTime = 33;
}
updateBlending(deltaTime);
// Start at root bone
// For each child
// - Set childs animation coordinate
// - Process that childs children
const Common::Array<BoneNode *> &bones = _model->getBones();
if (deltaTime >= 0) {
setNode(time, bones[0], nullptr);
_animTime = time;
}
}
void AnimHandler::enactCandidate() {
_anim = _candidateAnim;
_animTime = _candidateAnimTime;
_candidateAnim = nullptr;
_candidateAnimTime = -1;
_framesBeforeCandidateReady = 0;
}
void AnimHandler::startBlending() {
_blendTimeRemaining = _blendDuration;
_blendAnim = _anim;
_blendAnimTime = _animTime;
}
void AnimHandler::updateBlending(int32 deltaTime) {
_blendTimeRemaining -= deltaTime;
if (_blendTimeRemaining > 0) {
// If we are blending, also update the previous animation's time
_blendAnimTime += deltaTime;
if (_blendAnimTime >= (int32) _blendAnim->getLength()) {
_blendAnimTime = _blendAnim->getLength() - 1;
}
} else {
// Otherwise make sure blending is not enabled
stopBlending();
}
}
void AnimHandler::stopBlending() {
_blendAnim = nullptr;
_blendAnimTime = -1;
_blendTimeRemaining = 0;
}
void AnimHandler::resetBlending() {
stopBlending();
if (_candidateAnim) {
enactCandidate();
}
}
} // End of namespace Stark

View File

@@ -0,0 +1,81 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_ANIM_HANDLER_H
#define STARK_MODEL_ANIM_HANDLER_H
#include "common/scummsys.h"
namespace Stark {
class Model;
class BoneNode;
class SkeletonAnim;
/**
* Animate a skeletal model's bones according to an animation
*/
class AnimHandler {
public:
AnimHandler();
~AnimHandler();
/**
* Increment the animation timestamp, and apply bone animations if required
*/
void animate(uint32 time);
/** Set the skeletal model to animate */
void setModel(Model *model);
/** Set the skeletal animation to use */
void setAnim(SkeletonAnim *anim);
/** Stop blending and forget about the previous animation */
void resetBlending();
private:
void enactCandidate();
void startBlending();
void updateBlending(int32 deltaTime);
void stopBlending();
void setNode(uint32 time, BoneNode *bone, const BoneNode *parent);
static const uint32 _blendDuration = 300; // ms
SkeletonAnim *_anim;
int32 _animTime;
int32 _framesBeforeCandidateReady;
SkeletonAnim *_candidateAnim;
int32 _candidateAnimTime;
SkeletonAnim *_blendAnim;
int32 _blendAnimTime;
int32 _blendTimeRemaining;
Model *_model;
};
} // End of namespace Stark
#endif // STARK_MODEL_ANIM_HANDLER_H

View File

@@ -0,0 +1,233 @@
/* 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 "engines/stark/model/model.h"
#include "engines/stark/services/archiveloader.h"
#include "engines/stark/model/animhandler.h"
#include "engines/stark/gfx/texture.h"
#include "math/aabb.h"
namespace Stark {
Model::Model() :
_u1(0),
_u2(0.0) {
}
Model::~Model() {
for (Common::Array<VertNode *>::iterator it = _vertices.begin(); it != _vertices.end(); ++it)
delete *it;
for (Common::Array<Material *>::iterator it = _materials.begin(); it != _materials.end(); ++it)
delete *it;
for (Common::Array<Face *>::iterator it = _faces.begin(); it != _faces.end(); ++it)
delete *it;
for (Common::Array<BoneNode *>::iterator it = _bones.begin(); it != _bones.end(); ++it)
delete *it;
}
void Model::readFromStream(ArchiveReadStream *stream) {
uint32 id = stream->readUint32LE();
if (id != 4) {
error("Wrong magic 1 while reading actor '%d'", id);
}
uint32 format = stream->readUint32LE();
if (format == 256) {
_u1 = stream->readUint32LE();
} else if (format == 16) {
_u1 = 0;
} else {
error("Wrong format while reading actor '%d'", format);
}
uint32 id2 = stream->readUint32LE();
if (id2 != 0xDEADBABE) {
error("Wrong magic 2 while reading actor '%d'", id2);
}
_u2 = stream->readFloatLE();
uint32 numMaterials = stream->readUint32LE();
for (uint i = 0; i < numMaterials; ++i) {
Material *node = new Material();
node->name = stream->readString();
stream->readUint32LE(); // CHECKME: Unknown data
node->texture = stream->readString();
node->r = stream->readFloatLE();
node->g = stream->readFloatLE();
node->b = stream->readFloatLE();
_materials.push_back(node);
}
uint32 numUnknowns = stream->readUint32LE();
if (numUnknowns != 0) {
error("Found a mesh with numUnknowns != 0");
}
readBones(stream);
uint32 numMeshes = stream->readUint32LE();
if (numMeshes != 1) {
error("Found a mesh with numMeshes != 1 (%d)", numMeshes);
}
_name = stream->readString();
uint32 numFaces = stream->readUint32LE();
for (uint32 j = 0; j < numFaces; ++j) {
uint faceVertexIndexOffset = _vertices.size();
Face *face = new Face();
face->materialId = stream->readUint32LE();
uint32 numVertices = stream->readUint32LE();
for (uint32 k = 0; k < numVertices; ++k) {
VertNode *vert = new VertNode();
vert->_pos1 = stream->readVector3();
vert->_pos2 = stream->readVector3();
vert->_normal = stream->readVector3();
vert->_texS = stream->readFloatLE();
vert->_texT = stream->readFloatLE();
vert->_bone1 = stream->readUint32LE();
vert->_bone2 = stream->readUint32LE();
vert->_boneWeight = stream->readFloatLE();
_vertices.push_back(vert);
}
uint32 numTriangles = stream->readUint32LE();
face->vertexIndices.resize(numTriangles * 3); // 3 vertex indices per triangle
for (uint32 k = 0; k < numTriangles; ++k) {
face->vertexIndices[k * 3 + 0] = stream->readUint32LE() + faceVertexIndexOffset;
face->vertexIndices[k * 3 + 1] = stream->readUint32LE() + faceVertexIndexOffset;
face->vertexIndices[k * 3 + 2] = stream->readUint32LE() + faceVertexIndexOffset;
}
_faces.push_back(face);
}
buildBonesBoundingBoxes();
}
void Model::readBones(ArchiveReadStream *stream) {
uint32 numBones = stream->readUint32LE();
for (uint32 i = 0; i < numBones; ++i) {
BoneNode *node = new BoneNode();
node->_name = stream->readString();
node->_u1 = stream->readFloatLE();
uint32 len = stream->readUint32LE();
for (uint32 j = 0; j < len; ++j)
node->_children.push_back(stream->readUint32LE());
node->_idx = _bones.size();
_bones.push_back(node);
}
for (uint32 i = 0; i < numBones; ++i) {
BoneNode *node = _bones[i];
for (uint j = 0; j < node->_children.size(); ++j) {
_bones[node->_children[j]]->_parent = i;
}
}
}
void Model::buildBonesBoundingBoxes() {
for (uint i = 0; i < _bones.size(); i++) {
buildBoneBoundingBox(_bones[i]);
}
}
void Model::buildBoneBoundingBox(BoneNode *bone) const {
bone->_boundingBox.reset();
// Add all the vertices with a non zero weight for the bone to the bone's bounding box
for (uint k = 0; k < _vertices.size(); k++) {
VertNode *vert = _vertices[k];
if (vert->_bone1 == bone->_idx) {
bone->_boundingBox.expand(vert->_pos1);
}
if (vert->_bone2 == bone->_idx) {
bone->_boundingBox.expand(vert->_pos2);
}
}
}
bool Model::intersectRay(const Math::Ray &ray) const {
for (uint i = 0; i < _bones.size(); i++) {
if (_bones[i]->intersectRay(ray)) {
return true;
}
}
return false;
}
void Model::updateBoundingBox() {
_boundingBox.reset();
for (uint i = 0; i < _bones.size(); i++) {
_bones[i]->expandModelSpaceBB(_boundingBox);
}
}
Math::AABB Model::getBoundingBox() const {
return _boundingBox;
}
bool BoneNode::intersectRay(const Math::Ray &ray) const {
Math::Ray localRay = ray;
localRay.translate(-_animPos);
localRay.rotate(_animRot.inverse());
return localRay.intersectAABB(_boundingBox);
}
void BoneNode::expandModelSpaceBB(Math::AABB &aabb) const {
// Transform the bounding box
Math::Vector3d min = _boundingBox.getMin();
Math::Vector3d max = _boundingBox.getMax();
Math::Vector3d verts[8];
verts[0].set(min.x(), min.y(), min.z());
verts[1].set(max.x(), min.y(), min.z());
verts[2].set(min.x(), max.y(), min.z());
verts[3].set(min.x(), min.y(), max.z());
verts[4].set(max.x(), max.y(), min.z());
verts[5].set(max.x(), min.y(), max.z());
verts[6].set(min.x(), max.y(), max.z());
verts[7].set(max.x(), max.y(), max.z());
for (int i = 0; i < 8; ++i) {
_animRot.transform(verts[i]);
verts[i] += _animPos;
aabb.expand(verts[i]);
}
}
} // End of namespace Stark

133
engines/stark/model/model.h Normal file
View File

@@ -0,0 +1,133 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_MODEL_H
#define STARK_MODEL_MODEL_H
#include "common/array.h"
#include "common/str.h"
#include "math/ray.h"
#include "math/vector3d.h"
namespace Stark {
namespace Gfx {
class TextureSet;
}
class ArchiveReadStream;
class VertNode {
public:
Math::Vector3d _pos1, _pos2;
Math::Vector3d _normal;
float _texS, _texT;
uint32 _bone1, _bone2;
float _boneWeight;
};
struct Face {
uint32 materialId;
Common::Array<uint32> vertexIndices;
Face() : materialId(0) {}
};
struct Material {
Common::String name;
Common::String texture;
float r, g, b;
bool doubleSided;
Material() : r(0), g(0), b(0), doubleSided(false) {};
};
class BoneNode {
public:
BoneNode() : _parent(-1), _idx(0), _u1(0) {}
~BoneNode() { }
/** Perform a collision test with the ray */
bool intersectRay(const Math::Ray &ray) const;
/** Expand a bounding box with the model space BB of this bone */
void expandModelSpaceBB(Math::AABB &aabb) const;
Common::String _name;
float _u1;
Common::Array<uint32> _children;
int _parent;
uint32 _idx;
Math::Vector3d _animPos;
Math::Quaternion _animRot;
/** Bone space bounding box */
Math::AABB _boundingBox;
};
/**
* A 3D Model
*/
class Model {
public:
Model();
~Model();
/**
* Try and initialise object from the specified stream
*/
void readFromStream(ArchiveReadStream *stream);
const Common::Array<VertNode *> &getVertices() const { return _vertices; }
const Common::Array<Face *> &getFaces() const { return _faces; }
const Common::Array<Material *> &getMaterials() const { return _materials; }
const Common::Array<BoneNode *> &getBones() const { return _bones; };
/** Perform a collision test with a ray */
bool intersectRay(const Math::Ray &ray) const;
/** Update the model bounding box with the current animation state */
void updateBoundingBox();
/** Retrieve the model space bounding box for the current animation state */
Math::AABB getBoundingBox() const;
private:
void buildBonesBoundingBoxes();
void buildBoneBoundingBox(BoneNode *bone) const;
void readBones(ArchiveReadStream *stream);
Common::String _name;
uint32 _u1;
float _u2;
Common::Array<VertNode *> _vertices;
Common::Array<Material *> _materials;
Common::Array<Face *> _faces;
Common::Array<BoneNode *> _bones;
Math::AABB _boundingBox;
};
} // End of namespace Stark
#endif // STARK_MODEL_MODEL_H

View File

@@ -0,0 +1,107 @@
/* 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 "engines/stark/model/skeleton_anim.h"
#include "engines/stark/services/archiveloader.h"
namespace Stark {
SkeletonAnim::SkeletonAnim() :
_id(0),
_ver(0),
_u1(0),
_u2(0),
_time(0) {
}
void SkeletonAnim::createFromStream(ArchiveReadStream *stream) {
_id = stream->readUint32LE();
_ver = stream->readUint32LE();
if (_ver == 3) {
_u1 = 0;
_time = stream->readUint32LE();
_u2 = stream->readUint32LE();
} else {
_u1 = stream->readUint32LE();
_u2 = stream->readUint32LE();
_time = stream->readUint32LE();
}
if (_u2 != 0xdeadbabe) {
error("Wrong magic while reading animation");
}
uint32 num = stream->readUint32LE();
_boneAnims.resize(num);
for (uint32 i = 0; i < num; ++i) {
uint32 bone = stream->readUint32LE();
uint32 numKeys = stream->readUint32LE();
BoneAnim &boneAnim = _boneAnims[bone];
boneAnim._keys.resize(numKeys);
for (uint32 j = 0; j < numKeys; ++j) {
AnimKey &key = boneAnim._keys[j];
key._time = stream->readUint32LE();
key._rot = stream->readQuaternion();
key._pos = stream->readVector3();
}
}
}
void SkeletonAnim::getCoordForBone(uint32 time, int boneIdx, Math::Vector3d &pos, Math::Quaternion &rot) const {
const Common::Array<AnimKey> &keys = _boneAnims[boneIdx]._keys;
if (keys.size() == 1) {
// There is only one key for this bone, don't bother searching which one to use
pos = keys[0]._pos;
rot = keys[0]._rot;
return;
}
for (Common::Array<AnimKey>::const_iterator it = keys.begin(); it < keys.end(); ++it) {
if (it->_time > time) {
// Between two key frames, interpolate
const AnimKey *a = it;
--it;
const AnimKey *b = it;
float t = (float)(time - b->_time) / (float)(a->_time - b->_time);
pos = b->_pos + (a->_pos - b->_pos) * t;
rot = b->_rot.slerpQuat(a->_rot, t);
return;
} else if (it->_time == time || it == keys.end() - 1){
// At a key frame
// If not right one but didn't find any, then use last one as default
const AnimKey *key = it;
pos = key->_pos;
rot = key->_rot;
if (it == keys.end() - 1) {
warning("Unable to find keyframe for bone '%d' at %d ms, using default", boneIdx, time);
}
return;
}
}
}
} // End of namespace Stark

View File

@@ -0,0 +1,73 @@
/* 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/>.
*
*/
#ifndef STARK_MODEL_SKELETON_ANIM_H
#define STARK_MODEL_SKELETON_ANIM_H
#include "math/quat.h"
#include "math/vector3d.h"
#include "common/array.h"
namespace Stark {
class ArchiveReadStream;
/**
* Data structure responsible for skeletal animation of an actor object.
*/
class SkeletonAnim {
public:
SkeletonAnim();
void createFromStream(ArchiveReadStream *stream);
/**
* Get the interpolated bone coordinate for a given bone at a given animation timestamp
*/
void getCoordForBone(uint32 time, int boneIdx, Math::Vector3d &pos, Math::Quaternion &rot) const;
/**
* Get total animation length (in ms)
*/
uint32 getLength() const { return _time; }
/** The number of bones a skeleton must have to play this animation */
uint32 getBoneCount() const { return _boneAnims.size(); }
private:
struct AnimKey {
uint32 _time;
Math::Quaternion _rot;
Math::Vector3d _pos;
};
struct BoneAnim {
Common::Array<AnimKey> _keys;
};
uint32 _id, _ver, _u1, _u2, _time;
Common::Array<BoneAnim> _boneAnims;
};
} // End of namespace Stark
#endif // STARK_MODEL_SKELETON_ANIM_H