refactor: Reimplement GR2 models and JBA animations

Still not working properly...
This commit is contained in:
Vsevolod Kremianskii 2021-02-08 02:05:19 +07:00
parent ed1731c4b7
commit 4fa24a2842
22 changed files with 449 additions and 97 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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();

View file

@ -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());
}

View file

@ -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());

View file

@ -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

View file

@ -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 };

View file

@ -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

View file

@ -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

View file

@ -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);

View file

View 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

View file

@ -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

View file

@ -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 };

View file

@ -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);
}

View file

@ -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

View file

@ -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;

View file

@ -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) {