From 2ea59909feb326e4d57ff042bb58e0654d55c94b Mon Sep 17 00:00:00 2001 From: codereader Date: Sun, 19 Apr 2020 20:53:29 +0200 Subject: [PATCH] #5220: Towards fixing models losing their scale: preserve any modified scale at the point right before the scene is passed to the map writers. In regular save events no models will have a modified scale anymore since the rescaled models will have been written to disk already, but in the case of auto-saves or (prefab) export no models will have been processed. --- include/imodel.h | 5 ++ radiant/md5model/MD5ModelNode.cpp | 5 ++ radiant/md5model/MD5ModelNode.h | 1 + radiant/model/NullModelNode.cpp | 5 ++ radiant/model/NullModelNode.h | 1 + radiant/model/ScaledModelExporter.cpp | 74 +++++++++++++++++++++++++++ radiant/model/ScaledModelExporter.h | 4 +- radiant/modelfile/PicoModelNode.cpp | 5 ++ radiant/modelfile/PicoModelNode.h | 1 + 9 files changed, 99 insertions(+), 2 deletions(-) diff --git a/include/imodel.h b/include/imodel.h index 69a8c301b3..f580957004 100644 --- a/include/imodel.h +++ b/include/imodel.h @@ -7,6 +7,8 @@ #include "imodelsurface.h" #include +#include "math/Vector3.h" + /* Forward decls */ class AABB; class ModelSkin; @@ -110,6 +112,9 @@ class ModelNode // Returns true if this model's scale has been modified // and needs to be written to file virtual bool hasModifiedScale() = 0; + + // Returns the current scale of this model + virtual Vector3 getModelScale() = 0; }; typedef std::shared_ptr ModelNodePtr; diff --git a/radiant/md5model/MD5ModelNode.cpp b/radiant/md5model/MD5ModelNode.cpp index 9378c57dc1..1e0f82181b 100644 --- a/radiant/md5model/MD5ModelNode.cpp +++ b/radiant/md5model/MD5ModelNode.cpp @@ -45,6 +45,11 @@ bool MD5ModelNode::hasModifiedScale() return false; // not supported } +Vector3 MD5ModelNode::getModelScale() +{ + return Vector3(1, 1, 1); // not supported +} + void MD5ModelNode::lightsChanged() { _lightList->setDirty(); diff --git a/radiant/md5model/MD5ModelNode.h b/radiant/md5model/MD5ModelNode.h index 166b4c94ae..07d3d21f01 100644 --- a/radiant/md5model/MD5ModelNode.h +++ b/radiant/md5model/MD5ModelNode.h @@ -35,6 +35,7 @@ class MD5ModelNode : const model::IModel& getIModel() const override; model::IModel& getIModel() override; bool hasModifiedScale() override; + Vector3 getModelScale() override; void lightsChanged(); diff --git a/radiant/model/NullModelNode.cpp b/radiant/model/NullModelNode.cpp index 485a1dc030..6bb40e522d 100644 --- a/radiant/model/NullModelNode.cpp +++ b/radiant/model/NullModelNode.cpp @@ -49,6 +49,11 @@ bool NullModelNode::hasModifiedScale() return false; } +Vector3 NullModelNode::getModelScale() +{ + return Vector3(1,1,1); +} + void NullModelNode::testSelect(Selector& selector, SelectionTest& test) { _nullModel->testSelect(selector, test, localToWorld()); } diff --git a/radiant/model/NullModelNode.h b/radiant/model/NullModelNode.h index bae236d6b2..4949fc8499 100644 --- a/radiant/model/NullModelNode.h +++ b/radiant/model/NullModelNode.h @@ -33,6 +33,7 @@ class NullModelNode : const IModel& getIModel() const override; IModel& getIModel() override; bool hasModifiedScale() override; + Vector3 getModelScale() override; void testSelect(Selector& selector, SelectionTest& test) override; diff --git a/radiant/model/ScaledModelExporter.cpp b/radiant/model/ScaledModelExporter.cpp index d5a1ee2174..66b898c377 100644 --- a/radiant/model/ScaledModelExporter.cpp +++ b/radiant/model/ScaledModelExporter.cpp @@ -8,11 +8,13 @@ #include "igame.h" #include "ientity.h" #include "iscenegraph.h" +#include "imapresource.h" #include "os/fs.h" #include "os/path.h" #include "registry/registry.h" #include #include "string/case_conv.h" +#include "string/convert.h" #include #include "ModelExporter.h" @@ -20,11 +22,27 @@ namespace map { +namespace +{ + const std::string MODELSCALE_KEY = "editor_modelScale"; +} + void ScaledModelExporter::initialise() { _mapEventConn = GlobalMapModule().signal_mapEvent().connect( sigc::mem_fun(*this, &ScaledModelExporter::onMapEvent) ); + + // #5220: To cover having the scale of resized models preserved in + // auto-saves and prefabs, we subscribe to the exporting events + // and check for any models that still have a modified scale on it. + // That scale value is then written to the hosting entity's spawnargs. + GlobalMapResourceManager().signal_onResourceExporting().connect( + sigc::mem_fun(this, &ScaledModelExporter::onResourceExporting) + ); + GlobalMapResourceManager().signal_onResourceExported().connect( + sigc::mem_fun(this, &ScaledModelExporter::onResourceExported) + ); } void ScaledModelExporter::shutdown() @@ -32,6 +50,62 @@ void ScaledModelExporter::shutdown() _mapEventConn.disconnect(); } +namespace +{ + +inline void forEachScaledModel(const scene::IMapRootNodePtr& root, + const std::function& func) +{ + root->foreachNode([&](const scene::INodePtr& node) + { + if (Node_isEntity(node)) + { + // Find any model nodes below that one + node->foreachNode([&](const scene::INodePtr& child) + { + model::ModelNodePtr model = Node_getModel(child); + + if (model && model->hasModifiedScale()) + { + // Found a model with modified scale + func(*Node_getEntity(node), *model); + } + + return true; + }); + } + + return true; + }); +} + +} + +void ScaledModelExporter::onResourceExporting(const scene::IMapRootNodePtr& root) +{ + // Traverse the exported scene and check for any models that are still scaled, to + // persist that value in the exported scene. + // In "regular" map saves, all models already have been processed here at this point, + // and their scale is reset, so in this case the following traversal does nothing. + forEachScaledModel(root, [](Entity& entity, model::ModelNode& model) + { + // Persist the modified scale by placing a special editor spawnarg + entity.setKeyValue(MODELSCALE_KEY, string::to_string(model.getModelScale())); + }); +} + +void ScaledModelExporter::onResourceExported(const scene::IMapRootNodePtr& root) +{ + // In this post-export event, we remove any scale spawnargs added earlier + forEachScaledModel(root, [](Entity& entity, model::ModelNode& model) + { + if (!entity.getKeyValue(MODELSCALE_KEY).empty()) + { + entity.setKeyValue(MODELSCALE_KEY, ""); + } + }); +} + void ScaledModelExporter::onMapEvent(IMap::MapEvent ev) { if (ev == IMap::MapSaving) diff --git a/radiant/model/ScaledModelExporter.h b/radiant/model/ScaledModelExporter.h index 7f5b021c5d..58deb9870e 100644 --- a/radiant/model/ScaledModelExporter.h +++ b/radiant/model/ScaledModelExporter.h @@ -33,8 +33,8 @@ class ScaledModelExporter std::string generateUniqueModelFilename(const fs::path& outputPath, const fs::path& modelPath, const std::string& outputExtension); - void exportModel(const model::IModelExporterPtr& exporter, - const fs::path& modelOutputPath, const std::string& modelFilename); + void onResourceExporting(const scene::IMapRootNodePtr& root); + void onResourceExported(const scene::IMapRootNodePtr& root); }; } diff --git a/radiant/modelfile/PicoModelNode.cpp b/radiant/modelfile/PicoModelNode.cpp index 0bb1f27739..ec6ee70c00 100644 --- a/radiant/modelfile/PicoModelNode.cpp +++ b/radiant/modelfile/PicoModelNode.cpp @@ -57,6 +57,11 @@ bool PicoModelNode::hasModifiedScale() return _picoModel->getScale() != Vector3(1, 1, 1); } +Vector3 PicoModelNode::getModelScale() +{ + return _picoModel->getScale(); +} + const AABB& PicoModelNode::localAABB() const { return _picoModel->localAABB(); } diff --git a/radiant/modelfile/PicoModelNode.h b/radiant/modelfile/PicoModelNode.h index 8e0fe4288e..522dba75b4 100644 --- a/radiant/modelfile/PicoModelNode.h +++ b/radiant/modelfile/PicoModelNode.h @@ -52,6 +52,7 @@ class PicoModelNode : const IModel& getIModel() const override; IModel& getIModel() override; bool hasModifiedScale() override; + Vector3 getModelScale() override; // SkinnedModel implementation // Skin changed notify