refactor: Reimplement GR2 models and JBA animations
Still not working properly...
This commit is contained in:
parent
ed1731c4b7
commit
4fa24a2842
22 changed files with 449 additions and 97 deletions
|
@ -165,6 +165,7 @@ set(RENDER_HEADERS
|
|||
src/render/model/animation.h
|
||||
src/render/model/mdlfile.h
|
||||
src/render/model/model.h
|
||||
src/render/model/modelloader.h
|
||||
src/render/model/modelnode.h
|
||||
src/render/model/models.h
|
||||
src/render/shaders.h
|
||||
|
@ -200,6 +201,7 @@ set(RENDER_SOURCES
|
|||
src/render/model/animation.cpp
|
||||
src/render/model/mdlfile.cpp
|
||||
src/render/model/model.cpp
|
||||
src/render/model/modelloader.cpp
|
||||
src/render/model/modelnode.cpp
|
||||
src/render/model/models.cpp
|
||||
src/render/shaders.cpp
|
||||
|
|
|
@ -42,9 +42,11 @@ namespace reone {
|
|||
|
||||
namespace tor {
|
||||
|
||||
Gr2File::Gr2File(string resRef) :
|
||||
Gr2File::Gr2File(string resRef, vector<shared_ptr<Animation>> animations, shared_ptr<Model> skeleton) :
|
||||
BinaryFile(4, "GAWB"),
|
||||
_resRef(move(resRef)) {
|
||||
_resRef(move(resRef)),
|
||||
_animations(move(animations)),
|
||||
_skeleton(move(skeleton)) {
|
||||
}
|
||||
|
||||
void Gr2File::doLoad() {
|
||||
|
@ -75,6 +77,7 @@ void Gr2File::doLoad() {
|
|||
loadMeshes();
|
||||
loadSkeletonBones();
|
||||
loadAttachments();
|
||||
loadModel();
|
||||
}
|
||||
|
||||
void Gr2File::loadMeshes() {
|
||||
|
@ -367,6 +370,136 @@ unique_ptr<Gr2File::Attachment> Gr2File::readAttachment() {
|
|||
return move(attachment);
|
||||
}
|
||||
|
||||
static glm::mat4 getModelMatrix() {
|
||||
glm::mat4 transform(1.0f);
|
||||
transform *= glm::mat4_cast(glm::angleAxis(glm::pi<float>(), glm::vec3(0.0f, 0.0f, 1.0f)));
|
||||
transform *= glm::mat4_cast(glm::angleAxis(glm::half_pi<float>(), glm::vec3(1.0f, 0.0, 0.0f)));
|
||||
transform = glm::scale(transform, glm::vec3(10.0f));
|
||||
return move(transform);
|
||||
}
|
||||
|
||||
void Gr2File::loadModel() {
|
||||
shared_ptr<ModelNode> rootNode;
|
||||
int nodeIndex = 0;
|
||||
|
||||
if (_fileType == FileType::Skeleton) {
|
||||
rootNode = make_shared<ModelNode>(nodeIndex++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
rootNode->setAbsoluteTransform(getModelMatrix());
|
||||
|
||||
// Convert bones to model nodes
|
||||
vector<shared_ptr<ModelNode>> nodes;
|
||||
for (uint16_t i = 0; i < _numBones; ++i) {
|
||||
auto node = make_shared<ModelNode>(nodeIndex);
|
||||
node->setNodeNumber(nodeIndex);
|
||||
node->setName(_bones[i]->name);
|
||||
node->setAbsoluteTransform(rootNode->absoluteTransform() * glm::inverse(_bones[i]->rootToBone));
|
||||
nodes.push_back(move(node));
|
||||
++nodeIndex;
|
||||
}
|
||||
|
||||
// Reparent model nodes
|
||||
for (uint16_t i = 0; i < _numBones; ++i) {
|
||||
if (_bones[i]->parentIndex == 0xffffffff) {
|
||||
rootNode->addChild(nodes[i]);
|
||||
} else {
|
||||
nodes[_bones[i]->parentIndex]->addChild(nodes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// If skeleton is present, use it as the base
|
||||
if (_skeleton) {
|
||||
rootNode = _skeleton->rootNode();
|
||||
nodeIndex = _skeleton->maxNodeIndex() + 1;
|
||||
} else {
|
||||
rootNode = make_shared<ModelNode>(nodeIndex++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
rootNode->setAbsoluteTransform(getModelMatrix());
|
||||
}
|
||||
|
||||
// Convert meshes to model nodes
|
||||
for (uint16_t i = 0; i < _numMeshes; ++i) {
|
||||
auto node = make_shared<ModelNode>(nodeIndex);
|
||||
node->setNodeNumber(nodeIndex);
|
||||
node->setName(_meshes[i]->header.name);
|
||||
node->setMesh(_meshes[i]->mesh);
|
||||
|
||||
// If skeleton is present configure the model node skin
|
||||
if (_skeleton) {
|
||||
auto skin = make_shared<ModelNode::Skin>();
|
||||
for (uint16_t j = 0; j < _meshes[i]->header.numUsedBones; ++j) {
|
||||
auto boneNode = _skeleton->findNodeByName(_meshes[i]->bones[j]->name);
|
||||
skin->nodeIdxByBoneIdx.insert(make_pair(j, boneNode->index()));
|
||||
}
|
||||
node->setSkin(move(skin));
|
||||
}
|
||||
|
||||
node->setAbsoluteTransform(rootNode->absoluteTransform());
|
||||
rootNode->addChild(move(node));
|
||||
++nodeIndex;
|
||||
}
|
||||
}
|
||||
|
||||
rootNode->computeLocalTransforms();
|
||||
|
||||
_model = make_shared<Model>(_resRef, Model::Classification::Character, move(rootNode), _animations);
|
||||
}
|
||||
|
||||
static string getSkeletonByModel(const string &modelResRef) {
|
||||
if (boost::starts_with(modelResRef, "rancor_rancor_")) return "rancor_skeleton";
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
static vector<string> getAnimationsBySkeleton(const string &skeletonResRef) {
|
||||
vector<string> result;
|
||||
if (skeletonResRef == "rancor_skeleton") {
|
||||
result.push_back("dl_roar");
|
||||
}
|
||||
return move(result);
|
||||
}
|
||||
|
||||
shared_ptr<Model> Gr2ModelLoader::loadModel(GameID gameId, const string &resRef) {
|
||||
shared_ptr<Model> skeletonModel;
|
||||
vector<shared_ptr<Animation>> animations;
|
||||
|
||||
string skeletonResRef(getSkeletonByModel(resRef));
|
||||
if (!skeletonResRef.empty()) {
|
||||
shared_ptr<ByteArray> skeletonData(Resources::instance().get(skeletonResRef, ResourceType::Gr2));
|
||||
if (skeletonData) {
|
||||
Gr2File skeleton(skeletonResRef, vector<shared_ptr<Animation>>());
|
||||
skeleton.load(wrap(skeletonData));
|
||||
skeletonModel = skeleton.model();
|
||||
}
|
||||
|
||||
vector<string> animationResRefs(getAnimationsBySkeleton(skeletonResRef));
|
||||
for (auto &animResRef : animationResRefs) {
|
||||
shared_ptr<ByteArray> jbaData(Resources::instance().get(animResRef, ResourceType::Jba));
|
||||
if (jbaData) {
|
||||
JbaFile jba(animResRef, skeletonModel);
|
||||
jba.load(wrap(jbaData));
|
||||
shared_ptr<Animation> animation(jba.animation());
|
||||
if (animation) {
|
||||
// DEBUG: currently we only add a single animation and rename it to "cpause1"
|
||||
animation->setName("cpause1");
|
||||
animations.push_back(move(animation));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<ByteArray> geometryData(Resources::instance().get(resRef, ResourceType::Gr2));
|
||||
if (geometryData) {
|
||||
Gr2File geometry(resRef, move(animations), move(skeletonModel));
|
||||
geometry.load(wrap(geometryData));
|
||||
return geometry.model();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../render/model/model.h"
|
||||
#include "../../render/model/modelloader.h"
|
||||
#include "../../resource/format/binfile.h"
|
||||
|
||||
namespace reone {
|
||||
|
@ -35,7 +36,17 @@ public:
|
|||
Skeleton = 2
|
||||
};
|
||||
|
||||
Gr2File(std::string resRef);
|
||||
/**
|
||||
* @param resRef ResRef of the model
|
||||
* @param animations list of animations to pass to the loaded model
|
||||
* @param skeleton the skeleton model to append to the loaded model
|
||||
*/
|
||||
Gr2File(
|
||||
std::string resRef,
|
||||
std::vector<std::shared_ptr<render::Animation>> animations,
|
||||
std::shared_ptr<render::Model> skeleton = nullptr);
|
||||
|
||||
std::shared_ptr<render::Model> model() const { return _model; }
|
||||
|
||||
private:
|
||||
struct MeshHeader {
|
||||
|
@ -84,6 +95,8 @@ private:
|
|||
};
|
||||
|
||||
std::string _resRef;
|
||||
std::vector<std::shared_ptr<render::Animation>> _animations;
|
||||
std::shared_ptr<render::Model> _skeleton;
|
||||
|
||||
FileType _fileType { FileType::Geometry };
|
||||
uint16_t _numMeshes { 0 };
|
||||
|
@ -99,6 +112,7 @@ private:
|
|||
std::vector<std::shared_ptr<SkeletonBone>> _bones;
|
||||
std::vector<std::string> _materials;
|
||||
std::vector<std::shared_ptr<Attachment>> _attachments;
|
||||
std::shared_ptr<render::Model> _model;
|
||||
|
||||
void doLoad() override;
|
||||
|
||||
|
@ -106,6 +120,7 @@ private:
|
|||
void loadMaterials();
|
||||
void loadSkeletonBones();
|
||||
void loadAttachments();
|
||||
void loadModel();
|
||||
|
||||
std::unique_ptr<Gr2Mesh> readMesh();
|
||||
std::unique_ptr<MeshPiece> readMeshPiece();
|
||||
|
@ -115,6 +130,11 @@ private:
|
|||
std::unique_ptr<Attachment> readAttachment();
|
||||
};
|
||||
|
||||
class Gr2ModelLoader : public render::IModelLoader {
|
||||
public:
|
||||
std::shared_ptr<render::Model> loadModel(resource::GameID gameId, const std::string &resRef) override;
|
||||
};
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
#include "jbafile.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "glm/gtc/type_ptr.hpp"
|
||||
|
||||
#include "../../common/log.h"
|
||||
|
@ -31,11 +33,14 @@ namespace reone {
|
|||
|
||||
namespace tor {
|
||||
|
||||
static const glm::vec3 g_translationModifier { -0.001f, 0.001f, -0.001f };
|
||||
|
||||
JbaFile::JbaFile(const string &resRef) :
|
||||
JbaFile::JbaFile(const string &resRef, shared_ptr<Model> skeleton) :
|
||||
BinaryFile(4, "\0\0\0\0"),
|
||||
_resRef(resRef) {
|
||||
_resRef(resRef),
|
||||
_skeleton(skeleton) {
|
||||
|
||||
if (!skeleton) {
|
||||
throw invalid_argument("skeleton must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
void JbaFile::doLoad() {
|
||||
|
@ -47,10 +52,11 @@ void JbaFile::doLoad() {
|
|||
loadPartData();
|
||||
loadKeyframes();
|
||||
loadBones();
|
||||
loadAnimation();
|
||||
}
|
||||
|
||||
void JbaFile::loadHeader() {
|
||||
_length = readFloat();
|
||||
_length = 10.0f * readFloat();
|
||||
_fps = readFloat();
|
||||
_numParts = readUint32();
|
||||
|
||||
|
@ -86,15 +92,6 @@ static string describeVector(const glm::vec3 &vec) {
|
|||
return str(boost::format("(%.06f, %.06f, %.06f)") % vec.x % vec.y % vec.z);
|
||||
}
|
||||
|
||||
static string describeQuaternion(const glm::quat &q) {
|
||||
return str(boost::format("(%.06f, %.06f, %.06f, %.06f)") % q.x % q.y % q.z % q.w);
|
||||
}
|
||||
|
||||
static glm::quat getUnitQuaternion(const glm::vec3 &axis) {
|
||||
float w = glm::sqrt(1.0f - glm::dot(axis, axis));
|
||||
return glm::normalize(glm::quat(w, axis));
|
||||
}
|
||||
|
||||
void JbaFile::loadBoneData() {
|
||||
seek(alignAt80(tell()));
|
||||
vector<uint8_t> data(readArray<uint8_t>(48ll * _numBones));
|
||||
|
@ -102,15 +99,15 @@ void JbaFile::loadBoneData() {
|
|||
|
||||
for (uint32_t i = 0; i < _numBones; ++i) {
|
||||
JbaBone bone;
|
||||
bone.minTranslation = glm::make_vec3(dataPtr + 0) * g_translationModifier;
|
||||
bone.maxTranslation = glm::make_vec3(dataPtr + 3) * g_translationModifier;
|
||||
bone.minOrientation = getUnitQuaternion(glm::make_vec3(dataPtr + 6));
|
||||
bone.maxOrientation = getUnitQuaternion(glm::make_vec3(dataPtr + 9));
|
||||
bone.translationStride = glm::make_vec3(dataPtr + 0);
|
||||
bone.translationBase = glm::make_vec3(dataPtr + 3);
|
||||
bone.orientationStride = glm::make_vec3(dataPtr + 6);
|
||||
bone.orientationBase = glm::make_vec3(dataPtr + 9);
|
||||
|
||||
debug(boost::format("JBA: bone %d data: %s, %s, %s, %s")
|
||||
% i
|
||||
% describeVector(bone.minTranslation) % describeVector(bone.maxTranslation)
|
||||
% describeQuaternion(bone.minOrientation) % describeQuaternion(bone.maxOrientation), 2);
|
||||
% describeVector(bone.translationStride) % describeVector(bone.translationBase)
|
||||
% describeVector(bone.orientationStride) % describeVector(bone.orientationBase), 2);
|
||||
|
||||
_bones.push_back(move(bone));
|
||||
|
||||
|
@ -129,42 +126,39 @@ void JbaFile::loadPartData() {
|
|||
}
|
||||
}
|
||||
|
||||
static glm::vec3 decompressPosition(uint32_t value, const glm::vec3 &min, const glm::vec3 &max) {
|
||||
uint32_t z = (value & 0x3ff);
|
||||
uint32_t y = ((value >> 10) & 0x7ff);
|
||||
uint32_t x = ((value >> 21) & 0x7ff);
|
||||
static glm::vec3 decompressPosition(uint32_t value, const glm::vec3 &base, const glm::vec3 &stride) {
|
||||
float x = ((value >> 21) & 0x7ff);
|
||||
float y = ((value >> 10) & 0x7ff);
|
||||
float z = (value & 0x3ff);
|
||||
|
||||
float nx = (x & 0x1ff) / 511.0f;
|
||||
float ny = (y & 0x3ff) / 1023.0f;
|
||||
float nz = (z & 0x3ff) / 1023.0f;
|
||||
|
||||
bool sx = (x & !0x3ff) != 0;
|
||||
bool sy = (y & !0x3ff) != 0;
|
||||
bool sz = (z & !0x1ff) != 0;
|
||||
|
||||
glm::vec3 result(min);
|
||||
result.x += (sx ? -1.0f : 1.0f) * nx * max.x;
|
||||
result.y += (sy ? -1.0f : 1.0f) * ny * max.y;
|
||||
result.z += (sz ? -1.0f : 1.0f) * nz * max.z;
|
||||
|
||||
return move(result);
|
||||
return glm::vec3(base + stride * glm::vec3(x, y, z));
|
||||
}
|
||||
|
||||
static glm::quat decompressOrientation(const uint16_t *values, const glm::quat &min, const glm::quat &max) {
|
||||
float nx = (values[0] & 0x7fff) / 32767.0f;
|
||||
float ny = (values[1] & 0x7fff) / 32767.0f;
|
||||
float nz = (values[2] & 0x7fff) / 32767.0f;
|
||||
static glm::quat decompressOrientation(const uint16_t *values, const glm::vec3 &base, const glm::vec3 &stride) {
|
||||
bool sign = (values[0] & 0x8000) != 0;
|
||||
|
||||
bool sx = (values[0] & 0x8000) != 0;
|
||||
bool sy = (values[1] & 0x8000) != 0;
|
||||
bool sz = (values[2] & 0x8000) != 0;
|
||||
float x = (values[0] & 0x7fff);
|
||||
float y = (values[1] & 0xffff);
|
||||
float z = (values[2] & 0xffff);
|
||||
|
||||
glm::vec3 axis(min.x, min.y, min.z);
|
||||
axis.x += (sx ? -1.0f : 1.0f) * nx * max.x;
|
||||
axis.y += (sy ? -1.0f : 1.0f) * ny * max.y;
|
||||
axis.z += (sz ? -1.0f : 1.0f) * nz * max.z;
|
||||
glm::vec3 axis(base + stride * glm::vec3(x, y, z));
|
||||
float w;
|
||||
|
||||
return getUnitQuaternion(axis);
|
||||
float dot = glm::dot(axis, axis);
|
||||
if (dot > 1.0f) {
|
||||
w = 0.0f;
|
||||
} else {
|
||||
w = glm::sqrt(1.0f - dot);
|
||||
if (sign) {
|
||||
w *= -1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
return glm::normalize(glm::quat(w, axis));
|
||||
}
|
||||
|
||||
static string describeQuaternion(const glm::quat &q) {
|
||||
return str(boost::format("(%.06f, %.06f, %.06f, %.06f)") % q.x % q.y % q.z % q.w);
|
||||
}
|
||||
|
||||
vector<vector<JbaFile::JbaKeyframe>> JbaFile::readPartKeyframes() {
|
||||
|
@ -191,13 +185,13 @@ vector<vector<JbaFile::JbaKeyframe>> JbaFile::readPartKeyframes() {
|
|||
// Decompress 48-bit orientation poses
|
||||
for (uint32_t j = 0; j < keyframeLayoutPtr[0]; ++j) {
|
||||
vector<uint16_t> values(readArray<uint16_t>(3));
|
||||
boneKeyframes[j].orientation = decompressOrientation(&values[0], _bones[i].minOrientation, _bones[i].maxOrientation);
|
||||
boneKeyframes[j].orientation = decompressOrientation(&values[0], _bones[i].orientationBase, _bones[i].orientationStride);
|
||||
debug(boost::format("JBA: bone %u: keyframe %u: orientation: %04X %04X %04X -> %s") % i % j % values[0] % values[1] % values[2] % describeQuaternion(boneKeyframes[j].orientation), 2);
|
||||
}
|
||||
// Decompress 32-bit translation poses, if any
|
||||
for (uint32_t j = 0; j < keyframeLayoutPtr[2]; ++j) {
|
||||
uint32_t value = readUint32();
|
||||
boneKeyframes[j].translation = decompressPosition(value, _bones[i].minTranslation, _bones[i].maxTranslation);
|
||||
boneKeyframes[j].translation = decompressPosition(value, _bones[i].translationBase, _bones[i].translationStride);
|
||||
debug(boost::format("JBA: bone %u: keyframe %u: translation: %08X -> %s") % i % j % value % describeVector(boneKeyframes[j].translation), 2);
|
||||
}
|
||||
|
||||
|
@ -216,13 +210,13 @@ void JbaFile::loadKeyframes() {
|
|||
ignore(8);
|
||||
|
||||
vector<float> valuesAt08(readArray<float>(12));
|
||||
_maxTranslation = glm::make_vec3(&valuesAt08[0]) * g_translationModifier;
|
||||
_minTranslation = glm::make_vec3(&valuesAt08[3]) * g_translationModifier;
|
||||
_maxOrientation = getUnitQuaternion(glm::make_vec3(&valuesAt08[6]));
|
||||
_minOrientation = getUnitQuaternion(glm::make_vec3(&valuesAt08[9]));
|
||||
debug(boost::format("JBA: maxTranslation=%s minTranslation=%s maxOrientation=%s minOrientation=%s") %
|
||||
describeVector(_maxTranslation) % describeVector(_minTranslation) %
|
||||
describeQuaternion(_maxOrientation) % describeQuaternion(_minOrientation));
|
||||
_translationStride = glm::make_vec3(&valuesAt08[0]);
|
||||
_translationBase = glm::make_vec3(&valuesAt08[3]);
|
||||
_orientationStride = glm::make_vec3(&valuesAt08[6]);
|
||||
_orientationBase = glm::make_vec3(&valuesAt08[9]);
|
||||
debug(boost::format("JBA: translationStride=%s translationBase=%s orientationStride=%s orientationBase=%s") %
|
||||
describeVector(_translationStride) % describeVector(_translationBase) %
|
||||
describeVector(_orientationStride) % describeVector(_orientationBase));
|
||||
|
||||
_numKeyframes = readUint32();
|
||||
debug("JBA: numKeyframes=" + to_string(_numKeyframes));
|
||||
|
@ -233,7 +227,7 @@ void JbaFile::loadKeyframes() {
|
|||
vector<uint16_t> valuesAt48(readArray<uint16_t>(3 * _numKeyframes));
|
||||
const uint16_t *valuesAt48Ptr = &valuesAt48[0];
|
||||
for (uint32_t i = 0; i < _numKeyframes; ++i) {
|
||||
glm::quat orientation(decompressOrientation(valuesAt48Ptr, _minOrientation, _maxOrientation));
|
||||
glm::quat orientation(decompressOrientation(valuesAt48Ptr, _orientationBase, _orientationStride));
|
||||
debug(boost::format("JBA: keyframe %d orientation: %04X %04X %04X -> %s") % i %
|
||||
valuesAt48Ptr[0] % valuesAt48Ptr[1] % valuesAt48Ptr[2] %
|
||||
describeQuaternion(orientation), 2);
|
||||
|
@ -247,7 +241,7 @@ void JbaFile::loadKeyframes() {
|
|||
|
||||
vector<uint32_t> keyframeValues(readArray<uint32_t>(_numKeyframes));
|
||||
for (uint32_t i = 0; i < _numKeyframes; ++i) {
|
||||
glm::vec3 translation(decompressPosition(keyframeValues[i], _minTranslation, _maxTranslation));
|
||||
glm::vec3 translation(decompressPosition(keyframeValues[i], _translationBase, _translationStride));
|
||||
debug(boost::format("JBA: keyframe %d translation: %08X -> %s") % i % keyframeValues[i] % describeVector(translation), 2);
|
||||
_keyframes[i].translation = move(translation);
|
||||
}
|
||||
|
@ -269,6 +263,62 @@ void JbaFile::loadBones() {
|
|||
}
|
||||
}
|
||||
|
||||
void JbaFile::loadAnimation() {
|
||||
if (_numParts == 0 || _parts[0].keyframes.empty()) return;
|
||||
|
||||
// Determine the total number of keyframes
|
||||
|
||||
int numKeyframes = 0;
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
numKeyframes += _parts[i].keyframes[0].size();
|
||||
}
|
||||
float step = _length / static_cast<float>(numKeyframes - 1);
|
||||
|
||||
|
||||
// Convert keyframes to model nodes
|
||||
|
||||
int index = 0;
|
||||
auto rootNode = make_shared<ModelNode>(index++);
|
||||
rootNode->setName(_resRef + "_root");
|
||||
|
||||
for (uint32_t i = 0; i < _numParts; ++i) {
|
||||
vector<vector<JbaKeyframe>> partKeyframes(_parts[i].keyframes);
|
||||
|
||||
for (uint32_t j = 0; j < _numBones; ++j) {
|
||||
auto node = make_shared<ModelNode>(index);
|
||||
node->setNodeNumber(index);
|
||||
node->setName(_bones[j].name);
|
||||
|
||||
auto skeletonNode = _skeleton->findNodeByName(_bones[j].name);
|
||||
if (!skeletonNode) continue;
|
||||
|
||||
vector<JbaKeyframe> boneKeyframes(partKeyframes[j]);
|
||||
for (size_t k = 0; k < boneKeyframes.size(); ++k) {
|
||||
float time = k * step;
|
||||
|
||||
ModelNode::PositionKeyframe position;
|
||||
position.time = time;
|
||||
position.position = boneKeyframes[k].translation * _translationBase;
|
||||
node->addPositionKeyframe(move(position));
|
||||
|
||||
ModelNode::OrientationKeyframe orientation;
|
||||
orientation.time = time;
|
||||
orientation.orientation = skeletonNode->orientation() * boneKeyframes[k].orientation;
|
||||
node->addOrientationKeyframe(move(orientation));
|
||||
}
|
||||
|
||||
rootNode->addChild(move(node));
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Create the animation
|
||||
|
||||
vector<Animation::Event> events;
|
||||
_animation = make_shared<Animation>(_resRef, _length, 0.5f * _length, move(events), move(rootNode));
|
||||
}
|
||||
|
||||
} // namespace tor
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "glm/gtc/quaternion.hpp"
|
||||
#include "glm/vec3.hpp"
|
||||
|
||||
#include "../../render/model/animation.h"
|
||||
#include "../../render/model/model.h"
|
||||
#include "../../resource/format/binfile.h"
|
||||
|
||||
namespace reone {
|
||||
|
@ -28,7 +30,9 @@ namespace tor {
|
|||
|
||||
class JbaFile : public resource::BinaryFile {
|
||||
public:
|
||||
JbaFile(const std::string &resRef);
|
||||
JbaFile(const std::string &resRef, std::shared_ptr<render::Model> skeleton);
|
||||
|
||||
std::shared_ptr<render::Animation> animation() const { return _animation; }
|
||||
|
||||
private:
|
||||
struct JbaKeyframe {
|
||||
|
@ -43,15 +47,16 @@ private:
|
|||
};
|
||||
|
||||
struct JbaBone {
|
||||
glm::vec3 minTranslation { 0.0f };
|
||||
glm::vec3 maxTranslation { 0.0f };
|
||||
glm::quat minOrientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
glm::quat maxOrientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 translationStride { 0.0f };
|
||||
glm::vec3 translationBase { 0.0f };
|
||||
glm::vec3 orientationStride { 0.0f };
|
||||
glm::vec3 orientationBase { 0.0f };
|
||||
uint32_t index { 0 };
|
||||
std::string name;
|
||||
};
|
||||
|
||||
std::string _resRef;
|
||||
std::shared_ptr<render::Model> _skeleton;
|
||||
|
||||
float _length { 0.0f };
|
||||
float _fps { 0 };
|
||||
|
@ -59,14 +64,15 @@ private:
|
|||
uint32_t _numKeyframes { 0 };
|
||||
uint32_t _numBones { 0 };
|
||||
|
||||
glm::vec3 _maxTranslation { 0.0f };
|
||||
glm::vec3 _minTranslation { 0.0f };
|
||||
glm::quat _maxOrientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
glm::quat _minOrientation { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
glm::vec3 _translationStride { 0.0f };
|
||||
glm::vec3 _translationBase { 0.0f };
|
||||
glm::vec3 _orientationStride { 0.0f };
|
||||
glm::vec3 _orientationBase { 0.0f };
|
||||
|
||||
std::vector<JbaPart> _parts;
|
||||
std::vector<JbaKeyframe> _keyframes;
|
||||
std::vector<JbaBone> _bones;
|
||||
std::shared_ptr<render::Animation> _animation;
|
||||
|
||||
void doLoad();
|
||||
|
||||
|
@ -76,10 +82,9 @@ private:
|
|||
void loadPartData();
|
||||
void loadKeyframes();
|
||||
void loadBones();
|
||||
void loadAnimation();
|
||||
|
||||
std::vector<std::vector<JbaKeyframe>> readPartKeyframes();
|
||||
|
||||
int getPartByKeyframe(int keyframeIdx) const;
|
||||
};
|
||||
|
||||
} // namespace tor
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
#include "../common/jobs.h"
|
||||
#include "../common/log.h"
|
||||
#include "../common/pathutil.h"
|
||||
#include "../experimental/tor/gr2file.h"
|
||||
#include "../render/model/mdlfile.h"
|
||||
#include "../render/model/models.h"
|
||||
#include "../render/textures.h"
|
||||
#include "../render/walkmeshes.h"
|
||||
|
@ -47,6 +49,7 @@ using namespace reone::render;
|
|||
using namespace reone::resource;
|
||||
using namespace reone::scene;
|
||||
using namespace reone::script;
|
||||
using namespace reone::tor;
|
||||
using namespace reone::video;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
@ -100,7 +103,10 @@ void Game::init() {
|
|||
|
||||
Resources::instance().init(_gameId, _path);
|
||||
Cursors::instance().init(_gameId);
|
||||
|
||||
Models::instance().init(_gameId);
|
||||
registerModelLoaders();
|
||||
|
||||
Textures::instance().init(_gameId);
|
||||
AudioPlayer::instance().init(_options.audio);
|
||||
GUISounds::instance().init();
|
||||
|
@ -111,6 +117,11 @@ void Game::init() {
|
|||
_console.load();
|
||||
}
|
||||
|
||||
void Game::registerModelLoaders() {
|
||||
Models::instance().registerLoader(ResourceType::Mdl, make_shared<MdlModelLoader>());
|
||||
Models::instance().registerLoader(ResourceType::Gr2, make_shared<Gr2ModelLoader>());
|
||||
}
|
||||
|
||||
void Game::setCursorType(CursorType type) {
|
||||
if (_cursorType == type) return;
|
||||
|
||||
|
|
|
@ -284,13 +284,12 @@ private:
|
|||
void changeScreen(GameScreen screen);
|
||||
void updateVideo(float dt);
|
||||
void updateMusic();
|
||||
void registerModelLoaders();
|
||||
|
||||
std::string getMainMenuMusic() const;
|
||||
std::string getCharacterGenerationMusic() const;
|
||||
gui::GUI *getScreenGUI() const;
|
||||
|
||||
// END Initialization
|
||||
|
||||
// Loading
|
||||
|
||||
void loadCharacterGeneration();
|
||||
|
|
|
@ -237,7 +237,7 @@ void Creature::updateModelAnimation() {
|
|||
} else {
|
||||
shared_ptr<Animation> anim(model->model()->getAnimation(_animResolver.getPauseAnimation()));
|
||||
if (anim) {
|
||||
model->playAnimation(anim, AnimationFlags::loopBlend, 1.0f, model->model()->animationScale());
|
||||
model->playAnimation(anim, AnimationFlags::loop /* AnimationFlags::loopBlend */, 1.0f, model->model()->animationScale());
|
||||
if (_headModel) {
|
||||
_headModel->playAnimation(anim, AnimationFlags::loopBlend, 1.0f, model->model()->animationScale());
|
||||
}
|
||||
|
|
|
@ -40,6 +40,11 @@ namespace reone {
|
|||
|
||||
namespace game {
|
||||
|
||||
// This is a hook to replace MDL models with GR2 models (SWTOR).
|
||||
static const unordered_map<string, string> g_gr2Models {
|
||||
{ "c_rancors", "rancor_rancor_a01" }
|
||||
};
|
||||
|
||||
static const string g_headHookNode("headhook");
|
||||
static const string g_maskHookNode("gogglehook");
|
||||
|
||||
|
@ -53,12 +58,21 @@ shared_ptr<ModelSceneNode> CreatureModelBuilder::build() {
|
|||
string modelName(getBodyModelName());
|
||||
if (modelName.empty()) return nullptr;
|
||||
|
||||
shared_ptr<Model> model(Models::instance().get(modelName));
|
||||
bool gr2Model = false;
|
||||
auto maybeGr2Model = g_gr2Models.find(modelName);
|
||||
if (maybeGr2Model != g_gr2Models.end()) {
|
||||
modelName = maybeGr2Model->second;
|
||||
gr2Model = true;
|
||||
}
|
||||
|
||||
shared_ptr<Model> model(Models::instance().get(modelName, gr2Model ? ResourceType::Gr2 : ResourceType::Mdl));
|
||||
if (!model) return nullptr;
|
||||
|
||||
auto modelSceneNode = make_unique<ModelSceneNode>(&_creature->sceneGraph(), model);
|
||||
modelSceneNode->setLightingEnabled(true);
|
||||
|
||||
if (gr2Model) return move(modelSceneNode);
|
||||
|
||||
// Body texture
|
||||
|
||||
string bodyTextureName(getBodyTextureName());
|
||||
|
|
|
@ -63,6 +63,10 @@ shared_ptr<ModelNode> Animation::findNode(const string &name) const {
|
|||
return it != _nodeByName.end() ? it->second : nullptr;
|
||||
}
|
||||
|
||||
void Animation::setName(string name) {
|
||||
_name = move(name);
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -49,6 +49,8 @@ public:
|
|||
std::shared_ptr<ModelNode> rootNode() const { return _rootNode; }
|
||||
const std::vector<Event> &events() const { return _events; }
|
||||
|
||||
void setName(std::string name);
|
||||
|
||||
private:
|
||||
std::string _name;
|
||||
float _length { 0.0f };
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "glm/ext.hpp"
|
||||
|
||||
#include "../../common/log.h"
|
||||
#include "../../common/streamutil.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
#include "../model/models.h"
|
||||
|
@ -990,6 +991,17 @@ void MdlFile::readEmitter(ModelNode &node) {
|
|||
ignore(30);
|
||||
}
|
||||
|
||||
shared_ptr<Model> MdlModelLoader::loadModel(GameID gameId, const string &resRef) {
|
||||
shared_ptr<ByteArray> mdlData(Resources::instance().get(resRef, ResourceType::Mdl));
|
||||
shared_ptr<ByteArray> mdxData(Resources::instance().get(resRef, ResourceType::Mdx));
|
||||
if (mdlData && mdxData) {
|
||||
MdlFile mdl(gameId);
|
||||
mdl.load(wrap(mdlData), wrap(mdxData));
|
||||
return mdl.model();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "../../resource/types.h"
|
||||
|
||||
#include "../model/model.h"
|
||||
#include "../model/modelloader.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
@ -92,6 +93,11 @@ private:
|
|||
// END Controllers
|
||||
};
|
||||
|
||||
class MdlModelLoader : public IModelLoader {
|
||||
public:
|
||||
std::shared_ptr<Model> loadModel(resource::GameID gameId, const std::string &resRef) override;
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -65,10 +65,11 @@ public:
|
|||
|
||||
Classification classification() const { return _classification; }
|
||||
const std::string &name() const { return _name; }
|
||||
ModelNode &rootNode() const { return *_rootNode; }
|
||||
std::shared_ptr<ModelNode> rootNode() const { return _rootNode; }
|
||||
float animationScale() const { return _animationScale; }
|
||||
std::shared_ptr<Model> superModel() const { return _superModel; }
|
||||
const AABB &aabb() const { return _aabb; }
|
||||
int maxNodeIndex() { return _maxNodeIndex; }
|
||||
|
||||
void setAnimationScale(float scale);
|
||||
|
||||
|
|
0
src/render/model/modelloader.cpp
Normal file
0
src/render/model/modelloader.cpp
Normal file
41
src/render/model/modelloader.h
Normal file
41
src/render/model/modelloader.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021 The reone project contributors
|
||||
*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include "../../resource/types.h"
|
||||
|
||||
#include "model.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
||||
/**
|
||||
* An interface to abstract loading models from different formats.
|
||||
*/
|
||||
class IModelLoader {
|
||||
public:
|
||||
virtual std::shared_ptr<Model> loadModel(resource::GameID gameId, const std::string &resRef) = 0;
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
|
@ -39,6 +39,11 @@ void ModelNode::initGL() {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelNode::addChild(shared_ptr<ModelNode> child) {
|
||||
child->_parent = this;
|
||||
_children.push_back(move(child));
|
||||
}
|
||||
|
||||
void ModelNode::computeLocalTransforms() {
|
||||
if (_parent) {
|
||||
_localTransform = glm::inverse(_parent->_absTransform) * _absTransform;
|
||||
|
@ -71,6 +76,14 @@ void ModelNode::computeAbsoluteTransforms() {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelNode::addPositionKeyframe(PositionKeyframe keyframe) {
|
||||
_positionFrames.push_back(move(keyframe));
|
||||
}
|
||||
|
||||
void ModelNode::addOrientationKeyframe(OrientationKeyframe keyframe) {
|
||||
_orientationFrames.push_back(move(keyframe));
|
||||
}
|
||||
|
||||
bool ModelNode::getPosition(float time, glm::vec3 &position, float scale) const {
|
||||
if (_positionFrames.empty()) return false;
|
||||
|
||||
|
@ -157,6 +170,23 @@ void ModelNode::setName(string name) {
|
|||
_name = move(name);
|
||||
}
|
||||
|
||||
void ModelNode::setNodeNumber(uint16_t nodeNumber) {
|
||||
_nodeNumber = nodeNumber;
|
||||
}
|
||||
|
||||
void ModelNode::setAbsoluteTransform(glm::mat4 transform) {
|
||||
_absTransform = move(transform);
|
||||
_absTransformInv = glm::inverse(_absTransform);
|
||||
}
|
||||
|
||||
void ModelNode::setMesh(shared_ptr<ModelMesh> mesh) {
|
||||
_mesh = move(mesh);
|
||||
}
|
||||
|
||||
void ModelNode::setSkin(shared_ptr<Skin> skin) {
|
||||
_skin = move(skin);
|
||||
}
|
||||
|
||||
} // namespace render
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -70,6 +70,12 @@ public:
|
|||
|
||||
void initGL();
|
||||
|
||||
/**
|
||||
* Adds the specified node to the list of this nodes children. Also sets the
|
||||
* specified nodes parent pointer to this node.
|
||||
*/
|
||||
void addChild(std::shared_ptr<ModelNode> node);
|
||||
|
||||
/**
|
||||
* Recursively computes the local transform, position and orientation of
|
||||
* this node and its children. Absolute transform must be set prior to
|
||||
|
@ -83,6 +89,9 @@ public:
|
|||
*/
|
||||
void computeAbsoluteTransforms();
|
||||
|
||||
void addPositionKeyframe(PositionKeyframe keyframe);
|
||||
void addOrientationKeyframe(OrientationKeyframe keyframe);
|
||||
|
||||
bool isSelfIllumEnabled() const { return _selfIllumEnabled; }
|
||||
|
||||
bool getPosition(float time, glm::vec3 &position, float scale = 1.0f) const;
|
||||
|
@ -112,6 +121,10 @@ public:
|
|||
const std::vector<std::shared_ptr<ModelNode>> &children() const { return _children; }
|
||||
|
||||
void setName(std::string name);
|
||||
void setNodeNumber(uint16_t nodeNumber);
|
||||
void setAbsoluteTransform(glm::mat4 transform);
|
||||
void setMesh(std::shared_ptr<ModelMesh> mesh);
|
||||
void setSkin(std::shared_ptr<Skin> skin);
|
||||
|
||||
private:
|
||||
int _index { 0 };
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#include "models.h"
|
||||
|
||||
#include "../../common/log.h"
|
||||
#include "../../common/streamutil.h"
|
||||
#include "../../resource/resources.h"
|
||||
|
||||
|
@ -43,30 +44,30 @@ void Models::invalidateCache() {
|
|||
_cache.clear();
|
||||
}
|
||||
|
||||
shared_ptr<Model> Models::get(const string &resRef) {
|
||||
void Models::registerLoader(ResourceType type, shared_ptr<IModelLoader> loader) {
|
||||
_loaders.insert(make_pair(type, move(loader)));
|
||||
}
|
||||
|
||||
shared_ptr<Model> Models::get(const string &resRef, ResourceType type) {
|
||||
auto maybeModel = _cache.find(resRef);
|
||||
if (maybeModel != _cache.end()) {
|
||||
return maybeModel->second;
|
||||
}
|
||||
auto inserted = _cache.insert(make_pair(resRef, doGet(resRef)));
|
||||
auto inserted = _cache.insert(make_pair(resRef, doGet(resRef, type)));
|
||||
|
||||
return inserted.first->second;
|
||||
}
|
||||
|
||||
shared_ptr<Model> Models::doGet(const string &resRef) {
|
||||
shared_ptr<ByteArray> mdlData(Resources::instance().get(resRef, ResourceType::Mdl));
|
||||
shared_ptr<ByteArray> mdxData(Resources::instance().get(resRef, ResourceType::Mdx));
|
||||
shared_ptr<Model> model;
|
||||
|
||||
if (mdlData && mdxData) {
|
||||
MdlFile mdl(_gameId);
|
||||
mdl.load(wrap(mdlData), wrap(mdxData));
|
||||
model = mdl.model();
|
||||
shared_ptr<Model> Models::doGet(const string &resRef, ResourceType type) {
|
||||
auto maybeLoader = _loaders.find(type);
|
||||
if (maybeLoader == _loaders.end()) {
|
||||
warn("Models: model loader not found by ResType: " + to_string(static_cast<int>(type)));
|
||||
return nullptr;
|
||||
}
|
||||
shared_ptr<Model> model(maybeLoader->second->loadModel(_gameId, resRef));
|
||||
if (model) {
|
||||
model->initGL();
|
||||
}
|
||||
|
||||
return move(model);
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
#include "../types.h"
|
||||
|
||||
#include "modelloader.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace render {
|
||||
|
@ -38,17 +40,23 @@ public:
|
|||
void init(resource::GameID gameId);
|
||||
void invalidateCache();
|
||||
|
||||
std::shared_ptr<Model> get(const std::string &resRef);
|
||||
/**
|
||||
* Associates the specified model loader with the specified ResType.
|
||||
*/
|
||||
void registerLoader(resource::ResourceType type, std::shared_ptr<IModelLoader> loader);
|
||||
|
||||
std::shared_ptr<Model> get(const std::string &resRef, resource::ResourceType type = resource::ResourceType::Mdl);
|
||||
|
||||
private:
|
||||
resource::GameID _gameId { resource::GameID::KotOR };
|
||||
std::unordered_map<resource::ResourceType, std::shared_ptr<IModelLoader>> _loaders;
|
||||
std::unordered_map<std::string, std::shared_ptr<Model>> _cache;
|
||||
|
||||
Models() = default;
|
||||
Models(const Models &) = delete;
|
||||
Models &operator=(const Models &) = delete;
|
||||
|
||||
std::shared_ptr<Model> doGet(const std::string &resRef);
|
||||
std::shared_ptr<Model> doGet(const std::string &resRef, resource::ResourceType type);
|
||||
};
|
||||
|
||||
} // namespace render
|
||||
|
|
|
@ -85,7 +85,7 @@ static bool validateEmitter(const Emitter &emitter) {
|
|||
}
|
||||
|
||||
void ModelSceneNode::initModelNodes() {
|
||||
shared_ptr<ModelNodeSceneNode> rootNode(getModelNodeSceneNode(_model->rootNode()));
|
||||
shared_ptr<ModelNodeSceneNode> rootNode(getModelNodeSceneNode(*_model->rootNode()));
|
||||
addChild(rootNode);
|
||||
|
||||
stack<ModelNodeSceneNode *> nodes;
|
||||
|
|
|
@ -91,8 +91,8 @@ void SceneNodeAnimator::update(float dt) {
|
|||
|
||||
// Apply animation transforms to the managed scene node
|
||||
_absTransforms.clear();
|
||||
updateAbsoluteTransforms(_modelSceneNode->model()->rootNode());
|
||||
applyAnimationTransforms(_modelSceneNode->model()->rootNode());
|
||||
updateAbsoluteTransforms(*_modelSceneNode->model()->rootNode());
|
||||
applyAnimationTransforms(*_modelSceneNode->model()->rootNode());
|
||||
}
|
||||
|
||||
void SceneNodeAnimator::updateChannel(int channel, float dt) {
|
||||
|
|
Loading…
Reference in a new issue