diff --git a/CMakeLists.txt b/CMakeLists.txt index 9c5ab9ad..aed03760 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/experimental/tor/gr2file.cpp b/src/experimental/tor/gr2file.cpp index 5c7c11ef..2fd12452 100644 --- a/src/experimental/tor/gr2file.cpp +++ b/src/experimental/tor/gr2file.cpp @@ -42,9 +42,11 @@ namespace reone { namespace tor { -Gr2File::Gr2File(string resRef) : +Gr2File::Gr2File(string resRef, vector> animations, shared_ptr 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::readAttachment() { return move(attachment); } +static glm::mat4 getModelMatrix() { + glm::mat4 transform(1.0f); + transform *= glm::mat4_cast(glm::angleAxis(glm::pi(), glm::vec3(0.0f, 0.0f, 1.0f))); + transform *= glm::mat4_cast(glm::angleAxis(glm::half_pi(), glm::vec3(1.0f, 0.0, 0.0f))); + transform = glm::scale(transform, glm::vec3(10.0f)); + return move(transform); +} + +void Gr2File::loadModel() { + shared_ptr rootNode; + int nodeIndex = 0; + + if (_fileType == FileType::Skeleton) { + rootNode = make_shared(nodeIndex++); + rootNode->setName(_resRef + "_root"); + rootNode->setAbsoluteTransform(getModelMatrix()); + + // Convert bones to model nodes + vector> nodes; + for (uint16_t i = 0; i < _numBones; ++i) { + auto node = make_shared(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(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(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(); + 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(_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 getAnimationsBySkeleton(const string &skeletonResRef) { + vector result; + if (skeletonResRef == "rancor_skeleton") { + result.push_back("dl_roar"); + } + return move(result); +} + +shared_ptr Gr2ModelLoader::loadModel(GameID gameId, const string &resRef) { + shared_ptr skeletonModel; + vector> animations; + + string skeletonResRef(getSkeletonByModel(resRef)); + if (!skeletonResRef.empty()) { + shared_ptr skeletonData(Resources::instance().get(skeletonResRef, ResourceType::Gr2)); + if (skeletonData) { + Gr2File skeleton(skeletonResRef, vector>()); + skeleton.load(wrap(skeletonData)); + skeletonModel = skeleton.model(); + } + + vector animationResRefs(getAnimationsBySkeleton(skeletonResRef)); + for (auto &animResRef : animationResRefs) { + shared_ptr jbaData(Resources::instance().get(animResRef, ResourceType::Jba)); + if (jbaData) { + JbaFile jba(animResRef, skeletonModel); + jba.load(wrap(jbaData)); + shared_ptr 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 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 diff --git a/src/experimental/tor/gr2file.h b/src/experimental/tor/gr2file.h index 028c998a..0e824b6f 100644 --- a/src/experimental/tor/gr2file.h +++ b/src/experimental/tor/gr2file.h @@ -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> animations, + std::shared_ptr skeleton = nullptr); + + std::shared_ptr model() const { return _model; } private: struct MeshHeader { @@ -84,6 +95,8 @@ private: }; std::string _resRef; + std::vector> _animations; + std::shared_ptr _skeleton; FileType _fileType { FileType::Geometry }; uint16_t _numMeshes { 0 }; @@ -99,6 +112,7 @@ private: std::vector> _bones; std::vector _materials; std::vector> _attachments; + std::shared_ptr _model; void doLoad() override; @@ -106,6 +120,7 @@ private: void loadMaterials(); void loadSkeletonBones(); void loadAttachments(); + void loadModel(); std::unique_ptr readMesh(); std::unique_ptr readMeshPiece(); @@ -115,6 +130,11 @@ private: std::unique_ptr readAttachment(); }; +class Gr2ModelLoader : public render::IModelLoader { +public: + std::shared_ptr loadModel(resource::GameID gameId, const std::string &resRef) override; +}; + } // namespace tor } // namespace reone diff --git a/src/experimental/tor/jbafile.cpp b/src/experimental/tor/jbafile.cpp index 005cbb8e..358fc13a 100644 --- a/src/experimental/tor/jbafile.cpp +++ b/src/experimental/tor/jbafile.cpp @@ -17,6 +17,8 @@ #include "jbafile.h" +#include + #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 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 data(readArray(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> JbaFile::readPartKeyframes() { @@ -191,13 +185,13 @@ vector> JbaFile::readPartKeyframes() { // Decompress 48-bit orientation poses for (uint32_t j = 0; j < keyframeLayoutPtr[0]; ++j) { vector values(readArray(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 valuesAt08(readArray(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 valuesAt48(readArray(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 keyframeValues(readArray(_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(numKeyframes - 1); + + + // Convert keyframes to model nodes + + int index = 0; + auto rootNode = make_shared(index++); + rootNode->setName(_resRef + "_root"); + + for (uint32_t i = 0; i < _numParts; ++i) { + vector> partKeyframes(_parts[i].keyframes); + + for (uint32_t j = 0; j < _numBones; ++j) { + auto node = make_shared(index); + node->setNodeNumber(index); + node->setName(_bones[j].name); + + auto skeletonNode = _skeleton->findNodeByName(_bones[j].name); + if (!skeletonNode) continue; + + vector 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 events; + _animation = make_shared(_resRef, _length, 0.5f * _length, move(events), move(rootNode)); +} + } // namespace tor } // namespace reone diff --git a/src/experimental/tor/jbafile.h b/src/experimental/tor/jbafile.h index 4306ef14..4d942688 100644 --- a/src/experimental/tor/jbafile.h +++ b/src/experimental/tor/jbafile.h @@ -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 skeleton); + + std::shared_ptr 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 _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 _parts; std::vector _keyframes; std::vector _bones; + std::shared_ptr _animation; void doLoad(); @@ -76,10 +82,9 @@ private: void loadPartData(); void loadKeyframes(); void loadBones(); + void loadAnimation(); std::vector> readPartKeyframes(); - - int getPartByKeyframe(int keyframeIdx) const; }; } // namespace tor diff --git a/src/game/game.cpp b/src/game/game.cpp index 3d8574f3..26cd71ce 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -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()); + Models::instance().registerLoader(ResourceType::Gr2, make_shared()); +} + void Game::setCursorType(CursorType type) { if (_cursorType == type) return; diff --git a/src/game/game.h b/src/game/game.h index 7eb53e28..61e007a4 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -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(); diff --git a/src/game/object/creature.cpp b/src/game/object/creature.cpp index 118df4c9..8bb1663d 100644 --- a/src/game/object/creature.cpp +++ b/src/game/object/creature.cpp @@ -237,7 +237,7 @@ void Creature::updateModelAnimation() { } else { shared_ptr 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()); } diff --git a/src/game/object/creaturemodelbuilder.cpp b/src/game/object/creaturemodelbuilder.cpp index 910ff7fd..0e0d29e4 100644 --- a/src/game/object/creaturemodelbuilder.cpp +++ b/src/game/object/creaturemodelbuilder.cpp @@ -40,6 +40,11 @@ namespace reone { namespace game { +// This is a hook to replace MDL models with GR2 models (SWTOR). +static const unordered_map 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 CreatureModelBuilder::build() { string modelName(getBodyModelName()); if (modelName.empty()) return nullptr; - shared_ptr 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(Models::instance().get(modelName, gr2Model ? ResourceType::Gr2 : ResourceType::Mdl)); if (!model) return nullptr; auto modelSceneNode = make_unique(&_creature->sceneGraph(), model); modelSceneNode->setLightingEnabled(true); + if (gr2Model) return move(modelSceneNode); + // Body texture string bodyTextureName(getBodyTextureName()); diff --git a/src/render/model/animation.cpp b/src/render/model/animation.cpp index dd66030b..62ab0bf7 100644 --- a/src/render/model/animation.cpp +++ b/src/render/model/animation.cpp @@ -63,6 +63,10 @@ shared_ptr 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 diff --git a/src/render/model/animation.h b/src/render/model/animation.h index 066b0f5c..bd33e263 100644 --- a/src/render/model/animation.h +++ b/src/render/model/animation.h @@ -49,6 +49,8 @@ public: std::shared_ptr rootNode() const { return _rootNode; } const std::vector &events() const { return _events; } + void setName(std::string name); + private: std::string _name; float _length { 0.0f }; diff --git a/src/render/model/mdlfile.cpp b/src/render/model/mdlfile.cpp index cb0954f1..f9cf95b0 100644 --- a/src/render/model/mdlfile.cpp +++ b/src/render/model/mdlfile.cpp @@ -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 MdlModelLoader::loadModel(GameID gameId, const string &resRef) { + shared_ptr mdlData(Resources::instance().get(resRef, ResourceType::Mdl)); + shared_ptr 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 diff --git a/src/render/model/mdlfile.h b/src/render/model/mdlfile.h index 0f24f56e..56617b58 100644 --- a/src/render/model/mdlfile.h +++ b/src/render/model/mdlfile.h @@ -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 loadModel(resource::GameID gameId, const std::string &resRef) override; +}; + } // namespace render } // namespace reone diff --git a/src/render/model/model.h b/src/render/model/model.h index b4deaceb..a62c6d11 100644 --- a/src/render/model/model.h +++ b/src/render/model/model.h @@ -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 rootNode() const { return _rootNode; } float animationScale() const { return _animationScale; } std::shared_ptr superModel() const { return _superModel; } const AABB &aabb() const { return _aabb; } + int maxNodeIndex() { return _maxNodeIndex; } void setAnimationScale(float scale); diff --git a/src/render/model/modelloader.cpp b/src/render/model/modelloader.cpp new file mode 100644 index 00000000..e69de29b diff --git a/src/render/model/modelloader.h b/src/render/model/modelloader.h new file mode 100644 index 00000000..3c69b4ed --- /dev/null +++ b/src/render/model/modelloader.h @@ -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 . + */ + +#pragma once + +#include +#include + +#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 loadModel(resource::GameID gameId, const std::string &resRef) = 0; +}; + +} // namespace render + +} // namespace reone diff --git a/src/render/model/modelnode.cpp b/src/render/model/modelnode.cpp index 719c7c33..7e54e644 100644 --- a/src/render/model/modelnode.cpp +++ b/src/render/model/modelnode.cpp @@ -39,6 +39,11 @@ void ModelNode::initGL() { } } +void ModelNode::addChild(shared_ptr 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 mesh) { + _mesh = move(mesh); +} + +void ModelNode::setSkin(shared_ptr skin) { + _skin = move(skin); +} + } // namespace render } // namespace reone diff --git a/src/render/model/modelnode.h b/src/render/model/modelnode.h index 17ef364b..1d75b0c3 100644 --- a/src/render/model/modelnode.h +++ b/src/render/model/modelnode.h @@ -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 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> &children() const { return _children; } void setName(std::string name); + void setNodeNumber(uint16_t nodeNumber); + void setAbsoluteTransform(glm::mat4 transform); + void setMesh(std::shared_ptr mesh); + void setSkin(std::shared_ptr skin); private: int _index { 0 }; diff --git a/src/render/model/models.cpp b/src/render/model/models.cpp index 07ad32ae..b40fb066 100644 --- a/src/render/model/models.cpp +++ b/src/render/model/models.cpp @@ -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 Models::get(const string &resRef) { +void Models::registerLoader(ResourceType type, shared_ptr loader) { + _loaders.insert(make_pair(type, move(loader))); +} + +shared_ptr 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 Models::doGet(const string &resRef) { - shared_ptr mdlData(Resources::instance().get(resRef, ResourceType::Mdl)); - shared_ptr mdxData(Resources::instance().get(resRef, ResourceType::Mdx)); - shared_ptr model; - - if (mdlData && mdxData) { - MdlFile mdl(_gameId); - mdl.load(wrap(mdlData), wrap(mdxData)); - model = mdl.model(); +shared_ptr 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(type))); + return nullptr; } + shared_ptr model(maybeLoader->second->loadModel(_gameId, resRef)); if (model) { model->initGL(); } - return move(model); } diff --git a/src/render/model/models.h b/src/render/model/models.h index dcf6d872..832029c5 100644 --- a/src/render/model/models.h +++ b/src/render/model/models.h @@ -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 get(const std::string &resRef); + /** + * Associates the specified model loader with the specified ResType. + */ + void registerLoader(resource::ResourceType type, std::shared_ptr loader); + + std::shared_ptr get(const std::string &resRef, resource::ResourceType type = resource::ResourceType::Mdl); private: resource::GameID _gameId { resource::GameID::KotOR }; + std::unordered_map> _loaders; std::unordered_map> _cache; Models() = default; Models(const Models &) = delete; Models &operator=(const Models &) = delete; - std::shared_ptr doGet(const std::string &resRef); + std::shared_ptr doGet(const std::string &resRef, resource::ResourceType type); }; } // namespace render diff --git a/src/scene/node/modelscenenode.cpp b/src/scene/node/modelscenenode.cpp index a31121b3..0fa77425 100644 --- a/src/scene/node/modelscenenode.cpp +++ b/src/scene/node/modelscenenode.cpp @@ -85,7 +85,7 @@ static bool validateEmitter(const Emitter &emitter) { } void ModelSceneNode::initModelNodes() { - shared_ptr rootNode(getModelNodeSceneNode(_model->rootNode())); + shared_ptr rootNode(getModelNodeSceneNode(*_model->rootNode())); addChild(rootNode); stack nodes; diff --git a/src/scene/scenenodeanimator.cpp b/src/scene/scenenodeanimator.cpp index 51a9d315..ea08321c 100644 --- a/src/scene/scenenodeanimator.cpp +++ b/src/scene/scenenodeanimator.cpp @@ -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) {