diff --git a/rwengine/CMakeLists.txt b/rwengine/CMakeLists.txt index 5e9b8951..534744c3 100644 --- a/rwengine/CMakeLists.txt +++ b/rwengine/CMakeLists.txt @@ -41,8 +41,6 @@ set(RWENGINE_SOURCES src/data/PathData.hpp src/data/PedData.cpp src/data/PedData.hpp - src/data/Skeleton.cpp - src/data/Skeleton.hpp src/data/WeaponData.hpp src/data/ZoneData.hpp src/dynamics/CollisionInstance.cpp diff --git a/rwengine/src/ai/CharacterController.cpp b/rwengine/src/ai/CharacterController.cpp index f395bdce..49f5a4a2 100644 --- a/rwengine/src/ai/CharacterController.cpp +++ b/rwengine/src/ai/CharacterController.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include diff --git a/rwengine/src/data/ModelData.cpp b/rwengine/src/data/ModelData.cpp index 278d0001..88362551 100644 --- a/rwengine/src/data/ModelData.cpp +++ b/rwengine/src/data/ModelData.cpp @@ -1 +1,25 @@ #include "data/ModelData.hpp" + +void SimpleModelInfo::setupBigBuilding(const ModelInfoTable& models) { + if (loddistances_[0] > 300.f && atomics_[2] == nullptr) { + isbigbuilding_ = true; + findRelatedModel(models); + if (related_) { + loddistances_[2] = related_->getLargestLodDistance(); + } else { + loddistances_[2] = 100.f; + } + } +} + +void SimpleModelInfo::findRelatedModel(const ModelInfoTable& models) { + for (const auto& model : models) { + if (model.second.get() == this) continue; + const auto& othername = model.second->name; + if (othername.size() <= 3 || name.size() <= 3) continue; + if (othername.substr(3) == name.substr(3)) { + related_ = static_cast(model.second.get()); + break; + } + } +} diff --git a/rwengine/src/data/ModelData.hpp b/rwengine/src/data/ModelData.hpp index 488ec005..4b0d4386 100644 --- a/rwengine/src/data/ModelData.hpp +++ b/rwengine/src/data/ModelData.hpp @@ -8,7 +8,7 @@ #include #include -#include +#include #include #include #ifdef RW_WINDOWS @@ -108,6 +108,9 @@ private: std::unique_ptr collision; }; +using ModelInfoTable = + std::unordered_map>; + /** * Model data for simple types * @@ -121,8 +124,6 @@ public: int timeOn = 0; int timeOff = 24; int flags; - /// @todo Remove this? - bool LOD = false; /// Information loaded from PATH sections /// @todo remove this from here too :) std::vector paths; @@ -133,18 +134,19 @@ public: } /// @todo change with librw - void setAtomic(Model* model, int n, ModelFrame* atomic) { + void setAtomic(Clump* model, int n, AtomicPtr atomic) { model_ = model; + /// @todo disassociated the Atomic from Clump atomics_[n] = atomic; } /// @todo remove this - Model* getModel() const { + Clump* getModel() const { return model_; } - ModelFrame* getAtomic(int n) const { - return atomics_[n]; + Atomic* getAtomic(int n) const { + return atomics_[n].get(); } void setLodDistance(int n, float d) { @@ -157,6 +159,15 @@ public: return loddistances_[n]; } + Atomic* getDistanceAtomic(float d) { + for (auto i = 0; i < getNumAtomics(); ++i) { + if (d < loddistances_[i]) { + return atomics_[i].get(); + } + } + return nullptr; + } + void setNumAtomics(int num) { numatomics_ = num; } @@ -203,12 +214,49 @@ public: NO_ZBUFFER_WRITE = 1 << 6, }; + // Set up data for big building objects + void setupBigBuilding(const ModelInfoTable& models); + bool isBigBuilding() const { + return isbigbuilding_; + } + + void findRelatedModel(const ModelInfoTable& models); + + float getLargestLodDistance() const { + return furthest_ != 0 ? loddistances_[furthest_ - 1] + : loddistances_[numatomics_ - 1]; + } + + float getNearLodDistance() const { + return loddistances_[2]; + } + + void determineFurthest() { + furthest_ = 0; + if (numatomics_ == 2) { + furthest_ = loddistances_[0] >= loddistances_[1] ? 1 : 0; + } + if (numatomics_ == 3) { + furthest_ = loddistances_[0] >= loddistances_[1] + ? 1 + : loddistances_[1] >= loddistances_[2] ? 2 : 0; + } + } + + SimpleModelInfo* related() const { + return related_; + } + private: - Model* model_ = nullptr; - ModelFrame* atomics_[3] = {}; + Clump* model_ = nullptr; + std::array atomics_; float loddistances_[3] = {}; uint8_t numatomics_ = 0; uint8_t alpha_ = 0; /// @todo ask aap why + bool isbigbuilding_ = 0; + uint8_t furthest_ = 0; + + SimpleModelInfo* related_ = nullptr; }; /** @@ -231,11 +279,11 @@ public: ClumpModelInfo(ModelDataType type) : BaseModelInfo(type) { } - void setModel(Model* model) { + void setModel(Clump* model) { model_ = model; } - Model* getModel() const { + Clump* getModel() const { return model_; } @@ -249,7 +297,7 @@ public: } private: - Model* model_ = nullptr; + Clump* model_ = nullptr; }; /** diff --git a/rwengine/src/data/Skeleton.cpp b/rwengine/src/data/Skeleton.cpp deleted file mode 100644 index 1548d60e..00000000 --- a/rwengine/src/data/Skeleton.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include - -Skeleton::FrameTransform Skeleton::IdentityTransform = {glm::vec3(0.f), - glm::quat()}; -Skeleton::FrameData Skeleton::IdentityData = { - Skeleton::IdentityTransform, Skeleton::IdentityTransform, true}; - -Skeleton::Skeleton() { -} - -void Skeleton::setAllData(const Skeleton::FramesData& data) { - framedata = data; -} - -const Skeleton::FrameData& Skeleton::getData(unsigned int frameIdx) const { - auto fdit = framedata.find(frameIdx); - if (fdit == framedata.end()) { - return Skeleton::IdentityData; - } - - return fdit->second; -} - -void Skeleton::setData(unsigned int frameIdx, const Skeleton::FrameData& data) { - framedata[frameIdx] = data; -} - -void Skeleton::setEnabled(ModelFrame* frame, bool enabled) { - auto fdit = framedata.find(frame->getIndex()); - if (fdit != framedata.end()) { - fdit->second.enabled = enabled; - } else { - FrameTransform tf{frame->getDefaultTranslation(), - glm::quat_cast(frame->getDefaultRotation())}; - framedata.insert({frame->getIndex(), {tf, tf, enabled}}); - } -} - -void Skeleton::setEnabled(unsigned int frameIdx, bool enabled) { - auto fdit = framedata.find(frameIdx); - if (fdit == framedata.end()) { - framedata.insert({frameIdx, - {Skeleton::IdentityTransform, - Skeleton::IdentityTransform, enabled}}); - } else { - fdit->second.enabled = enabled; - } -} - -const Skeleton::FrameTransform& Skeleton::getInterpolated( - unsigned int frameIdx) const { - auto itit = interpolateddata.find(frameIdx); - if (itit == interpolateddata.end()) { - return Skeleton::IdentityTransform; - } - - return itit->second; -} - -void Skeleton::interpolate(float alpha) { - interpolateddata.clear(); - - for (auto i = framedata.begin(); i != framedata.end(); ++i) { - auto& t2 = i->second.a.translation; - auto& t1 = i->second.b.translation; - - auto& r2 = i->second.a.rotation; - auto& r1 = i->second.b.rotation; - - interpolateddata.insert( - {i->first, {glm::mix(t1, t2, alpha), glm::slerp(r1, r2, alpha)}}); - } -} - -glm::mat4 Skeleton::getMatrix(unsigned int frameIdx) const { - const FrameTransform& ft = getInterpolated(frameIdx); - - glm::mat4 m; - - m = glm::translate(m, ft.translation); - m = m * glm::mat4_cast(ft.rotation); - - return m; -} - -glm::mat4 Skeleton::getMatrix(ModelFrame* frame) const { - auto itit = interpolateddata.find(frame->getIndex()); - if (itit != interpolateddata.end()) { - glm::mat4 m; - - m = glm::translate(m, itit->second.translation); - m = m * glm::mat4_cast(itit->second.rotation); - - return m; - } - - return frame->getTransform(); -} diff --git a/rwengine/src/data/Skeleton.hpp b/rwengine/src/data/Skeleton.hpp deleted file mode 100644 index 0c487989..00000000 --- a/rwengine/src/data/Skeleton.hpp +++ /dev/null @@ -1,59 +0,0 @@ -#ifndef _SKELETON_HPP_ -#define _SKELETON_HPP_ - -#include -#include -#include -#include - -class ModelFrame; -/** - * Data class for additional frame transformation and meta data. - * - * Provides interfaces to modify and query the visibility of model frames, - * as well as their transformation. Modified by Animator to animate models. - */ -class Skeleton { -public: - struct FrameTransform { - glm::vec3 translation; - glm::quat rotation; - }; - - static FrameTransform IdentityTransform; - - struct FrameData { - FrameTransform a; - FrameTransform b; - bool enabled; - }; - - static FrameData IdentityData; - - typedef std::map FramesData; - typedef std::map TransformData; - - Skeleton(); - - void setAllData(const FramesData& data); - - const FrameData& getData(unsigned int frameIdx) const; - - void setData(unsigned int frameIdx, const FrameData& data); - void setEnabled(ModelFrame* frame, bool enabled); - - void setEnabled(unsigned int frameIdx, bool enabled); - - const FrameTransform& getInterpolated(unsigned int frameIdx) const; - - glm::mat4 getMatrix(unsigned int frameIdx) const; - glm::mat4 getMatrix(ModelFrame* frame) const; - - void interpolate(float alpha); - -private: - FramesData framedata; - TransformData interpolateddata; -}; - -#endif \ No newline at end of file diff --git a/rwengine/src/engine/Animator.cpp b/rwengine/src/engine/Animator.cpp index fe7355b8..a03cd2c9 100644 --- a/rwengine/src/engine/Animator.cpp +++ b/rwengine/src/engine/Animator.cpp @@ -1,11 +1,10 @@ -#include -#include +#include #include #include #include +#include -Animator::Animator(Model* model, Skeleton* skeleton) - : model(model), skeleton(skeleton) { +Animator::Animator(Clump* model) : model(model) { } void Animator::tick(float dt) { @@ -18,21 +17,21 @@ void Animator::tick(float dt) { glm::quat rotation; }; +#if 0 // Blend all active animations together - std::map blendFrames; + std::map blendFrames; +#endif for (AnimationState& state : animations) { - RW_CHECK(state.animation != nullptr, - "AnimationState with no animation"); if (state.animation == nullptr) continue; - if (state.boneInstances.size() == 0) { - for (unsigned int f = 0; f < model->frames.size(); ++f) { - auto bit = - state.animation->bones.find(model->frames[f]->getName()); - if (bit != state.animation->bones.end()) { - state.boneInstances.insert({bit->second, {f}}); + if (state.boneInstances.empty()) { + for (const auto& bone : state.animation->bones) { + auto frame = model->findFrame(bone.first); + if (!frame) { + continue; } + state.boneInstances.insert({bone.second, frame}); } } @@ -72,23 +71,20 @@ void Animator::tick(float dt) { blendFrames[b.second.frameIndex] = xform; } #else - blendFrames[b.second.frameIndex] = xform; + b.second->setTranslation(b.second->getDefaultTranslation() + + xform.translation); + b.second->setRotation(glm::mat3_cast(xform.rotation)); #endif } } +#if 0 for (auto& p : blendFrames) { - auto& data = skeleton->getData(p.first); - Skeleton::FrameData fd; - fd.b = data.a; - fd.enabled = data.enabled; - - fd.a.translation = model->frames[p.first]->getDefaultTranslation() + - p.second.translation; - fd.a.rotation = p.second.rotation; - - skeleton->setData(p.first, fd); + p.first->setTranslation(p.first->getDefaultTranslation() + + p.second.translation); + p.first->setRotation(glm::mat3_cast(p.second.rotation)); } +#endif } bool Animator::isCompleted(unsigned int slot) const { diff --git a/rwengine/src/engine/Animator.hpp b/rwengine/src/engine/Animator.hpp index d9441426..dc1cd894 100644 --- a/rwengine/src/engine/Animator.hpp +++ b/rwengine/src/engine/Animator.hpp @@ -8,11 +8,9 @@ #include #include -class Model; +class Clump; class ModelFrame; -class Skeleton; - /** * @brief calculates animation frame matrices, as well as procedural frame * animation. @@ -24,13 +22,6 @@ class Skeleton; * The Animator will blend all active animations together. */ class Animator { - /** - * @brief Stores data required to animate a model frame - */ - struct BoneInstanceData { - unsigned int frameIndex; - }; - /** * @brief The AnimationState struct stores information about playing * animations @@ -43,18 +34,13 @@ class Animator { float speed; /// Automatically restart bool repeat; - std::map boneInstances; + std::map boneInstances; }; /** * @brief model The model being animated. */ - Model* model; - - /** - * @brief Skeleton instance. - */ - Skeleton* skeleton; + Clump* model; /** * @brief Currently playing animations @@ -62,7 +48,7 @@ class Animator { std::vector animations; public: - Animator(Model* model, Skeleton* skeleton); + Animator(Clump* model); Animation* getAnimation(unsigned int slot) { if (slot < animations.size()) { diff --git a/rwengine/src/engine/GameData.cpp b/rwengine/src/engine/GameData.cpp index 02c38d90..e4960fc7 100644 --- a/rwengine/src/engine/GameData.cpp +++ b/rwengine/src/engine/GameData.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -115,6 +115,13 @@ void GameData::loadLevelFile(const std::string& path) { } } } + + for (const auto& model : modelinfo) { + if (model.second->type() == ModelDataType::SimpleInfo) { + auto simple = static_cast(model.second.get()); + simple->setupBigBuilding(modelinfo); + } + } } void GameData::loadIDE(const std::string& path) { @@ -358,12 +365,12 @@ TextureArchive GameData::loadTextureArchive(const std::string& name) { void GameData::getNameAndLod(std::string& name, int& lod) { auto lodpos = name.rfind("_l"); if (lodpos != std::string::npos) { - lod = std::atoi(name.substr(lodpos + 1).c_str()); + lod = std::atoi(name.substr(lodpos + 2).c_str()); name = name.substr(0, lodpos); } } -Model* GameData::loadClump(const std::string& name) { +Clump* GameData::loadClump(const std::string& name) { auto file = index.openFile(name); if (!file) { logger->error("Data", "Failed to load model " + name); @@ -390,9 +397,9 @@ void GameData::loadModelFile(const std::string& name) { } // Associate the frames with models. - for (auto& frame : m->frames) { + for (const auto& atomic : m->getAtomics()) { /// @todo this is useful elsewhere, please move elsewhere - std::string name = frame->getName(); + std::string name = atomic->getFrame()->getName(); int lod = 0; getNameAndLod(name, lod); for (auto& model : modelinfo) { @@ -402,7 +409,9 @@ void GameData::loadModelFile(const std::string& name) { } if (boost::iequals(info->name, name)) { auto simple = static_cast(info); - simple->setAtomic(m, lod, frame); + simple->setAtomic(m, lod, atomic); + auto identity = std::make_shared(); + atomic->setFrame(identity); } } } @@ -458,11 +467,13 @@ void GameData::loadModel(ModelID model) { if (isSimple) { auto simple = static_cast(info); // Associate atomics - for (auto& frame : m->frames) { - auto name = frame->getName(); + for (auto& atomic : m->getAtomics()) { + auto name = atomic->getFrame()->getName(); int lod = 0; getNameAndLod(name, lod); - simple->setAtomic(m, lod, frame); + simple->setAtomic(m, lod, atomic); + auto identity = std::make_shared(); + atomic->setFrame(identity); } } else { // Associate clumps diff --git a/rwengine/src/engine/GameData.hpp b/rwengine/src/engine/GameData.hpp index f6a2df36..5b729841 100644 --- a/rwengine/src/engine/GameData.hpp +++ b/rwengine/src/engine/GameData.hpp @@ -133,7 +133,7 @@ public: /** * Loads an archived model and returns it directly */ - Model* loadClump(const std::string& name); + Clump* loadClump(const std::string& name); /** * Loads a DFF and associates its atomics with models. diff --git a/rwengine/src/engine/GameWorld.cpp b/rwengine/src/engine/GameWorld.cpp index 97167379..a0422047 100644 --- a/rwengine/src/engine/GameWorld.cpp +++ b/rwengine/src/engine/GameWorld.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -124,20 +124,6 @@ bool GameWorld::placeItems(const std::string& name) { } } - // Attempt to Associate LODs. - for (auto& p : instancePool.objects) { - auto object = p.second; - InstanceObject* instance = static_cast(object); - auto modelinfo = instance->getModelInfo(); - if (!modelinfo->LOD && modelinfo->name.length() > 3) { - auto lodInstit = - modelInstances.find("LOD" + modelinfo->name.substr(3)); - if (lodInstit != modelInstances.end()) { - instance->LODinstance = lodInstit->second; - } - } - } - return true; } else { logger->error("Data", "Failed to load IPL " + path); @@ -170,8 +156,8 @@ InstanceObject* GameWorld::createInstance(const uint16_t id, "World", "Instance with missing model: " + std::to_string(id)); } - auto instance = new InstanceObject( - this, pos, rot, glm::vec3(1.f, 1.f, 1.f), oi, nullptr, dydata); + auto instance = + new InstanceObject(this, pos, rot, glm::vec3(1.f), oi, dydata); instancePool.insert(instance); allObjects.push_back(instance); @@ -280,18 +266,23 @@ VehicleObject* GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, auto model = vti->getModel(); auto info = data->vehicleInfo.find(vti->handling_); - if (model && info != data->vehicleInfo.end()) { - if (info->second->wheels.size() == 0 && - info->second->seats.size() == 0) { - for (const ModelFrame* f : model->frames) { - const std::string& name = f->getName(); + if (model && info != data->vehicleInfo.end() && + info->second->wheels.size() == 0 && info->second->seats.size() == 0) { + auto root = model->getFrame(); + for (const auto& frame : root->getChildren()) { + const std::string& name = frame->getName(); - if (name.size() > 5 && name.substr(0, 5) == "wheel") { - auto frameTrans = f->getMatrix(); - info->second->wheels.push_back({glm::vec3(frameTrans[3])}); - } - if (name == "ped_frontseat") { - auto p = f->getDefaultTranslation(); + if (name.size() > 5 && name.substr(0, 5) == "wheel") { + const auto& frameTrans = frame->getWorldTransform(); + info->second->wheels.push_back({glm::vec3(frameTrans[3])}); + } + + if (name == "chassis_dummy") { + // These are nested within chassis_dummy + auto frontseat = frame->findDescendant("ped_frontseat"); + auto backseat = frame->findDescendant("ped_backseat"); + if (frontseat) { + auto p = frontseat->getDefaultTranslation(); // Left seat p.x = -p.x; info->second->seats.front.push_back({p}); @@ -299,10 +290,10 @@ VehicleObject* GameWorld::createVehicle(const uint16_t id, const glm::vec3& pos, p.x = -p.x; info->second->seats.front.push_back({p}); } - if (name == "ped_backseat") { + if (backseat) { // @todo how does this work for the barracks, ambulance // or coach? - auto p = f->getDefaultTranslation(); + auto p = backseat->getDefaultTranslation(); // Left seat p.x = -p.x; info->second->seats.back.push_back({p}); diff --git a/rwengine/src/items/Weapon.cpp b/rwengine/src/items/Weapon.cpp index 5f3d1a48..560161de 100644 --- a/rwengine/src/items/Weapon.cpp +++ b/rwengine/src/items/Weapon.cpp @@ -1,23 +1,14 @@ -#include #include #include #include void Weapon::fireHitscan(WeaponData* weapon, CharacterObject* owner) { - auto handFrame = owner->getModel()->findFrame("srhand"); - glm::mat4 handMatrix; - if (handFrame) { - while (handFrame->getParent()) { - handMatrix = - owner->skeleton->getMatrix(handFrame->getIndex()) * handMatrix; - handFrame = handFrame->getParent(); - } - } + auto handFrame = owner->getClump()->findFrame("srhand"); + glm::mat4 handMatrix = handFrame->getWorldTransform(); const auto& raydirection = owner->getLookDirection(); const auto rayend = owner->getPosition() + raydirection * weapon->hitRange; - auto handPos = glm::vec3(handMatrix[3]); - auto fireOrigin = owner->getPosition() + owner->getRotation() * handPos; + auto fireOrigin = glm::vec3(handMatrix[3]); float dmg = weapon->damage; owner->engine->doWeaponScan({dmg, fireOrigin, rayend, weapon}); diff --git a/rwengine/src/loaders/LoaderIDE.cpp b/rwengine/src/loaders/LoaderIDE.cpp index 29c67d36..0c84a9b7 100644 --- a/rwengine/src/loaders/LoaderIDE.cpp +++ b/rwengine/src/loaders/LoaderIDE.cpp @@ -82,6 +82,8 @@ bool LoaderIDE::load(const std::string &filename, const PedStatsList &stats) { objs->setLodDistance(i, atof(buff.c_str())); } + objs->determineFurthest(); + getline(strstream, buff, ','); objs->flags = atoi(buff.c_str()); @@ -96,12 +98,6 @@ bool LoaderIDE::load(const std::string &filename, const PedStatsList &stats) { objs->timeOff = 24; } - /// @todo Figure out how LOD stuff is intended to work - if (objs->name.find("LOD", 0, 3) != std::string::npos && - objs->name != "LODistancoast01") { - objs->LOD = true; - } - objects.emplace(objs->id(), std::move(objs)); break; } diff --git a/rwengine/src/objects/CharacterObject.cpp b/rwengine/src/objects/CharacterObject.cpp index 5983fcd3..ba3608b9 100644 --- a/rwengine/src/objects/CharacterObject.cpp +++ b/rwengine/src/objects/CharacterObject.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -71,10 +70,10 @@ CharacterObject::CharacterObject(GameWorld* engine, const glm::vec3& pos, animations.ko_shot_front = engine->data->animations["ko_shot_front"]; auto info = getModelInfo(); + setClump(ClumpPtr(info->getModel()->clone())); if (info->getModel()) { setModel(info->getModel()); - skeleton = new Skeleton; - animator = new Animator(getModel(), skeleton); + animator = new Animator(getClump().get()); createActor(); } @@ -108,9 +107,8 @@ void CharacterObject::createActor(const glm::vec2& size) { physCharacter = new btKinematicCharacterController(physObject, physShape, 0.30f, 2); #else - physCharacter = - new btKinematicCharacterController(physObject, physShape, 0.30f, - btVector3(0.f, 0.f, 1.f)); + physCharacter = new btKinematicCharacterController( + physObject, physShape, 0.30f, btVector3(0.f, 0.f, 1.f)); #endif physCharacter->setFallSpeed(20.f); physCharacter->setUseGhostSweepTest(true); @@ -220,42 +218,42 @@ glm::vec3 CharacterObject::updateMovementAnimation(float dt) { } // If we have to, interrogate the movement animation - if (movementAnimation != animations.idle) { - if (!getModel()->frames[0]->getChildren().empty()) { - ModelFrame* root = getModel()->frames[0]->getChildren()[0]; - auto it = movementAnimation->bones.find(root->getName()); - if (it != movementAnimation->bones.end()) { - AnimationBone* rootBone = it->second; - float step = dt; - const float duration = - animator->getAnimation(AnimIndexMovement)->duration; - float animTime = fmod( - animator->getAnimationTime(AnimIndexMovement), duration); - - // Handle any remaining transformation before the end of the - // keyframes - if ((animTime + step) > duration) { - glm::vec3 a = - rootBone->getInterpolatedKeyframe(animTime).position; - glm::vec3 b = - rootBone->getInterpolatedKeyframe(duration).position; - glm::vec3 d = (b - a); - animTranslate.y += d.y; - step -= (duration - animTime); - animTime = 0.f; - } + const auto& modelroot = getClump()->getFrame(); + if (movementAnimation != animations.idle && + !modelroot->getChildren().empty()) { + const auto& root = modelroot->getChildren()[0]; + auto it = movementAnimation->bones.find(root->getName()); + if (it != movementAnimation->bones.end()) { + AnimationBone* rootBone = it->second; + float step = dt; + const float duration = + animator->getAnimation(AnimIndexMovement)->duration; + float animTime = + fmod(animator->getAnimationTime(AnimIndexMovement), duration); + // Handle any remaining transformation before the end of the + // keyframes + if ((animTime + step) > duration) { glm::vec3 a = rootBone->getInterpolatedKeyframe(animTime).position; glm::vec3 b = - rootBone->getInterpolatedKeyframe(animTime + step).position; + rootBone->getInterpolatedKeyframe(duration).position; glm::vec3 d = (b - a); animTranslate.y += d.y; - - Skeleton::FrameData fd = skeleton->getData(root->getIndex()); - fd.a.translation.y = 0.f; - skeleton->setData(root->getIndex(), fd); + step -= (duration - animTime); + animTime = 0.f; } + + glm::vec3 a = rootBone->getInterpolatedKeyframe(animTime).position; + glm::vec3 b = + rootBone->getInterpolatedKeyframe(animTime + step).position; + glm::vec3 d = (b - a); + animTranslate.y += d.y; + + // Kludge: Drop y component of root bone + auto t = glm::vec3(root->getTransform()[3]); + t.y = 0.f; + root->setTranslation(t); } } @@ -276,10 +274,10 @@ void CharacterObject::tick(float dt) { } } -void CharacterObject::setRotation(const glm::quat &orientation) -{ +void CharacterObject::setRotation(const glm::quat& orientation) { m_look.x = glm::roll(orientation); rotation = orientation; + getClump()->getFrame()->setRotation(glm::mat3_cast(rotation)); } #include @@ -293,15 +291,13 @@ void CharacterObject::changeCharacterModel(const std::string& name) { engine->data->loadTXD(modelName + ".txd"); auto newmodel = engine->data->loadClump(modelName + ".dff"); - if (skeleton) { + if (animator) { delete animator; - delete skeleton; } setModel(newmodel); - skeleton = new Skeleton; - animator = new Animator(getModel(), skeleton); + animator = new Animator(getClump().get()); } void CharacterObject::updateCharacter(float dt) { @@ -332,6 +328,7 @@ void CharacterObject::updateCharacter(float dt) { yaw += std::atan2(movement.z, movement.x); } rotation = glm::quat(glm::vec3(0.f, 0.f, yaw)); + getClump()->getFrame()->setRotation(glm::mat3_cast(rotation)); } walkDir = rotation * walkDir; @@ -352,6 +349,7 @@ void CharacterObject::updateCharacter(float dt) { auto Pos = physCharacter->getGhostObject()->getWorldTransform().getOrigin(); position = glm::vec3(Pos.x(), Pos.y(), Pos.z()); + getClump()->getFrame()->setTranslation(position); // Handle above waist height water. auto wi = engine->data->getWaterIndexAt(getPosition()); @@ -407,6 +405,7 @@ void CharacterObject::setPosition(const glm::vec3& pos) { physCharacter->warp(bpos); } position = pos; + getClump()->getFrame()->setTranslation(pos); } bool CharacterObject::isAlive() const { @@ -611,8 +610,7 @@ void CharacterObject::useItem(bool active, bool primary) { if (!currentState.primaryActive && active) { // If we've just started, activate controller->setNextActivity(new Activities::UseItem(item)); - } - else if (currentState.primaryActive && !active) { + } else if (currentState.primaryActive && !active) { // UseItem will cancel itself upon !primaryActive } currentState.primaryActive = active; diff --git a/rwengine/src/objects/CharacterObject.hpp b/rwengine/src/objects/CharacterObject.hpp index f95c28cd..4ffed1c3 100644 --- a/rwengine/src/objects/CharacterObject.hpp +++ b/rwengine/src/objects/CharacterObject.hpp @@ -98,7 +98,7 @@ struct AnimationGroup { * @brief The CharacterObject struct * Implements Character object behaviours. */ -class CharacterObject : public GameObject { +class CharacterObject : public GameObject, public ClumpObject { private: CharacterState currentState; diff --git a/rwengine/src/objects/CutsceneObject.cpp b/rwengine/src/objects/CutsceneObject.cpp index 6d1b55b3..bc35a74e 100644 --- a/rwengine/src/objects/CutsceneObject.cpp +++ b/rwengine/src/objects/CutsceneObject.cpp @@ -1,9 +1,8 @@ -#include #include #include CutsceneObject::CutsceneObject(GameWorld *engine, const glm::vec3 &pos, - const glm::quat &rot, Model *model, + const glm::quat &rot, Clump *model, BaseModelInfo *modelinfo) : GameObject(engine, pos, rot, modelinfo) , _parent(nullptr) @@ -14,8 +13,8 @@ CutsceneObject::CutsceneObject(GameWorld *engine, const glm::vec3 &pos, else { setModel(getModelInfo()->getModel()); } - skeleton = new Skeleton; - animator = new Animator(getModel(), skeleton); + setClump(ClumpPtr(getModel()->clone())); + animator = new Animator(getClump().get()); } CutsceneObject::~CutsceneObject() { diff --git a/rwengine/src/objects/CutsceneObject.hpp b/rwengine/src/objects/CutsceneObject.hpp index 280db419..6a05f4f8 100644 --- a/rwengine/src/objects/CutsceneObject.hpp +++ b/rwengine/src/objects/CutsceneObject.hpp @@ -6,13 +6,13 @@ /** * @brief Object type used for cutscene animations. */ -class CutsceneObject : public GameObject { +class CutsceneObject : public GameObject, public ClumpObject { GameObject* _parent; ModelFrame* _bone; public: CutsceneObject(GameWorld* engine, const glm::vec3& pos, - const glm::quat& rot, Model* model, + const glm::quat& rot, Clump* model, BaseModelInfo* modelinfo); ~CutsceneObject(); diff --git a/rwengine/src/objects/GameObject.cpp b/rwengine/src/objects/GameObject.cpp index 08f6741a..c80d51ed 100644 --- a/rwengine/src/objects/GameObject.cpp +++ b/rwengine/src/objects/GameObject.cpp @@ -1,4 +1,3 @@ -#include #include #include #include @@ -9,9 +8,6 @@ GameObject::~GameObject() { if (animator) { delete animator; } - if (skeleton) { - delete skeleton; - } if (modelinfo_) { modelinfo_->removeReference(); diff --git a/rwengine/src/objects/GameObject.hpp b/rwengine/src/objects/GameObject.hpp index 917b21f3..7a6de40a 100644 --- a/rwengine/src/objects/GameObject.hpp +++ b/rwengine/src/objects/GameObject.hpp @@ -2,7 +2,7 @@ #ifndef _GAMEOBJECT_HPP_ #define _GAMEOBJECT_HPP_ -#include +#include #include #include #include @@ -11,9 +11,7 @@ #include #include -class Skeleton; class CharacterController; -class ModelFrame; class Animator; class GameWorld; @@ -33,7 +31,7 @@ class GameObject { /** * Model used for rendering */ - Model* model_; + Clump* model_; protected: void changeModelInfo(BaseModelInfo* next) { @@ -47,7 +45,6 @@ public: GameWorld* engine; Animator* animator; /// Object's animator. - Skeleton* skeleton; bool inWater; @@ -72,7 +69,6 @@ public: , rotation(rot) , engine(engine) , animator(nullptr) - , skeleton(nullptr) , inWater(false) , _lastHeight(std::numeric_limits::max()) , visible(true) @@ -106,14 +102,14 @@ public: /** * @return The model used in rendering */ - Model* getModel() const { + Clump* getModel() const { return model_; } /** * Changes the current model, used for re-dressing chars */ - void setModel(Model* model) { + void setModel(Clump* model) { model_ = model; } @@ -235,7 +231,7 @@ public: return lifetime; } - void updateTransform(const glm::vec3& pos, const glm::quat& rot) { + virtual void updateTransform(const glm::vec3& pos, const glm::quat& rot) { _lastPosition = position; _lastRotation = rotation; position = pos; @@ -246,4 +242,18 @@ private: ObjectLifetime lifetime; }; +class ClumpObject { + ClumpPtr clump_; + +protected: + void setClump(ClumpPtr ptr) { + clump_ = ptr; + } + +public: + const ClumpPtr& getClump() const { + return clump_; + } +}; + #endif // __GAMEOBJECTS_HPP__ diff --git a/rwengine/src/objects/InstanceObject.cpp b/rwengine/src/objects/InstanceObject.cpp index 1b40898c..5490d956 100644 --- a/rwengine/src/objects/InstanceObject.cpp +++ b/rwengine/src/objects/InstanceObject.cpp @@ -7,17 +7,23 @@ InstanceObject::InstanceObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, const glm::vec3& scale, - BaseModelInfo* modelinfo, InstanceObject* lod, + BaseModelInfo* modelinfo, std::shared_ptr dyn) : GameObject(engine, pos, rot, modelinfo) , health(100.f) , scale(scale) , body(nullptr) - , LODinstance(lod) , dynamics(dyn) , _enablePhysics(false) { if (modelinfo) { changeModel(modelinfo); + setPosition(pos); + setRotation(rot); + + // Disable the main island LOD + if (modelinfo->name.find("IslandLOD") != std::string::npos) { + setVisible(false); + } /// @todo store path information properly if (modelinfo->type() == ModelDataType::SimpleInfo) { @@ -133,6 +139,17 @@ void InstanceObject::changeModel(BaseModelInfo* incoming) { setModel(getModelInfo()->getModel()); auto collision = getModelInfo()->getCollision(); + auto modelatomic = getModelInfo()->getAtomic(0); + if (modelatomic) { + auto previousatomic = atomic_; + atomic_ = modelatomic->clone(); + if (previousatomic) { + atomic_->setFrame(previousatomic->getFrame()); + } else { + atomic_->setFrame(std::make_shared()); + } + } + if (collision) { body.reset(new CollisionInstance); body->createPhysicsBody(this, collision, dynamics.get()); @@ -141,11 +158,25 @@ void InstanceObject::changeModel(BaseModelInfo* incoming) { } } +void InstanceObject::setPosition(const glm::vec3& pos) { + if (body) { + auto& wtr = body->getBulletBody()->getWorldTransform(); + wtr.setOrigin(btVector3(pos.x, pos.y, pos.z)); + } + if (atomic_) { + atomic_->getFrame()->setTranslation(pos); + } + GameObject::setPosition(pos); +} + void InstanceObject::setRotation(const glm::quat& r) { if (body) { auto& wtr = body->getBulletBody()->getWorldTransform(); wtr.setRotation(btQuaternion(r.x, r.y, r.z, r.w)); } + if (atomic_) { + atomic_->getFrame()->setRotation(glm::mat3_cast(r)); + } GameObject::setRotation(r); } @@ -181,3 +212,11 @@ void InstanceObject::setSolid(bool solid) { } body->getBulletBody()->setCollisionFlags(flags); } + +void InstanceObject::updateTransform(const glm::vec3& pos, + const glm::quat& rot) { + position = pos; + rotation = rot; + getAtomic()->getFrame()->setRotation(glm::mat3_cast(rot)); + getAtomic()->getFrame()->setTranslation(pos); +} diff --git a/rwengine/src/objects/InstanceObject.hpp b/rwengine/src/objects/InstanceObject.hpp index b669da22..9e1b0e06 100644 --- a/rwengine/src/objects/InstanceObject.hpp +++ b/rwengine/src/objects/InstanceObject.hpp @@ -14,16 +14,20 @@ class InstanceObject : public GameObject { float health; bool visible = true; + /** + * The Atomic instance for this object + */ + AtomicPtr atomic_; + public: glm::vec3 scale; std::unique_ptr body; - InstanceObject* LODinstance; std::shared_ptr dynamics; bool _enablePhysics; InstanceObject(GameWorld* engine, const glm::vec3& pos, const glm::quat& rot, const glm::vec3& scale, - BaseModelInfo* modelinfo, InstanceObject* lod, + BaseModelInfo* modelinfo, std::shared_ptr dyn); ~InstanceObject(); @@ -31,10 +35,15 @@ public: return Instance; } + const AtomicPtr& getAtomic() const { + return atomic_; + } + void tick(float dt); void changeModel(BaseModelInfo* incoming); + virtual void setPosition(const glm::vec3& pos); virtual void setRotation(const glm::quat& r); virtual bool takeDamage(const DamageInfo& damage); @@ -51,6 +60,8 @@ public: float getHealth() const { return health; } + + void updateTransform(const glm::vec3& pos, const glm::quat& rot) override; }; #endif diff --git a/rwengine/src/objects/VehicleObject.cpp b/rwengine/src/objects/VehicleObject.cpp index 964acf9d..6af37b78 100644 --- a/rwengine/src/objects/VehicleObject.cpp +++ b/rwengine/src/objects/VehicleObject.cpp @@ -1,7 +1,6 @@ #include +#include #include -#include -#include #include #include #include @@ -70,11 +69,7 @@ public: const auto& rot = tform.getRotation(); auto r2 = inv * glm::quat(rot.w(), rot.x(), rot.y(), rot.z()); - auto skeleton = m_object->skeleton; - auto& prev = skeleton->getData(m_part->dummy->getIndex()).a; - auto next = prev; - next.rotation = r2; - skeleton->setData(m_part->dummy->getIndex(), {next, prev, true}); + m_part->dummy->setRotation(glm::mat3_cast(r2)); } private: @@ -148,23 +143,28 @@ VehicleObject::VehicleObject(GameWorld* engine, const glm::vec3& pos, : 1.f - info->handling.tractionBias); } - // Hide all LOD and damage frames. - skeleton = new Skeleton; - setModel(getVehicle()->getModel()); + setClump(ClumpPtr(getModelInfo()->getModel()->clone())); - for (ModelFrame* frame : getModel()->frames) { - auto& name = frame->getName(); - bool isDam = name.find("_dam") != name.npos; - bool isLod = name.find("lo") != name.npos; - bool isDum = name.find("_dummy") != name.npos; - /*bool isOk = name.find("_ok") != name.npos;*/ - if (isDam || isLod || isDum) { - skeleton->setEnabled(frame, false); + // Locate the Atomics for the chassis_hi and chassis_vlo frames + for (const auto& atomic : getClump()->getAtomics()) { + auto frame = atomic->getFrame().get(); + if (frame->getName() == "chassis_vlo") { + chassislow_ = atomic.get(); } + if (frame->getName() == "chassis_hi") { + chassishigh_ = atomic.get(); + } + } + // Create meta-data for dummy parts + auto chassisframe = getClump()->findFrame("chassis_dummy"); + RW_CHECK(chassisframe, "Vehicle has no chassis_dummy"); + for (auto& frame : chassisframe->getChildren()) { + auto& name = frame->getName(); + bool isDum = name.find("_dummy") != name.npos; if (isDum) { - registerPart(frame); + registerPart(frame.get()); } } } @@ -184,6 +184,7 @@ VehicleObject::~VehicleObject() { void VehicleObject::setPosition(const glm::vec3& pos) { GameObject::setPosition(pos); + getClump()->getFrame()->setTranslation(pos); if (collision->getBulletBody()) { auto bodyOrigin = btVector3(position.x, position.y, position.z); for (auto& part : dynamicParts) { @@ -201,6 +202,7 @@ void VehicleObject::setPosition(const glm::vec3& pos) { } void VehicleObject::setRotation(const glm::quat& orientation) { + getClump()->getFrame()->setRotation(glm::mat3_cast(orientation)); if (collision->getBulletBody()) { auto t = collision->getBulletBody()->getWorldTransform(); t.setRotation(btQuaternion(orientation.x, orientation.y, orientation.z, @@ -210,6 +212,14 @@ void VehicleObject::setRotation(const glm::quat& orientation) { GameObject::setRotation(orientation); } +void VehicleObject::updateTransform(const glm::vec3& pos, + const glm::quat& rot) { + position = pos; + rotation = rot; + getClump()->getFrame()->setRotation(glm::mat3_cast(rot)); + getClump()->getFrame()->setTranslation(pos); +} + #include void VehicleObject::tick(float dt) { @@ -550,18 +560,14 @@ bool VehicleObject::takeDamage(const GameObject::DamageInfo& dmg) { if (p->normal == nullptr) continue; - if (skeleton->getData(p->normal->getIndex()).enabled) { - auto& geom = - getModel()->geometries[p->normal->getGeometries()[0]]; - auto pp = - p->normal->getMatrix() * glm::vec4(0.f, 0.f, 0.f, 1.f); - float td = glm::distance( - glm::vec3(pp) + geom->geometryBounds.center, dpoint); - if (td < geom->geometryBounds.radius * 1.2f) { - setPartState(p, DAM); - } - /// @todo determine when doors etc. should un-latch + /// @todo correct logic + float damageradius = 0.1f; + auto center = glm::vec3(p->dummy->getWorldTransform()[3]); + float td = glm::distance(center, dpoint); + if (td < damageradius * 1.2f) { + setPartState(p, DAM); } + /// @todo determine when doors etc. should un-latch } } @@ -571,11 +577,11 @@ bool VehicleObject::takeDamage(const GameObject::DamageInfo& dmg) { void VehicleObject::setPartState(VehicleObject::Part* part, VehicleObject::FrameState state) { if (state == VehicleObject::OK) { - if (part->normal) skeleton->setEnabled(part->normal, true); - if (part->damaged) skeleton->setEnabled(part->damaged, false); + if (part->normal) part->normal->setFlag(Atomic::ATOMIC_RENDER, true); + if (part->damaged) part->damaged->setFlag(Atomic::ATOMIC_RENDER, false); } else if (state == VehicleObject::DAM) { - if (part->normal) skeleton->setEnabled(part->normal, false); - if (part->damaged) skeleton->setEnabled(part->damaged, true); + if (part->normal) part->normal->setFlag(Atomic::ATOMIC_RENDER, false); + if (part->damaged) part->damaged->setFlag(Atomic::ATOMIC_RENDER, true); } } @@ -606,10 +612,7 @@ void VehicleObject::setPartLocked(VehicleObject::Part* part, bool locked) { destroyObjectHinge(part); // Restore default bone transform - auto dt = part->dummy->getDefaultTranslation(); - auto dr = glm::quat_cast(part->dummy->getDefaultRotation()); - Skeleton::FrameTransform tf{dt, dr}; - skeleton->setData(part->dummy->getIndex(), {tf, tf, true}); + part->dummy->reset(); } } @@ -638,26 +641,31 @@ VehicleObject::Part* VehicleObject::getPart(const std::string& name) { return nullptr; } -ModelFrame* findStateFrame(ModelFrame* f, const std::string& state) { - auto it = std::find_if( - f->getChildren().begin(), f->getChildren().end(), [&](ModelFrame* c) { - return c->getName().find(state) != std::string::npos; - }); - if (it != f->getChildren().end()) { - return *it; - } - return nullptr; -} - void VehicleObject::registerPart(ModelFrame* mf) { - auto normal = findStateFrame(mf, "_ok"); - auto damage = findStateFrame(mf, "_dam"); + auto dummynameend = mf->getName().find("_dummy"); + RW_CHECK(dummynameend != std::string::npos, + "Can't create part from non-dummy"); + auto dummyname = mf->getName().substr(0, dummynameend); + auto normalframe = mf->findDescendant(dummyname + "_hi_ok"); + auto damageframe = mf->findDescendant(dummyname + "_hi_dam"); - if (normal == nullptr && damage == nullptr) { + if (normalframe == nullptr && damageframe == nullptr) { // Not actually a useful part, just a dummy. return; } + // Find the Atomics for the part + Atomic *normal = nullptr, *damage = nullptr; + for (const auto& atomic : getClump()->getAtomics()) { + if (atomic->getFrame().get() == normalframe) { + normal = atomic.get(); + } + if (atomic->getFrame().get() == damageframe) { + damage = atomic.get(); + damage->setFlag(Atomic::ATOMIC_RENDER, false); + } + } + dynamicParts.insert( {mf->getName(), {mf, normal, damage, nullptr, nullptr, false, 0.f, 0.f, 0.f}}); @@ -672,20 +680,12 @@ void VehicleObject::createObjectHinge(Part* part) { auto& fn = part->dummy->getName(); - ModelFrame* okframe = part->normal; - - if (okframe->getGeometries().size() == 0) return; - - auto& geom = getModel()->geometries[okframe->getGeometries()[0]]; - auto gbounds = geom->geometryBounds; - if (fn.find("door") != fn.npos) { hingeAxis = {0.f, 0.f, 1.f}; // hingePosition = {0.f, 0.2f, 0.f}; boxSize = {0.15f, 0.5f, 0.6f}; // boxOffset = {0.f,-0.2f, gbounds.center.z/2.f}; - auto tf = gbounds.center; - boxOffset = btVector3(tf.x, tf.y, tf.z); + boxOffset = btVector3(0.f, -0.25f, 0.f); hingePosition = -boxOffset; if (sign < 0.f) { diff --git a/rwengine/src/objects/VehicleObject.hpp b/rwengine/src/objects/VehicleObject.hpp index 870b59ca..91b1507b 100644 --- a/rwengine/src/objects/VehicleObject.hpp +++ b/rwengine/src/objects/VehicleObject.hpp @@ -16,13 +16,15 @@ class btTransform; * @class VehicleObject * Implements Vehicle behaviours. */ -class VehicleObject : public GameObject { +class VehicleObject : public GameObject, public ClumpObject { private: float steerAngle; float throttle; float brake; bool handbrake; + Atomic* chassishigh_ = nullptr; + Atomic* chassislow_ = nullptr; public: VehicleInfoHandle info; glm::u8vec3 colourPrimary; @@ -36,8 +38,8 @@ public: struct Part { ModelFrame* dummy; - ModelFrame* normal; - ModelFrame* damaged; + Atomic* normal; + Atomic* damaged; btRigidBody* body; btHingeConstraint* constraint; bool moveToAngle; @@ -58,10 +60,15 @@ public: void setRotation(const glm::quat& orientation); + void updateTransform(const glm::vec3& pos, const glm::quat& rot) override; + VehicleModelInfo* getVehicle() const { return getModelInfo(); } + Atomic* getHighLOD() const { return chassishigh_; } + Atomic* getLowLOD() const { return chassislow_; } + Type type() { return Vehicle; } diff --git a/rwengine/src/render/DebugDraw.hpp b/rwengine/src/render/DebugDraw.hpp index 833dc12c..3c2ad88e 100644 --- a/rwengine/src/render/DebugDraw.hpp +++ b/rwengine/src/render/DebugDraw.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include #include class DebugDraw : public btIDebugDraw { @@ -28,7 +28,7 @@ public: protected: int debugMode; - std::vector lines; + std::vector lines; size_t maxlines; GeometryBuffer *lineBuff; DrawBuffer *dbuff; diff --git a/rwengine/src/render/GameRenderer.cpp b/rwengine/src/render/GameRenderer.cpp index a345c622..c0a33b5d 100644 --- a/rwengine/src/render/GameRenderer.cpp +++ b/rwengine/src/render/GameRenderer.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #include @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -318,8 +317,7 @@ void GameRenderer::renderWorld(GameWorld* world, const ViewCamera& camera, m, glm::vec3(i.radius + 0.15f * glm::sin(_renderWorld->getGameTime() * 5.f))); - objectRenderer.renderFrame(sphereModel, sphereModel->frames[0], m, - nullptr, 1.f, renderList); + objectRenderer.renderClump(sphereModel, m, nullptr, renderList); } // Render arrows above anything that isn't radar only (or hidden) @@ -346,8 +344,7 @@ void GameRenderer::renderWorld(GameWorld* world, const ViewCamera& camera, glm::vec3(0.f, 0.f, 2.5f + glm::sin(a) * 0.5f)); model = glm::rotate(model, a, glm::vec3(0.f, 0.f, 1.f)); model = glm::scale(model, glm::vec3(1.5f, 1.5f, 1.5f)); - objectRenderer.renderFrame(arrowModel, arrowModel->frames[0], model, - nullptr, 1.f, renderList); + objectRenderer.renderClump(arrowModel, model, nullptr, renderList); } RW_PROFILE_END(); @@ -475,57 +472,6 @@ void GameRenderer::renderPostProcess() { renderer->drawArrays(glm::mat4(), &ssRectDraw, wdp); } -void GameRenderer::renderGeometry(Model* model, size_t g, - const glm::mat4& modelMatrix, float opacity, - GameObject* object) { - for (size_t sg = 0; sg < model->geometries[g]->subgeom.size(); ++sg) { - Model::SubGeometry& subgeom = model->geometries[g]->subgeom[sg]; - - Renderer::DrawParameters dp; - - dp.colour = {255, 255, 255, 255}; - dp.count = subgeom.numIndices; - dp.start = subgeom.start; - dp.textures = {0}; - - if (model->geometries[g]->materials.size() > subgeom.material) { - Model::Material& mat = - model->geometries[g]->materials[subgeom.material]; - - if (mat.textures.size() > 0) { - auto tex = mat.textures[0].texture; - if (tex) { - dp.textures = {tex->getName()}; - } - } - - if ((model->geometries[g]->flags & - RW::BSGeometry::ModuleMaterialColor) == - RW::BSGeometry::ModuleMaterialColor) { - dp.colour = mat.colour; - - if (object && object->type() == GameObject::Vehicle) { - auto vehicle = static_cast(object); - if (dp.colour.r == 60 && dp.colour.g == 255 && - dp.colour.b == 0) { - dp.colour = glm::u8vec4(vehicle->colourPrimary, 255); - } else if (dp.colour.r == 255 && dp.colour.g == 0 && - dp.colour.b == 175) { - dp.colour = glm::u8vec4(vehicle->colourSecondary, 255); - } - } - } - - dp.colour.a *= opacity; - - dp.diffuse = mat.diffuseIntensity; - dp.ambient = mat.ambientIntensity; - } - - renderer->draw(modelMatrix, &model->geometries[g]->dbuff, dp); - } -} - void GameRenderer::renderEffects(GameWorld* world) { renderer->useProgram(particleProg); @@ -656,49 +602,6 @@ void GameRenderer::drawColour(const glm::vec4& colour, glm::vec4 extents) { renderer->invalidate(); } -bool GameRenderer::renderFrame(Model* m, ModelFrame* f, const glm::mat4& matrix, - GameObject* object, float opacity, - bool queueTransparent) { - auto localmatrix = matrix; - bool vis = true; - - if (object && object->skeleton) { - // Skeleton is loaded with the correct matrix via Animator. - localmatrix *= object->skeleton->getMatrix(f); - - vis = object->skeleton->getData(f->getIndex()).enabled; - } else { - localmatrix *= f->getTransform(); - } - - if (vis) { - for (size_t g : f->getGeometries()) { - if (!object || !object->animator) { - RW::BSGeometryBounds& bounds = m->geometries[g]->geometryBounds; - - glm::vec3 boundpos = bounds.center + glm::vec3(localmatrix[3]); - if (!_camera.frustum.intersects(boundpos, bounds.radius)) { - culled++; - continue; - } - } - - renderGeometry(m, g, localmatrix, opacity, object); - } - } - - for (ModelFrame* c : f->getChildren()) { - renderFrame(m, c, localmatrix, object, queueTransparent); - } - return true; -} - -void GameRenderer::renderModel(Model* model, const glm::mat4& modelMatrix, - GameObject* object) { - renderFrame(model, model->frames[model->rootFrameIdx], modelMatrix, object, - 1.f); -} - void GameRenderer::renderPaths() { /*glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, debugTex); diff --git a/rwengine/src/render/GameRenderer.hpp b/rwengine/src/render/GameRenderer.hpp index 91daf69f..8a4fb4ef 100644 --- a/rwengine/src/render/GameRenderer.hpp +++ b/rwengine/src/render/GameRenderer.hpp @@ -16,7 +16,7 @@ class Logger; #include "TextRenderer.hpp" #include "WaterRenderer.hpp" -class Model; +class Clump; class ModelFrame; class GameWorld; class GameObject; @@ -51,30 +51,6 @@ class GameRenderer { /** The low-level drawing interface to use */ Renderer* renderer; - /** Stores data for deferring transparent objects */ - struct RQueueEntry { - Model* model; - size_t g; - size_t sg; - glm::mat4 matrix; - Renderer::DrawParameters dp; - GameObject* object; - }; - - /** - * @brief renders a model's frame. - * @param m - * @param f - * @param matrix - * @param object - * @param queueTransparent abort the draw if the frame contains transparent - * materials - * @return True if the frame was drawn, false if it should be queued - */ - bool renderFrame(Model* m, ModelFrame* f, const glm::mat4& matrix, - GameObject* object, float opacity, - bool queueTransparent = true); - // Temporary variables used during rendering float _renderAlpha; GameWorld* _renderWorld; @@ -155,15 +131,6 @@ public: void drawTexture(TextureData* texture, glm::vec4 extents); void drawColour(const glm::vec4& colour, glm::vec4 extents); - /** - * Renders a model (who'd have thought) - */ - void renderModel(Model*, const glm::mat4& modelMatrix, - GameObject* = nullptr); - - void renderGeometry(Model*, size_t geom, const glm::mat4& modelMatrix, - float opacity, GameObject* = nullptr); - /** method for rendering AI debug information */ void renderPaths(); @@ -207,15 +174,15 @@ public: * * GameRenderer will take ownership of the Model* pointer */ - void setSpecialModel(SpecialModel usage, Model* model) { + void setSpecialModel(SpecialModel usage, Clump* model) { specialmodels_[usage].reset(model); } private: /// Hard-coded models to use for each of the special models - std::unique_ptr + std::unique_ptr specialmodels_[SpecialModel::SpecialModelCount]; - Model* getSpecialModel(SpecialModel usage) const { + Clump* getSpecialModel(SpecialModel usage) const { return specialmodels_[usage].get(); } }; diff --git a/rwengine/src/render/ObjectRenderer.cpp b/rwengine/src/render/ObjectRenderer.cpp index 9477c19c..b8cc5eb1 100644 --- a/rwengine/src/render/ObjectRenderer.cpp +++ b/rwengine/src/render/ObjectRenderer.cpp @@ -1,6 +1,5 @@ +#include #include -#include -#include #include #include #include @@ -17,12 +16,15 @@ #include #endif -constexpr float kDrawDistanceFactor = 1.0f; +constexpr float kDrawDistanceFactor = 1.5f; constexpr float kWorldDrawDistanceFactor = kDrawDistanceFactor; -#if 0 // There's no distance based culling for these types of objects yet constexpr float kVehicleDrawDistanceFactor = kDrawDistanceFactor; +#if 0 // There's no distance based culling for these types of objects yet constexpr float kPedestrianDrawDistanceFactor = kDrawDistanceFactor; #endif +constexpr float kMagicLODDistance = 330.f; +constexpr float kVehicleLODDistance = 70.f; +constexpr float kVehicleDrawDistance = 280.f; RenderKey createKey(bool transparent, float normalizedDepth, Renderer::Textures& textures) { @@ -33,12 +35,10 @@ RenderKey createKey(bool transparent, float normalizedDepth, uint8_t(0xFF & (textures.size() > 0 ? textures[0] : 0)) << 0; } -void ObjectRenderer::renderGeometry(Model* model, size_t g, - const glm::mat4& modelMatrix, float opacity, +void ObjectRenderer::renderGeometry(Geometry* geom, + const glm::mat4& modelMatrix, GameObject* object, RenderList& outList) { - for (size_t sg = 0; sg < model->geometries[g]->subgeom.size(); ++sg) { - Model::SubGeometry& subgeom = model->geometries[g]->subgeom[sg]; - + for (SubGeometry& subgeom : geom->subgeom) { bool isTransparent = false; Renderer::DrawParameters dp; @@ -55,9 +55,8 @@ void ObjectRenderer::renderGeometry(Model* model, size_t g, !(modelinfo->flags & SimpleModelInfo::NO_ZBUFFER_WRITE); } - if (model->geometries[g]->materials.size() > subgeom.material) { - Model::Material& mat = - model->geometries[g]->materials[subgeom.material]; + if (geom->materials.size() > subgeom.material) { + Geometry::Material& mat = geom->materials[subgeom.material]; if (mat.textures.size() > 0) { auto tex = mat.textures[0].texture; @@ -69,8 +68,7 @@ void ObjectRenderer::renderGeometry(Model* model, size_t g, } } - if ((model->geometries[g]->flags & - RW::BSGeometry::ModuleMaterialColor) == + if ((geom->flags & RW::BSGeometry::ModuleMaterialColor) == RW::BSGeometry::ModuleMaterialColor) { dp.colour = mat.colour; @@ -86,7 +84,7 @@ void ObjectRenderer::renderGeometry(Model* model, size_t g, } } - dp.visibility = opacity; + dp.visibility = 1.f; if (dp.colour.a < 255) { isTransparent = true; @@ -104,49 +102,48 @@ void ObjectRenderer::renderGeometry(Model* model, size_t g, (m_camera.frustum.far - m_camera.frustum.near); outList.emplace_back( createKey(isTransparent, depth * depth, dp.textures), modelMatrix, - &model->geometries[g]->dbuff, dp); + &geom->dbuff, dp); } } -bool ObjectRenderer::renderFrame(Model* m, ModelFrame* f, - const glm::mat4& matrix, GameObject* object, - float opacity, RenderList& outList) { - auto localmatrix = matrix; - bool vis = true; - if (object && object->skeleton) { - // Skeleton is loaded with the correct matrix via Animator. - localmatrix *= object->skeleton->getMatrix(f); +void ObjectRenderer::renderAtomic(Atomic* atomic, + const glm::mat4& worldtransform, + GameObject* object, RenderList& render) { + RW_CHECK(atomic->getGeometry(), "Can't render an atomic without geometry"); + RW_CHECK(atomic->getFrame(), "Can't render an atomic without a frame"); - vis = object->skeleton->getData(f->getIndex()).enabled; - } else { - localmatrix *= f->getTransform(); + const auto& geometry = atomic->getGeometry(); + const auto& frame = atomic->getFrame(); + + RW::BSGeometryBounds& bounds = geometry->geometryBounds; + + auto transform = worldtransform * frame->getWorldTransform(); + + glm::vec3 boundpos = bounds.center + glm::vec3(transform[3]); + if (!m_camera.frustum.intersects(boundpos, bounds.radius)) { + culled++; + return; } - if (vis) { - for (size_t g : f->getGeometries()) { - if (!object || !object->animator) { - RW::BSGeometryBounds& bounds = m->geometries[g]->geometryBounds; + renderGeometry(geometry.get(), transform, object, render); +} - glm::vec3 boundpos = bounds.center + glm::vec3(localmatrix[3]); - if (!m_camera.frustum.intersects(boundpos, bounds.radius)) { - culled++; - continue; - } - } - - renderGeometry(m, g, localmatrix, opacity, object, outList); +void ObjectRenderer::renderClump(Clump* model, const glm::mat4& worldtransform, + GameObject* object, RenderList& render) { + for (const auto& atomic : model->getAtomics()) { + const auto flags = atomic->getFlags(); + if ((flags & Atomic::ATOMIC_RENDER) == 0) { + continue; } - } - for (ModelFrame* c : f->getChildren()) { - renderFrame(m, c, localmatrix, object, opacity, outList); + renderAtomic(atomic.get(), worldtransform, object, render); } - return true; } void ObjectRenderer::renderInstance(InstanceObject* instance, RenderList& outList) { - if (!instance->getModel()) { + const auto& atomic = instance->getAtomic(); + if (!atomic) { return; } @@ -169,127 +166,61 @@ void ObjectRenderer::renderInstance(InstanceObject* instance, return; } - auto matrixModel = instance->getTimeAdjustedTransform(m_renderAlpha); + float mindist = glm::length(instance->getPosition() - m_camera.position) / + kDrawDistanceFactor; - float mindist = glm::length(instance->getPosition() - m_camera.position) - - instance->getModel()->getBoundingRadius(); - mindist *= 1.f / kDrawDistanceFactor; + if (mindist > modelinfo->getLargestLodDistance()) { + culled++; + return; + } - Model* model = nullptr; - ModelFrame* frame = nullptr; - - // These are used to gracefully fade out things that are just out of view - // distance. - Model* fadingModel = nullptr; - ModelFrame* fadingFrame = nullptr; - auto fadingMatrix = matrixModel; - float opacity = 0.f; - constexpr float fadeRange = 50.f; - - /// @todo replace this block with the correct logic - if (modelinfo->getNumAtomics() == 1) { - // Is closest point greater than the *object* draw distance - float objectRange = modelinfo->getLodDistance(0); - float overlap = (mindist - objectRange); - if (mindist > objectRange) { - // Check for LOD instances - if (instance->LODinstance) { - // Is the closest point greater than the *LOD* draw distance - auto lodmodelinfo = - instance->LODinstance->getModelInfo(); - float LODrange = lodmodelinfo->getLodDistance(0); - if (mindist <= LODrange && instance->LODinstance->getModel()) { - // The model matrix needs to be for the LOD instead - matrixModel = - instance->LODinstance->getTimeAdjustedTransform( - m_renderAlpha); - // If the object is only just out of range, keep - // rendering it and screen-door the LOD. - if (overlap < fadeRange) { - model = instance->LODinstance->getModel(); - fadingModel = instance->getModel(); - opacity = 1.f - (overlap / fadeRange); - } else { - model = instance->LODinstance->getModel(); - } - } - } - // We don't have a LOD object, so fade out gracefully. - else if (overlap < fadeRange) { - fadingModel = instance->getModel(); - opacity = 1.f - (overlap / fadeRange); - } - } - // Otherwise, if we aren't marked as a LOD model, we can render - else if (!modelinfo->LOD) { - model = instance->getModel(); - } - } else { - auto root = instance->getModel()->frames[0]; - auto objectModel = instance->getModel(); - fadingFrame = nullptr; - fadingModel = nullptr; - - matrixModel *= root->getTransform(); - - for (int i = 0; i < modelinfo->getNumAtomics() - 1; ++i) { - auto ind = (modelinfo->getNumAtomics() - 1) - i; - float lodDistance = modelinfo->getLodDistance(i); - if (mindist > lodDistance) { - fadingFrame = root->getChildren()[ind]; - fadingModel = objectModel; - opacity = 1.f - ((mindist - lodDistance) / fadeRange); - } else { - model = objectModel; - frame = root->getChildren()[ind]; - } + if (modelinfo->isBigBuilding() && + mindist < modelinfo->getNearLodDistance() && + mindist < kMagicLODDistance) { + auto related = modelinfo->related(); + if (!related || related->isLoaded()) { + culled++; + return; } } - if (model) { - frame = frame ? frame : model->frames[0]; - renderFrame(model, frame, - matrixModel * glm::inverse(frame->getTransform()), instance, - 1.f, outList); + Atomic* distanceatomic = + modelinfo->getDistanceAtomic(mindist / kDrawDistanceFactor); + if (!distanceatomic) { + return; } - if (fadingModel) { - if (opacity >= 0.01f) { - fadingFrame = fadingFrame ? fadingFrame : fadingModel->frames[0]; - renderFrame( - fadingModel, fadingFrame, - fadingMatrix * glm::inverse(fadingFrame->getTransform()), - instance, opacity, outList); - } + + if (atomic->getGeometry() != distanceatomic->getGeometry()) { + atomic->setGeometry(distanceatomic->getGeometry()); } + + // Render the atomic the instance thinks it should be + renderAtomic(atomic.get(), glm::mat4(), instance, outList); } void ObjectRenderer::renderCharacter(CharacterObject* pedestrian, RenderList& outList) { - glm::mat4 matrixModel; + const auto& clump = pedestrian->getClump(); if (pedestrian->getCurrentVehicle()) { auto vehicle = pedestrian->getCurrentVehicle(); + const auto& vehicleclump = vehicle->getClump(); auto seat = pedestrian->getCurrentSeat(); - matrixModel = vehicle->getTimeAdjustedTransform(m_renderAlpha); + auto matrixModel = vehicleclump->getFrame()->getWorldTransform(); if (pedestrian->isEnteringOrExitingVehicle()) { matrixModel = glm::translate(matrixModel, vehicle->getSeatEntryPosition(seat)); + clump->getFrame()->setTransform(matrixModel); } else { if (seat < vehicle->info->seats.size()) { matrixModel = glm::translate(matrixModel, vehicle->info->seats[seat].offset); + clump->getFrame()->setTransform(matrixModel); } } - } else { - matrixModel = pedestrian->getTimeAdjustedTransform(m_renderAlpha); } - if (!pedestrian->getModel()) return; - - auto root = pedestrian->getModel()->frames[0]; - - renderFrame(pedestrian->getModel(), root->getChildren()[0], matrixModel, - pedestrian, 1.f, outList); + renderClump(pedestrian->getClump().get(), glm::mat4(), nullptr, outList); auto item = pedestrian->getActiveItem(); const auto& weapon = pedestrian->engine->data->weaponData[item]; @@ -298,81 +229,73 @@ void ObjectRenderer::renderCharacter(CharacterObject* pedestrian, return; // No model for this item } - auto handFrame = pedestrian->getModel()->findFrame("srhand"); - glm::mat4 localMatrix; + auto handFrame = pedestrian->getClump()->findFrame("srhand"); if (handFrame) { - while (handFrame->getParent()) { - localMatrix = - pedestrian->skeleton->getMatrix(handFrame->getIndex()) * - localMatrix; - handFrame = handFrame->getParent(); - } + auto simple = + m_world->data->findModelInfo(weapon->modelID); + auto itematomic = simple->getAtomic(0); + renderAtomic(itematomic, handFrame->getWorldTransform(), nullptr, + outList); } - - // Assume items are all simple - auto simple = - m_world->data->findModelInfo(weapon->modelID); - auto geometry = simple->getAtomic(0)->getGeometries().at(0); - renderGeometry(simple->getModel(), geometry, matrixModel * localMatrix, 1.f, - nullptr, outList); } void ObjectRenderer::renderVehicle(VehicleObject* vehicle, RenderList& outList) { - RW_CHECK(vehicle->getModel(), "Vehicle model is null"); - - if (!vehicle->getModel()) { + const auto& clump = vehicle->getClump(); + RW_CHECK(clump, "Vehicle clump is null"); + if (!clump) { return; } - glm::mat4 matrixModel = vehicle->getTimeAdjustedTransform(m_renderAlpha); + float mindist = glm::length(vehicle->getPosition() - m_camera.position) / kVehicleDrawDistanceFactor; + if (mindist < kVehicleLODDistance) { + // Swich visibility to the high LOD + vehicle->getHighLOD()->setFlag(Atomic::ATOMIC_RENDER, true); + vehicle->getLowLOD()->setFlag(Atomic::ATOMIC_RENDER, false); + } else if (mindist < kVehicleDrawDistance) { + // Switch to low + vehicle->getHighLOD()->setFlag(Atomic::ATOMIC_RENDER, false); + vehicle->getLowLOD()->setFlag(Atomic::ATOMIC_RENDER, true); + } else { + culled++; + return; + } - renderFrame(vehicle->getModel(), vehicle->getModel()->frames[0], - matrixModel, vehicle, 1.f, outList); + renderClump(clump.get(), glm::mat4(), vehicle, outList); auto modelinfo = vehicle->getVehicle(); - - // Draw wheels n' stuff auto woi = m_world->data->findModelInfo(modelinfo->wheelmodel_); - if (!woi || !woi->isLoaded()) { + if (!woi || !woi->isLoaded() || !woi->getDistanceAtomic(mindist)) { return; } - auto wheelgeom = woi->getAtomic(0)->getGeometries().at(0); + + auto wheelatomic = woi->getDistanceAtomic(mindist); for (size_t w = 0; w < vehicle->info->wheels.size(); ++w) { auto& wi = vehicle->physVehicle->getWheelInfo(w); // Construct our own matrix so we can use the local transform vehicle->physVehicle->updateWheelTransform(w, false); - /// @todo migrate this into Vehicle physics tick so we can - /// interpolate old -> new - - glm::mat4 wheelM(matrixModel); auto up = -wi.m_wheelDirectionCS; auto right = wi.m_wheelAxleCS; auto fwd = up.cross(right); btQuaternion steerQ(up, wi.m_steering); btQuaternion rollQ(right, -wi.m_rotation); - btMatrix3x3 basis(right[0], fwd[0], up[0], right[1], fwd[1], up[1], right[2], fwd[2], up[2]); - - btTransform t; - t.setBasis(btMatrix3x3(steerQ) * btMatrix3x3(rollQ) * basis); - t.setOrigin(wi.m_chassisConnectionPointCS + - wi.m_wheelDirectionCS * - wi.m_raycastInfo.m_suspensionLength); - + btTransform t( + btMatrix3x3(steerQ) * btMatrix3x3(rollQ) * basis, + wi.m_chassisConnectionPointCS + + wi.m_wheelDirectionCS * wi.m_raycastInfo.m_suspensionLength); + glm::mat4 wheelM; t.getOpenGLMatrix(glm::value_ptr(wheelM)); - wheelM = matrixModel * wheelM; - + wheelM = clump->getFrame()->getWorldTransform() * wheelM; wheelM = glm::scale(wheelM, glm::vec3(modelinfo->wheelscale_)); if (wi.m_chassisConnectionPointCS.x() < 0.f) { wheelM = glm::scale(wheelM, glm::vec3(-1.f, 1.f, 1.f)); } - renderGeometry(woi->getModel(), wheelgeom, wheelM, 1.f, nullptr, - outList); + renderAtomic(wheelatomic, wheelM, nullptr, outList); } } @@ -385,59 +308,29 @@ void ObjectRenderer::renderPickup(PickupObject* pickup, RenderList& outList) { auto odata = pickup->getModelInfo(); - auto model = odata->getModel(); - auto itemModel = odata->getAtomic(0); - auto geom = 0; - if (!itemModel->getGeometries().empty()) { - geom = itemModel->getGeometries()[0]; - } else if (!itemModel->getChildren().empty()) { - geom = itemModel->getChildren()[0]->getGeometries()[0]; - } + auto atomic = odata->getAtomic(0); - renderGeometry(model, geom, modelMatrix, 1.f, pickup, outList); + renderAtomic(atomic, modelMatrix, nullptr, outList); } void ObjectRenderer::renderCutsceneObject(CutsceneObject* cutscene, RenderList& outList) { if (!m_world->state->currentCutscene) return; + const auto& clump = cutscene->getClump(); - if (!cutscene->getModel()) { - return; - } - - glm::mat4 matrixModel; auto cutsceneOffset = m_world->state->currentCutscene->meta.sceneOffset + glm::vec3(0.f, 0.f, 1.f); + glm::mat4 cutscenespace; + cutscenespace = glm::translate(cutscenespace, cutsceneOffset); if (cutscene->getParentActor()) { - matrixModel = glm::translate(matrixModel, cutsceneOffset); - // matrixModel = - // cutscene->getParentActor()->getTimeAdjustedTransform(_renderAlpha); - // matrixModel = glm::translate(matrixModel, glm::vec3(0.f, 0.f, 1.f)); - glm::mat4 localMatrix; - auto boneframe = cutscene->getParentFrame(); - while (boneframe) { - localMatrix = cutscene->getParentActor()->skeleton->getMatrix( - boneframe->getIndex()) * - localMatrix; - boneframe = boneframe->getParent(); - } - matrixModel = matrixModel * localMatrix; - } else { - matrixModel = glm::translate(matrixModel, cutsceneOffset); + auto parent = cutscene->getParentFrame(); + cutscenespace *= parent->getWorldTransform(); + cutscenespace = + glm::rotate(cutscenespace, glm::half_pi(), {0.f, 1.f, 0.f}); } - auto model = cutscene->getModel(); - if (cutscene->getParentActor()) { - glm::mat4 align; - /// @todo figure out where this 90 degree offset is coming from. - align = glm::rotate(align, glm::half_pi(), {0.f, 1.f, 0.f}); - renderFrame(model, model->frames[0], matrixModel * align, cutscene, 1.f, - outList); - } else { - renderFrame(model, model->frames[0], matrixModel, cutscene, 1.f, - outList); - } + renderClump(clump.get(), cutscenespace, nullptr, outList); } void ObjectRenderer::renderProjectile(ProjectileObject* projectile, @@ -447,18 +340,11 @@ void ObjectRenderer::renderProjectile(ProjectileObject* projectile, auto odata = m_world->data->findModelInfo( projectile->getProjectileInfo().weapon->modelID); - auto model = odata->getModel(); - auto modelframe = odata->getAtomic(0); - auto geom = modelframe->getGeometries().at(0); - - renderGeometry(model, geom, modelMatrix, 1.f, projectile, outList); + auto atomic = odata->getAtomic(0); + renderAtomic(atomic, modelMatrix, nullptr, outList); } void ObjectRenderer::buildRenderList(GameObject* object, RenderList& outList) { - if (object->skeleton) { - object->skeleton->interpolate(m_renderAlpha); - } - // Right now specialized on each object type switch (object->type()) { case GameObject::Instance: diff --git a/rwengine/src/render/ObjectRenderer.hpp b/rwengine/src/render/ObjectRenderer.hpp index e8e9fb86..6b483994 100644 --- a/rwengine/src/render/ObjectRenderer.hpp +++ b/rwengine/src/render/ObjectRenderer.hpp @@ -37,11 +37,25 @@ public: size_t culled; void buildRenderList(GameObject* object, RenderList& outList); - bool renderFrame(Model* m, ModelFrame* f, const glm::mat4& matrix, - GameObject* object, float opacity, RenderList& outList); + void renderGeometry(Geometry* geom, const glm::mat4& modelMatrix, + GameObject* object, RenderList& outList); - void renderGeometry(Model* model, size_t g, const glm::mat4& modelMatrix, - float opacity, GameObject* object, RenderList& outList); + /** + * @brief renderAtomic renders an atomic with the given worldtransform + * @param model + * @param atomic + * @param worldtransform + * @param object + */ + void renderAtomic(Atomic* atomic, const glm::mat4& worldtransform, GameObject* object, RenderList& render); + + /** + * @brief renderClump Renders all visible atomics in the clump + * @param model + * @param worldtransform + * @param render + */ + void renderClump(Clump* model, const glm::mat4& worldtransform, GameObject* object, RenderList& render); private: GameWorld* m_world; diff --git a/rwengine/src/script/modules/GTA3ModuleImpl.inl b/rwengine/src/script/modules/GTA3ModuleImpl.inl index 762842df..c2f77a26 100644 --- a/rwengine/src/script/modules/GTA3ModuleImpl.inl +++ b/rwengine/src/script/modules/GTA3ModuleImpl.inl @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -8455,11 +8454,15 @@ void opcode_02f4(const ScriptArguments& args, const ScriptObject object0, const RW_UNUSED(model); RW_UNUSED(object1); auto id = args[1].integer; - auto actor = args.getObject(0); + auto actor = static_cast(args.getObject(0)); CutsceneObject* object = args.getWorld()->createCutsceneObject(id, args.getWorld()->state->currentCutscene->meta.sceneOffset ); - auto headframe = actor->getModel()->findFrame("shead"); - actor->skeleton->setEnabled(headframe, false); + auto headframe = actor->getClump()->findFrame("shead"); + for (const auto& atomic : actor->getClump()->getAtomics()) { + if (atomic->getFrame().get() == headframe) { + atomic->setFlag(Atomic::ATOMIC_RENDER, false); + } + } object->setParentActor(actor, headframe); *args[2].globalInteger = object->getGameObjectID(); diff --git a/rwgame/states/IngameState.cpp b/rwgame/states/IngameState.cpp index 15d26abd..747bd1ba 100644 --- a/rwgame/states/IngameState.cpp +++ b/rwgame/states/IngameState.cpp @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include #include diff --git a/rwlib/CMakeLists.txt b/rwlib/CMakeLists.txt index 02469696..d978b715 100644 --- a/rwlib/CMakeLists.txt +++ b/rwlib/CMakeLists.txt @@ -23,8 +23,8 @@ SET(RWLIB_SOURCES "source/platform/FileIndex.hpp" "source/platform/FileIndex.cpp" - "source/data/Model.hpp" - "source/data/Model.cpp" + "source/data/Clump.hpp" + "source/data/Clump.cpp" "source/loaders/LoaderIMG.hpp" "source/loaders/LoaderIMG.cpp" diff --git a/rwlib/source/data/Clump.cpp b/rwlib/source/data/Clump.cpp new file mode 100644 index 00000000..58951cb8 --- /dev/null +++ b/rwlib/source/data/Clump.cpp @@ -0,0 +1,145 @@ +#include "data/Clump.hpp" +#include +#include + +#include + +Geometry::Geometry() : flags(0) { +} + +Geometry::~Geometry() { +} + +ModelFrame::ModelFrame(unsigned int index, glm::mat3 dR, glm::vec3 dT) + : index(index) + , defaultRotation(dR) + , defaultTranslation(dT) + , parent_(nullptr) { + reset(); +} + +void ModelFrame::reset() { + matrix = glm::translate(glm::mat4(), defaultTranslation) * + glm::mat4(defaultRotation); + updateHierarchyTransform(); +} + +void ModelFrame::updateHierarchyTransform() { + // Update our own transformation + if (parent_) { + worldtransform_ = parent_->getWorldTransform() * matrix; + } else { + worldtransform_ = matrix; + } + for (const auto& child : children_) { + child->updateHierarchyTransform(); + } +} + +void ModelFrame::addChild(ModelFramePtr child) { + // Make sure the child is an orphan + if (child->getParent()) { + auto& other_children = child->getParent()->children_; + other_children.erase( + std::remove(other_children.begin(), other_children.end(), child), + other_children.end()); + } + child->parent_ = this; + children_.push_back(child); +} + +ModelFrame* ModelFrame::findDescendant(const std::string& name) const { + for (const auto& frame : children_) { + if (frame->getName() == name) { + return frame.get(); + } + + auto result = frame->findDescendant(name); + if (result) { + return result; + } + } + + return nullptr; +} + +ModelFramePtr ModelFrame::cloneHierarchy() const { + auto self = std::make_shared(getIndex(), getDefaultRotation(), + getDefaultTranslation()); + self->setName(getName()); + for (const auto& child : getChildren()) { + auto childclone = child->cloneHierarchy(); + self->addChild(childclone); + } + + return self; +} + +AtomicPtr Atomic::clone() const { + auto newatomic = std::make_shared(); + newatomic->setGeometry(getGeometry()); + newatomic->setFrame(getFrame()); + newatomic->setFlags(getFlags()); + return newatomic; +} + +ModelFrame* Clump::findFrame(const std::string& name) const { + if (rootframe_->getName() == name) { + return rootframe_.get(); + } + + return rootframe_->findDescendant(name); +} + +Clump::~Clump() { +} + +void Clump::recalculateMetrics() { + boundingRadius = std::numeric_limits::min(); + for (const auto& atomic : atomics_) { + const auto& geometry = atomic->getGeometry(); + if (!geometry) { + continue; + } + const auto& bounds = geometry->geometryBounds; + boundingRadius = std::max(boundingRadius, + glm::length(bounds.center) + bounds.radius); + } +} + +Clump* Clump::clone() const { + // Clone frame hierarchy + auto newroot = rootframe_->cloneHierarchy(); + auto clump = new Clump; + clump->setFrame(newroot); + + // This isn't the most optimal implementation, but this code is likely + // to be replaced soon. + auto find_new_frame = [&](const ModelFramePtr& old) -> ModelFramePtr { + std::queue open; + open.push(newroot); + while (!open.empty()) { + auto frame = open.front(); + open.pop(); + if (frame->getIndex() == old->getIndex()) { + return frame; + } + for (const auto& child : frame->getChildren()) { + open.push(child); + } + } + return nullptr; + }; + + // Generate new atomics + for (const auto& atomic : getAtomics()) { + auto newatomic = atomic->clone(); + // Replace the original frame with the cloned frame + if (atomic->getFrame()) { + newatomic->setFrame(find_new_frame(atomic->getFrame())); + } + clump->addAtomic(newatomic); + } + + return clump; +} diff --git a/rwlib/source/data/Clump.hpp b/rwlib/source/data/Clump.hpp new file mode 100644 index 00000000..ee14b3ce --- /dev/null +++ b/rwlib/source/data/Clump.hpp @@ -0,0 +1,283 @@ +#pragma once +#ifndef _MODEL_HPP_ +#define _MODEL_HPP_ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +// Forward Declerations +class ModelFrame; +struct Geometry; +class Atomic; +class Clump; + +// Pointer types +using ModelFramePtr = std::shared_ptr; +using GeometryPtr = std::shared_ptr; +using AtomicPtr = std::shared_ptr; +using AtomicList = std::vector; +using ClumpPtr = std::shared_ptr; + +/** + * ModelFrame stores transformation hierarchy + */ +class ModelFrame { + unsigned int index; + glm::mat3 defaultRotation; + glm::vec3 defaultTranslation; + glm::mat4 matrix; + glm::mat4 worldtransform_; + ModelFrame* parent_; + std::string name; + std::vector children_; + +public: + ModelFrame(unsigned int index = 0, glm::mat3 dR = glm::mat3(), + glm::vec3 dT = glm::vec3()); + + void reset(); + + void setTransform(const glm::mat4& m) { + matrix = m; + updateHierarchyTransform(); + } + + const glm::mat4& getTransform() const { + return matrix; + } + + void setName(const std::string& fname) { + name = fname; + } + + unsigned int getIndex() const { + return index; + } + + glm::vec3 getDefaultTranslation() const { + return defaultTranslation; + } + + glm::mat3 getDefaultRotation() const { + return defaultRotation; + } + + void setTranslation(const glm::vec3& t) { + matrix[3] = glm::vec4(t, matrix[3][3]); + updateHierarchyTransform(); + } + + void setRotation(const glm::mat3& r) { + for (unsigned int i = 0; i < 3; i++) { + matrix[i] = glm::vec4(r[i], matrix[i][3]); + } + updateHierarchyTransform(); + } + + /** + * Updates the cached matrix + */ + void updateHierarchyTransform(); + + /** + * @return the cached world transformation for this Frame + */ + const glm::mat4& getWorldTransform() const { + return worldtransform_; + } + + ModelFrame* getParent() const { + return parent_; + } + + void addChild(ModelFramePtr child); + + const std::vector& getChildren() const { + return children_; + } + + const std::string& getName() const { + return name; + } + + ModelFrame* findDescendant(const std::string& name) const; + + ModelFramePtr cloneHierarchy() const; +}; + +/** + * Subgeometry + */ + +struct SubGeometry { + GLuint start = 0; + size_t material = 0; + std::vector indices; + size_t numIndices = 0; +}; + +struct GeometryVertex { + glm::vec3 position; /* 0 */ + glm::vec3 normal; /* 24 */ + glm::vec2 texcoord; /* 48 */ + glm::u8vec4 colour; /* 64 */ + + /** @see GeometryBuffer */ + static const AttributeList vertex_attributes() { + return {{ATRS_Position, 3, sizeof(GeometryVertex), 0ul}, + {ATRS_Normal, 3, sizeof(GeometryVertex), sizeof(float) * 3}, + {ATRS_TexCoord, 2, sizeof(GeometryVertex), sizeof(float) * 6}, + {ATRS_Colour, 4, sizeof(GeometryVertex), sizeof(float) * 8, + GL_UNSIGNED_BYTE}}; + } +}; + +/** + * Geometry + */ +struct Geometry { + enum FaceType { Triangles = 0, TriangleStrip = 1 }; + + struct Texture { + std::string name; + std::string alphaName; + TextureData::Handle texture; + }; + + enum { MTF_PrimaryColour = 1 << 0, MTF_SecondaryColour = 1 << 1 }; + + struct Material { + std::vector textures; + glm::u8vec4 colour; + + uint8_t flags; + + float diffuseIntensity; + float ambientIntensity; + }; + + DrawBuffer dbuff; + GeometryBuffer gbuff; + + GLuint EBO; + + RW::BSGeometryBounds geometryBounds; + + uint32_t clumpNum; + + FaceType facetype; + + uint32_t flags; + + std::vector materials; + std::vector subgeom; + + Geometry(); + ~Geometry(); +}; + +/** + * @brief The Atomic struct + */ +class Atomic { + ModelFramePtr frame_; + GeometryPtr geometry_; + uint32_t flags_; + +public: + enum { + // If this is set, the atomic will be rendered + ATOMIC_RENDER = 0x04 + }; + + void setFrame(ModelFramePtr frame) { + frame_ = frame; + } + + const ModelFramePtr& getFrame() const { + return frame_; + } + + void setGeometry(GeometryPtr geom) { + geometry_ = geom; + } + + const GeometryPtr& getGeometry() const { + return geometry_; + } + + void setFlags(uint32_t flags) { + flags_ = flags; + } + + uint32_t getFlags() const { + return flags_; + } + + void setFlag(uint32_t flag, bool set) { + if (set) { + flags_ |= flag; + } + else { + flags_ &= ~flag; + } + } + + AtomicPtr clone() const; +}; + +/** + * A clump is a collection of Frames and Atomics + */ +class Clump { +public: + /** + * @brief findFrame Locates frame with name anywhere in the hierarchy + * @param name + * @return + */ + ModelFrame* findFrame(const std::string& name) const; + + ~Clump(); + + void recalculateMetrics(); + + float getBoundingRadius() const { + return boundingRadius; + } + + void addAtomic(AtomicPtr& atomic) { + atomics_.push_back(atomic); + } + + const AtomicList& getAtomics() const { + return atomics_; + } + + void setFrame(ModelFramePtr& root) { + rootframe_ = root; + } + + const ModelFramePtr& getFrame() const { + return rootframe_; + } + + /** + * @return A Copy of the frames and atomics in this clump + */ + Clump* clone() const; + +private: + float boundingRadius; + AtomicList atomics_; + ModelFramePtr rootframe_; +}; + +#endif diff --git a/rwlib/source/data/Model.cpp b/rwlib/source/data/Model.cpp deleted file mode 100644 index 3a7985ab..00000000 --- a/rwlib/source/data/Model.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "data/Model.hpp" -#include - -#include - -Model::Geometry::Geometry() : flags(0) { -} - -Model::Geometry::~Geometry() { -} - -ModelFrame::ModelFrame(unsigned int index, ModelFrame* parent, glm::mat3 dR, - glm::vec3 dT) - : index(index) - , defaultRotation(dR) - , defaultTranslation(dT) - , parentFrame(parent) { - if (parent != nullptr) { - parent->childs.push_back(this); - } - reset(); -} - -void ModelFrame::reset() { - matrix = glm::translate(glm::mat4(), defaultTranslation) * - glm::mat4(defaultRotation); -} - -void ModelFrame::addGeometry(size_t idx) { - geometries.push_back(idx); -} - -Model::~Model() { - for (auto mf : frames) { - delete mf; - } -} - -void Model::recalculateMetrics() { - boundingRadius = std::numeric_limits::min(); - for (size_t g = 0; g < geometries.size(); g++) { - RW::BSGeometryBounds& bounds = geometries[g]->geometryBounds; - boundingRadius = std::max(boundingRadius, - glm::length(bounds.center) + bounds.radius); - } -} diff --git a/rwlib/source/data/Model.hpp b/rwlib/source/data/Model.hpp deleted file mode 100644 index d2043a9d..00000000 --- a/rwlib/source/data/Model.hpp +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once -#ifndef _MODEL_HPP_ -#define _MODEL_HPP_ -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -/** - * ModelFrame stores the hierarchy of a model's geometry as well as default - * transformations. - */ -class ModelFrame { - unsigned int index; - glm::mat3 defaultRotation; - glm::vec3 defaultTranslation; - glm::mat4 matrix; - ModelFrame* parentFrame; - std::string name; - std::vector geometries; - std::vector childs; - -public: - ModelFrame(unsigned int index, ModelFrame* parent, glm::mat3 dR, - glm::vec3 dT); - - void reset(); - void setTransform(const glm::mat4& m); - const glm::mat4& getTransform() const { - return matrix; - } - - void setName(const std::string& fname) { - name = fname; - } - - void addGeometry(size_t idx); - - unsigned int getIndex() const { - return index; - } - - glm::vec3 getDefaultTranslation() const { - return defaultTranslation; - } - - glm::mat3 getDefaultRotation() const { - return defaultRotation; - } - - glm::mat4 getMatrix() const { - return (parentFrame ? parentFrame->getMatrix() : glm::mat4()) * matrix; - } - - ModelFrame* getParent() const { - return parentFrame; - } - - const std::vector& getChildren() const { - return childs; - } - - const std::string& getName() const { - return name; - } - - const std::vector& getGeometries() const { - return geometries; - } -}; - -/** - * Model stores all the data contained within a DFF, as well as data required - * to render them. - */ -class Model { -public: - enum FaceType { Triangles = 0, TriangleStrip = 1 }; - - std::uint32_t numAtomics; - - struct Texture { - std::string name; - std::string alphaName; - TextureData::Handle texture; - }; - - enum { MTF_PrimaryColour = 1 << 0, MTF_SecondaryColour = 1 << 1 }; - - struct Material { - std::vector textures; - glm::u8vec4 colour; - - uint8_t flags; - - float diffuseIntensity; - float ambientIntensity; - }; - - struct SubGeometry { - GLuint start = 0; - size_t material; - std::vector indices; - size_t numIndices; - }; - - struct GeometryVertex { - glm::vec3 position; /* 0 */ - glm::vec3 normal; /* 24 */ - glm::vec2 texcoord; /* 48 */ - glm::u8vec4 colour; /* 64 */ - - /** @see GeometryBuffer */ - static const AttributeList vertex_attributes() { - return { - {ATRS_Position, 3, sizeof(GeometryVertex), 0ul}, - {ATRS_Normal, 3, sizeof(GeometryVertex), sizeof(float) * 3}, - {ATRS_TexCoord, 2, sizeof(GeometryVertex), sizeof(float) * 6}, - {ATRS_Colour, 4, sizeof(GeometryVertex), sizeof(float) * 8, - GL_UNSIGNED_BYTE}}; - } - }; - - struct Geometry { - DrawBuffer dbuff; - GeometryBuffer gbuff; - - GLuint EBO; - - RW::BSGeometryBounds geometryBounds; - - uint32_t clumpNum; - - FaceType facetype; - - uint32_t flags; - - std::vector materials; - std::vector subgeom; - - Geometry(); - ~Geometry(); - }; - - struct Atomic { - uint32_t frame; - uint32_t geometry; - }; - - std::vector frames; - /** @TODO clean up this mess a little */ - std::vector> geometries; - std::vector atomics; - - int32_t rootFrameIdx; - - ModelFrame* findFrame(const std::string& name) const { - auto fit = - std::find_if(frames.begin(), frames.end(), - [&](ModelFrame* f) { return f->getName() == name; }); - return fit != frames.end() ? *fit : nullptr; - } - - ~Model(); - - void recalculateMetrics(); - - float getBoundingRadius() const { - return boundingRadius; - } - -private: - float boundingRadius; -}; - -#endif diff --git a/rwlib/source/loaders/LoaderDFF.cpp b/rwlib/source/loaders/LoaderDFF.cpp index 1425d4f1..b90ba8c5 100644 --- a/rwlib/source/loaders/LoaderDFF.cpp +++ b/rwlib/source/loaders/LoaderDFF.cpp @@ -1,5 +1,6 @@ -#include +#include #include +#include #include #include @@ -40,7 +41,7 @@ struct RWBSFrame { uint32_t matrixflags; // Not used }; -void LoaderDFF::readFrameList(Model *model, const RWBStream &stream) { +LoaderDFF::FrameList LoaderDFF::readFrameList(const RWBStream &stream) { auto listStream = stream.getInnerStream(); auto listStructID = listStream.getNextChunk(); @@ -53,21 +54,22 @@ void LoaderDFF::readFrameList(Model *model, const RWBStream &stream) { unsigned int numFrames = *(std::uint32_t *)headerPtr; headerPtr += sizeof(std::uint32_t); - model->frames.reserve(numFrames); + FrameList framelist; + framelist.reserve(numFrames); for (size_t f = 0; f < numFrames; ++f) { auto data = (RWBSFrame *)headerPtr; headerPtr += sizeof(RWBSFrame); + auto frame = + std::make_shared(f, data->rotation, data->position); - ModelFrame *parent = nullptr; - if (data->index != -1) { - parent = model->frames[data->index]; - } else { - model->rootFrameIdx = f; + RW_CHECK(data->index < int(framelist.size()), + "Frame parent out of bounds"); + if (data->index != -1 && data->index < int(framelist.size())) { + framelist[data->index]->addChild(frame); } - auto frame = new ModelFrame(f, parent, data->rotation, data->position); - model->frames.push_back(frame); + framelist.push_back(frame); } size_t namedFrames = 0; @@ -87,8 +89,8 @@ void LoaderDFF::readFrameList(Model *model, const RWBStream &stream) { std::transform(fname.begin(), fname.end(), fname.begin(), ::tolower); - if (namedFrames < model->frames.size()) { - model->frames[namedFrames++]->setName(fname); + if (namedFrames < framelist.size()) { + framelist[namedFrames++]->setName(fname); } } break; default: @@ -100,9 +102,11 @@ void LoaderDFF::readFrameList(Model *model, const RWBStream &stream) { break; } } + + return framelist; } -void LoaderDFF::readGeometryList(Model *model, const RWBStream &stream) { +LoaderDFF::GeometryList LoaderDFF::readGeometryList(const RWBStream &stream) { auto listStream = stream.getInnerStream(); auto listStructID = listStream.getNextChunk(); @@ -115,21 +119,24 @@ void LoaderDFF::readGeometryList(Model *model, const RWBStream &stream) { unsigned int numGeometries = *(std::uint32_t *)headerPtr; headerPtr += sizeof(std::uint32_t); - model->geometries.reserve(numGeometries); + std::vector geometrylist; + geometrylist.reserve(numGeometries); for (auto chunkID = listStream.getNextChunk(); chunkID != 0; chunkID = listStream.getNextChunk()) { switch (chunkID) { - case CHUNK_GEOMETRY: - readGeometry(model, listStream); - break; + case CHUNK_GEOMETRY: { + geometrylist.push_back(readGeometry(listStream)); + } break; default: break; } } + + return geometrylist; } -void LoaderDFF::readGeometry(Model *model, const RWBStream &stream) { +GeometryPtr LoaderDFF::readGeometry(const RWBStream &stream) { auto geomStream = stream.getInnerStream(); auto geomStructID = geomStream.getNextChunk(); @@ -137,7 +144,7 @@ void LoaderDFF::readGeometry(Model *model, const RWBStream &stream) { throw DFFLoaderException("Geometry missing struct chunk"); } - std::shared_ptr geom(new Model::Geometry); + auto geom = std::make_shared(); char *headerPtr = geomStream.getCursor(); @@ -156,7 +163,7 @@ void LoaderDFF::readGeometry(Model *model, const RWBStream &stream) { /*unsigned int numFrames = *(std::uint32_t*)headerPtr;*/ headerPtr += sizeof(std::uint32_t); - std::vector verts; + std::vector verts; verts.resize(numVerts); if (geomStream.getChunkVersion() < 0x1003FFFF) { @@ -216,26 +223,24 @@ void LoaderDFF::readGeometry(Model *model, const RWBStream &stream) { } } - // Add the geometry to the model now so that it can be accessed. - model->geometries.push_back(geom); - // Process the geometry child sections for (auto chunkID = geomStream.getNextChunk(); chunkID != 0; chunkID = geomStream.getNextChunk()) { switch (chunkID) { case CHUNK_MATERIALLIST: - readMaterialList(model, geomStream); + readMaterialList(geom, geomStream); break; case CHUNK_EXTENSION: - readGeometryExtension(model, geomStream); + readGeometryExtension(geom, geomStream); break; default: break; } } - geom->dbuff.setFaceType( - geom->facetype == Model::Triangles ? GL_TRIANGLES : GL_TRIANGLE_STRIP); + geom->dbuff.setFaceType(geom->facetype == Geometry::Triangles + ? GL_TRIANGLES + : GL_TRIANGLE_STRIP); geom->gbuff.uploadVertices(verts); geom->dbuff.addGeometry(&geom->gbuff); @@ -244,16 +249,18 @@ void LoaderDFF::readGeometry(Model *model, const RWBStream &stream) { size_t icount = std::accumulate( geom->subgeom.begin(), geom->subgeom.end(), 0u, - [](size_t a, const Model::SubGeometry &b) { return a + b.numIndices; }); + [](size_t a, const SubGeometry &b) { return a + b.numIndices; }); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * icount, 0, GL_STATIC_DRAW); for (auto &sg : geom->subgeom) { glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, sg.start * sizeof(uint32_t), sizeof(uint32_t) * sg.numIndices, sg.indices.data()); } + + return geom; } -void LoaderDFF::readMaterialList(Model *model, const RWBStream &stream) { +void LoaderDFF::readMaterialList(GeometryPtr &geom, const RWBStream &stream) { auto listStream = stream.getInnerStream(); auto listStructID = listStream.getNextChunk(); @@ -263,13 +270,13 @@ void LoaderDFF::readMaterialList(Model *model, const RWBStream &stream) { unsigned int numMaterials = *(std::uint32_t *)listStream.getCursor(); - model->geometries.back()->materials.reserve(numMaterials); + geom->materials.reserve(numMaterials); RWBStream::ChunkID chunkID; while ((chunkID = listStream.getNextChunk())) { switch (chunkID) { case CHUNK_MATERIAL: - readMaterial(model, listStream); + readMaterial(geom, listStream); break; default: break; @@ -277,7 +284,7 @@ void LoaderDFF::readMaterialList(Model *model, const RWBStream &stream) { } } -void LoaderDFF::readMaterial(Model *model, const RWBStream &stream) { +void LoaderDFF::readMaterial(GeometryPtr &geom, const RWBStream &stream) { auto materialStream = stream.getInnerStream(); auto matStructID = materialStream.getNextChunk(); @@ -287,7 +294,7 @@ void LoaderDFF::readMaterial(Model *model, const RWBStream &stream) { char *matData = materialStream.getCursor(); - Model::Material material; + Geometry::Material material; // Unkown matData += sizeof(std::uint32_t); @@ -306,21 +313,22 @@ void LoaderDFF::readMaterial(Model *model, const RWBStream &stream) { matData += sizeof(float); material.flags = 0; - model->geometries.back()->materials.push_back(material); - RWBStream::ChunkID chunkID; while ((chunkID = materialStream.getNextChunk())) { switch (chunkID) { case CHUNK_TEXTURE: - readTexture(model, materialStream); + readTexture(material, materialStream); break; default: break; } } + + geom->materials.push_back(material); } -void LoaderDFF::readTexture(Model *model, const RWBStream &stream) { +void LoaderDFF::readTexture(Geometry::Material &material, + const RWBStream &stream) { auto texStream = stream.getInnerStream(); auto texStructID = texStream.getNextChunk(); @@ -343,18 +351,18 @@ void LoaderDFF::readTexture(Model *model, const RWBStream &stream) { TextureData::Handle textureinst = texturelookup ? texturelookup(name, alpha) : nullptr; - model->geometries.back()->materials.back().textures.push_back( - {name, alpha, textureinst}); + material.textures.push_back({name, alpha, textureinst}); } -void LoaderDFF::readGeometryExtension(Model *model, const RWBStream &stream) { +void LoaderDFF::readGeometryExtension(GeometryPtr &geom, + const RWBStream &stream) { auto extStream = stream.getInnerStream(); RWBStream::ChunkID chunkID; while ((chunkID = extStream.getNextChunk())) { switch (chunkID) { case CHUNK_BINMESHPLG: - readBinMeshPLG(model, extStream); + readBinMeshPLG(geom, extStream); break; default: break; @@ -362,11 +370,10 @@ void LoaderDFF::readGeometryExtension(Model *model, const RWBStream &stream) { } } -void LoaderDFF::readBinMeshPLG(Model *model, const RWBStream &stream) { +void LoaderDFF::readBinMeshPLG(GeometryPtr &geom, const RWBStream &stream) { auto data = stream.getCursor(); - model->geometries.back()->facetype = - static_cast(*(std::uint32_t *)data); + geom->facetype = static_cast(*(std::uint32_t *)data); data += sizeof(std::uint32_t); unsigned int numSplits = *(std::uint32_t *)data; @@ -375,12 +382,12 @@ void LoaderDFF::readBinMeshPLG(Model *model, const RWBStream &stream) { // Number of triangles. data += sizeof(std::uint32_t); - model->geometries.back()->subgeom.reserve(numSplits); + geom->subgeom.reserve(numSplits); size_t start = 0; for (size_t s = 0; s < numSplits; ++s) { - Model::SubGeometry sg; + SubGeometry sg; sg.numIndices = *(std::uint32_t *)data; data += sizeof(std::uint32_t); sg.material = *(std::uint32_t *)data; @@ -393,11 +400,13 @@ void LoaderDFF::readBinMeshPLG(Model *model, const RWBStream &stream) { sizeof(std::uint32_t) * sg.numIndices); data += sizeof(std::uint32_t) * sg.numIndices; - model->geometries.back()->subgeom.push_back(sg); + geom->subgeom.push_back(sg); } } -void LoaderDFF::readAtomic(Model *model, const RWBStream &stream) { +AtomicPtr LoaderDFF::readAtomic(FrameList &framelist, + GeometryList &geometrylist, + const RWBStream &stream) { auto atomicStream = stream.getInnerStream(); auto atomicStructID = atomicStream.getNextChunk(); @@ -405,19 +414,33 @@ void LoaderDFF::readAtomic(Model *model, const RWBStream &stream) { throw DFFLoaderException("Atomic missing struct chunk"); } - Model::Atomic atom; auto data = atomicStream.getCursor(); - atom.frame = *(std::uint32_t *)data; + auto frame = *(std::uint32_t *)data; data += sizeof(std::uint32_t); - atom.geometry = *(std::uint32_t *)data; - model->frames[atom.frame]->addGeometry(atom.geometry); - model->atomics.push_back(atom); + auto geometry = *(std::uint32_t *)data; + data += sizeof(std::uint32_t); + auto flags = *(std::uint32_t *) data; - /// @todo are any atomic extensions important? + // Verify the atomic's particulars + RW_CHECK(frame < framelist.size(), "atomic frame " << frame + << " out of bounds"); + RW_CHECK(geometry < geometrylist.size(), + "atomic geometry " << geometry << " out of bounds"); + + auto atomic = std::make_shared(); + if (geometry < geometrylist.size()) { + atomic->setGeometry(geometrylist[geometry]); + } + if (frame < framelist.size()) { + atomic->setFrame(framelist[frame]); + } + atomic->setFlags(flags); + + return atomic; } -Model *LoaderDFF::loadFromMemory(FileHandle file) { - auto model = new Model; +Clump *LoaderDFF::loadFromMemory(FileHandle file) { + auto model = new Clump; RWBStream rootStream(file->data, file->length); @@ -434,26 +457,40 @@ Model *LoaderDFF::loadFromMemory(FileHandle file) { } // There is only one value in the struct section. - model->numAtomics = *(std::uint32_t *)rootStream.getCursor(); + auto numAtomics = *(std::uint32_t *)rootStream.getCursor(); + RW_UNUSED(numAtomics); + + GeometryList geometrylist; + FrameList framelist; // Process everything inside the clump stream. RWBStream::ChunkID chunkID; while ((chunkID = modelStream.getNextChunk())) { switch (chunkID) { case CHUNK_FRAMELIST: - readFrameList(model, modelStream); + framelist = readFrameList(modelStream); break; case CHUNK_GEOMETRYLIST: - readGeometryList(model, modelStream); - break; - case CHUNK_ATOMIC: - readAtomic(model, modelStream); + geometrylist = readGeometryList(modelStream); break; + case CHUNK_ATOMIC: { + auto atomic = readAtomic(framelist, geometrylist, modelStream); + RW_CHECK(atomic, "Failed to read atomic"); + if (!atomic) { + // Abort reading the rest of the clump + return nullptr; + } + model->addAtomic(atomic); + } break; default: break; } } + if (!framelist.empty()) { + model->setFrame(framelist[0]); + } + // Ensure the model has cached metrics model->recalculateMetrics(); diff --git a/rwlib/source/loaders/LoaderDFF.hpp b/rwlib/source/loaders/LoaderDFF.hpp index 828b927f..a36e0dbc 100644 --- a/rwlib/source/loaders/LoaderDFF.hpp +++ b/rwlib/source/loaders/LoaderDFF.hpp @@ -2,6 +2,7 @@ #ifndef _LOADERDFF_HPP_ #define _LOADERDFF_HPP_ +#include #include #include #include @@ -9,8 +10,6 @@ #include #include -class Model; - class DFFLoaderException { std::string _message; @@ -24,40 +23,39 @@ public: }; class LoaderDFF { - /** - * @brief loads a Frame List chunk from stream into model. - * @param model - * @param stream - */ - void readFrameList(Model* model, const RWBStream& stream); - - void readGeometryList(Model* model, const RWBStream& stream); - - void readGeometry(Model* model, const RWBStream& stream); - - void readMaterialList(Model* model, const RWBStream& stream); - - void readMaterial(Model* model, const RWBStream& stream); - - void readTexture(Model* model, const RWBStream& stream); - - void readGeometryExtension(Model* model, const RWBStream& stream); - - void readBinMeshPLG(Model* model, const RWBStream& stream); - - void readAtomic(Model* model, const RWBStream& stream); - public: using TextureLookupCallback = std::function; + using GeometryList = std::vector; + using FrameList = std::vector; - Model* loadFromMemory(FileHandle file); + Clump* loadFromMemory(FileHandle file); void setTextureLookupCallback(TextureLookupCallback tlc) { texturelookup = tlc; } + private: TextureLookupCallback texturelookup; + + FrameList readFrameList(const RWBStream& stream); + + GeometryList readGeometryList(const RWBStream& stream); + + GeometryPtr readGeometry(const RWBStream& stream); + + void readMaterialList(GeometryPtr& geom, const RWBStream& stream); + + void readMaterial(GeometryPtr& geom, const RWBStream& stream); + + void readTexture(Geometry::Material& material, const RWBStream& stream); + + void readGeometryExtension(GeometryPtr& geom, const RWBStream& stream); + + void readBinMeshPLG(GeometryPtr& geom, const RWBStream& stream); + + AtomicPtr readAtomic(FrameList& framelist, GeometryList& geometrylist, + const RWBStream& stream); }; #endif diff --git a/rwviewer/ViewerWidget.cpp b/rwviewer/ViewerWidget.cpp index fcd863a0..c2a8ea50 100644 --- a/rwviewer/ViewerWidget.cpp +++ b/rwviewer/ViewerWidget.cpp @@ -2,17 +2,16 @@ #include #include #include -#include -#include +#include #include #include -#include -#include -#include - #include +#include #include #include +#include +#include +#include ViewerWidget::ViewerWidget(QGLFormat g, QWidget* parent, const QGLWidget* shareWidget, Qt::WindowFlags f) @@ -81,9 +80,8 @@ void ViewerWidget::paintGL() { r.setViewport(width(), height()); - if (dummyObject && dummyObject->animator && dummyObject->skeleton) { + if (dummyObject && dummyObject->animator) { dummyObject->animator->tick(1.f / 60.f); - dummyObject->skeleton->interpolate(1.f); } r.getRenderer()->invalidate(); @@ -103,7 +101,7 @@ void ViewerWidget::paintGL() { vc.frustum.fov = viewFov; vc.frustum.aspectRatio = width() / (height() * 1.f); - Model* model = activeModel; + Clump* model = activeModel; if (model != _lastModel) { _lastModel = model; emit modelChanged(_lastModel); @@ -113,10 +111,15 @@ void ViewerWidget::paintGL() { cos(viewAngles.x) * cos(viewAngles.y), sin(viewAngles.y)); if (model) { + model->getFrame()->updateHierarchyTransform(); + + // Ensure camera is still accurate + vc.position = eye * viewDistance; glm::mat4 proj = vc.frustum.projection(); - glm::mat4 view = - glm::lookAt(eye * viewDistance, glm::vec3(0.f, 0.f, 0.f), - glm::vec3(0.f, 0.f, 1.f)); + glm::mat4 view = glm::lookAt(vc.position, glm::vec3(0.f, 0.f, 0.f), + glm::vec3(0.f, 0.f, 1.f)); + vc.rotation = -glm::quat_cast(view); + vc.frustum.update(proj * view); r.getRenderer()->setSceneParameters( {proj, view, glm::vec4(0.15f), glm::vec4(0.7f), glm::vec4(1.f), @@ -126,9 +129,12 @@ void ViewerWidget::paintGL() { r.setupRender(); - r.renderModel(model, m, dummyObject); + ObjectRenderer renderer(world(), vc, 1.f, 0); + RenderList renders; + renderer.renderClump(model, glm::mat4(), nullptr, renders); + r.getRenderer()->drawBatched(renders); - drawFrameWidget(model->frames[model->rootFrameIdx]); + drawFrameWidget(model->getFrame().get()); r.renderPostProcess(); } else if (world()->allObjects.size() > 0) { vc.frustum.fov = glm::radians(90.f); @@ -143,26 +149,24 @@ void ViewerWidget::paintGL() { void ViewerWidget::drawFrameWidget(ModelFrame* f, const glm::mat4& m) { auto thisM = m * f->getTransform(); - if (f->getGeometries().size() == 0) { - Renderer::DrawParameters dp; - dp.count = _frameWidgetGeom->getCount(); - dp.start = 0; - dp.ambient = 1.f; - dp.diffuse = 1.f; - if (f == selectedFrame) { - dp.colour = {255, 255, 0, 255}; - // Sorry! - glLineWidth(10.f); - } else { - dp.colour = {255, 255, 255, 255}; - glLineWidth(1.f); - } - dp.textures = {whiteTex}; - renderer->getRenderer()->drawArrays(thisM, _frameWidgetDraw, dp); + Renderer::DrawParameters dp; + dp.count = _frameWidgetGeom->getCount(); + dp.start = 0; + dp.ambient = 1.f; + dp.diffuse = 1.f; + if (f == selectedFrame) { + dp.colour = {255, 255, 0, 255}; + // Sorry! + glLineWidth(10.f); + } else { + dp.colour = {255, 255, 255, 255}; + glLineWidth(1.f); } + dp.textures = {whiteTex}; + renderer->getRenderer()->drawArrays(thisM, _frameWidgetDraw, dp); for (auto c : f->getChildren()) { - drawFrameWidget(c, thisM); + drawFrameWidget(c.get(), thisM); } } @@ -197,7 +201,7 @@ void ViewerWidget::showObject(qint16 item) { } } -void ViewerWidget::showModel(Model* model) { +void ViewerWidget::showModel(Clump* model) { if (dummyObject) gworld->destroyObject(dummyObject); dummyObject = nullptr; activeModel = model; @@ -259,7 +263,7 @@ void ViewerWidget::keyReleaseEvent(QKeyEvent* e) { if (e->key() == Qt::Key_Shift) moveFast = false; } -Model* ViewerWidget::currentModel() const { +Clump* ViewerWidget::currentModel() const { return activeModel; } diff --git a/rwviewer/ViewerWidget.hpp b/rwviewer/ViewerWidget.hpp index 6911c7d1..ba216814 100644 --- a/rwviewer/ViewerWidget.hpp +++ b/rwviewer/ViewerWidget.hpp @@ -2,7 +2,7 @@ #ifndef _VIEWERWIDGET_HPP_ #define _VIEWERWIDGET_HPP_ #include -#include +#include #include #include #include @@ -16,7 +16,7 @@ #include class GameRenderer; -class Model; +class Clump; class ViewerWidget : public QGLWidget { Q_OBJECT @@ -27,12 +27,12 @@ class ViewerWidget : public QGLWidget { QTimer timer; GameWorld* gworld; - Model* activeModel; + Clump* activeModel; ModelFrame* selectedFrame; GameObject* dummyObject; quint16 currentObjectID; - Model* _lastModel; + Clump* _lastModel; Animation* canimation; float viewDistance; @@ -60,7 +60,7 @@ public: virtual void paintGL(); - Model* currentModel() const; + Clump* currentModel() const; GameObject* currentObject() const; GameWorld* world(); @@ -68,7 +68,7 @@ public: public slots: void showObject(qint16 item); - void showModel(Model* model); + void showModel(Clump* model); void selectFrame(ModelFrame* frame); void exportModel(); @@ -81,7 +81,7 @@ signals: void fileOpened(const QString& file); - void modelChanged(Model* model); + void modelChanged(Clump* model); protected: void keyPressEvent(QKeyEvent*) override; diff --git a/rwviewer/models/DFFFramesTreeModel.cpp b/rwviewer/models/DFFFramesTreeModel.cpp index bb202cc3..3950482c 100644 --- a/rwviewer/models/DFFFramesTreeModel.cpp +++ b/rwviewer/models/DFFFramesTreeModel.cpp @@ -1,10 +1,9 @@ #include "DFFFramesTreeModel.hpp" -#include -#include +#include -DFFFramesTreeModel::DFFFramesTreeModel(Model* m, Skeleton* skel, +DFFFramesTreeModel::DFFFramesTreeModel(Clump* m, QObject* parent) - : QAbstractItemModel(parent), model(m), skeleton(skel) { + : QAbstractItemModel(parent), model(m) { } int DFFFramesTreeModel::columnCount(const QModelIndex& parent) const { @@ -27,10 +26,10 @@ int DFFFramesTreeModel::rowCount(const QModelIndex& parent) const { QModelIndex DFFFramesTreeModel::index(int row, int column, const QModelIndex& parent) const { if (parent.row() == -1 && parent.column() == -1) { - return createIndex(row, column, model->frames[model->rootFrameIdx]); + return createIndex(row, column, model->getFrame().get()); } ModelFrame* f = static_cast(parent.internalPointer()); - ModelFrame* p = f->getChildren()[row]; + ModelFrame* p = f->getChildren()[row].get(); return createIndex(row, column, p); } @@ -40,7 +39,7 @@ QModelIndex DFFFramesTreeModel::parent(const QModelIndex& child) const { auto cp = c->getParent(); if (cp->getParent()) { for (size_t i = 0; i < cp->getParent()->getChildren().size(); ++i) { - if (cp->getParent()->getChildren()[i] == c->getParent()) { + if (cp->getParent()->getChildren()[i].get() == c->getParent()) { return createIndex(i, 0, c->getParent()); } } @@ -59,10 +58,7 @@ QVariant DFFFramesTreeModel::data(const QModelIndex& index, int role) const { } } else if (role == Qt::CheckStateRole) { if (index.column() == 0) { - if (skeleton) { - return skeleton->getData(f->getIndex()).enabled ? Qt::Checked - : Qt::Unchecked; - } + return true; } } return QVariant(); @@ -77,11 +73,10 @@ bool DFFFramesTreeModel::setData(const QModelIndex& index, ModelFrame* f = static_cast(index.internalPointer()); if (role == Qt::CheckStateRole) { - if (index.column() == 0 && skeleton) { + if (index.column() == 0) { if ((Qt::CheckState)value.toInt() == Qt::Checked) { - skeleton->setEnabled(f, true); + RW_UNIMPLEMENTED("Hiding Frames"); } else { - skeleton->setEnabled(f, false); } return true; } @@ -97,7 +92,7 @@ Qt::ItemFlags DFFFramesTreeModel::flags(const QModelIndex& index) const { Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; - if (index.column() == 0 && skeleton) { + if (index.column() == 0) { flags |= Qt::ItemIsUserCheckable; } diff --git a/rwviewer/models/DFFFramesTreeModel.hpp b/rwviewer/models/DFFFramesTreeModel.hpp index d839dfbe..f4ec521f 100644 --- a/rwviewer/models/DFFFramesTreeModel.hpp +++ b/rwviewer/models/DFFFramesTreeModel.hpp @@ -4,15 +4,13 @@ #include #include -class Model; -class Skeleton; +class Clump; class DFFFramesTreeModel : public QAbstractItemModel { - Model* model; - Skeleton* skeleton; + Clump* model; public: - explicit DFFFramesTreeModel(Model* m, Skeleton* skel, QObject* parent = 0); + explicit DFFFramesTreeModel(Clump* m, QObject* parent = 0); virtual int columnCount(const QModelIndex& parent = QModelIndex()) const; diff --git a/rwviewer/views/ModelViewer.cpp b/rwviewer/views/ModelViewer.cpp index 4ba8df6f..be210123 100644 --- a/rwviewer/views/ModelViewer.cpp +++ b/rwviewer/views/ModelViewer.cpp @@ -1,6 +1,5 @@ #include "ModelViewer.hpp" #include -#include #include #include #include @@ -9,7 +8,7 @@ ModelViewer::ModelViewer(ViewerWidget* viewer, QWidget* parent, Qt::WindowFlags f) - : ViewerInterface(parent, f), viewing(nullptr), skeleton(nullptr) { + : ViewerInterface(parent, f), viewing(nullptr) { mainSplit = new QSplitter; mainLayout = new QVBoxLayout; @@ -40,21 +39,16 @@ void ModelViewer::setViewerWidget(ViewerWidget* widget) { showModel(viewing); } -void ModelViewer::showModel(Model* model) { +void ModelViewer::showModel(Clump* model) { viewing = model; - if (skeleton) { - delete skeleton; - } - skeleton = new Skeleton(); viewerWidget->showModel(model); - frames->setModel(model, nullptr); + frames->setModel(model); } void ModelViewer::showObject(uint16_t object) { viewerWidget->showObject(object); viewing = viewerWidget->currentModel(); - skeleton = viewerWidget->currentObject()->skeleton; - frames->setModel(viewing, skeleton); + frames->setModel(viewing); } void ModelViewer::loadAnimations(const QString& file) { diff --git a/rwviewer/views/ModelViewer.hpp b/rwviewer/views/ModelViewer.hpp index 1b08ba79..c8085c6d 100644 --- a/rwviewer/views/ModelViewer.hpp +++ b/rwviewer/views/ModelViewer.hpp @@ -14,16 +14,14 @@ #include class ViewerWidget; -class Model; -class Skeleton; +class Clump; class ModelFramesWidget; class Animation; class ModelViewer : public ViewerInterface { Q_OBJECT - Model* viewing; - Skeleton* skeleton; + Clump* viewing; QSplitter* mainSplit; QVBoxLayout* mainLayout; @@ -45,7 +43,7 @@ public slots: /** * Display a raw model */ - void showModel(Model* model); + void showModel(Clump* model); /** * Display a game object's model diff --git a/rwviewer/views/ObjectViewer.hpp b/rwviewer/views/ObjectViewer.hpp index b5d5773f..f7418cf4 100644 --- a/rwviewer/views/ObjectViewer.hpp +++ b/rwviewer/views/ObjectViewer.hpp @@ -12,7 +12,7 @@ #include class ViewerWidget; -class Model; +class Clump; class ObjectViewer : public ViewerInterface { Q_OBJECT @@ -40,7 +40,7 @@ protected: signals: - void modelChanged(Model* model); + void modelChanged(Clump* model); void showObjectModel(uint16_t object); diff --git a/rwviewer/widgets/ModelFramesWidget.cpp b/rwviewer/widgets/ModelFramesWidget.cpp index 98c8e437..51720ce5 100644 --- a/rwviewer/widgets/ModelFramesWidget.cpp +++ b/rwviewer/widgets/ModelFramesWidget.cpp @@ -1,28 +1,15 @@ #include "ModelFramesWidget.hpp" -#include +#include #include -void ModelFramesWidget::updateInfoBox(Model* model, ModelFrame* f) { +void ModelFramesWidget::updateInfoBox(Clump* model, ModelFrame* f) { if (f == nullptr) { _frameLabel->setText(""); } else { - auto labText = QString("Name: %1\nTranslation: %2\nTextures:%3") + auto labText = QString("Name: %1\nTranslation: %2") .arg(QString::fromStdString(f->getName())) .arg(QString::fromStdString( glm::to_string(f->getDefaultTranslation()))); - QString geomString; - for (size_t gi : f->getGeometries()) { - auto& g = model->geometries[gi]; - // for(Model::SubGeometry& sg : g->subgeom) - for (Model::Material& m : g->materials) { - for (Model::Texture& t : m.textures) { - geomString += QString("\n %1 (%2)") - .arg(t.name.c_str()) - .arg(t.alphaName.c_str()); - } - } - } - labText = labText.arg(geomString); _frameLabel->setText(labText); } } @@ -46,7 +33,7 @@ ModelFramesWidget::ModelFramesWidget(QWidget* parent, Qt::WindowFlags flags) setLayout(_layout); } -void ModelFramesWidget::setModel(Model* model, Skeleton* skeleton) { +void ModelFramesWidget::setModel(Clump* model) { if (framemodel) { delete framemodel; framemodel = nullptr; @@ -54,7 +41,7 @@ void ModelFramesWidget::setModel(Model* model, Skeleton* skeleton) { } gmodel = model; if (model != nullptr) { - framemodel = new DFFFramesTreeModel(model, skeleton, this); + framemodel = new DFFFramesTreeModel(model, this); tree->setModel(framemodel); tree->setDisabled(false); connect(tree->selectionModel(), diff --git a/rwviewer/widgets/ModelFramesWidget.hpp b/rwviewer/widgets/ModelFramesWidget.hpp index 1bf17861..3bdf48f8 100644 --- a/rwviewer/widgets/ModelFramesWidget.hpp +++ b/rwviewer/widgets/ModelFramesWidget.hpp @@ -13,7 +13,7 @@ class ModelFrame; class ModelFramesWidget : public QWidget { Q_OBJECT - Model* gmodel; + Clump* gmodel; DFFFramesTreeModel* framemodel; QTreeView* tree; QVBoxLayout* _layout; @@ -21,7 +21,7 @@ class ModelFramesWidget : public QWidget { private slots: - void updateInfoBox(Model* model, ModelFrame* f); + void updateInfoBox(Clump* model, ModelFrame* f); void selectedModelChanged(const QModelIndex&, const QModelIndex&); @@ -30,7 +30,7 @@ public: public slots: - void setModel(Model* model, Skeleton* skeleton); + void setModel(Clump* model); signals: diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index e663ed70..e8d4eb3d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -38,7 +38,6 @@ set(TEST_SOURCES "test_rwbstream.cpp" "test_SaveGame.cpp" "test_scriptmachine.cpp" - "test_skeleton.cpp" "test_state.cpp" "test_text.cpp" "test_trafficdirector.cpp" diff --git a/tests/test_animation.cpp b/tests/test_animation.cpp index b4d2654d..36848543 100644 --- a/tests/test_animation.cpp +++ b/tests/test_animation.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include #include "test_globals.hpp" @@ -10,14 +9,13 @@ BOOST_AUTO_TEST_SUITE(AnimationTests) #if RW_TEST_WITH_DATA BOOST_AUTO_TEST_CASE(test_matrix) { { - Skeleton skeleton; Animation animation; /** Models are currently needed to relate animation bones <=> model * frame #s. */ auto test_model = Global::get().d->loadClump("player.dff"); - Animator animator(test_model, &skeleton); + Animator animator(test_model); animation.duration = 1.f; animation.bones["player"] = new AnimationBone{ @@ -35,17 +33,15 @@ BOOST_AUTO_TEST_CASE(test_matrix) { animator.tick(0.0f); - BOOST_CHECK(skeleton.getData(0).a.translation == - glm::vec3(0.f, 0.f, 0.f)); - BOOST_CHECK(skeleton.getData(0).b.translation == + const auto& root = test_model->findFrame("player"); + + BOOST_CHECK(glm::vec3(root->getTransform()[3]) == glm::vec3(0.f, 0.f, 0.f)); animator.tick(1.0f); - BOOST_CHECK(skeleton.getData(0).a.translation == + BOOST_CHECK(glm::vec3(root->getTransform()[3]) == glm::vec3(0.f, 1.f, 0.f)); - BOOST_CHECK(skeleton.getData(0).b.translation == - glm::vec3(0.f, 0.f, 0.f)); } } #endif diff --git a/tests/test_loaderdff.cpp b/tests/test_loaderdff.cpp index ff8f2317..2875f16c 100644 --- a/tests/test_loaderdff.cpp +++ b/tests/test_loaderdff.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include "test_globals.hpp" BOOST_AUTO_TEST_SUITE(LoaderDFFTests) @@ -11,26 +11,17 @@ BOOST_AUTO_TEST_CASE(test_load_dff) { LoaderDFF loader; - Model* m = loader.loadFromMemory(d); + Clump* m = loader.loadFromMemory(d); BOOST_REQUIRE(m != nullptr); - BOOST_REQUIRE_EQUAL(m->frames.size(), 40); + BOOST_REQUIRE(m->getFrame()); - BOOST_REQUIRE_EQUAL(m->geometries.size(), 16); + BOOST_REQUIRE(!m->getAtomics().empty()); + const auto& atomic = m->getAtomics()[0]; - BOOST_REQUIRE_EQUAL(m->geometries[0]->subgeom.size(), 5); - - for (auto& g : m->geometries) { - BOOST_CHECK_GT(g->geometryBounds.radius, 0.f); - } - - BOOST_REQUIRE(m->atomics.size() > 0); - - for (Model::Atomic& a : m->atomics) { - BOOST_CHECK(a.frame < m->frames.size()); - BOOST_CHECK(a.geometry < m->geometries.size()); - } + BOOST_REQUIRE(atomic->getGeometry()); + BOOST_REQUIRE(atomic->getFrame()); delete m; } @@ -38,4 +29,44 @@ BOOST_AUTO_TEST_CASE(test_load_dff) { #endif +BOOST_AUTO_TEST_CASE(test_clump_clone) { + { + auto frame1 = std::make_shared(0); + frame1->setName("Frame1"); + auto frame2 = std::make_shared(1); + frame2->setName("Frame2"); + + frame1->addChild(frame2); + + auto geometry = std::make_shared(); + + auto atomic = std::make_shared(); + atomic->setFrame(frame2); + atomic->setGeometry(geometry); + + auto clump = std::make_shared(); + clump->addAtomic(atomic); + clump->setFrame(frame1); + + // Now clone and verify that: + // The hierarchy has the same data but different objects + // The atomics have the same data but different objects + // Suspected correct behaviour: + // The geometry is the same for each atomic + + auto newclump = std::shared_ptr(clump->clone()); + BOOST_REQUIRE(newclump); + + BOOST_CHECK_NE(newclump, clump); + + BOOST_REQUIRE(newclump->getFrame()); + BOOST_CHECK_NE(newclump->getFrame(), clump->getFrame()); + BOOST_CHECK_EQUAL(newclump->getFrame()->getChildren().size(), 1); + + BOOST_CHECK_EQUAL(newclump->getAtomics().size(), 1); + + BOOST_CHECK_EQUAL(frame1->getName(), newclump->getFrame()->getName()); + } +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/test_skeleton.cpp b/tests/test_skeleton.cpp deleted file mode 100644 index cc7ad527..00000000 --- a/tests/test_skeleton.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include -#include -#include - -BOOST_AUTO_TEST_SUITE(SkeletonTests) - -BOOST_AUTO_TEST_CASE(test_methods) { - Skeleton::FrameTransform t1{glm::vec3(0.f, 0.f, 0.f), - glm::quat(0.f, 0.f, 0.f, 0.f)}; - Skeleton::FrameTransform t2{glm::vec3(1.f, 0.f, 0.f), - glm::quat(0.f, 0.f, 1.f, 0.f)}; - - Skeleton skeleton; - - skeleton.setAllData({{0, {t1, t2, true}}}); - - BOOST_CHECK(skeleton.getData(0).a.translation == t1.translation); - BOOST_CHECK(skeleton.getData(0).a.rotation == t1.rotation); - - BOOST_CHECK(skeleton.getData(0).b.translation == t2.translation); - BOOST_CHECK(skeleton.getData(0).b.rotation == t2.rotation); - - BOOST_CHECK(skeleton.getData(0).enabled); - - skeleton.setData(0, {t2, t1, false}); - - BOOST_CHECK(skeleton.getData(0).a.translation == t2.translation); - BOOST_CHECK(skeleton.getData(0).a.rotation == t2.rotation); - - BOOST_CHECK(skeleton.getData(0).b.translation == t1.translation); - BOOST_CHECK(skeleton.getData(0).b.rotation == t1.rotation); - - BOOST_CHECK(!skeleton.getData(0).enabled); -} - -BOOST_AUTO_TEST_CASE(test_interpolate) { - Skeleton::FrameTransform t1{glm::vec3(0.f, 0.f, 0.f), - glm::quat(0.f, 0.f, 0.f, 0.f)}; - Skeleton::FrameTransform t2{glm::vec3(1.f, 0.f, 0.f), - glm::quat(0.f, 0.f, 1.f, 0.f)}; - - Skeleton skeleton; - - skeleton.setAllData({{0, {t2, t1, true}}}); - - /** Without calling Skeleton::interpolate(alpha) the result is identity */ - - BOOST_CHECK(skeleton.getInterpolated(0).translation == - Skeleton::IdentityTransform.translation); - BOOST_CHECK(skeleton.getInterpolated(0).rotation == - Skeleton::IdentityTransform.rotation); - - skeleton.interpolate(0.f); - - BOOST_CHECK(skeleton.getInterpolated(0).translation == t1.translation); - BOOST_CHECK(skeleton.getInterpolated(0).rotation == t1.rotation); - - skeleton.interpolate(1.f); - - BOOST_CHECK(skeleton.getInterpolated(0).translation == t2.translation); - BOOST_CHECK(skeleton.getInterpolated(0).rotation == t2.rotation); -} -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/test_vehicle.cpp b/tests/test_vehicle.cpp index 1fb1a9ad..a50dedc7 100644 --- a/tests/test_vehicle.cpp +++ b/tests/test_vehicle.cpp @@ -1,6 +1,5 @@ #include -#include -#include +#include #include #include "test_globals.hpp" @@ -41,8 +40,11 @@ BOOST_AUTO_TEST_CASE(vehicle_parts) { BOOST_REQUIRE(part->normal != nullptr); BOOST_REQUIRE(part->damaged != nullptr); - BOOST_CHECK_EQUAL(part->normal->getName(), "bonnet_hi_ok"); - BOOST_CHECK_EQUAL(part->damaged->getName(), "bonnet_hi_dam"); + BOOST_REQUIRE(part->normal->getFrame()); + BOOST_REQUIRE(part->damaged->getFrame()); + + BOOST_CHECK_EQUAL(part->normal->getFrame()->getName(), "bonnet_hi_ok"); + BOOST_CHECK_EQUAL(part->damaged->getFrame()->getName(), "bonnet_hi_dam"); Global::get().e->destroyObject(vehicle); } @@ -55,17 +57,16 @@ BOOST_AUTO_TEST_CASE(vehicle_part_vis) { BOOST_REQUIRE(vehicle->getModel() != nullptr); VehicleObject::Part* bonnetpart = vehicle->getPart("bonnet_dummy"); - auto skel = vehicle->skeleton; vehicle->setPartState(bonnetpart, VehicleObject::DAM); - BOOST_CHECK(!skel->getData(bonnetpart->normal->getIndex()).enabled); - BOOST_CHECK(skel->getData(bonnetpart->damaged->getIndex()).enabled); + BOOST_CHECK((bonnetpart->normal->getFlags() & Atomic::ATOMIC_RENDER) == 0); + BOOST_CHECK((bonnetpart->damaged->getFlags() & Atomic::ATOMIC_RENDER) != 0); vehicle->setPartState(bonnetpart, VehicleObject::OK); - BOOST_CHECK(skel->getData(bonnetpart->normal->getIndex()).enabled); - BOOST_CHECK(!skel->getData(bonnetpart->damaged->getIndex()).enabled); + BOOST_CHECK((bonnetpart->normal->getFlags() & Atomic::ATOMIC_RENDER) != 0); + BOOST_CHECK((bonnetpart->damaged->getFlags() & Atomic::ATOMIC_RENDER) == 0); Global::get().e->destroyObject(vehicle); }