From 138a7ac4344686a657c28e6f24058a464c975402 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Thu, 26 Sep 2019 02:44:49 +0300 Subject: [PATCH 01/79] Expression parser: Try to parse strings as number literals (bug #5097) --- CHANGELOG.md | 1 + components/compiler/exprparser.cpp | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 811a276f1..9dfb026c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -127,6 +127,7 @@ Bug #5089: Swimming/Underwater creatures only swim around ground level Bug #5092: NPCs with enchanted weapons play sound when out of charges Bug #5093: Hand to hand sound plays on knocked out enemies + Bug #5097: String arguments can't be parsed as number literals in scripts Bug #5099: Non-swimming enemies will enter water if player is water walking Bug #5103: Sneaking state behavior is still inconsistent Bug #5104: Black Dart's enchantment doesn't trigger at low Enchant levels diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index e3a306b8f..871800fa7 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -324,6 +325,21 @@ namespace Compiler mExplicit = name2; return true; } + + // This is terrible, but of course we must have this for legacy content. + // Convert the string to a number even if it's impossible and use it as a number literal. + // Can't use stof/atof or to_string out of locale concerns. + float number; + std::stringstream stream(name2); + stream >> number; + stream.str(std::string()); + stream.clear(); + stream << number; + + pushFloatLiteral(number); + mTokenLoc = loc; + getErrorHandler().warning ("Parsing a non-variable string as a number: " + stream.str(), loc); + return true; } else { From d1d6ba3ed0728e87289bd479d105a0b240e6a6cd Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 7 Oct 2019 20:19:12 +0200 Subject: [PATCH 02/79] Fix rebuild path for walking actors Ignore z coordinate for not swimming nor flying actors to calculate distance from actor destination to last path point. If walking actor destination point is floating above the ground then a point on navmesh may be too far away when z coordinate is included. In this case path will be rebuild on each AI_REACTION_TIME. --- apps/openmw/mwmechanics/aipackage.cpp | 29 +++++++++++++++++++++++---- apps/openmw/mwmechanics/aipackage.hpp | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 646b37669..8336ca971 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -22,6 +22,27 @@ #include +namespace MWMechanics +{ + static float distance(const osg::Vec2f& lhs, const osg::Vec2f& rhs) + { + return (lhs - rhs).length2(); + } + + static float distanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + return distance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); + } + + static float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs, const MWWorld::Ptr& actor) + { + const auto world = MWBase::Environment::get().getWorld(); + if (world->isSwimming(actor) || world->isFlying(actor)) + return distance(lhs, rhs); + return distanceIgnoreZ(lhs, rhs); + } +} + MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : @@ -133,7 +154,7 @@ bool MWMechanics::AiPackage::pathTo(const MWWorld::Ptr& actor, const osg::Vec3f& if (!mIsShortcutting) { - if (wasShortcutting || doesPathNeedRecalc(dest, actor.getCell())) // if need to rebuild path + if (wasShortcutting || doesPathNeedRecalc(dest, actor)) // if need to rebuild path { const auto pathfindingHalfExtents = world->getPathfindingHalfExtents(actor); mPathFinder.buildPath(actor, position, dest, actor.getCell(), getPathGridGraph(actor.getCell()), @@ -328,11 +349,11 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin return false; } -bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell) +bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const { return mPathFinder.getPath().empty() - || (distance(mPathFinder.getPath().back(), newDest) > 10) - || mPathFinder.getPathCell() != currentCell; + || distance(mPathFinder.getPath().back(), newDest, actor) > 10 + || mPathFinder.getPathCell() != actor.getCell(); } bool MWMechanics::AiPackage::isNearInactiveCell(osg::Vec3f position) diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index 6bb12342a..20b4c390e 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -120,7 +120,7 @@ namespace MWMechanics /// Check if the way to the destination is clear, taking into account actor speed bool checkWayIsClearForActor(const osg::Vec3f& startPoint, const osg::Vec3f& endPoint, const MWWorld::Ptr& actor); - bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::CellStore* currentCell); + bool doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const; void evadeObstacles(const MWWorld::Ptr& actor); From cbf8deeb31acd4572d7e00198effa3087f2ba686 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 7 Oct 2019 21:05:41 +0200 Subject: [PATCH 03/79] Remove unused default settings --- files/settings-default.cfg | 6 ------ 1 file changed, 6 deletions(-) diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f8c31eed7..6ae183ad6 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -711,12 +711,6 @@ max smooth path size = 1024 # Maximum number of triangles in each node of mesh AABB tree (value > 0) triangles per chunk = 256 -# Enable debug log (true, false) -enable log = false - -# Write debug log to this file -log path = detournavigator.log - # Write recast mesh to file in .obj format for each use to update nav mesh (true, false) enable write recast mesh to file = false From bfe6005ed484923fa72b183793f51062ce53cbbb Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 7 Oct 2019 21:29:41 +0200 Subject: [PATCH 04/79] Increase default recast scale factor To make 4 voxels fit into sStepSizeUp = 34: "recast scale factor" = 4 * "cell size" / sStepSizeUp --- docs/source/reference/modding/settings/navigator.rst | 4 ++-- files/settings-default.cfg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 42494a476..db2ad66cf 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -176,12 +176,12 @@ recast scale factor :Type: floating point :Range: > 0.0 -:Default: 0.017647058823529415 +:Default: 0.023529411764705882 Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size "recast scale factor" / "cell size". Default value calculates by this equation: -sStepSizeUp * "recast scale factor" / "cell size" = 3 (max climb height should be equal to 3 voxels). +sStepSizeUp * "recast scale factor" / "cell size" = 4 (max climb height should be equal to 4 voxels). Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures. Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 6ae183ad6..f999cbb01 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -642,8 +642,8 @@ enable = true # Scale of NavMesh coordinates to world coordinates (value > 0.0). Recastnavigation builds voxels for world geometry. # Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size # "recast scale factor" / "cell size". Default value calculates by this equation: -# sStepSizeUp * "recast scale factor" / "cell size" = 3 (max climb height should be equal to 3 voxels) -recast scale factor = 0.017647058823529415 +# sStepSizeUp * "recast scale factor" / "cell size" = 4 (max climb height should be equal to 4 voxels) +recast scale factor = 0.023529411764705882 # The z-axis cell size to use for fields. (value > 0.0) # Defines voxel/grid/cell size. So their values have significant From 2fc819cdae4de34b948398e0d0cfb170ee83fb56 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Thu, 10 Oct 2019 13:29:25 +0400 Subject: [PATCH 05/79] Encode ID's in all places to UTF-8 (bug #3977) --- CHANGELOG.md | 1 + apps/esmtool/record.cpp | 8 ++++---- apps/essimporter/convertscpt.cpp | 2 +- apps/essimporter/importinventory.cpp | 2 +- .../opencs/model/tools/referenceablecheck.cpp | 2 +- apps/opencs/model/tools/regioncheck.cpp | 2 +- apps/opencs/model/world/actoradapter.cpp | 2 +- .../model/world/nestedcoladapterimp.cpp | 2 +- apps/opencs/model/world/refidadapterimp.hpp | 2 +- apps/openmw/mwsound/soundmanagerimp.cpp | 2 +- apps/openmw/mwworld/containerstore.cpp | 6 +++--- components/esm/loadcont.cpp | 9 +++++++-- components/esm/loadcont.hpp | 2 +- components/esm/loadregn.cpp | 9 +++++++-- components/esm/loadregn.hpp | 2 +- components/esm/loadscpt.cpp | 19 ++++++++----------- components/esm/loadscpt.hpp | 2 +- 17 files changed, 41 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bafad417..2c5ce7d6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Bug #3778: [Mod] Improved Thrown Weapon Projectiles - weapons have wrong transformation during throw animation Bug #3812: Wrong multiline tooltips width when word-wrapping is enabled Bug #3894: Hostile spell effects not detected/present on first frame of OnPCHitMe + Bug #3977: Non-ASCII characters in object ID's are not supported Bug #4077: Enchanted items are not recharged if they are not in the player's inventory Bug #4202: Open .omwaddon files without needing toopen openmw-cs first Bug #4240: Ash storm origin coordinates and hand shielding animation behavior are incorrect diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index c4b14a341..e8f444489 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -607,7 +607,7 @@ void Record::print() std::cout << " Weight: " << mData.mWeight << std::endl; for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem.toString() << std::endl; + << " Item: " << item.mItem << std::endl; std::cout << " Deleted: " << mIsDeleted << std::endl; } @@ -653,7 +653,7 @@ void Record::print() for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem.toString() << std::endl; + << " Item: " << item.mItem << std::endl; for (const std::string &spell : mData.mSpells.mList) std::cout << " Spell: " << spell << std::endl; @@ -1073,7 +1073,7 @@ void Record::print() for (const ESM::ContItem &item : mData.mInventory.mList) std::cout << " Inventory: Count: " << Misc::StringUtils::format("%4d", item.mCount) - << " Item: " << item.mItem.toString() << std::endl; + << " Item: " << item.mItem << std::endl; for (const std::string &spell : mData.mSpells.mList) std::cout << " Spell: " << spell << std::endl; @@ -1192,7 +1192,7 @@ void Record::print() if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; for (const ESM::Region::SoundRef &soundref : mData.mSoundList) - std::cout << " Sound: " << (int)soundref.mChance << " = " << soundref.mSound.toString() << std::endl; + std::cout << " Sound: " << (int)soundref.mChance << " = " << soundref.mSound << std::endl; } template<> diff --git a/apps/essimporter/convertscpt.cpp b/apps/essimporter/convertscpt.cpp index ca81ebbbf..484a2782a 100644 --- a/apps/essimporter/convertscpt.cpp +++ b/apps/essimporter/convertscpt.cpp @@ -9,7 +9,7 @@ namespace ESSImport void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out) { - out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString()); + out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName); out.mRunning = scpt.mRunning; convertSCRI(scpt.mSCRI, out.mLocals); } diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp index 8c7d07f63..177213a13 100644 --- a/apps/essimporter/importinventory.cpp +++ b/apps/essimporter/importinventory.cpp @@ -17,7 +17,7 @@ namespace ESSImport esm.getHT(contItem); InventoryItem item; - item.mId = contItem.mItem.toString(); + item.mId = contItem.mItem; item.mCount = contItem.mCount; item.mRelativeEquipmentSlot = -1; item.mLockLevel = 0; diff --git a/apps/opencs/model/tools/referenceablecheck.cpp b/apps/opencs/model/tools/referenceablecheck.cpp index 73c28f564..981e8c195 100644 --- a/apps/opencs/model/tools/referenceablecheck.cpp +++ b/apps/opencs/model/tools/referenceablecheck.cpp @@ -910,7 +910,7 @@ void CSMTools::ReferenceableCheckStage::inventoryListCheck( { for (size_t i = 0; i < itemList.size(); ++i) { - std::string itemName = itemList[i].mItem.toString(); + std::string itemName = itemList[i].mItem; CSMWorld::RefIdData::LocalIndex localIndex = mReferencables.searchId(itemName); if (localIndex.first == -1) diff --git a/apps/opencs/model/tools/regioncheck.cpp b/apps/opencs/model/tools/regioncheck.cpp index 1e6f64786..27a73be93 100644 --- a/apps/opencs/model/tools/regioncheck.cpp +++ b/apps/opencs/model/tools/regioncheck.cpp @@ -45,7 +45,7 @@ void CSMTools::RegionCheckStage::perform (int stage, CSMDoc::Messages& messages) for (const ESM::Region::SoundRef& sound : region.mSoundList) { if (sound.mChance > 100) - messages.add(id, "Chance of '" + sound.mSound.toString() + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning); + messages.add(id, "Chance of '" + sound.mSound + "' sound to play is over 100 percent", "", CSMDoc::Message::Severity_Warning); } /// \todo check data members that can't be edited in the table view diff --git a/apps/opencs/model/world/actoradapter.cpp b/apps/opencs/model/world/actoradapter.cpp index 0e9b0745c..5ed80a1e4 100644 --- a/apps/opencs/model/world/actoradapter.cpp +++ b/apps/opencs/model/world/actoradapter.cpp @@ -538,7 +538,7 @@ namespace CSMWorld for (auto& item : npc.mInventory.mList) { if (item.mCount <= 0) continue; - std::string itemId = item.mItem.toString(); + std::string itemId = item.mItem; addNpcItem(itemId, data); } } diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 5ae0dabaf..76338efe5 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -396,7 +396,7 @@ namespace CSMWorld ESM::Region::SoundRef soundRef = soundList[subRowIndex]; switch (subColIndex) { - case 0: return QString(soundRef.mSound.toString().c_str()); + case 0: return QString(soundRef.mSound.c_str()); case 1: return soundRef.mChance; default: throw std::runtime_error("Region sounds subcolumn index out of range"); } diff --git a/apps/opencs/model/world/refidadapterimp.hpp b/apps/opencs/model/world/refidadapterimp.hpp index 16b7739f7..6cb6fcd9c 100644 --- a/apps/opencs/model/world/refidadapterimp.hpp +++ b/apps/opencs/model/world/refidadapterimp.hpp @@ -1257,7 +1257,7 @@ namespace CSMWorld switch (subColIndex) { - case 0: return QString::fromUtf8(content.mItem.toString().c_str()); + case 0: return QString::fromUtf8(content.mItem.c_str()); case 1: return content.mCount; default: throw std::runtime_error("Trying to access non-existing column in the nested table!"); diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index cef791c85..e01df7aa9 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -924,7 +924,7 @@ namespace MWSound { if(r - pos < sndref.mChance) { - playSound(sndref.mSound.toString(), 1.0f, 1.0f); + playSound(sndref.mSound, 1.0f, 1.0f); break; } pos += sndref.mChance; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 21ad9dfe1..8c5861e73 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -495,7 +495,7 @@ void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std:: for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); ++iter) { - std::string id = Misc::StringUtils::lowerCase(iter->mItem.toString()); + std::string id = Misc::StringUtils::lowerCase(iter->mItem); addInitialItem(id, owner, iter->mCount); } @@ -626,10 +626,10 @@ void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MW if (it->mCount >= 0) continue; - std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem.toString()); + std::string itemOrList = Misc::StringUtils::lowerCase(it->mItem); //If it's levelled list, restock if there's need to do so. - if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem.toString())) + if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem)) { std::map::iterator listInMap = allowedForReplace.find(itemOrList); diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 5ee785fb8..107aea7cf 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -9,8 +9,10 @@ namespace ESM void InventoryList::add(ESMReader &esm) { + esm.getSubHeader(); ContItem ci; - esm.getHT(ci, 36); + esm.getT(ci.mCount); + ci.mItem.assign(esm.getString(32)); mList.push_back(ci); } @@ -18,7 +20,10 @@ namespace ESM { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { - esm.writeHNT("NPCO", *it, 36); + esm.startSubRecord("NPCO"); + esm.writeT(it->mCount); + esm.writeFixedSizeString(it->mItem, 32); + esm.endRecord("NPCO"); } } diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 4c847f4e2..0cac58074 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -19,7 +19,7 @@ class ESMWriter; struct ContItem { int mCount; - NAME32 mItem; + std::string mItem; }; /// InventoryList, NPCO subrecord diff --git a/components/esm/loadregn.cpp b/components/esm/loadregn.cpp index e708bbb4e..98edca48f 100644 --- a/components/esm/loadregn.cpp +++ b/components/esm/loadregn.cpp @@ -62,8 +62,10 @@ namespace ESM break; case ESM::FourCC<'S','N','A','M'>::value: { + esm.getSubHeader(); SoundRef sr; - esm.getHT(sr, 33); + sr.mSound.assign(esm.getString(32)); + esm.getT(sr.mChance); mSoundList.push_back(sr); break; } @@ -103,7 +105,10 @@ namespace ESM esm.writeHNT("CNAM", mMapColor); for (std::vector::const_iterator it = mSoundList.begin(); it != mSoundList.end(); ++it) { - esm.writeHNT("SNAM", *it); + esm.startSubRecord("SNAM"); + esm.writeFixedSizeString(it->mSound, 32); + esm.writeT(it->mChance); + esm.endRecord("NPCO"); } } diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index a946e488e..4adda5197 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -37,7 +37,7 @@ struct Region // Reference to a sound that is played randomly in this region struct SoundRef { - NAME32 mSound; + std::string mSound; unsigned char mChance; }; // 33 bytes #pragma pack(pop) diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index b41dc496f..10a91d2e2 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -77,10 +77,10 @@ namespace ESM { case ESM::FourCC<'S','C','H','D'>::value: { - SCHD data; - esm.getHT(data, 52); - mData = data.mData; - mId = data.mName.toString(); + esm.getSubHeader(); + mId = esm.getString(32); + esm.getT(mData); + hasHeader = true; break; } @@ -131,13 +131,10 @@ namespace ESM for (std::vector::const_iterator it = mVarNames.begin(); it != mVarNames.end(); ++it) varNameString.append(*it); - SCHD data; - memset(&data, 0, sizeof(data)); - - data.mData = mData; - data.mName.assign(mId); - - esm.writeHNT("SCHD", data, 52); + esm.startSubRecord("SCHD"); + esm.writeFixedSizeString(mId, 32); + esm.writeT(mData, 20); + esm.endRecord("SCHD"); if (isDeleted) { diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index b8a06406d..64cedb095 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -31,7 +31,7 @@ public: }; struct SCHD { - NAME32 mName; + std::string mName; Script::SCHDstruct mData; }; // 52 bytes From 0220bcdef3b33a1f292eb137e53ad5325a8bbe2c Mon Sep 17 00:00:00 2001 From: naratzul Date: Sat, 12 Oct 2019 11:08:43 +0500 Subject: [PATCH 06/79] Add Visual Studio 2019 support --- CI/before_script.msvc.sh | 60 +++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 5ed7283fd..60bdb9c97 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -103,7 +103,7 @@ Options: Build unit tests / Google test -u Configure for unity builds. - -v <2013/2015/2017> + -v <2013/2015/2017/2019> Choose the Visual Studio version to use. -n Produce NMake makefiles instead of a Visual Studio solution. @@ -251,6 +251,18 @@ if [ -z $VS_VERSION ]; then fi case $VS_VERSION in + 16|16.0|2019 ) + GENERATOR="Visual Studio 16 2019" + TOOLSET="vc142" + MSVC_REAL_VER="16" + MSVC_VER="14.2" + MSVC_YEAR="2015" + MSVC_DISPLAY_YEAR="2019" + BOOST_VER="1.71.0" + BOOST_VER_URL="1_71_0" + BOOST_VER_SDK="107100" + ;; + 15|15.0|2017 ) GENERATOR="Visual Studio 15 2017" TOOLSET="vc141" @@ -258,6 +270,9 @@ case $VS_VERSION in MSVC_VER="14.1" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2017" + BOOST_VER="1.67.0" + BOOST_VER_URL="1_67_0" + BOOST_VER_SDK="106700" ;; 14|14.0|2015 ) @@ -267,6 +282,9 @@ case $VS_VERSION in MSVC_VER="14.0" MSVC_YEAR="2015" MSVC_DISPLAY_YEAR="2015" + BOOST_VER="1.67.0" + BOOST_VER_URL="1_67_0" + BOOST_VER_SDK="106700" ;; 12|12.0|2013 ) @@ -276,6 +294,9 @@ case $VS_VERSION in MSVC_VER="12.0" MSVC_YEAR="2013" MSVC_DISPLAY_YEAR="2013" + BOOST_VER="1.58.0" + BOOST_VER_URL="1_58_0" + BOOST_VER_SDK="105800" ;; esac @@ -315,7 +336,7 @@ case $CONFIGURATION in ;; esac -if [ ${BITS} -eq 64 ]; then +if [ $BITS -eq 64 ] && [ $MSVC_REAL_VER -lt 16 ]; then GENERATOR="${GENERATOR} Win64" fi @@ -323,7 +344,15 @@ if [ -n "$NMAKE" ]; then GENERATOR="NMake Makefiles" fi -add_cmake_opts "-G\"$GENERATOR\"" +if [ $MSVC_REAL_VER -ge 16 ]; then + if [ $BITS -eq 64 ]; then + add_cmake_opts "-G\"$GENERATOR\" -A x64" + else + add_cmake_opts "-G\"$GENERATOR\" -A Win32" + fi +else + add_cmake_opts "-G\"$GENERATOR\"" +fi if [ -n "$NMAKE" ]; then add_cmake_opts "-DCMAKE_BUILD_TYPE=${BUILD_CONFIG}" @@ -351,9 +380,9 @@ if [ -z $SKIP_DOWNLOAD ]; then # Boost if [ -z $APPVEYOR ]; then - download "Boost 1.67.0" \ - "https://sourceforge.net/projects/boost/files/boost-binaries/1.67.0/boost_1_67_0-msvc-${MSVC_VER}-${BITS}.exe" \ - "boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe" + download "Boost ${BOOST_VER}" \ + "https://sourceforge.net/projects/boost/files/boost-binaries/${BOOST_VER}/boost_${BOOST_VER_URL}-msvc-${MSVC_VER}-${BITS}.exe" \ + "boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe" fi # Bullet @@ -444,13 +473,9 @@ echo # Boost if [ -z $APPVEYOR ]; then - printf "Boost 1.67.0... " + printf "Boost ${BOOST_VER}... " else - if [ "${MSVC_VER}" -eq 12.0 ]; then - printf "Boost 1.58.0 AppVeyor... " - else - printf "Boost 1.67.0 AppVeyor... " - fi + printf "Boost ${BOOST_VER} AppVeyor... " fi { if [ -z $APPVEYOR ]; then @@ -468,13 +493,13 @@ fi exit 1; fi - if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION 106700" Boost/boost/version.hpp > /dev/null; then + if [ -d ${BOOST_SDK} ] && grep "BOOST_VERSION ${BOOST_VER_SDK}" Boost/boost/version.hpp > /dev/null; then printf "Exists. " elif [ -z $SKIP_EXTRACT ]; then rm -rf Boost CI_EXTRA_INNO_OPTIONS="" [ -n "$CI" ] && CI_EXTRA_INNO_OPTIONS="//SUPPRESSMSGBOXES //LOG='boost_install.log'" - "${DEPS}/boost-1.67.0-msvc${MSVC_VER}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} + "${DEPS}/boost-${BOOST_VER}-msvc${MSVC_VER}-win${BITS}.exe" //DIR="${CWD_DRIVE_ROOT}" //VERYSILENT //NORESTART ${CI_EXTRA_INNO_OPTIONS} mv "${CWD_DRIVE_ROOT_BASH}" "${BOOST_SDK}" fi add_cmake_opts -DBOOST_ROOT="$BOOST_SDK" \ @@ -483,11 +508,8 @@ fi echo Done. else # Appveyor unstable has all the boost we need already - if [ $MSVC_REAL_VER -eq 12 ]; then - BOOST_SDK="c:/Libraries/boost_1_58_0" - else - BOOST_SDK="c:/Libraries/boost_1_67_0" - fi + BOOST_SDK="c:/Libraries/boost_${BOOST_VER_URL}" + if [ $MSVC_REAL_VER -eq 15 ]; then LIB_SUFFIX="1" else From bc5d54a16156695facf8b62f5835fd099e68840d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 12 Oct 2019 19:06:10 +0300 Subject: [PATCH 07/79] Fix GetEffect result when running on dead actors --- apps/openmw/mwmechanics/actors.cpp | 1 + apps/openmw/mwscript/miscextensions.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 4b6b1f4a7..7a0c0c754 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -1826,6 +1826,7 @@ namespace MWMechanics // One case where we need this is to make sure bound items are removed upon death stats.modifyMagicEffects(MWMechanics::MagicEffects()); stats.getActiveSpells().clear(); + stats.getSpells().clear(); // Make sure spell effects are removed purgeSpellEffects(stats.getActorId()); diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index f7cc92865..bc6f9ea8e 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -441,7 +441,7 @@ namespace MWScript MWMechanics::MagicEffects effects = stats.getSpells().getMagicEffects(); effects += stats.getActiveSpells().getMagicEffects(); - if (ptr.getClass().hasInventoryStore(ptr)) + if (ptr.getClass().hasInventoryStore(ptr) && !stats.isDeathAnimationFinished()) { MWWorld::InventoryStore& store = ptr.getClass().getInventoryStore(ptr); effects += store.getMagicEffects(); From 8056107c6e7824b780a2a98c4ca5a9749d986e5b Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 5 Oct 2019 15:02:42 +0300 Subject: [PATCH 08/79] More minor NIF improvements --- components/nif/controller.hpp | 2 +- components/nif/data.cpp | 10 ++++---- components/nif/property.cpp | 42 ++++++++++++++++----------------- components/nif/property.hpp | 16 ++++--------- components/nifosg/nifloader.cpp | 42 ++++++++++++++++----------------- 5 files changed, 50 insertions(+), 62 deletions(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 52ab6f1f6..2fe28fe1d 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -112,7 +112,7 @@ class NiUVController : public Controller { public: NiUVDataPtr data; - int uvSet; + unsigned int uvSet; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/data.cpp b/components/nif/data.cpp index 5f7722794..4e1487f69 100644 --- a/components/nif/data.cpp +++ b/components/nif/data.cpp @@ -102,12 +102,12 @@ void NiTriStripsData::read(NIFStream *nif) std::vector lengths; nif->getUShorts(lengths, numStrips); + if (!numStrips) + return; + + strips.resize(numStrips); for (int i = 0; i < numStrips; i++) - { - std::vector strip; - nif->getUShorts(strip, lengths[i]); - strips.emplace_back(strip); - } + nif->getUShorts(strips[i], lengths[i]); } void NiAutoNormalParticlesData::read(NIFStream *nif) diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 47c6d35b3..1398326be 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -18,14 +18,13 @@ void NiTexturingProperty::Texture::read(NIFStream *nif) if(!inUse) return; texture.read(nif); - clamp = nif->getInt(); - filter = nif->getInt(); - uvSet = nif->getInt(); + clamp = nif->getUInt(); + nif->skip(4); // Filter mode. Ignoring because global filtering settings are more sensible + uvSet = nif->getUInt(); - // I have no idea, but I think these are actually two - // PS2-specific shorts (ps2L and ps2K), followed by an unknown - // short. - nif->skip(6); + // Two PS2-specific shorts. + nif->skip(4); + nif->skip(2); // Unknown short } void NiTexturingProperty::Texture::post(NIFFile *nif) @@ -36,26 +35,25 @@ void NiTexturingProperty::Texture::post(NIFFile *nif) void NiTexturingProperty::read(NIFStream *nif) { Property::read(nif); - apply = nif->getInt(); + apply = nif->getUInt(); - // Unknown, always 7. Probably the number of textures to read - // below - nif->getInt(); + unsigned int numTextures = nif->getUInt(); - textures[0].read(nif); // Base - textures[1].read(nif); // Dark - textures[2].read(nif); // Detail - textures[3].read(nif); // Gloss (never present) - textures[4].read(nif); // Glow - textures[5].read(nif); // Bump map - if(textures[5].inUse) + if (!numTextures) + return; + + textures.resize(numTextures); + for (unsigned int i = 0; i < numTextures; i++) { + textures[i].read(nif); // Ignore these at the moment - /*float lumaScale =*/ nif->getFloat(); - /*float lumaOffset =*/ nif->getFloat(); - /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + if (i == 5 && textures[5].inUse) // Bump map settings + { + /*float lumaScale =*/ nif->getFloat(); + /*float lumaOffset =*/ nif->getFloat(); + /*const Vector4 *lumaMatrix =*/ nif->getVector4(); + } } - textures[6].read(nif); // Decal } void NiTexturingProperty::post(NIFFile *nif) diff --git a/components/nif/property.hpp b/components/nif/property.hpp index f46f8ef27..a3f399b83 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -51,17 +51,10 @@ public: 3 - wrapS wrapT */ - /* Filter: - 0 - nearest - 1 - bilinear - 2 - trilinear - 3, 4, 5 - who knows - */ bool inUse; NiSourceTexturePtr texture; - int clamp, uvSet, filter; - short unknown2; + unsigned int clamp, uvSet; void read(NIFStream *nif); void post(NIFFile *nif); @@ -74,7 +67,7 @@ public: 3 - hilight // These two are for PS2 only? 4 - hilight2 */ - int apply; + unsigned int apply; /* * The textures in this list are as follows: @@ -82,7 +75,7 @@ public: * 0 - Base texture * 1 - Dark texture * 2 - Detail texture - * 3 - Gloss texture (never used?) + * 3 - Gloss texture * 4 - Glow texture * 5 - Bump map texture * 6 - Decal texture @@ -96,10 +89,9 @@ public: GlowTexture = 4, BumpTexture = 5, DecalTexture = 6, - NumTextures = 7 // Sentry value }; - Texture textures[7]; + std::vector textures; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 58e336b5d..f32224ca8 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -262,7 +262,7 @@ namespace NifOsg osg::ref_ptr textkeys (new TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); if (nif->getUseSkinning()) { @@ -288,7 +288,7 @@ namespace NifOsg return created; } - void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + void applyNodeProperties(const Nif::Node *nifNode, osg::Node *applyTo, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i setTextureSize(image->s(), image->t()); texture2d->setName("envMap"); - unsigned int clamp = static_cast(textureEffect->clamp); - int wrapT = (clamp) & 0x1; - int wrapS = (clamp >> 1) & 0x1; + bool wrapT = textureEffect->clamp & 0x1; + bool wrapS = (textureEffect->clamp >> 1) & 0x1; texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); @@ -492,7 +491,7 @@ namespace NifOsg } osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) + std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool isAnimated, TextKeyMap* textKeys, osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; @@ -657,7 +656,7 @@ namespace NifOsg return node; } - void handleMeshControllers(const Nif::Node *nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) + void handleMeshControllers(const Nif::Node *nifNode, osg::Node* node, SceneUtil::CompositeStateSetUpdater* composite, const std::vector &boundTextures, int animflags) { for (Nif::ControllerPtr ctrl = nifNode->controller; !ctrl.empty(); ctrl = ctrl->next) { @@ -666,7 +665,7 @@ namespace NifOsg if (ctrl->recType == Nif::RC_NiUVController) { const Nif::NiUVController *niuvctrl = static_cast(ctrl.getPtr()); - const int uvSet = niuvctrl->uvSet; + const unsigned int uvSet = niuvctrl->uvSet; std::set texUnits; // UVController should work only for textures which use a given UV Set, usually 0. for (unsigned int i=0; i& vertices, const std::vector& normals, const std::vector>& uvlist, const std::vector& colors, const std::vector& boundTextures, const std::string& name) + void triCommonToGeometry(osg::Geometry *geometry, const std::vector& vertices, const std::vector& normals, const std::vector>& uvlist, const std::vector& colors, const std::vector& boundTextures, const std::string& name) { if (!vertices.empty()) geometry->setVertexArray(new osg::Vec3Array(vertices.size(), vertices.data())); @@ -1057,9 +1056,9 @@ namespace NifOsg geometry->setColorArray(new osg::Vec4Array(colors.size(), colors.data()), osg::Array::BIND_PER_VERTEX); int textureStage = 0; - for (const int uvSet : boundTextures) + for (const unsigned int uvSet : boundTextures) { - if (uvSet >= (int)uvlist.size()) + if (uvSet >= uvlist.size()) { Log(Debug::Verbose) << "Out of bounds UV set " << uvSet << " on shape \"" << name << "\" in " << mFilename; if (!uvlist.empty()) @@ -1072,7 +1071,7 @@ namespace NifOsg } } - void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void triShapeToGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { bool vertexColorsPresent = false; if (nifNode->recType == Nif::RC_NiTriShape) @@ -1119,7 +1118,7 @@ namespace NifOsg applyDrawableProperties(parentNode, drawableProps, composite, vertexColorsPresent, animflags, false); } - void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleTriShape(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); osg::ref_ptr drawable; @@ -1151,7 +1150,7 @@ namespace NifOsg parentNode->addChild(drawable); } - osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, osg::ref_ptr sourceGeometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + osg::ref_ptr handleMorphGeometry(const Nif::NiGeomMorpherController* morpher, osg::ref_ptr sourceGeometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { osg::ref_ptr morphGeom = new SceneUtil::MorphGeometry; morphGeom->setSourceGeometry(sourceGeometry); @@ -1167,7 +1166,7 @@ namespace NifOsg } void handleSkinnedTriShape(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, - const std::vector& boundTextures, int animflags) + const std::vector& boundTextures, int animflags) { assert(nifNode->recType == Nif::RC_NiTriShape || nifNode->recType == Nif::RC_NiTriStrips); osg::ref_ptr geometry (new osg::Geometry); @@ -1392,7 +1391,7 @@ namespace NifOsg return image; } - void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { if (!boundTextures.empty()) { @@ -1403,7 +1402,7 @@ namespace NifOsg } // If this loop is changed such that the base texture isn't guaranteed to end up in texture unit 0, the shadow casting shader will need to be updated accordingly. - for (int i=0; itextures.size(); ++i) { if (texprop->textures[i].inUse) { @@ -1451,14 +1450,13 @@ namespace NifOsg else texture2d = new osg::Texture2D; - unsigned int clamp = static_cast(tex.clamp); - int wrapT = (clamp) & 0x1; - int wrapS = (clamp >> 1) & 0x1; + bool wrapT = tex.clamp & 0x1; + bool wrapS = (tex.clamp >> 1) & 0x1; texture2d->setWrap(osg::Texture::WRAP_S, wrapS ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); texture2d->setWrap(osg::Texture::WRAP_T, wrapT ? osg::Texture::REPEAT : osg::Texture::CLAMP_TO_EDGE); - int texUnit = boundTextures.size(); + unsigned int texUnit = boundTextures.size(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); @@ -1547,7 +1545,7 @@ namespace NifOsg } void handleProperty(const Nif::Property *property, - osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) + osg::Node *node, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) { switch (property->recType) { From 7c4743fdd1534930181276f5d5f260a102685ed9 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 9 Oct 2019 17:08:32 +0300 Subject: [PATCH 09/79] Convert recordptr typedefs to using directives --- components/nif/recordptr.hpp | 48 ++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index fbd148a04..d165111b8 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -143,31 +143,31 @@ class NiAutoNormalParticlesData; class NiPalette; struct NiParticleModifier; -typedef RecordPtrT NodePtr; -typedef RecordPtrT ExtraPtr; -typedef RecordPtrT NiUVDataPtr; -typedef RecordPtrT NiPosDataPtr; -typedef RecordPtrT NiVisDataPtr; -typedef RecordPtrT ControllerPtr; -typedef RecordPtrT NamedPtr; -typedef RecordPtrT NiSkinDataPtr; -typedef RecordPtrT NiMorphDataPtr; -typedef RecordPtrT NiPixelDataPtr; -typedef RecordPtrT NiFloatDataPtr; -typedef RecordPtrT NiColorDataPtr; -typedef RecordPtrT NiKeyframeDataPtr; -typedef RecordPtrT NiTriShapeDataPtr; -typedef RecordPtrT NiTriStripsDataPtr; -typedef RecordPtrT NiSkinInstancePtr; -typedef RecordPtrT NiSourceTexturePtr; -typedef RecordPtrT NiRotatingParticlesDataPtr; -typedef RecordPtrT NiAutoNormalParticlesDataPtr; -typedef RecordPtrT NiPalettePtr; -typedef RecordPtrT NiParticleModifierPtr; +using NodePtr = RecordPtrT; +using ExtraPtr = RecordPtrT; +using NiUVDataPtr = RecordPtrT; +using NiPosDataPtr = RecordPtrT; +using NiVisDataPtr = RecordPtrT; +using ControllerPtr = RecordPtrT; +using NamedPtr = RecordPtrT; +using NiSkinDataPtr = RecordPtrT; +using NiMorphDataPtr = RecordPtrT; +using NiPixelDataPtr = RecordPtrT; +using NiFloatDataPtr = RecordPtrT; +using NiColorDataPtr = RecordPtrT; +using NiKeyframeDataPtr = RecordPtrT; +using NiTriShapeDataPtr = RecordPtrT; +using NiTriStripsDataPtr = RecordPtrT; +using NiSkinInstancePtr = RecordPtrT; +using NiSourceTexturePtr = RecordPtrT; +using NiRotatingParticlesDataPtr = RecordPtrT; +using NiAutoNormalParticlesDataPtr = RecordPtrT; +using NiPalettePtr = RecordPtrT; +using NiParticleModifierPtr = RecordPtrT; -typedef RecordListT NodeList; -typedef RecordListT PropertyList; -typedef RecordListT NiSourceTextureList; +using NodeList = RecordListT; +using PropertyList = RecordListT; +using NiSourceTextureList = RecordListT; } // Namespace #endif From 81832f8e17cf4d90cb52e3811c457d9d42d1d3c6 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Oct 2019 12:40:34 +0200 Subject: [PATCH 10/79] Reuse distance functions --- apps/openmw/mwmechanics/aipackage.cpp | 23 +---------------------- apps/openmw/mwmechanics/combat.cpp | 15 +++++++++------ apps/openmw/mwmechanics/combat.hpp | 3 +++ apps/openmw/mwmechanics/pathfinding.cpp | 8 ++++++++ apps/openmw/mwmechanics/pathfinding.hpp | 14 +++++++++++++- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/apps/openmw/mwmechanics/aipackage.cpp b/apps/openmw/mwmechanics/aipackage.cpp index 8336ca971..37f5f5bf7 100644 --- a/apps/openmw/mwmechanics/aipackage.cpp +++ b/apps/openmw/mwmechanics/aipackage.cpp @@ -22,27 +22,6 @@ #include -namespace MWMechanics -{ - static float distance(const osg::Vec2f& lhs, const osg::Vec2f& rhs) - { - return (lhs - rhs).length2(); - } - - static float distanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) - { - return distance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); - } - - static float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs, const MWWorld::Ptr& actor) - { - const auto world = MWBase::Environment::get().getWorld(); - if (world->isSwimming(actor) || world->isFlying(actor)) - return distance(lhs, rhs); - return distanceIgnoreZ(lhs, rhs); - } -} - MWMechanics::AiPackage::~AiPackage() {} MWMechanics::AiPackage::AiPackage() : @@ -352,7 +331,7 @@ bool MWMechanics::AiPackage::checkWayIsClearForActor(const osg::Vec3f& startPoin bool MWMechanics::AiPackage::doesPathNeedRecalc(const osg::Vec3f& newDest, const MWWorld::Ptr& actor) const { return mPathFinder.getPath().empty() - || distance(mPathFinder.getPath().back(), newDest, actor) > 10 + || getPathDistance(actor, mPathFinder.getPath().back(), newDest) > 10 || mPathFinder.getPathCell() != actor.getCell(); } diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9258c6b2d..27dc0b473 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -20,6 +20,7 @@ #include "spellcasting.hpp" #include "difficultyscaling.hpp" #include "actorutil.hpp" +#include "pathfinding.hpp" namespace { @@ -467,13 +468,8 @@ namespace MWMechanics { osg::Vec3f pos1 (actor1.getRefData().getPosition().asVec3()); osg::Vec3f pos2 (actor2.getRefData().getPosition().asVec3()); - if (canActorMoveByZAxis(actor2)) - { - pos1.z() = 0.f; - pos2.z() = 0.f; - } - float d = (pos1 - pos2).length(); + float d = getAggroDistance(actor2, pos1, pos2); static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( "iFightDistanceBase")->mValue.getInteger(); @@ -489,4 +485,11 @@ namespace MWMechanics return (magicEffects.get(ESM::MagicEffect::Invisibility).getMagnitude() > 0) || (magicEffects.get(ESM::MagicEffect::Chameleon).getMagnitude() > 75); } + + float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + if (canActorMoveByZAxis(actor)) + return distanceIgnoreZ(lhs, rhs); + return distance(lhs, rhs); + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index fd2717b19..3d4a1bd77 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -55,6 +55,9 @@ void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon, float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2); bool isTargetMagicallyHidden(const MWWorld::Ptr& target); + +float getAggroDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); + } #endif diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 900f97a67..97c29680c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -18,6 +18,7 @@ #include "pathgrid.hpp" #include "coordinateconverter.hpp" +#include "actorutil.hpp" namespace { @@ -84,6 +85,13 @@ namespace namespace MWMechanics { + float getPathDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + if (canActorMoveByZAxis(actor)) + return distance(lhs, rhs); + return distanceIgnoreZ(lhs, rhs); + } + bool checkWayIsClear(const osg::Vec3f& from, const osg::Vec3f& to, float offsetXY) { osg::Vec3f dir = to - from; diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index f762b6f18..b413810f4 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -13,17 +13,29 @@ namespace MWWorld { class CellStore; class ConstPtr; + class Ptr; } namespace MWMechanics { class PathgridGraph; - inline float distance(const osg::Vec3f& lhs, const osg::Vec3f& rhs) + template + inline float distance(const T& lhs, const T& rhs) { + static_assert(std::is_same::value + || std::is_same::value, + "T is not a position"); return (lhs - rhs).length(); } + inline float distanceIgnoreZ(const osg::Vec3f& lhs, const osg::Vec3f& rhs) + { + return distance(osg::Vec2f(lhs.x(), lhs.y()), osg::Vec2f(rhs.x(), rhs.y())); + } + + float getPathDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs); + inline float getZAngleToDir(const osg::Vec3f& dir) { return std::atan2(dir.x(), dir.y()); From 43b1b9dfa2b2f317230ae7f5d1ebf0e937497744 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sat, 19 Oct 2019 22:18:58 +0300 Subject: [PATCH 11/79] Weather-related fixes (incl. bug #4783) Simplify some calculations Fix Blizzard weather direction Fix sky direction during storm --- CHANGELOG.md | 1 + apps/openmw/mwrender/sky.cpp | 16 ++++++++++------ apps/openmw/mwworld/weather.cpp | 30 ++++++++++-------------------- apps/openmw/mwworld/weather.hpp | 1 - 4 files changed, 21 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8bf9f48..51e18a439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ Bug #4768: Fallback numerical value recovery chokes on invalid arguments Bug #4775: Slowfall effect resets player jumping flag Bug #4778: Interiors of Illusion puzzle in Sotha Sil Expanded mod is broken + Bug #4783: Blizzard behavior is incorrect Bug #4787: Sneaking makes 1st person walking/bobbing animation super-slow Bug #4797: Player sneaking and running stances are not accounted for when in air Bug #4800: Standing collisions are not updated immediately when an object is teleported without a cell change diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index b58b31906..3996f472c 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -225,7 +225,7 @@ protected: virtual void apply(osg::StateSet *stateset, osg::NodeVisitor *nv) { osg::TexMat* texMat = static_cast(stateset->getTextureAttribute(0, osg::StateAttribute::TEXMAT)); - texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, mAnimationTimer, 0.f))); + texMat->setMatrix(osg::Matrix::translate(osg::Vec3f(0, -mAnimationTimer, 0.f))); stateset->setTextureAttribute(0, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); stateset->setTextureAttribute(1, mTexture, osg::StateAttribute::ON|osg::StateAttribute::OVERRIDE); @@ -1108,7 +1108,7 @@ SkyManager::SkyManager(osg::Group* parentNode, Resource::SceneManager* sceneMana , mMonth(0) , mCloudAnimationTimer(0.f) , mRainTimer(0.f) - , mStormDirection(0,-1,0) + , mStormDirection(0,1,0) , mClouds() , mNextClouds() , mCloudBlendFactor(0.0f) @@ -1597,10 +1597,14 @@ void SkyManager::update(float duration) osg::Quat quat; quat.makeRotate(osg::Vec3f(0,1,0), mStormDirection); - if (mParticleNode) - mParticleNode->setAttitude(quat); - mCloudNode->setAttitude(quat); + if (mParticleNode) + { + // Morrowind deliberately rotates the blizzard mesh, so so should we. + if (mCurrentParticleEffect == "meshes\\blizzard.nif") + quat.makeRotate(osg::Vec3f(-1,0,0), mStormDirection); + mParticleNode->setAttitude(quat); + } } else mCloudNode->setAttitude(osg::Quat()); @@ -1636,7 +1640,7 @@ void SkyManager::updateRainParameters() { if (mRainShooter) { - float angle = -std::atan2(1, 50.f/mWindSpeed); + float angle = -std::atan(mWindSpeed/50.f); mRainShooter->setVelocity(osg::Vec3f(0, mRainSpeed*std::sin(angle), -mRainSpeed/std::cos(angle))); mRainShooter->setAngle(angle); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 79a305fee..2f054488b 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -661,17 +661,6 @@ void WeatherManager::playerTeleported(const std::string& playerRegion, bool isEx } } -osg::Vec3f WeatherManager::calculateStormDirection() -{ - osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3()); - playerPos.z() = 0; - osg::Vec3f redMountainPos (25000, 70000, 0); - osg::Vec3f stormDirection = (playerPos - redMountainPos); - stormDirection.normalize(); - - return stormDirection; -} - float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) { float targetSpeed = std::min(8.0f * mWeatherSettings[weatherId].mWindSpeed, 70.f); @@ -682,15 +671,7 @@ float WeatherManager::calculateWindSpeed(int weatherId, float currentSpeed) float updatedSpeed = (Misc::Rng::rollClosedProbability() - 0.5f) * multiplier * targetSpeed + currentSpeed; if (updatedSpeed > 0.5f * targetSpeed && updatedSpeed < 2.f * targetSpeed) - { currentSpeed = updatedSpeed; - } - - // Take in account direction to the Red Mountain, when needed - if (weatherId == 6 || weatherId == 7) - { - currentSpeed = (calculateStormDirection() * currentSpeed).length(); - } return currentSpeed; } @@ -750,7 +731,16 @@ void WeatherManager::update(float duration, bool paused, const TimeStamp& time, if (mIsStorm) { - mStormDirection = calculateStormDirection(); + osg::Vec3f stormDirection(0, 1, 0); + if (mResult.mParticleEffect == "meshes\\ashcloud.nif" || mResult.mParticleEffect == "meshes\\blightcloud.nif") + { + osg::Vec3f playerPos (MWMechanics::getPlayer().getRefData().getPosition().asVec3()); + playerPos.z() = 0; + osg::Vec3f redMountainPos (25000, 70000, 0); + stormDirection = (playerPos - redMountainPos); + stormDirection.normalize(); + } + mStormDirection = stormDirection; mRendering.getSkyManager()->setStormDirection(mStormDirection); } diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index c4f787739..696f7c688 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -370,7 +370,6 @@ namespace MWWorld bool updateWeatherRegion(const std::string& playerRegion); void updateWeatherTransitions(const float elapsedRealSeconds); void forceWeather(const int weatherID); - osg::Vec3f calculateStormDirection(); bool inTransition(); void addWeatherTransition(const int weatherID); From e4d0af6a6de7d30cf8cd687ab808f4d5e5f7e20d Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 19 Oct 2019 22:01:32 +0200 Subject: [PATCH 12/79] Use z coordinate for path distance when diff by z is greater then actor height --- apps/openmw/mwmechanics/pathfinding.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index 97c29680c..f7e7c277c 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -81,13 +81,20 @@ namespace const auto realHalfExtents = world->getHalfExtents(actor); return 2 * std::max(realHalfExtents.x(), realHalfExtents.y()); } + + float getHeight(const MWWorld::ConstPtr& actor) + { + const auto world = MWBase::Environment::get().getWorld(); + const auto halfExtents = world->getHalfExtents(actor); + return 2.0 * halfExtents.z(); + } } namespace MWMechanics { float getPathDistance(const MWWorld::Ptr& actor, const osg::Vec3f& lhs, const osg::Vec3f& rhs) { - if (canActorMoveByZAxis(actor)) + if (std::abs(lhs.z() - rhs.z()) > getHeight(actor) || canActorMoveByZAxis(actor)) return distance(lhs, rhs); return distanceIgnoreZ(lhs, rhs); } From fc7b4d73a8cb5ed2c1eb94ed3d22796eb269e768 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Sun, 20 Oct 2019 13:30:52 +0300 Subject: [PATCH 13/79] Remove on-strike enchantment support for ranged weapon types (bug #5190) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/combat.cpp | 6 ++---- apps/openmw/mwmechanics/enchanting.cpp | 17 ++++++++++------- apps/openmw/mwmechanics/enchanting.hpp | 1 + 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8bf9f48..c5795c208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -163,6 +163,7 @@ Bug #5177: Editor: Unexplored map tiles get corrupted after a file with terrain is saved Bug #5182: OnPCEquip doesn't trigger on skipped beast race attempts to equip something not equippable by beasts Bug #5186: Equipped item enchantments don't affect creatures + Bug #5190: On-strike enchantments can be applied to and used with non-projectile ranged weapons Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9258c6b2d..a6d542935 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -245,10 +245,8 @@ namespace MWMechanics reduceWeaponCondition(damage, validVictim, weapon, attacker); - // Apply "On hit" effect of the weapon & projectile - bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, weapon, hitPosition, true); - if (weapon != projectile) - appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); + // Apply "On hit" effect of the projectile + bool appliedEnchantment = applyOnStrikeEnchantment(attacker, victim, projectile, hitPosition, true); if (validVictim) { diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index 20ba99825..e9ae64337 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -14,6 +14,7 @@ #include "creaturestats.hpp" #include "spellcasting.hpp" #include "actorutil.hpp" +#include "weapontype.hpp" namespace MWMechanics { @@ -25,13 +26,13 @@ namespace MWMechanics void Enchanting::setOldItem(const MWWorld::Ptr& oldItem) { mOldItemPtr=oldItem; + mWeaponType = -1; + mObjectType.clear(); if(!itemEmpty()) { mObjectType = mOldItemPtr.getTypeName(); - } - else - { - mObjectType=""; + if (mObjectType == typeid(ESM::Weapon).name()) + mWeaponType = mOldItemPtr.get()->mBase->mData.mType; } } @@ -119,7 +120,7 @@ namespace MWMechanics return; } } - else if(mObjectType == typeid(ESM::Weapon).name()) + else if (mWeaponType != -1) { // Weapon switch(mCastStyle) { @@ -129,11 +130,13 @@ namespace MWMechanics case ESM::Enchantment::WhenUsed: if (powerfulSoul) mCastStyle = ESM::Enchantment::ConstantEffect; - else + else if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) mCastStyle = ESM::Enchantment::WhenStrikes; return; default: // takes care of Constant effect too - mCastStyle = ESM::Enchantment::WhenStrikes; + mCastStyle = ESM::Enchantment::WhenUsed; + if (getWeaponType(mWeaponType)->mWeaponClass != ESM::WeaponType::Ranged) + mCastStyle = ESM::Enchantment::WhenStrikes; return; } } diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 6f72d96a8..6996fa24d 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -23,6 +23,7 @@ namespace MWMechanics std::string mNewItemName; std::string mObjectType; + int mWeaponType; public: Enchanting(); From 224144f2b833dbd92e6d760fa2b8333e177df727 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Oct 2019 17:20:13 +0200 Subject: [PATCH 14/79] Add ignored recastnavigation files to .gitigore --- .gitignore | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.gitignore b/.gitignore index 0ee4a6399..e95139b8f 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,13 @@ moc_*.cxx *.[ao] *.so venv/ + +## recastnavigation unused files +extern/recastnavigation/.travis.yml +extern/recastnavigation/CONTRIBUTING.md +extern/recastnavigation/Docs/ +extern/recastnavigation/Doxyfile +extern/recastnavigation/README.md +extern/recastnavigation/RecastDemo/ +extern/recastnavigation/Tests/ +extern/recastnavigation/appveyor.yml From acb939a81b4d9bcec609b0824177c5501ccd065b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Oct 2019 17:22:38 +0200 Subject: [PATCH 15/79] Update Recastnavigation to c40188c796f089f89a42e0b939d934178dbcfc5c --- extern/recastnavigation/.gitignore | 13 ++ extern/recastnavigation/.id | 2 +- .../DebugUtils/Source/DetourDebugDraw.cpp | 4 +- .../Detour/Include/DetourNavMesh.h | 21 ++- .../Detour/Include/DetourNavMeshQuery.h | 2 - .../Detour/Source/DetourCommon.cpp | 18 +- .../Detour/Source/DetourNavMesh.cpp | 160 ++++++++++++------ .../Detour/Source/DetourNavMeshQuery.cpp | 129 ++------------ .../Recast/Source/RecastMeshDetail.cpp | 3 +- 9 files changed, 177 insertions(+), 175 deletions(-) diff --git a/extern/recastnavigation/.gitignore b/extern/recastnavigation/.gitignore index 7c12d58f0..98f17e4b7 100644 --- a/extern/recastnavigation/.gitignore +++ b/extern/recastnavigation/.gitignore @@ -9,6 +9,16 @@ *.so *.idb +## Linux exes have no extension +RecastDemo/Bin/RecastDemo +RecastDemo/Bin/Tests + +# Build directory +RecastDemo/Build + +# Ignore meshes +RecastDemo/Bin/Meshes/* + ## Logs and databases # *.log *.sql @@ -28,6 +38,9 @@ Thumbs.db ## xcode specific *xcuserdata* +## SDL contrib +RecastDemo/Contrib/SDL/* + ## Generated doc files Docs/html diff --git a/extern/recastnavigation/.id b/extern/recastnavigation/.id index 4ff8c7be6..f15ce513c 100644 --- a/extern/recastnavigation/.id +++ b/extern/recastnavigation/.id @@ -1 +1 @@ -3087e805b02d5eb8fff7851234fa2b3f71290eba +c40188c796f089f89a42e0b939d934178dbcfc5c diff --git a/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp b/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp index dd4bad3fd..4ca0581c7 100644 --- a/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp +++ b/extern/recastnavigation/DebugUtils/Source/DetourDebugDraw.cpp @@ -101,7 +101,9 @@ static void drawPolyBoundaries(duDebugDraw* dd, const dtMeshTile* tile, } for (int m = 0, n = 2; m < 3; n=m++) { - if (((t[3] >> (n*2)) & 0x3) == 0) continue; // Skip inner detail edges. + if ((dtGetDetailTriEdgeFlags(t[3], n) & DT_DETAIL_EDGE_BOUNDARY) == 0) + continue; + if (distancePtLine2d(tv[n],v0,v1) < thr && distancePtLine2d(tv[m],v0,v1) < thr) { diff --git a/extern/recastnavigation/Detour/Include/DetourNavMesh.h b/extern/recastnavigation/Detour/Include/DetourNavMesh.h index 02ee5e78c..98293c49d 100644 --- a/extern/recastnavigation/Detour/Include/DetourNavMesh.h +++ b/extern/recastnavigation/Detour/Include/DetourNavMesh.h @@ -130,6 +130,11 @@ enum dtRaycastOptions DT_RAYCAST_USE_COSTS = 0x01, ///< Raycast should calculate movement cost along the ray and fill RaycastHit::cost }; +enum dtDetailTriEdgeFlags +{ + DT_DETAIL_EDGE_BOUNDARY = 0x01, ///< Detail triangle edge is part of the poly boundary +}; + /// Limit raycasting during any angle pahfinding /// The limit is given as a multiple of the character radius @@ -287,7 +292,8 @@ struct dtMeshTile /// The detail mesh's unique vertices. [(x, y, z) * dtMeshHeader::detailVertCount] float* detailVerts; - /// The detail mesh's triangles. [(vertA, vertB, vertC) * dtMeshHeader::detailTriCount] + /// The detail mesh's triangles. [(vertA, vertB, vertC, triFlags) * dtMeshHeader::detailTriCount]. + /// See dtDetailTriEdgeFlags and dtGetDetailTriEdgeFlags. unsigned char* detailTris; /// The tile bounding volume nodes. [Size: dtMeshHeader::bvNodeCount] @@ -305,6 +311,15 @@ private: dtMeshTile& operator=(const dtMeshTile&); }; +/// Get flags for edge in detail triangle. +/// @param triFlags[in] The flags for the triangle (last component of detail vertices above). +/// @param edgeIndex[in] The index of the first vertex of the edge. For instance, if 0, +/// returns flags for edge AB. +inline int dtGetDetailTriEdgeFlags(unsigned char triFlags, int edgeIndex) +{ + return (triFlags >> (edgeIndex * 2)) & 0x3; +} + /// Configuration parameters used to define multi-tile navigation meshes. /// The values are used to allocate space during the initialization of a navigation mesh. /// @see dtNavMesh::init() @@ -636,6 +651,8 @@ private: /// Find nearest polygon within a tile. dtPolyRef findNearestPolyInTile(const dtMeshTile* tile, const float* center, const float* halfExtents, float* nearestPt) const; + /// Returns whether position is over the poly and the height at the position if so. + bool getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const; /// Returns closest point on polygon. void closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const; @@ -655,6 +672,8 @@ private: unsigned int m_tileBits; ///< Number of tile bits in the tile ID. unsigned int m_polyBits; ///< Number of poly bits in the tile ID. #endif + + friend class dtNavMeshQuery; }; /// Allocates a navigation mesh object using the Detour allocator. diff --git a/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h b/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h index 1c23e4857..0b40371be 100644 --- a/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h +++ b/extern/recastnavigation/Detour/Include/DetourNavMeshQuery.h @@ -119,8 +119,6 @@ public: }; - - /// Provides information about raycast hit /// filled by dtNavMeshQuery::raycast /// @ingroup detour diff --git a/extern/recastnavigation/Detour/Source/DetourCommon.cpp b/extern/recastnavigation/Detour/Source/DetourCommon.cpp index 3886f14b0..b89d7512c 100644 --- a/extern/recastnavigation/Detour/Source/DetourCommon.cpp +++ b/extern/recastnavigation/Detour/Source/DetourCommon.cpp @@ -203,14 +203,18 @@ void dtCalcPolyCenter(float* tc, const unsigned short* idx, int nidx, const floa bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b, const float* c, float& h) { + const float EPS = 1e-6f; float v0[3], v1[3], v2[3]; - dtVsub(v0, c,a); - dtVsub(v1, b,a); - dtVsub(v2, p,a); + dtVsub(v0, c, a); + dtVsub(v1, b, a); + dtVsub(v2, p, a); // Compute scaled barycentric coordinates float denom = v0[0] * v1[2] - v0[2] * v1[0]; + if (fabsf(denom) < EPS) + return false; + float u = v1[2] * v2[0] - v1[0] * v2[2]; float v = v0[0] * v2[2] - v0[2] * v2[0]; @@ -220,13 +224,9 @@ bool dtClosestHeightPointTriangle(const float* p, const float* a, const float* b v = -v; } - // The (sloppy) epsilon is needed to allow to get height of points which - // are interpolated along the edges of the triangles. - float epsilon = - 1e-4f * denom; - // If point lies inside the triangle, return interpolated ycoord. - if (u >= epsilon && v >= epsilon && (u+v) <= denom - epsilon) { - h = a[1] + (v0[1]*u + v1[1]*v) / denom; + if (u >= 0.0f && v >= 0.0f && (u + v) <= denom) { + h = a[1] + (v0[1] * u + v1[1] * v) / denom; return true; } return false; diff --git a/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp b/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp index 17df26d8c..a655d1d89 100644 --- a/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp +++ b/extern/recastnavigation/Detour/Source/DetourNavMesh.cpp @@ -616,63 +616,84 @@ void dtNavMesh::baseOffMeshLinks(dtMeshTile* tile) } } -void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const +namespace { - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - getTileAndPolyByRefUnsafe(ref, &tile, &poly); - - // Off-mesh connections don't have detail polygons. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + template + void closestPointOnDetailEdges(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* closest) { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist(pos, v0); - const float d1 = dtVdist(pos, v1); - const float u = d0 / (d0+d1); - dtVlerp(closest, v0, v1, u); - if (posOverPoly) - *posOverPoly = false; - return; + const unsigned int ip = (unsigned int)(poly - tile->polys); + const dtPolyDetail* pd = &tile->detailMeshes[ip]; + + float dmin = FLT_MAX; + float tmin = 0; + const float* pmin = 0; + const float* pmax = 0; + + for (int i = 0; i < pd->triCount; i++) + { + const unsigned char* tris = &tile->detailTris[(pd->triBase + i) * 4]; + const int ANY_BOUNDARY_EDGE = + (DT_DETAIL_EDGE_BOUNDARY << 0) | + (DT_DETAIL_EDGE_BOUNDARY << 2) | + (DT_DETAIL_EDGE_BOUNDARY << 4); + if (onlyBoundary && (tris[3] & ANY_BOUNDARY_EDGE) == 0) + continue; + + const float* v[3]; + for (int j = 0; j < 3; ++j) + { + if (tris[j] < poly->vertCount) + v[j] = &tile->verts[poly->verts[tris[j]] * 3]; + else + v[j] = &tile->detailVerts[(pd->vertBase + (tris[j] - poly->vertCount)) * 3]; + } + + for (int k = 0, j = 2; k < 3; j = k++) + { + if ((dtGetDetailTriEdgeFlags(tris[3], j) & DT_DETAIL_EDGE_BOUNDARY) == 0 && + (onlyBoundary || tris[j] < tris[k])) + { + // Only looking at boundary edges and this is internal, or + // this is an inner edge that we will see again or have already seen. + continue; + } + + float t; + float d = dtDistancePtSegSqr2D(pos, v[j], v[k], t); + if (d < dmin) + { + dmin = d; + tmin = t; + pmin = v[j]; + pmax = v[k]; + } + } + } + + dtVlerp(closest, pmin, pmax, tmin); } - +} + +bool dtNavMesh::getPolyHeight(const dtMeshTile* tile, const dtPoly* poly, const float* pos, float* height) const +{ + // Off-mesh connections do not have detail polys and getting height + // over them does not make sense. + if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + return false; + const unsigned int ip = (unsigned int)(poly - tile->polys); const dtPolyDetail* pd = &tile->detailMeshes[ip]; - // Clamp point to be inside the polygon. float verts[DT_VERTS_PER_POLYGON*3]; - float edged[DT_VERTS_PER_POLYGON]; - float edget[DT_VERTS_PER_POLYGON]; const int nv = poly->vertCount; for (int i = 0; i < nv; ++i) dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); - dtVcopy(closest, pos); - if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget)) - { - // Point is outside the polygon, dtClamp to nearest edge. - float dmin = edged[0]; - int imin = 0; - for (int i = 1; i < nv; ++i) - { - if (edged[i] < dmin) - { - dmin = edged[i]; - imin = i; - } - } - const float* va = &verts[imin*3]; - const float* vb = &verts[((imin+1)%nv)*3]; - dtVlerp(closest, va, vb, edget[imin]); - - if (posOverPoly) - *posOverPoly = false; - } - else - { - if (posOverPoly) - *posOverPoly = true; - } + if (!dtPointInPolygon(pos, verts, nv)) + return false; + + if (!height) + return true; // Find height at the location. for (int j = 0; j < pd->triCount; ++j) @@ -687,12 +708,53 @@ void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* close v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; } float h; - if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h)) + if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) { - closest[1] = h; - break; + *height = h; + return true; } } + + // If all triangle checks failed above (can happen with degenerate triangles + // or larger floating point values) the point is on an edge, so just select + // closest. This should almost never happen so the extra iteration here is + // ok. + float closest[3]; + closestPointOnDetailEdges(tile, poly, pos, closest); + *height = closest[1]; + return true; +} + +void dtNavMesh::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const +{ + const dtMeshTile* tile = 0; + const dtPoly* poly = 0; + getTileAndPolyByRefUnsafe(ref, &tile, &poly); + + dtVcopy(closest, pos); + if (getPolyHeight(tile, poly, pos, &closest[1])) + { + if (posOverPoly) + *posOverPoly = true; + return; + } + + if (posOverPoly) + *posOverPoly = false; + + // Off-mesh connections don't have detail polygons. + if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) + { + const float* v0 = &tile->verts[poly->verts[0]*3]; + const float* v1 = &tile->verts[poly->verts[1]*3]; + float t; + dtDistancePtSegSqr2D(pos, v0, v1, t); + dtVlerp(closest, v0, v1, t); + return; + } + + // Outside poly that is not an offmesh connection. + closestPointOnDetailEdges(tile, poly, pos, closest); } dtPolyRef dtNavMesh::findNearestPolyInTile(const dtMeshTile* tile, diff --git a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp b/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp index c5ef385f9..839ee1e81 100644 --- a/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp +++ b/extern/recastnavigation/Detour/Source/DetourNavMeshQuery.cpp @@ -514,88 +514,14 @@ dtStatus dtNavMeshQuery::findRandomPointAroundCircle(dtPolyRef startRef, const f dtStatus dtNavMeshQuery::closestPointOnPoly(dtPolyRef ref, const float* pos, float* closest, bool* posOverPoly) const { dtAssert(m_nav); - const dtMeshTile* tile = 0; - const dtPoly* poly = 0; - if (dtStatusFailed(m_nav->getTileAndPolyByRef(ref, &tile, &poly))) + if (!m_nav->isValidPolyRef(ref) || + !pos || !dtVisfinite(pos) || + !closest) + { return DT_FAILURE | DT_INVALID_PARAM; - if (!tile) - return DT_FAILURE | DT_INVALID_PARAM; - - if (!pos || !dtVisfinite(pos) || !closest) - return DT_FAILURE | DT_INVALID_PARAM; - - // Off-mesh connections don't have detail polygons. - if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) - { - const float* v0 = &tile->verts[poly->verts[0]*3]; - const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist(pos, v0); - const float d1 = dtVdist(pos, v1); - const float u = d0 / (d0+d1); - dtVlerp(closest, v0, v1, u); - if (posOverPoly) - *posOverPoly = false; - return DT_SUCCESS; } - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - - // Clamp point to be inside the polygon. - float verts[DT_VERTS_PER_POLYGON*3]; - float edged[DT_VERTS_PER_POLYGON]; - float edget[DT_VERTS_PER_POLYGON]; - const int nv = poly->vertCount; - for (int i = 0; i < nv; ++i) - dtVcopy(&verts[i*3], &tile->verts[poly->verts[i]*3]); - - dtVcopy(closest, pos); - if (!dtDistancePtPolyEdgesSqr(pos, verts, nv, edged, edget)) - { - // Point is outside the polygon, dtClamp to nearest edge. - float dmin = edged[0]; - int imin = 0; - for (int i = 1; i < nv; ++i) - { - if (edged[i] < dmin) - { - dmin = edged[i]; - imin = i; - } - } - const float* va = &verts[imin*3]; - const float* vb = &verts[((imin+1)%nv)*3]; - dtVlerp(closest, va, vb, edget[imin]); - - if (posOverPoly) - *posOverPoly = false; - } - else - { - if (posOverPoly) - *posOverPoly = true; - } - - // Find height at the location. - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - float h; - if (dtClosestHeightPointTriangle(closest, v[0], v[1], v[2], h)) - { - closest[1] = h; - break; - } - } - + m_nav->closestPointOnPoly(ref, pos, closest, posOverPoly); return DT_SUCCESS; } @@ -662,7 +588,7 @@ dtStatus dtNavMeshQuery::closestPointOnPolyBoundary(dtPolyRef ref, const float* /// @par /// -/// Will return #DT_FAILURE if the provided position is outside the xz-bounds +/// Will return #DT_FAILURE | DT_INVALID_PARAM if the provided position is outside the xz-bounds /// of the polygon. /// dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* height) const @@ -676,44 +602,25 @@ dtStatus dtNavMeshQuery::getPolyHeight(dtPolyRef ref, const float* pos, float* h if (!pos || !dtVisfinite2D(pos)) return DT_FAILURE | DT_INVALID_PARAM; - + + // We used to return success for offmesh connections, but the + // getPolyHeight in DetourNavMesh does not do this, so special + // case it here. if (poly->getType() == DT_POLYTYPE_OFFMESH_CONNECTION) { const float* v0 = &tile->verts[poly->verts[0]*3]; const float* v1 = &tile->verts[poly->verts[1]*3]; - const float d0 = dtVdist2D(pos, v0); - const float d1 = dtVdist2D(pos, v1); - const float u = d0 / (d0+d1); + float t; + dtDistancePtSegSqr2D(pos, v0, v1, t); if (height) - *height = v0[1] + (v1[1] - v0[1]) * u; + *height = v0[1] + (v1[1] - v0[1])*t; + return DT_SUCCESS; } - else - { - const unsigned int ip = (unsigned int)(poly - tile->polys); - const dtPolyDetail* pd = &tile->detailMeshes[ip]; - for (int j = 0; j < pd->triCount; ++j) - { - const unsigned char* t = &tile->detailTris[(pd->triBase+j)*4]; - const float* v[3]; - for (int k = 0; k < 3; ++k) - { - if (t[k] < poly->vertCount) - v[k] = &tile->verts[poly->verts[t[k]]*3]; - else - v[k] = &tile->detailVerts[(pd->vertBase+(t[k]-poly->vertCount))*3]; - } - float h; - if (dtClosestHeightPointTriangle(pos, v[0], v[1], v[2], h)) - { - if (height) - *height = h; - return DT_SUCCESS; - } - } - } - - return DT_FAILURE | DT_INVALID_PARAM; + + return m_nav->getPolyHeight(tile, poly, pos, height) + ? DT_SUCCESS + : DT_FAILURE | DT_INVALID_PARAM; } class dtFindNearestPolyQuery : public dtPolyQuery diff --git a/extern/recastnavigation/Recast/Source/RecastMeshDetail.cpp b/extern/recastnavigation/Recast/Source/RecastMeshDetail.cpp index 68ab726aa..9a423cab8 100644 --- a/extern/recastnavigation/Recast/Source/RecastMeshDetail.cpp +++ b/extern/recastnavigation/Recast/Source/RecastMeshDetail.cpp @@ -1141,7 +1141,8 @@ static void getHeightData(rcContext* ctx, const rcCompactHeightfield& chf, static unsigned char getEdgeFlags(const float* va, const float* vb, const float* vpoly, const int npoly) { - // Return true if edge (va,vb) is part of the polygon. + // The flag returned by this function matches dtDetailTriEdgeFlags in Detour. + // Figure out if edge (va,vb) is part of the polygon boundary. static const float thrSqr = rcSqr(0.001f); for (int i = 0, j = npoly-1; i < npoly; j=i++) { From 07660b56052bcb91257b9ce1ad4e6c3e16acd898 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 20 Oct 2019 18:59:14 +0200 Subject: [PATCH 16/79] Fix detournavigator tests --- .../detournavigator/navigator.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/apps/openmw_test_suite/detournavigator/navigator.cpp b/apps/openmw_test_suite/detournavigator/navigator.cpp index 5f5433b05..516f2c60f 100644 --- a/apps/openmw_test_suite/detournavigator/navigator.cpp +++ b/apps/openmw_test_suite/detournavigator/navigator.cpp @@ -123,14 +123,14 @@ namespace osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), - osg::Vec3f(215, -215, 1.877177715301513671875), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010501861572265625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), + osg::Vec3f(215, -215, 1.87718021869659423828125), })) << mPath; } @@ -173,14 +173,14 @@ namespace osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), - osg::Vec3f(215, -215, 1.877177715301513671875), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010501861572265625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), + osg::Vec3f(215, -215, 1.87718021869659423828125), })) << mPath; mNavigator->addObject(ObjectId(&compoundShape), compoundShape, btTransform::getIdentity()); @@ -292,14 +292,14 @@ namespace osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), - osg::Vec3f(215, -215, 1.877177715301513671875), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010501861572265625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), + osg::Vec3f(215, -215, 1.87718021869659423828125), })) << mPath; } @@ -645,14 +645,14 @@ namespace osg::Vec3f(25.41626739501953125, -25.41626739501953125, -67.9694671630859375), osg::Vec3f(45.450958251953125, -45.450958251953125, -60.5882568359375), osg::Vec3f(65.48564910888671875, -65.48564910888671875, -53.20705413818359375), - osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.82585906982421875), + osg::Vec3f(85.5203399658203125, -85.5203399658203125, -45.8258514404296875), osg::Vec3f(105.55503082275390625, -105.55503082275390625, -38.44464874267578125), osg::Vec3f(125.5897216796875, -125.5897216796875, -31.063449859619140625), osg::Vec3f(145.6244049072265625, -145.6244049072265625, -23.6822509765625), - osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010540008544921875), - osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985321044921875), - osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386486053466796875), - osg::Vec3f(215, -215, 1.877177715301513671875), + osg::Vec3f(165.659088134765625, -165.659088134765625, -16.3010501861572265625), + osg::Vec3f(185.6937713623046875, -185.6937713623046875, -8.91985416412353515625), + osg::Vec3f(205.7284698486328125, -205.7284698486328125, -1.5386505126953125), + osg::Vec3f(215, -215, 1.87718021869659423828125), })) << mPath; } } From 045ceeac117680107f37e3f9db52638cd2b097a1 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 6 Oct 2019 13:39:27 +0200 Subject: [PATCH 17/79] Replace foreach macro by for-loop --- apps/launcher/datafilespage.cpp | 12 +++--- apps/launcher/maindialog.cpp | 9 ++-- apps/launcher/settingspage.cpp | 3 +- apps/opencs/editor.cpp | 4 +- apps/opencs/view/doc/filedialog.cpp | 2 +- apps/opencs/view/doc/view.cpp | 6 +-- apps/opencs/view/world/regionmap.cpp | 4 +- apps/wizard/componentselectionpage.cpp | 3 +- apps/wizard/existinginstallationpage.cpp | 2 +- apps/wizard/inisettings.cpp | 3 +- apps/wizard/mainwizard.cpp | 6 ++- apps/wizard/unshield/unshieldworker.cpp | 42 ++++++++++++------- components/config/gamesettings.cpp | 5 ++- components/config/launchersettings.cpp | 7 ++-- .../contentselector/model/contentmodel.cpp | 24 +++++------ .../contentselector/view/contentselector.cpp | 8 ++-- 16 files changed, 83 insertions(+), 57 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index 8554b620b..a1603b4d4 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -95,7 +95,7 @@ bool Launcher::DataFilesPage::loadSettings() qDebug() << "The current profile is: " << currentProfile; - foreach (const QString &item, profiles) + for (const QString &item : profiles) addProfile (item, false); // Hack: also add the current profile @@ -114,7 +114,7 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) if (!mDataLocal.isEmpty()) paths.insert(0, mDataLocal); - foreach(const QString &path, paths) + for (const QString &path : paths) mSelector->addFiles(path); PathIterator pathIterator(paths); @@ -127,7 +127,7 @@ QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, QStringList files = mLauncherSettings.getContentListFiles(profileName); QStringList filepaths; - foreach(const QString& file, files) + for (const QString& file : files) { QString filepath = pathIterator.findFirstPath(file); @@ -152,7 +152,8 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); QStringList fileNames; - foreach(const ContentSelectorModel::EsmFile *item, items) { + for (const ContentSelectorModel::EsmFile *item : items) + { fileNames.append(item->fileName()); } mLauncherSettings.setContentList(profileName, fileNames); @@ -164,7 +165,8 @@ QStringList Launcher::DataFilesPage::selectedFilePaths() //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); QStringList filePaths; - foreach(const ContentSelectorModel::EsmFile *item, items) { + for (const ContentSelectorModel::EsmFile *item : items) + { filePaths.append(item->filePath()); } return filePaths; diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 637017ce8..6a1711007 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -280,7 +280,8 @@ bool Launcher::MainDialog::setupLauncherSettings() paths.append(QString(Config::LauncherSettings::sLauncherConfigFileName)); paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); - foreach (const QString &path, paths) { + for (const QString &path : paths) + { qDebug() << "Loading config file:" << path.toUtf8().constData(); QFile file(path); if (file.exists()) { @@ -338,7 +339,8 @@ bool Launcher::MainDialog::setupGameSettings() paths.append(localPath + QString("openmw.cfg")); paths.append(userPath + QString("openmw.cfg")); - foreach (const QString &path2, paths) { + for (const QString &path2 : paths) + { qDebug() << "Loading config file:" << path2.toUtf8().constData(); file.setFileName(path2); @@ -366,7 +368,8 @@ bool Launcher::MainDialog::setupGameData() QStringList dataDirs; // Check if the paths actually contain data files - foreach (const QString path3, mGameSettings.getDataDirs()) { + for (const QString& path3 : mGameSettings.getDataDirs()) + { QDir dir(path3); QStringList filters; filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 843b51391..d112916dc 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -61,7 +61,8 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, // Detect Morrowind configuration files QStringList iniPaths; - foreach (const QString &path, mGameSettings.getDataDirs()) { + for (const QString &path : mGameSettings.getDataDirs()) + { QDir dir(path); dir.setPath(dir.canonicalPath()); // Resolve symlinks diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 0ce031548..0c3d006c4 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -228,7 +228,7 @@ void CS::Editor::openFiles (const boost::filesystem::path &savePath, const std:: if(discoveredFiles.empty()) { - foreach(const QString &path, mFileDialog.selectedFilePaths()) + for (const QString &path : mFileDialog.selectedFilePaths()) files.push_back(path.toUtf8().constData()); } else @@ -245,7 +245,7 @@ void CS::Editor::createNewFile (const boost::filesystem::path &savePath) { std::vector files; - foreach (const QString &path, mFileDialog.selectedFilePaths()) { + for (const QString &path : mFileDialog.selectedFilePaths()) { files.push_back(path.toUtf8().constData()); } diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 7693276c6..7a3fe398f 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -47,7 +47,7 @@ QStringList CSVDoc::FileDialog::selectedFilePaths() { QStringList filePaths; - foreach (ContentSelectorModel::EsmFile *file, mSelector->selectedFiles() ) + for (ContentSelectorModel::EsmFile *file : mSelector->selectedFiles() ) filePaths.append(file->filePath()); return filePaths; diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 343495518..529f21165 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -404,7 +404,7 @@ void CSVDoc::View::updateSubViewIndices(SubView *view) updateTitle(); - foreach (SubView *subView, mSubViews) + for (SubView *subView : mSubViews) { if (!subView->isFloating()) { @@ -546,7 +546,7 @@ void CSVDoc::View::addSubView (const CSMWorld::UniversalId& id, const std::strin // User setting to reuse sub views (on a per top level view basis) if (windows["reuse"].isTrue()) { - foreach(SubView *sb, mSubViews) + for (SubView *sb : mSubViews) { bool isSubViewReferenceable = sb->getUniversalId().getType() == CSMWorld::UniversalId::Type_Referenceable; @@ -975,7 +975,7 @@ void CSVDoc::View::resizeViewHeight (int height) void CSVDoc::View::toggleShowStatusBar (bool show) { - foreach (QObject *view, mSubViewWindow.children()) + for (QObject *view : mSubViewWindow.children()) { if (CSVDoc::SubView *subView = dynamic_cast (view)) subView->setStatusBar (show); diff --git a/apps/opencs/view/world/regionmap.cpp b/apps/opencs/view/world/regionmap.cpp index 31e5d94ca..af91464b6 100644 --- a/apps/opencs/view/world/regionmap.cpp +++ b/apps/opencs/view/world/regionmap.cpp @@ -343,7 +343,7 @@ std::vector< CSMWorld::UniversalId > CSVWorld::RegionMap::getDraggedRecords() co { QModelIndexList selected(getSelectedCells(true, false)); std::vector ids; - foreach (QModelIndex it, selected) + for (const QModelIndex& it : selected) { ids.push_back( CSMWorld::UniversalId( @@ -351,7 +351,7 @@ std::vector< CSMWorld::UniversalId > CSVWorld::RegionMap::getDraggedRecords() co model()->data(it, CSMWorld::RegionMap::Role_CellId).toString().toUtf8().constData())); } selected = getSelectedCells(false, true); - foreach (QModelIndex it, selected) + for (const QModelIndex& it : selected) { ids.push_back( CSMWorld::UniversalId( diff --git a/apps/wizard/componentselectionpage.cpp b/apps/wizard/componentselectionpage.cpp index d99884966..b5a9eaab9 100644 --- a/apps/wizard/componentselectionpage.cpp +++ b/apps/wizard/componentselectionpage.cpp @@ -139,7 +139,8 @@ bool Wizard::ComponentSelectionPage::validatePage() mWizard->mInstallations[path].hasBloodmoon = false; QList items = componentsList->findItems(QLatin1String("Bloodmoon"), Qt::MatchStartsWith); - foreach (QListWidgetItem *item, items) { + for (QListWidgetItem *item : items) + { item->setText(QLatin1String("Bloodmoon")); item->setCheckState(Qt::Checked); } diff --git a/apps/wizard/existinginstallationpage.cpp b/apps/wizard/existinginstallationpage.cpp index 4c05f5500..2434f42cf 100644 --- a/apps/wizard/existinginstallationpage.cpp +++ b/apps/wizard/existinginstallationpage.cpp @@ -31,7 +31,7 @@ void Wizard::ExistingInstallationPage::initializePage() // Hide the default item if there are installations to choose from installationsList->item(0)->setHidden(!paths.isEmpty()); - foreach (const QString &path, paths) + for (const QString &path : paths) { if (installationsList->findItems(path, Qt::MatchExactly).isEmpty()) { diff --git a/apps/wizard/inisettings.cpp b/apps/wizard/inisettings.cpp index 2367f5c4d..4aacbaec9 100644 --- a/apps/wizard/inisettings.cpp +++ b/apps/wizard/inisettings.cpp @@ -21,7 +21,8 @@ QStringList Wizard::IniSettings::findKeys(const QString &text) { QStringList result; - foreach (const QString &key, mSettings.keys()) { + for (const QString &key : mSettings.keys()) + { if (key.startsWith(text)) result << key; diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index 57d080cf8..1353173f4 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -162,7 +162,8 @@ void Wizard::MainWizard::setupGameSettings() paths.append(QLatin1String("openmw.cfg")); paths.append(globalPath + QLatin1String("openmw.cfg")); - foreach (const QString &path2, paths) { + for (const QString &path2 : paths) + { qDebug() << "Loading config file:" << path2.toUtf8().constData(); file.setFileName(path2); @@ -222,7 +223,8 @@ void Wizard::MainWizard::setupLauncherSettings() void Wizard::MainWizard::setupInstallations() { // Check if the paths actually contain a Morrowind installation - foreach (const QString path, mGameSettings.getDataDirs()) { + for (const QString& path : mGameSettings.getDataDirs()) + { if (findFiles(QLatin1String("Morrowind"), path)) addInstallation(path); diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index a997c9324..96e959983 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -217,7 +217,8 @@ bool Wizard::UnshieldWorker::removeDirectory(const QString &dirName) QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); - foreach(QFileInfo info, list) { + for (const QFileInfo& info : list) + { if (info.isDir()) { result = removeDirectory(info.absoluteFilePath()); } else { @@ -279,7 +280,8 @@ bool Wizard::UnshieldWorker::copyDirectory(const QString &source, const QString QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); - foreach (const QFileInfo &info, list) { + for (const QFileInfo &info : list) + { QString relativePath(info.absoluteFilePath()); relativePath.remove(source); @@ -315,7 +317,8 @@ bool Wizard::UnshieldWorker::installFiles(const QString &fileName, const QString QStringList files(findFiles(fileName, path, flags)); - foreach (const QString &file, files) { + for (const QString &file : files) + { QFileInfo info(file); emit textChanged(tr("Installing: %1").arg(info.fileName())); @@ -339,7 +342,8 @@ bool Wizard::UnshieldWorker::installDirectories(const QString &dirName, const QS QStringList directories(findDirectories(dirName, path, recursive)); - foreach (const QString &dir, directories) { + for (const QString &dir : directories) + { QFileInfo info(dir); emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); if (!copyDirectory(info.absoluteFilePath(), getPath() + QDir::separator() + info.fileName(), keepSource)) @@ -460,7 +464,8 @@ bool Wizard::UnshieldWorker::setupComponent(Component component) QStringList list(findFiles(QLatin1String("data1.hdr"), disk.absolutePath())); - foreach (const QString &file, list) { + for (const QString &file : list) + { qDebug() << "current archive: " << file; @@ -579,7 +584,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString << QLatin1String("Textures") << QLatin1String("Video"); - foreach (const QString &dir, directories) { + for (const QString &dir : directories) + { if (!installDirectories(dir, temp.absolutePath())) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(dir, temp.absolutePath())); @@ -588,7 +594,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString } // Install directories from disk - foreach (const QString &dir, directories) { + for (const QString &dir : directories) + { if (!installDirectories(dir, info.absolutePath(), false, true)) { emit error(tr("Could not install directory!"), tr("Installing %1 to %2 failed.").arg(dir, info.absolutePath())); @@ -603,7 +610,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString << QLatin1String(".top") << QLatin1String(".mrk"); - foreach (const QString &extension, extensions) { + for (const QString &extension : extensions) + { if (!installFiles(extension, info.absolutePath(), Qt::MatchEndsWith)) { emit error(tr("Could not install translation file!"), tr("Failed to install *%1 files.").arg(extension)); @@ -617,7 +625,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString files << QLatin1String("Morrowind.esm") << QLatin1String("Morrowind.bsa"); - foreach (const QString &file, files) { + for (const QString &file : files) + { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not install Morrowind data file!"), tr("Failed to install %1.").arg(file)); @@ -658,7 +667,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString files << QLatin1String("Tribunal.esm") << QLatin1String("Tribunal.bsa"); - foreach (const QString &file, files) { + for (const QString &file : files) + { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not find Tribunal data file!"), tr("Failed to find %1.").arg(file)); @@ -683,7 +693,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString files << QLatin1String("Bloodmoon.esm") << QLatin1String("Bloodmoon.bsa"); - foreach (const QString &file, files) { + for (const QString &file : files) + { if (!installFile(file, temp.absolutePath())) { emit error(tr("Could not find Bloodmoon data file!"), tr("Failed to find %1.").arg(file)); @@ -696,7 +707,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString emit textChanged(tr("Updating Morrowind configuration file")); - foreach (const QString &inx, list) { + for (const QString &inx : list) + { mIniSettings.parseInx(inx); } } @@ -705,7 +717,8 @@ bool Wizard::UnshieldWorker::installComponent(Component component, const QString QStringList datafiles(findDirectories(QLatin1String("Data Files"), temp.absolutePath())); datafiles.append(findDirectories(QLatin1String("Data Files"), info.absolutePath())); - foreach (const QString &dir, datafiles) { + for (const QString &dir : datafiles) + { QFileInfo info(dir); emit textChanged(tr("Installing: %1 directory").arg(info.fileName())); @@ -849,7 +862,8 @@ QStringList Wizard::UnshieldWorker::findFiles(const QString &fileName, const QSt QFileInfoList list(dir.entryInfoList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst)); - foreach(QFileInfo info, list) { + for (const QFileInfo& info : list) + { if (info.isSymLink()) continue; diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index b20805a11..fc1d81368 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -44,7 +44,8 @@ void Config::GameSettings::validatePaths() QStringList paths = mSettings.values(QString("data")); Files::PathContainer dataDirs; - foreach (const QString &path, paths) { + for (const QString &path : paths) + { QByteArray bytes = path.toUtf8(); dataDirs.push_back(Files::PathContainer::value_type(std::string(bytes.constData(), bytes.length()))); } @@ -511,7 +512,7 @@ bool Config::GameSettings::hasMaster() void Config::GameSettings::setContentList(const QStringList& fileNames) { remove(sContentKey); - foreach(const QString& fileName, fileNames) + for (const QString& fileName : fileNames) { setMultiValue(sContentKey, fileName); } diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index 8f3498826..91bf450f4 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -29,7 +29,8 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) QStringList result; - foreach (const QString ¤tKey, keys) { + for (const QString ¤tKey : keys) + { if (keyRe.indexIn(currentKey) != -1) { @@ -110,7 +111,7 @@ void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) } // if any existing profile in launcher matches the content list, make that profile the default - foreach(const QString &listName, getContentLists()) + for (const QString &listName : getContentLists()) { if (isEqual(files, getContentListFiles(listName))) { @@ -140,7 +141,7 @@ void Config::LauncherSettings::setContentList(const QString& contentListName, co { removeContentList(contentListName); QString key = makeContentListKey(contentListName); - foreach(const QString& fileName, fileNames) + for (const QString& fileName : fileNames) { setMultiValue(key, fileName); } diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 390dde223..86208d7af 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -70,7 +70,7 @@ const ContentSelectorModel::EsmFile *ContentSelectorModel::ContentModel::item(co if (name.contains ('/')) fp = EsmFile::FileProperty_FilePath; - foreach (const EsmFile *file, mFiles) + for (const EsmFile *file : mFiles) { if (name.compare(file->fileProperty (fp).toString(), Qt::CaseInsensitive) == 0) return file; @@ -108,7 +108,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index // addon can be checked if its gamefile is // ... special case, addon with no dependency can be used with any gamefile. bool gamefileChecked = (file->gameFiles().count() == 0); - foreach (const QString &fileName, file->gameFiles()) + for (const QString &fileName : file->gameFiles()) { for (QListIterator dependencyIter(mFiles); dependencyIter.hasNext(); dependencyIter.next()) { @@ -283,7 +283,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const else return success; - foreach (EsmFile *file2, mFiles) + for (EsmFile *file2 : mFiles) { if (file2->gameFiles().contains(fileName, Qt::CaseInsensitive)) { @@ -346,7 +346,7 @@ QMimeData *ContentSelectorModel::ContentModel::mimeData(const QModelIndexList &i { QByteArray encodedData; - foreach (const QModelIndex &index, indexes) + for (const QModelIndex &index : indexes) { if (!index.isValid()) continue; @@ -424,7 +424,7 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); - foreach (const QString &path2, dir.entryList()) + for (const QString &path2 : dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path2)); @@ -486,7 +486,7 @@ void ContentSelectorModel::ContentModel::clearFiles() QStringList ContentSelectorModel::ContentModel::gameFiles() const { QStringList gameFiles; - foreach(const ContentSelectorModel::EsmFile *file, mFiles) + for (const ContentSelectorModel::EsmFile *file : mFiles) { if (file->isGameFile()) { @@ -557,7 +557,7 @@ void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileL { mPluginsWithLoadOrderError.clear(); int previousPosition = -1; - foreach (const QString &filepath, fileList) + for (const QString &filepath : fileList) { if (setCheckState(filepath, true)) { @@ -598,7 +598,7 @@ void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const { QList errors = QList(); - foreach(const QString &dependentfileName, file->gameFiles()) + for (const QString &dependentfileName : file->gameFiles()) { const EsmFile* dependentFile = item(dependentfileName); @@ -627,7 +627,7 @@ QString ContentSelectorModel::ContentModel::toolTip(const EsmFile *file) const { QString text(""); int index = indexFromItem(item(file->filePath())).row(); - foreach(const LoadOrderError& error, checkForLoadOrderErrors(file, index)) + for (const LoadOrderError& error : checkForLoadOrderErrors(file, index)) { text += "

"; text += error.toolTip(); @@ -672,7 +672,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, //if we're checking an item, ensure all "upstream" files (dependencies) are checked as well. if (state == Qt::Checked) { - foreach (QString upstreamName, file->gameFiles()) + for (const QString& upstreamName : file->gameFiles()) { const EsmFile *upstreamFile = item(upstreamName); @@ -689,7 +689,7 @@ bool ContentSelectorModel::ContentModel::setCheckState(const QString &filepath, //otherwise, if we're unchecking an item (or the file is a game file) ensure all downstream files are unchecked. if (state == Qt::Unchecked) { - foreach (const EsmFile *downstreamFile, mFiles) + for (const EsmFile *downstreamFile : mFiles) { QFileInfo fileInfo(filepath); QString filename = fileInfo.fileName(); @@ -714,7 +714,7 @@ ContentSelectorModel::ContentFileList ContentSelectorModel::ContentModel::checke // TODO: // First search for game files and next addons, // so we get more or less correct game files vs addons order. - foreach (EsmFile *file, mFiles) + for (EsmFile *file : mFiles) if (isChecked(file->filePath())) list << file; diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index 89c389556..57f1fdcf3 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -72,7 +72,7 @@ void ContentSelectorView::ContentSelector::setProfileContent(const QStringList & { clearCheckStates(); - foreach (const QString &filepath, fileList) + for (const QString &filepath : fileList) { const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); if (file && file->isGameFile()) @@ -139,7 +139,7 @@ void ContentSelectorView::ContentSelector::addFiles(const QString &path) mContentModel->addFiles(path); // add any game files to the combo box - foreach(const QString gameFileName, mContentModel->gameFiles()) + for (const QString& gameFileName : mContentModel->gameFiles()) { if (ui.gameFileView->findText(gameFileName) == -1) { @@ -225,7 +225,7 @@ void ContentSelectorView::ContentSelector::slotShowContextMenu(const QPoint& pos void ContentSelectorView::ContentSelector::setCheckStateForMultiSelectedItems(bool checked) { Qt::CheckState checkState = checked ? Qt::Checked : Qt::Unchecked; - foreach(const QModelIndex& index, ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource(index); if (mContentModel->data(sourceIndex, Qt::CheckStateRole).toInt() != checkState) @@ -249,7 +249,7 @@ void ContentSelectorView::ContentSelector::slotCopySelectedItemsPaths() { QClipboard *clipboard = QApplication::clipboard(); QString filepaths; - foreach (const QModelIndex& index, ui.addonView->selectionModel()->selectedIndexes()) + for (const QModelIndex& index : ui.addonView->selectionModel()->selectedIndexes()) { int row = mAddonProxyModel->mapToSource(index).row(); const ContentSelectorModel::EsmFile *file = mContentModel->item(row); From 4c94fcd52bc479d5869e7a10da5eda986fe0d9a8 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 21 Oct 2019 14:28:12 +0200 Subject: [PATCH 18/79] add msvc2019 and ditch msvc2015 --- CI/before_script.msvc.sh | 6 +++--- appveyor.yml | 7 ++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 60bdb9c97..1eae71c7f 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -247,7 +247,7 @@ if [ -z $CONFIGURATION ]; then fi if [ -z $VS_VERSION ]; then - VS_VERSION="2013" + VS_VERSION="2017" fi case $VS_VERSION in @@ -507,10 +507,10 @@ fi add_cmake_opts -DBoost_COMPILER="-${TOOLSET}" echo Done. else - # Appveyor unstable has all the boost we need already + # Appveyor has all the boost we need already BOOST_SDK="c:/Libraries/boost_${BOOST_VER_URL}" - if [ $MSVC_REAL_VER -eq 15 ]; then + if [ $MSVC_REAL_VER -ge 15 ]; then LIB_SUFFIX="1" else LIB_SUFFIX="0" diff --git a/appveyor.yml b/appveyor.yml index 90a9cb1bc..5095e7abd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -8,13 +8,10 @@ branches: environment: matrix: - - msvc: 2015 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 - msvc: 2017 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 -matrix: - allow_failures: - - msvc: 2015 + - msvc: 2019 + APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 platform: # - Win32 From e7caf7a037bf73a376ffb20f70e28ccfd4cad687 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 21 Oct 2019 15:38:10 +0200 Subject: [PATCH 19/79] bump that qt --- CI/before_script.msvc.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index 1eae71c7f..a79e27438 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -641,7 +641,7 @@ echo if [ -z $APPVEYOR ]; then printf "Qt 5.7.0... " else - printf "Qt 5.10 AppVeyor... " + printf "Qt 5.13 AppVeyor... " fi { if [ $BITS -eq 64 ]; then @@ -679,7 +679,7 @@ fi add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll" echo Done. else - QT_SDK="C:/Qt/5.10/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}" + QT_SDK="C:/Qt/5.13/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}" add_cmake_opts -DDESIRED_QT_VERSION=5 \ -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" From 5a8bfac4df19fb1391d68036d6b96486fc3a7551 Mon Sep 17 00:00:00 2001 From: Bret Curtis Date: Mon, 21 Oct 2019 15:40:38 +0200 Subject: [PATCH 20/79] hard code Qt to version --- CI/before_script.msvc.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/before_script.msvc.sh b/CI/before_script.msvc.sh index a79e27438..e9fbb117b 100644 --- a/CI/before_script.msvc.sh +++ b/CI/before_script.msvc.sh @@ -679,7 +679,7 @@ fi add_qt_platform_dlls "$(pwd)/plugins/platforms/qwindows${SUFFIX}.dll" echo Done. else - QT_SDK="C:/Qt/5.13/msvc${MSVC_DISPLAY_YEAR}${SUFFIX}" + QT_SDK="C:/Qt/5.13/msvc2017${SUFFIX}" add_cmake_opts -DDESIRED_QT_VERSION=5 \ -DQT_QMAKE_EXECUTABLE="${QT_SDK}/bin/qmake.exe" \ -DCMAKE_PREFIX_PATH="$QT_SDK" From a7930073e807607dc7823399c9a9f4fbc0335957 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 29 Sep 2019 16:16:19 +0200 Subject: [PATCH 21/79] Move settings parser declaration to separate header --- components/CMakeLists.txt | 2 +- components/settings/categories.hpp | 16 ++ components/settings/parser.cpp | 290 ++++++++++++++++++++++++++++ components/settings/parser.hpp | 30 +++ components/settings/settings.cpp | 299 +---------------------------- components/settings/settings.hpp | 6 +- 6 files changed, 340 insertions(+), 303 deletions(-) create mode 100644 components/settings/categories.hpp create mode 100644 components/settings/parser.cpp create mode 100644 components/settings/parser.hpp diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index ed52c8111..412717aaa 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -29,7 +29,7 @@ endif (GIT_CHECKOUT) # source files add_component_dir (settings - settings + settings parser ) add_component_dir (bsa diff --git a/components/settings/categories.hpp b/components/settings/categories.hpp new file mode 100644 index 000000000..d6cd042f6 --- /dev/null +++ b/components/settings/categories.hpp @@ -0,0 +1,16 @@ +#ifndef COMPONENTS_SETTINGS_CATEGORIES_H +#define COMPONENTS_SETTINGS_CATEGORIES_H + +#include +#include +#include +#include + +namespace Settings +{ + using CategorySetting = std::pair; + using CategorySettingVector = std::set>; + using CategorySettingValueMap = std::map; +} + +#endif // COMPONENTS_SETTINGS_CATEGORIES_H diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp new file mode 100644 index 000000000..38b165973 --- /dev/null +++ b/components/settings/parser.cpp @@ -0,0 +1,290 @@ +#include "parser.hpp" + +#include + +#include + +#include +#include +#include + +void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, CategorySettingValueMap& settings) +{ + mFile = file; + boost::filesystem::ifstream stream; + stream.open(boost::filesystem::path(file)); + Log(Debug::Info) << "Loading settings file: " << file; + std::string currentCategory; + mLine = 0; + while (!stream.eof() && !stream.fail()) + { + ++mLine; + std::string line; + std::getline( stream, line ); + + size_t i = 0; + if (!skipWhiteSpace(i, line)) + continue; + + if (line[i] == '#') // skip comment + continue; + + if (line[i] == '[') + { + size_t end = line.find(']', i); + if (end == std::string::npos) + fail("unterminated category"); + + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + i = end+1; + } + + if (!skipWhiteSpace(i, line)) + continue; + + if (currentCategory.empty()) + fail("empty category name"); + + size_t settingEnd = line.find('=', i); + if (settingEnd == std::string::npos) + fail("unterminated setting name"); + + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) + fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); + } +} + +void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, CategorySettingValueMap& settings) +{ + using CategorySettingStatusMap = std::map; + + // No options have been written to the file yet. + CategorySettingStatusMap written; + for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { + written[it->first] = false; + } + + // Have we substantively changed the settings file? + bool changed = false; + + // Were there any lines at all in the file? + bool existing = false; + + // The category/section we're currently in. + std::string currentCategory; + + // Open the existing settings.cfg file to copy comments. This might not be the same file + // as the output file if we're copying the setting from the default settings.cfg for the + // first time. A minor change in API to pass the source file might be in order here. + boost::filesystem::ifstream istream; + boost::filesystem::path ipath(file); + istream.open(ipath); + + // Create a new string stream to write the current settings to. It's likely that the + // input file and the output file are the same, so this stream serves as a temporary file + // of sorts. The setting files aren't very large so there's no performance issue. + std::stringstream ostream; + + // For every line in the input file... + while (!istream.eof() && !istream.fail()) { + std::string line; + std::getline(istream, line); + + // The current character position in the line. + size_t i = 0; + + // Don't add additional newlines at the end of the file. + if (istream.eof()) continue; + + // Copy entirely blank lines. + if (!skipWhiteSpace(i, line)) { + ostream << line << std::endl; + continue; + } + + // There were at least some comments in the input file. + existing = true; + + // Copy comments. + if (line[i] == '#') { + ostream << line << std::endl; + continue; + } + + // Category heading. + if (line[i] == '[') { + size_t end = line.find(']', i); + // This should never happen unless the player edited the file while playing. + if (end == std::string::npos) { + ostream << "# unterminated category: " << line << std::endl; + changed = true; + continue; + } + + // Ensure that all options in the current category have been written. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " + << mit->first.second << " = " << settings[mit->first]; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // Update the current category. + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + + // Write the (new) current category to the file. + ostream << "[" << currentCategory << "]" << std::endl; + // Log(Debug::Verbose) << "Wrote category: " << currentCategory; + + // A setting can apparently follow the category on an input line. That's rather + // inconvenient, since it makes it more likely to have duplicative sections, + // which our algorithm doesn't like. Do the best we can. + i = end+1; + } + + // Truncate trailing whitespace, since we're changing the file anayway. + if (!skipWhiteSpace(i, line)) + continue; + + // If we've found settings before the first category, something's wrong. This + // should never happen unless the player edited the file while playing, since + // the loadSettingsFile() logic rejects it. + if (currentCategory.empty()) { + ostream << "# empty category name: " << line << std::endl; + changed = true; + continue; + } + + // Which setting was at this location in the input file? + size_t settingEnd = line.find('=', i); + // This should never happen unless the player edited the file while playing. + if (settingEnd == std::string::npos) { + ostream << "# unterminated setting name: " << line << std::endl; + changed = true; + continue; + } + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + // Get the existing value so we can see if we've changed it. + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + // Construct the setting map key to determine whether the setting has already been + // written to the file. + CategorySetting key = std::make_pair(currentCategory, setting); + CategorySettingStatusMap::iterator finder = written.find(key); + + // Settings not in the written map are definitely invalid. Currently, this can only + // happen if the player edited the file while playing, because loadSettingsFile() + // will accept anything and pass it along in the map, but in the future, we might + // want to handle invalid settings more gracefully here. + if (finder == written.end()) { + ostream << "# invalid setting: " << line << std::endl; + changed = true; + continue; + } + + // Write the current value of the setting to the file. The key must exist in the + // settings map because of how written was initialized and finder != end(). + ostream << setting << " = " << settings[key] << std::endl; + // Mark that setting as written. + finder->second = true; + // Did we really change it? + if (value != settings[key]) { + Log(Debug::Verbose) << "Changed setting: [" << currentCategory << "] " + << setting << " = " << settings[key]; + changed = true; + } + // No need to write the current line, because we just emitted a replacement. + + // Curiously, it appears that comments at the ends of lines with settings are not + // allowed, and the comment becomes part of the value. Was that intended? + } + + // We're done with the input stream file. + istream.close(); + + // Ensure that all options in the current category have been written. We must complete + // the current category at the end of the file before moving on to any new categories. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + if (mit->second == false && mit->first.first == currentCategory) { + Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first]; + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + mit->second = true; + changed = true; + } + } + + // If there was absolutely nothing in the file (or more likely the file didn't + // exist), start the newly created file with a helpful comment. + if (!existing) { + ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; + ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; + ostream << "# to its default, simply remove it from this file. For available" << std::endl; + ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; + ostream << "#" << std::endl; + ostream << "# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl; + } + + // We still have one more thing to do before we're completely done writing the file. + // It's possible that there are entirely new categories, or that the input file had + // disappeared completely, so we need ensure that all settings are written to the file + // regardless of those issues. + for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { + // If the setting hasn't been written yet. + if (mit->second == false) { + // If the catgory has changed, write a new category header. + if (mit->first.first != currentCategory) { + currentCategory = mit->first.first; + Log(Debug::Verbose) << "Created new setting section: " << mit->first.first; + ostream << std::endl; + ostream << "[" << currentCategory << "]" << std::endl; + } + Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " + << mit->first.second << " = " << settings[mit->first]; + // Then write the setting. No need to mark it as written because we're done. + ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + changed = true; + } + } + + // Now install the newly written file in the requested place. + if (changed) { + Log(Debug::Info) << "Updating settings file: " << ipath; + boost::filesystem::ofstream ofstream; + ofstream.open(ipath); + ofstream << ostream.rdbuf(); + ofstream.close(); + } +} + +bool Settings::SettingsFileParser::skipWhiteSpace(size_t& i, std::string& str) +{ + while (i < str.size() && std::isspace(str[i], std::locale::classic())) + { + ++i; + } + return i < str.size(); +} + +void Settings::SettingsFileParser::fail(const std::string& message) +{ + std::stringstream error; + error << "Error on line " << mLine << " in " << mFile << ":\n" << message; + throw std::runtime_error(error.str()); +} diff --git a/components/settings/parser.hpp b/components/settings/parser.hpp new file mode 100644 index 000000000..bd319e01c --- /dev/null +++ b/components/settings/parser.hpp @@ -0,0 +1,30 @@ +#ifndef COMPONENTS_SETTINGS_PARSER_H +#define COMPONENTS_SETTINGS_PARSER_H + +#include "categories.hpp" + +#include + +namespace Settings +{ + class SettingsFileParser + { + public: + void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings); + + void saveSettingsFile(const std::string& file, CategorySettingValueMap& settings); + + private: + /// Increment i until it longer points to a whitespace character + /// in the string or has reached the end of the string. + /// @return false if we have reached the end of the string + bool skipWhiteSpace(size_t& i, std::string& str); + + void fail(const std::string& message); + + std::string mFile; + int mLine = 0; + }; +} + +#endif // _COMPONENTS_SETTINGS_PARSER_H diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 42f65fc55..8bbde7a64 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,12 +1,9 @@ #include "settings.hpp" - -#include +#include "parser.hpp" #include #include -#include -#include #include namespace Settings @@ -16,300 +13,6 @@ CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); -typedef std::map< CategorySetting, bool > CategorySettingStatusMap; - -class SettingsFileParser -{ -public: - SettingsFileParser() : mLine(0) {} - - void loadSettingsFile (const std::string& file, CategorySettingValueMap& settings) - { - mFile = file; - boost::filesystem::ifstream stream; - stream.open(boost::filesystem::path(file)); - Log(Debug::Info) << "Loading settings file: " << file; - std::string currentCategory; - mLine = 0; - while (!stream.eof() && !stream.fail()) - { - ++mLine; - std::string line; - std::getline( stream, line ); - - size_t i = 0; - if (!skipWhiteSpace(i, line)) - continue; - - if (line[i] == '#') // skip comment - continue; - - if (line[i] == '[') - { - size_t end = line.find(']', i); - if (end == std::string::npos) - fail("unterminated category"); - - currentCategory = line.substr(i+1, end - (i+1)); - boost::algorithm::trim(currentCategory); - i = end+1; - } - - if (!skipWhiteSpace(i, line)) - continue; - - if (currentCategory.empty()) - fail("empty category name"); - - size_t settingEnd = line.find('=', i); - if (settingEnd == std::string::npos) - fail("unterminated setting name"); - - std::string setting = line.substr(i, (settingEnd-i)); - boost::algorithm::trim(setting); - - size_t valueBegin = settingEnd+1; - std::string value = line.substr(valueBegin); - boost::algorithm::trim(value); - - if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) - fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); - } - } - - void saveSettingsFile (const std::string& file, CategorySettingValueMap& settings) - { - // No options have been written to the file yet. - CategorySettingStatusMap written; - for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { - written[it->first] = false; - } - - // Have we substantively changed the settings file? - bool changed = false; - - // Were there any lines at all in the file? - bool existing = false; - - // The category/section we're currently in. - std::string currentCategory; - - // Open the existing settings.cfg file to copy comments. This might not be the same file - // as the output file if we're copying the setting from the default settings.cfg for the - // first time. A minor change in API to pass the source file might be in order here. - boost::filesystem::ifstream istream; - boost::filesystem::path ipath(file); - istream.open(ipath); - - // Create a new string stream to write the current settings to. It's likely that the - // input file and the output file are the same, so this stream serves as a temporary file - // of sorts. The setting files aren't very large so there's no performance issue. - std::stringstream ostream; - - // For every line in the input file... - while (!istream.eof() && !istream.fail()) { - std::string line; - std::getline(istream, line); - - // The current character position in the line. - size_t i = 0; - - // Don't add additional newlines at the end of the file. - if (istream.eof()) continue; - - // Copy entirely blank lines. - if (!skipWhiteSpace(i, line)) { - ostream << line << std::endl; - continue; - } - - // There were at least some comments in the input file. - existing = true; - - // Copy comments. - if (line[i] == '#') { - ostream << line << std::endl; - continue; - } - - // Category heading. - if (line[i] == '[') { - size_t end = line.find(']', i); - // This should never happen unless the player edited the file while playing. - if (end == std::string::npos) { - ostream << "# unterminated category: " << line << std::endl; - changed = true; - continue; - } - - // Ensure that all options in the current category have been written. - for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { - if (mit->second == false && mit->first.first == currentCategory) { - Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " - << mit->first.second << " = " << settings[mit->first]; - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; - mit->second = true; - changed = true; - } - } - - // Update the current category. - currentCategory = line.substr(i+1, end - (i+1)); - boost::algorithm::trim(currentCategory); - - // Write the (new) current category to the file. - ostream << "[" << currentCategory << "]" << std::endl; - // Log(Debug::Verbose) << "Wrote category: " << currentCategory; - - // A setting can apparently follow the category on an input line. That's rather - // inconvenient, since it makes it more likely to have duplicative sections, - // which our algorithm doesn't like. Do the best we can. - i = end+1; - } - - // Truncate trailing whitespace, since we're changing the file anayway. - if (!skipWhiteSpace(i, line)) - continue; - - // If we've found settings before the first category, something's wrong. This - // should never happen unless the player edited the file while playing, since - // the loadSettingsFile() logic rejects it. - if (currentCategory.empty()) { - ostream << "# empty category name: " << line << std::endl; - changed = true; - continue; - } - - // Which setting was at this location in the input file? - size_t settingEnd = line.find('=', i); - // This should never happen unless the player edited the file while playing. - if (settingEnd == std::string::npos) { - ostream << "# unterminated setting name: " << line << std::endl; - changed = true; - continue; - } - std::string setting = line.substr(i, (settingEnd-i)); - boost::algorithm::trim(setting); - - // Get the existing value so we can see if we've changed it. - size_t valueBegin = settingEnd+1; - std::string value = line.substr(valueBegin); - boost::algorithm::trim(value); - - // Construct the setting map key to determine whether the setting has already been - // written to the file. - CategorySetting key = std::make_pair(currentCategory, setting); - CategorySettingStatusMap::iterator finder = written.find(key); - - // Settings not in the written map are definitely invalid. Currently, this can only - // happen if the player edited the file while playing, because loadSettingsFile() - // will accept anything and pass it along in the map, but in the future, we might - // want to handle invalid settings more gracefully here. - if (finder == written.end()) { - ostream << "# invalid setting: " << line << std::endl; - changed = true; - continue; - } - - // Write the current value of the setting to the file. The key must exist in the - // settings map because of how written was initialized and finder != end(). - ostream << setting << " = " << settings[key] << std::endl; - // Mark that setting as written. - finder->second = true; - // Did we really change it? - if (value != settings[key]) { - Log(Debug::Verbose) << "Changed setting: [" << currentCategory << "] " - << setting << " = " << settings[key]; - changed = true; - } - // No need to write the current line, because we just emitted a replacement. - - // Curiously, it appears that comments at the ends of lines with settings are not - // allowed, and the comment becomes part of the value. Was that intended? - } - - // We're done with the input stream file. - istream.close(); - - // Ensure that all options in the current category have been written. We must complete - // the current category at the end of the file before moving on to any new categories. - for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { - if (mit->second == false && mit->first.first == currentCategory) { - Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first]; - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; - mit->second = true; - changed = true; - } - } - - // If there was absolutely nothing in the file (or more likely the file didn't - // exist), start the newly created file with a helpful comment. - if (!existing) { - ostream << "# This is the OpenMW user 'settings.cfg' file. This file only contains" << std::endl; - ostream << "# explicitly changed settings. If you would like to revert a setting" << std::endl; - ostream << "# to its default, simply remove it from this file. For available" << std::endl; - ostream << "# settings, see the file 'settings-default.cfg' or the documentation at:" << std::endl; - ostream << "#" << std::endl; - ostream << "# https://openmw.readthedocs.io/en/master/reference/modding/settings/index.html" << std::endl; - } - - // We still have one more thing to do before we're completely done writing the file. - // It's possible that there are entirely new categories, or that the input file had - // disappeared completely, so we need ensure that all settings are written to the file - // regardless of those issues. - for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { - // If the setting hasn't been written yet. - if (mit->second == false) { - // If the catgory has changed, write a new category header. - if (mit->first.first != currentCategory) { - currentCategory = mit->first.first; - Log(Debug::Verbose) << "Created new setting section: " << mit->first.first; - ostream << std::endl; - ostream << "[" << currentCategory << "]" << std::endl; - } - Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first]; - // Then write the setting. No need to mark it as written because we're done. - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; - changed = true; - } - } - - // Now install the newly written file in the requested place. - if (changed) { - Log(Debug::Info) << "Updating settings file: " << ipath; - boost::filesystem::ofstream ofstream; - ofstream.open(ipath); - ofstream << ostream.rdbuf(); - ofstream.close(); - } - } - -private: - /// Increment i until it longer points to a whitespace character - /// in the string or has reached the end of the string. - /// @return false if we have reached the end of the string - bool skipWhiteSpace(size_t& i, std::string& str) - { - while (i < str.size() && std::isspace(str[i], std::locale::classic())) - { - ++i; - } - return i < str.size(); - } - - void fail(const std::string& message) - { - std::stringstream error; - error << "Error on line " << mLine << " in " << mFile << ":\n" << message; - throw std::runtime_error(error.str()); - } - - std::string mFile; - int mLine; -}; - void Manager::clear() { mDefaultSettings.clear(); diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 3d1cabede..17d237fc3 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -1,16 +1,14 @@ #ifndef COMPONENTS_SETTINGS_H #define COMPONENTS_SETTINGS_H +#include "categories.hpp" + #include #include #include namespace Settings { - typedef std::pair < std::string, std::string > CategorySetting; - typedef std::set< std::pair > CategorySettingVector; - typedef std::map < CategorySetting, std::string > CategorySettingValueMap; - /// /// \brief Settings management (can change during runtime) /// From 862f50346c6b85f1814ce2be627bff22b795508b Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 29 Sep 2019 17:42:01 +0200 Subject: [PATCH 22/79] Add tests for settings parser --- apps/openmw_test_suite/CMakeLists.txt | 2 + apps/openmw_test_suite/settings/parser.cpp | 411 +++++++++++++++++++++ 2 files changed, 413 insertions(+) create mode 100644 apps/openmw_test_suite/settings/parser.cpp diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 12775035b..e216ec759 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -25,6 +25,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) detournavigator/recastmeshobject.cpp detournavigator/navmeshtilescache.cpp detournavigator/tilecachedrecastmeshmanager.cpp + + settings/parser.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/settings/parser.cpp b/apps/openmw_test_suite/settings/parser.cpp new file mode 100644 index 000000000..d54360fc2 --- /dev/null +++ b/apps/openmw_test_suite/settings/parser.cpp @@ -0,0 +1,411 @@ +#include + +#include + +#include + +namespace +{ + using namespace testing; + using namespace Settings; + + struct SettingsFileParserTest : Test + { + SettingsFileParser mLoader; + SettingsFileParser mSaver; + + template + void withSettingsFile( const std::string& content, F&& f) + { + const auto path = std::string(UnitTest::GetInstance()->current_test_info()->name()) + ".cfg"; + + { + boost::filesystem::ofstream stream; + stream.open(path); + stream << content; + stream.close(); + } + + f(path); + } + }; + + TEST_F(SettingsFileParserTest, load_empty_file) + { + const std::string content; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap()); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_empty_section) + { + const std::string content = + "[Section]\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap()); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_section_and_key) + { + const std::string content = + "[Section]\n" + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), "value"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_section_and_key_and_line_comments) + { + const std::string content = + "# foo\n" + "[Section]\n" + "# bar\n" + "key = value\n" + "# baz\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), "value"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_section_and_key_file_and_inline_section_comment) + { + const std::string content = + "[Section] # foo\n" + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_single_section_and_key_and_inline_key_comment) + { + const std::string content = + "[Section]\n" + "key = value # foo\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), "value # foo"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_section_and_key_and_whitespaces) + { + const std::string content = + " [ Section ] \n" + " key = value \n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), "value"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_quoted_string_value) + { + const std::string content = + "[Section]\n" + R"(key = "value")" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), R"("value")"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_quoted_string_value_and_eol) + { + const std::string content = + "[Section]\n" + R"(key = "value"\n)" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), R"("value"\n)"} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_empty_value) + { + const std::string content = + "[Section]\n" + "key =\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), ""} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_empty_key) + { + const std::string content = + "[Section]\n" + "=\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", ""), ""} + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_multiple_keys) + { + const std::string content = + "[Section]\n" + "key1 = value1\n" + "key2 = value2\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key1"), "value1"}, + {CategorySetting("Section", "key2"), "value2"}, + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_multiple_sections) + { + const std::string content = + "[Section1]\n" + "key1 = value1\n" + "[Section2]\n" + "key2 = value2\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section1", "key1"), "value1"}, + {CategorySetting("Section2", "key2"), "value2"}, + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_multiple_sections_and_keys) + { + const std::string content = + "[Section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "[Section2]\n" + "key3 = value3\n" + "key4 = value4\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section1", "key1"), "value1"}, + {CategorySetting("Section1", "key2"), "value2"}, + {CategorySetting("Section2", "key3"), "value3"}, + {CategorySetting("Section2", "key4"), "value4"}, + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_repeated_sections) + { + const std::string content = + "[Section]\n" + "key1 = value1\n" + "[Section]\n" + "key2 = value2\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key1"), "value1"}, + {CategorySetting("Section", "key2"), "value2"}, + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_repeated_keys) + { + const std::string content = + "[Section]\n" + "key = value\n" + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_with_repeated_keys_in_differrent_sections) + { + const std::string content = + "[Section1]\n" + "key = value\n" + "[Section2]\n" + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section1", "key"), "value"}, + {CategorySetting("Section2", "key"), "value"}, + })); + }); + } + + TEST_F(SettingsFileParserTest, file_with_unterminated_section) + { + const std::string content = + "[Section" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_with_single_empty_section_name) + { + const std::string content = + "[]\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap()); + }); + } + + TEST_F(SettingsFileParserTest, file_with_key_and_without_section) + { + const std::string content = + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_with_key_in_empty_name_section) + { + const std::string content = + "[]" + "key = value\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_with_unterminated_key) + { + const std::string content = + "[Section]\n" + "key\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + EXPECT_THROW(mLoader.loadSettingsFile(path, map), std::runtime_error); + }); + } + + TEST_F(SettingsFileParserTest, file_with_empty_lines) + { + const std::string content = + "\n" + "[Section]\n" + "\n" + "key = value\n" + "\n" + ; + + withSettingsFile(content, [this] (const auto& path) { + CategorySettingValueMap map; + mLoader.loadSettingsFile(path, map); + + EXPECT_EQ(map, CategorySettingValueMap({ + {CategorySetting("Section", "key"), "value"} + })); + }); + } +} From 275f552fcf07eef8b0273576210b03bda53c5242 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 29 Sep 2019 17:41:07 +0200 Subject: [PATCH 23/79] Do not modify settings on save --- components/settings/parser.cpp | 22 +++++++++++----------- components/settings/parser.hpp | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/components/settings/parser.cpp b/components/settings/parser.cpp index 38b165973..3767bb15d 100644 --- a/components/settings/parser.cpp +++ b/components/settings/parser.cpp @@ -62,13 +62,13 @@ void Settings::SettingsFileParser::loadSettingsFile(const std::string& file, Cat } } -void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, CategorySettingValueMap& settings) +void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings) { using CategorySettingStatusMap = std::map; // No options have been written to the file yet. CategorySettingStatusMap written; - for (CategorySettingValueMap::iterator it = settings.begin(); it != settings.end(); ++it) { + for (auto it = settings.begin(); it != settings.end(); ++it) { written[it->first] = false; } @@ -133,8 +133,8 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, Cat for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { Log(Debug::Verbose) << "Added new setting: [" << currentCategory << "] " - << mit->first.second << " = " << settings[mit->first]; - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + << mit->first.second << " = " << settings.at(mit->first); + ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; mit->second = true; changed = true; } @@ -200,13 +200,13 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, Cat // Write the current value of the setting to the file. The key must exist in the // settings map because of how written was initialized and finder != end(). - ostream << setting << " = " << settings[key] << std::endl; + ostream << setting << " = " << settings.at(key) << std::endl; // Mark that setting as written. finder->second = true; // Did we really change it? - if (value != settings[key]) { + if (value != settings.at(key)) { Log(Debug::Verbose) << "Changed setting: [" << currentCategory << "] " - << setting << " = " << settings[key]; + << setting << " = " << settings.at(key); changed = true; } // No need to write the current line, because we just emitted a replacement. @@ -223,8 +223,8 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, Cat for (CategorySettingStatusMap::iterator mit = written.begin(); mit != written.end(); ++mit) { if (mit->second == false && mit->first.first == currentCategory) { Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first]; - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + << mit->first.second << " = " << settings.at(mit->first); + ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; mit->second = true; changed = true; } @@ -256,9 +256,9 @@ void Settings::SettingsFileParser::saveSettingsFile(const std::string& file, Cat ostream << "[" << currentCategory << "]" << std::endl; } Log(Debug::Verbose) << "Added new setting: [" << mit->first.first << "] " - << mit->first.second << " = " << settings[mit->first]; + << mit->first.second << " = " << settings.at(mit->first); // Then write the setting. No need to mark it as written because we're done. - ostream << mit->first.second << " = " << settings[mit->first] << std::endl; + ostream << mit->first.second << " = " << settings.at(mit->first) << std::endl; changed = true; } } diff --git a/components/settings/parser.hpp b/components/settings/parser.hpp index bd319e01c..449e54223 100644 --- a/components/settings/parser.hpp +++ b/components/settings/parser.hpp @@ -12,7 +12,7 @@ namespace Settings public: void loadSettingsFile(const std::string& file, CategorySettingValueMap& settings); - void saveSettingsFile(const std::string& file, CategorySettingValueMap& settings); + void saveSettingsFile(const std::string& file, const CategorySettingValueMap& settings); private: /// Increment i until it longer points to a whitespace character From d3a3b2f1f63f248a871a77ab2d3c58602b0f5fb7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 11 Aug 2019 15:01:48 +0400 Subject: [PATCH 24/79] Shields holstering support (feature #5193) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 40 ++++- apps/openmw/mwrender/actoranimation.cpp | 158 ++++++++++++++++++ apps/openmw/mwrender/actoranimation.hpp | 5 + apps/openmw/mwrender/animation.hpp | 3 + apps/openmw/mwrender/creatureanimation.cpp | 1 + apps/openmw/mwrender/npcanimation.cpp | 61 +++++++ apps/openmw/mwrender/npcanimation.hpp | 1 + .../reference/modding/settings/game.rst | 16 ++ files/settings-default.cfg | 3 + 10 files changed, 282 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd8bf9f48..e47b8ee2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,7 @@ Feature #5132: Unique animations for different weapon types Feature #5146: Safe Dispose corpse Feature #5147: Show spell magicka cost in spell buying window + Feature #5193: Weapon sheathing Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption Task #4789: Optimize cell transitions diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 2dbbfea35..4b9287f31 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -985,7 +985,11 @@ void CharacterController::handleTextKey(const std::string &groupname, const std: size_t off = groupname.size()+2; size_t len = evt.size() - off; - if(evt.compare(off, len, "equip attach") == 0) + if(groupname == "shield" && evt.compare(off, len, "equip attach") == 0) + mAnimation->showCarriedLeft(true); + else if(groupname == "shield" && evt.compare(off, len, "unequip detach") == 0) + mAnimation->showCarriedLeft(false); + else if(evt.compare(off, len, "equip attach") == 0) mAnimation->showWeapons(true); else if(evt.compare(off, len, "unequip detach") == 0) mAnimation->showWeapons(false); @@ -1193,7 +1197,7 @@ bool CharacterController::updateCarriedLeftVisible(const int weaptype) const // Shields/torches shouldn't be visible during any operation involving two hands // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", // but they are also present in weapon drawing animation. - return !(getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); + return mAnimation->updateCarriedLeftVisible(weaptype); } bool CharacterController::updateWeaponState(CharacterState& idle) @@ -1275,8 +1279,19 @@ bool CharacterController::updateWeaponState(CharacterState& idle) { // Note: we do not disable unequipping animation automatically to avoid body desync weapgroup = getWeaponAnimation(mWeaponType); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, false, + int unequipMask = MWRender::Animation::BlendMask_All; + bool useShieldAnims = mAnimation->useShieldAnimations(); + if (useShieldAnims && mWeaponType != ESM::Weapon::HandToHand && mWeaponType != ESM::Weapon::Spell && !(mWeaponType == ESM::Weapon::None && weaptype == ESM::Weapon::Spell)) + { + unequipMask = unequipMask |~MWRender::Animation::BlendMask_LeftArm; + mAnimation->play("shield", Priority_Block, + MWRender::Animation::BlendMask_LeftArm, true, + 1.0f, "unequip start", "unequip stop", 0.0f, 0); + } + else if (mWeaponType == ESM::Weapon::HandToHand) + mAnimation->showCarriedLeft(false); + + mAnimation->play(weapgroup, priorityWeapon, unequipMask, false, 1.0f, "unequip start", "unequip stop", 0.0f, 0); mUpperBodyState = UpperCharState_UnEquipingWeap; @@ -1301,7 +1316,10 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (weaptype != mWeaponType) { forcestateupdate = true; - mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + bool useShieldAnims = mAnimation->useShieldAnimations(); + if (!useShieldAnims) + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); + weapgroup = getWeaponAnimation(weaptype); // Note: controllers for ranged weapon should use time for beginning of animation to play shooting properly, @@ -1316,8 +1334,16 @@ bool CharacterController::updateWeaponState(CharacterState& idle) if (weaptype != ESM::Weapon::None) { mAnimation->showWeapons(false); - mAnimation->play(weapgroup, priorityWeapon, - MWRender::Animation::BlendMask_All, true, + int equipMask = MWRender::Animation::BlendMask_All; + if (useShieldAnims && weaptype != ESM::Weapon::Spell) + { + equipMask = equipMask |~MWRender::Animation::BlendMask_LeftArm; + mAnimation->play("shield", Priority_Block, + MWRender::Animation::BlendMask_LeftArm, true, + 1.0f, "equip start", "equip stop", 0.0f, 0); + } + + mAnimation->play(weapgroup, priorityWeapon, equipMask, true, 1.0f, "equip start", "equip stop", 0.0f, 0); mUpperBodyState = UpperCharState_EquipingWeap; diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index d05215b72..eac6c7a44 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -62,6 +62,7 @@ ActorAnimation::~ActorAnimation() } mScabbard.reset(); + mHolsteredShield.reset(); } PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor) @@ -83,6 +84,163 @@ PartHolderPtr ActorAnimation::attachMesh(const std::string& model, const std::st return PartHolderPtr(new PartHolder(instance)); } +std::string ActorAnimation::getShieldMesh(MWWorld::ConstPtr shield) const +{ + std::string mesh = shield.getClass().getModel(shield); + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); + if(mResourceSystem->getVFS()->exists(holsteredName)) + { + osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); + SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); + shieldTemplate->accept(findVisitor); + osg::ref_ptr sheathNode = findVisitor.mFoundNode; + if(!sheathNode) + return std::string(); + } + + return mesh; +} + +bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const +{ + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (shieldSheathing) + { + const MWWorld::Class &cls = mPtr.getClass(); + MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); + if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) + { + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) + { + if(stats.getDrawState() != MWMechanics::DrawState_Weapon) + return false; + + if (weapon != inv.end()) + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); + } + else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + return true; + } + } + } + } + + return !(MWMechanics::getWeaponType(weaptype)->mFlags & ESM::WeaponType::TwoHanded); +} + +void ActorAnimation::updateHolsteredShield(bool showCarriedLeft) +{ + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (!shieldSheathing) + return; + + if (!mPtr.getClass().hasInventoryStore(mPtr)) + return; + + mHolsteredShield.reset(); + + if (showCarriedLeft) + return; + + const MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) + return; + + // Can not show holdstered shields with two-handed weapons at all + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + if(weapon == inv.end()) + return; + + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + if (MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded) + return; + } + + std::string mesh = getShieldMesh(*shield); + if (mesh.empty()) + return; + + std::string boneName = "Bip01 AttachShield"; + osg::Vec4f glowColor = shield->getClass().getEnchantmentColor(*shield); + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); + bool isEnchanted = !shield->getClass().getEnchantment(*shield).empty(); + + // If we have no dedicated sheath model, use basic shield model as fallback. + if (!mResourceSystem->getVFS()->exists(holsteredName)) + mHolsteredShield = attachMesh(mesh, boneName, isEnchanted, &glowColor); + else + mHolsteredShield = attachMesh(holsteredName, boneName, isEnchanted, &glowColor); + + if (!mHolsteredShield) + return; + + SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); + mHolsteredShield->getNode()->accept(findVisitor); + osg::Group* shieldNode = findVisitor.mFoundNode; + + // If mesh author declared an empty sheath node, use transformation from this node, but use the common shield mesh. + // This approach allows to tweak shield position without need to store the whole shield mesh in the _sh file. + if (shieldNode && !shieldNode->getNumChildren()) + { + osg::ref_ptr fallbackNode = mResourceSystem->getSceneManager()->getInstance(mesh, shieldNode); + if (isEnchanted) + SceneUtil::addEnchantedGlow(shieldNode, mResourceSystem, glowColor); + } + + if (mAlpha != 1.f) + mResourceSystem->getSceneManager()->recreateShaders(mHolsteredShield->getNode()); +} + +bool ActorAnimation::useShieldAnimations() const +{ + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (!shieldSheathing) + return false; + + const MWWorld::Class &cls = mPtr.getClass(); + if (!cls.hasInventoryStore(mPtr)) + return false; + + if (getTextKeyTime("shield: equip attach") < 0 || getTextKeyTime("shield: unequip detach") < 0) + return false; + + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (weapon != inv.end() && shield != inv.end() && + shield->getTypeName() == typeid(ESM::Armor).name() && + !getShieldMesh(*shield).empty()) + { + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); + } + else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + return true; + } + + return false; +} + osg::Group* ActorAnimation::getBoneByName(const std::string& boneName) { if (!mObjectRoot) diff --git a/apps/openmw/mwrender/actoranimation.hpp b/apps/openmw/mwrender/actoranimation.hpp index 038dcde6d..14c687a5d 100644 --- a/apps/openmw/mwrender/actoranimation.hpp +++ b/apps/openmw/mwrender/actoranimation.hpp @@ -38,11 +38,15 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener virtual void itemAdded(const MWWorld::ConstPtr& item, int count); virtual void itemRemoved(const MWWorld::ConstPtr& item, int count); virtual bool isArrowAttached() const { return false; } + virtual bool useShieldAnimations() const; + bool updateCarriedLeftVisible(const int weaptype) const; protected: osg::Group* getBoneByName(const std::string& boneName); virtual void updateHolsteredWeapon(bool showHolsteredWeapons); + virtual void updateHolsteredShield(bool showCarriedLeft); virtual void updateQuiver(); + virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const; virtual std::string getHolsteredWeaponBoneName(const MWWorld::ConstPtr& weapon); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename, bool enchantedGlow, osg::Vec4f* glowColor); virtual PartHolderPtr attachMesh(const std::string& model, const std::string& bonename) @@ -52,6 +56,7 @@ class ActorAnimation : public Animation, public MWWorld::ContainerStoreListener }; PartHolderPtr mScabbard; + PartHolderPtr mHolsteredShield; private: void addHiddenItemLight(const MWWorld::ConstPtr& item, const ESM::Light* esmLight); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 763c7a917..b4d4ac664 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -153,6 +153,8 @@ public: void setTextKeyListener(TextKeyListener* listener); + virtual bool updateCarriedLeftVisible(const int weaptype) const { return false; }; + protected: class AnimationTime : public SceneUtil::ControllerSource { @@ -453,6 +455,7 @@ public: /// @note The matching is case-insensitive. const osg::Node* getNode(const std::string& name) const; + virtual bool useShieldAnimations() const { return false; } virtual void showWeapons(bool showWeapon) {} virtual void showCarriedLeft(bool show) {} virtual void setWeaponGroup(const std::string& group, bool relativeDuration) {} diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index 6bece05ec..baa695cda 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -90,6 +90,7 @@ void CreatureWeaponAnimation::updateParts() updateHolsteredWeapon(!mShowWeapons); updateQuiver(); + updateHolsteredShield(mShowCarriedLeft); if (mShowWeapons) updatePart(mWeapon, MWWorld::InventoryStore::Slot_CarriedRight); diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index d56ac9bd0..cde3b3041 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -24,6 +24,8 @@ #include // TextKeyMapHolder +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -511,6 +513,55 @@ void NpcAnimation::updateNpcBase() mWeaponAnimationTime->updateStartTime(); } +std::string NpcAnimation::getShieldMesh(MWWorld::ConstPtr shield) const +{ + std::string mesh = shield.getClass().getModel(shield); + const ESM::Armor *armor = shield.get()->mBase; + std::vector bodyparts = armor->mParts.mParts; + if (!bodyparts.empty()) + { + const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); + const MWWorld::Store &partStore = store.get(); + + // For NPCs try to get shield model from bodyparts first, with ground model as fallback + for (auto & part : bodyparts) + { + if (part.mPart != ESM::PRT_Shield) + continue; + + std::string bodypartName; + if (!mNpc->isMale() && !part.mFemale.empty()) + bodypartName = part.mFemale; + else if (!part.mMale.empty()) + bodypartName = part.mMale; + + if (!bodypartName.empty()) + { + const ESM::BodyPart *bodypart = 0; + bodypart = partStore.search(bodypartName); + if (bodypart->mData.mType != ESM::BodyPart::MT_Armor) + return ""; + else if (!bodypart->mModel.empty()) + mesh = "meshes\\" + bodypart->mModel; + } + } + } + + std::string holsteredName = mesh; + holsteredName = holsteredName.replace(holsteredName.size()-4, 4, "_sh.nif"); + if(mResourceSystem->getVFS()->exists(holsteredName)) + { + osg::ref_ptr shieldTemplate = mResourceSystem->getSceneManager()->getInstance(holsteredName); + SceneUtil::FindByNameVisitor findVisitor ("Bip01 Sheath"); + shieldTemplate->accept(findVisitor); + osg::ref_ptr sheathNode = findVisitor.mFoundNode; + if(!sheathNode) + return std::string(); + } + + return mesh; +} + void NpcAnimation::updateParts() { if (!mObjectRoot.get()) @@ -954,6 +1005,8 @@ void NpcAnimation::showCarriedLeft(bool show) } else removeIndividualPart(ESM::PRT_Shield); + + updateHolsteredShield(mShowCarriedLeft); } void NpcAnimation::attachArrow() @@ -1051,6 +1104,14 @@ void NpcAnimation::setWeaponGroup(const std::string &group, bool relativeDuratio void NpcAnimation::equipmentChanged() { + static const bool shieldSheathing = Settings::Manager::getBool("shield sheathing", "Game"); + if (shieldSheathing) + { + int weaptype; + MWMechanics::getActiveWeapon(mPtr, &weaptype); + showCarriedLeft(updateCarriedLeftVisible(weaptype)); + } + updateParts(); } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index bed07dcdc..2d6d3a05f 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -98,6 +98,7 @@ private: protected: virtual void addControllers(); virtual bool isArrowAttached() const; + virtual std::string getShieldMesh(MWWorld::ConstPtr shield) const; public: /** diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 7bfa60c6c..20e041130 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -184,6 +184,22 @@ Otherwise they wait for the enemies or the player to do an attack first. Please note this setting has not been extensively tested and could have side effects with certain quests. This setting can be toggled in Advanced tab of the launcher. +shield sheathing +---------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true, OpenMW will utilize shield sheathing-compatible assets to display holstered shields. + +To make use of this, you need to have an xbase_anim_sh.nif file with weapon bones that will be injected into the skeleton. +Also you can use additional _sh meshes for more precise shield placement. +Warning: this feature may conflict with mods that use pseudo-shields to emulate item in actor's hand (e.g. books, baskets, pick axes). +To avoid conflicts, you can use _sh mesh without "Bip01 Sheath" node for such "shields" meshes, or declare its bodypart as Clothing type, not as Armor. +Also you can use an _sh node with empty "Bip01 Sheath" node. +In this case the engine will use basic shield model, but will use transformations from the "Bip01 Sheath" node. + weapon sheathing ---------------- diff --git a/files/settings-default.cfg b/files/settings-default.cfg index f8c31eed7..e4cc58d09 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -255,6 +255,9 @@ strength influences hand to hand = 0 # Render holstered weapons (with quivers and scabbards), requires modded assets weapon sheathing = false +# Render holstered shield when it is not in actor's hands, requires modded assets +shield sheathing = false + # Allow non-standard ammunition solely to bypass normal weapon resistance or weakness only appropriate ammunition bypasses resistance = false From 16138fc896002dc3925cdd3775bf49e1996bcecc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 11 Sep 2019 12:59:15 +0300 Subject: [PATCH 25/79] Transient land shape editing --- CHANGELOG.md | 2 + apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/prefs/state.cpp | 6 +- apps/opencs/view/render/cell.cpp | 31 +- apps/opencs/view/render/cell.hpp | 12 + .../view/render/pagedworldspacewidget.cpp | 47 +- .../view/render/pagedworldspacewidget.hpp | 10 + apps/opencs/view/render/terrainselection.cpp | 12 +- apps/opencs/view/render/terrainshapemode.cpp | 1156 +++++++++++++++++ apps/opencs/view/render/terrainshapemode.hpp | 157 +++ apps/opencs/view/render/terrainstorage.cpp | 213 +++ apps/opencs/view/render/terrainstorage.hpp | 14 + .../view/render/unpagedworldspacewidget.cpp | 5 + .../view/render/unpagedworldspacewidget.hpp | 3 + apps/opencs/view/render/worldspacewidget.hpp | 3 + .../view/widget/scenetoolshapebrush.cpp | 265 ++++ .../view/widget/scenetoolshapebrush.hpp | 130 ++ components/esmterrain/storage.cpp | 102 -- components/esmterrain/storage.hpp | 124 +- 19 files changed, 2168 insertions(+), 128 deletions(-) create mode 100644 apps/opencs/view/render/terrainshapemode.cpp create mode 100644 apps/opencs/view/render/terrainshapemode.hpp create mode 100644 apps/opencs/view/widget/scenetoolshapebrush.cpp create mode 100644 apps/opencs/view/widget/scenetoolshapebrush.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d369f2d25..ad8c814ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,8 @@ Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch + Feature #4840: Editor: Transient terrain change support + Feature #????: Editor: Land shape editing, land selection Feature #4859: Make water reflections more configurable Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 00855dad0..a62dcb42f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -82,14 +82,14 @@ opencs_units_noqt (view/world opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton - scenetooltoggle2 scenetooltexturebrush completerpopup coloreditor colorpickerpopup droplineedit + scenetooltoggle2 scenetooltexturebrush scenetoolshapebrush completerpopup coloreditor colorpickerpopup droplineedit ) opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode actor terrainselection + cellwater terraintexturemode actor terrainselection terrainshapemode ) opencs_units_noqt (view/render diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index bfe907c19..35a8ef2df 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -170,7 +170,7 @@ void CSMPrefs::State::declare() "list go to the first/last item"); declareCategory ("3D Scene Input"); - + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); declareSeparator(); @@ -178,7 +178,7 @@ void CSMPrefs::State::declare() declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); declareSeparator(); @@ -248,6 +248,8 @@ void CSMPrefs::State::declare() addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); + declareInt ("shapebrush-maximumsize", "Maximum texture brush size", 100). + setMin (1); declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a0c408df0..3915ee4fb 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -134,7 +134,7 @@ void CSVRender::Cell::updateLand() else { mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, - mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain)); + mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain)); } mTerrain->loadCell(esmLand.mX, esmLand.mY); @@ -169,6 +169,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st { std::pair result = CSMWorld::CellCoordinates::fromId (id); + mTerrainStorage = new TerrainStorage(mData); + if (result.second) mCoordinates = result.first; @@ -347,6 +349,33 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int return addObjects (start, end); } +void CSVRender::Cell::setAlteredHeight(int inCellX, int inCellY, float height) +{ + mTerrainStorage->setAlteredHeight(inCellX, inCellY, height); + mUpdateLand = true; +} + +float CSVRender::Cell::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) +{ + return mTerrainStorage->getSumOfAlteredAndTrueHeight(cellX, cellY, inCellX, inCellY); +} + +float* CSVRender::Cell::getAlteredHeights() +{ + return mTerrainStorage->getAlteredHeights(); +} + +float* CSVRender::Cell::getAlteredHeight(int inCellX, int inCellY) +{ + return mTerrainStorage->getAlteredHeight(inCellX, inCellY); +} + +void CSVRender::Cell::resetAlteredHeights() +{ + mTerrainStorage->resetHeights(); + mUpdateLand = true; +} + void CSVRender::Cell::pathgridModified() { if (mPathgrid) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 444608688..36ac7ff81 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -9,6 +9,7 @@ #include #include "../../model/world/cellcoordinates.hpp" +#include "terrainstorage.hpp" class QModelIndex; @@ -58,6 +59,7 @@ namespace CSVRender int mSubMode; unsigned int mSubModeElementMask; bool mUpdateLand, mLandDeleted; + TerrainStorage *mTerrainStorage; /// Ignored if cell does not have an object with the given ID. /// @@ -118,6 +120,16 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); + void setAlteredHeight(int inCellX, int inCellY, float height); + + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + + float* getAlteredHeights(); + + float* getAlteredHeight(int inCellX, int inCellY); + + void resetAlteredHeights(); + void pathgridModified(); void pathgridRemoved(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 540a15dd1..21624740e 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -25,6 +25,7 @@ #include "cameracontroller.hpp" #include "cellarrow.hpp" #include "terraintexturemode.hpp" +#include "terrainshapemode.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { @@ -137,11 +138,9 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( /// \todo replace EditMode with suitable subclasses tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), - "terrain-shape"); + new TerrainShapeMode (this, mRootNode, tool), "terrain-shape"); tool->addButton ( - new TerrainTextureMode (this, mRootNode, tool), - "terrain-texture"); + new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); @@ -791,6 +790,46 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& poi return 0; } +CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const +{ + std::map::const_iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second; + else + return 0; +} + +void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) searchResult->second->setAlteredHeight(inCellX, inCellY, height); +} + +float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeights(const CSMWorld::CellCoordinates& coords) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) return searchResult->second->getAlteredHeights(); + return nullptr; +} + +float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) return searchResult->second->getAlteredHeight(inCellX, inCellY); + return nullptr; +} + +void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights() +{ + std::map::iterator iter (mCells.begin()); + + while (iter!=mCells.end()) + { + iter->second->resetAlteredHeights(); + ++iter; + } +} + std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 6672c2268..db93e8435 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -124,6 +124,16 @@ namespace CSVRender virtual Cell* getCell(const osg::Vec3d& point) const; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const; + + void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); + + float* getCellAlteredHeights(const CSMWorld::CellCoordinates& coords); + + float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); + + void resetAllAlteredHeights(); + virtual std::vector > getSelection (unsigned int elementMask) const; diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 225cfc20b..092688da2 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -249,13 +249,11 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); - std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); + CSMWorld::CellCoordinates coords (cellX, cellY); - CSMDoc::Document& document = mWorldspaceWidget->getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + float landHeight = 0.f; + if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) + landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY); - return mPointer[localY*ESM::Land::LAND_SIZE + localX]; + return landHeight; } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp new file mode 100644 index 000000000..aa9125696 --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -0,0 +1,1156 @@ +#include "terrainshapemode.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "../widget/modebutton.hpp" +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolshapebrush.hpp" + +#include "../../model/doc/document.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/world/columnbase.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" +#include "../../model/world/land.hpp" +#include "../../model/world/resourcetable.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" + +#include "editmode.hpp" +#include "pagedworldspacewidget.hpp" +#include "mask.hpp" +#include "object.hpp" // Something small needed regarding pointers from here () +#include "terrainselection.hpp" +#include "worldspacewidget.hpp" + +CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), + mBrushSize(0), + mBrushShape(0), + mShapeBrushScenetool(0), + mDragMode(InteractionType_None), + mParentNode(parentNode), + mIsEditing(false), + mTotalDiffY(0), + mShapeEditTool(0), + mShapeEditToolStrength(0), + mTargetHeight(0) +{ +} + +void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) +{ + if (!mTerrainShapeSelection) + { + mTerrainShapeSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape)); + } + + if(!mShapeBrushScenetool) + { + mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); + connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate())); + connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setBrushShape(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int))); + } + + EditMode::activate(toolbar); + toolbar->addTool (mShapeBrushScenetool); +} + +void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) +{ + if(mShapeBrushScenetool) + { + toolbar->removeTool (mShapeBrushScenetool); + delete mShapeBrushScenetool; + mShapeBrushScenetool = 0; + } + EditMode::deactivate(toolbar); +} + +void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here +{ +} + +void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) +{ + mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + + if (hit.hit && hit.tag == 0) + { + } + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } +} + +void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) +{ + if(hit.hit && hit.tag == 0) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); + } +} + +void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) +{ + if(hit.hit && hit.tag == 0) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); + } +} + +bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) +{ + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + + mDragMode = InteractionType_PrimaryEdit; + + if (hit.hit && hit.tag == 0) + { + mEditingPos = hit.worldPos; + mIsEditing = true; + if (mShapeEditTool == 4) + { + std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + mTargetHeight = landShapePointer[inCellY * landSize + inCellX]; + } + } + + return true; +} + +bool CSVRender::TerrainShapeMode::secondaryEditStartDrag (const QPoint& pos) +{ + return false; +} + +bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos) +{ + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mDragMode = InteractionType_PrimarySelect; + if (!hit.hit || hit.tag != 0) + { + mDragMode = InteractionType_None; + return false; + } + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + return false; +} + +bool CSVRender::TerrainShapeMode::secondarySelectStartDrag (const QPoint& pos) +{ + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mDragMode = InteractionType_SecondarySelect; + if (!hit.hit || hit.tag != 0) + { + mDragMode = InteractionType_None; + return false; + } + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + return false; +} + +void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +{ + if (mDragMode == InteractionType_PrimaryEdit) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + mTotalDiffY += diffY; + if (mIsEditing == true && mShapeEditTool == 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + if (mIsEditing == true && mShapeEditTool > 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + } + + if (mDragMode == InteractionType_PrimarySelect) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + } + + if (mDragMode == InteractionType_SecondarySelect) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + } +} + +void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) +{ + if (mDragMode == InteractionType_PrimaryEdit) + { + if (mIsEditing == true) + { + mTotalDiffY = 0; + mIsEditing = false; + } + + std::sort(mAlteredCells.begin(), mAlteredCells.end()); + std::set removeDuplicates(mAlteredCells.begin(), mAlteredCells.end()); + mAlteredCells.assign(removeDuplicates.begin(), removeDuplicates.end()); + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + QUndoStack& undoStack = document.getUndoStack(); + + undoStack.beginMacro ("Edit shape and normal records"); + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < landSize; ++i) + { + for(int j = 0; j < landSize; ++j) + { + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (paged->getCellAlteredHeight(cellCoordinates, i, j)) + landShapeNew[j * landSize + i] = landShapePointer[j * landSize + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + else + landShapeNew[j * landSize + i] = 0; + } + } + } + if (allowLandShapeEditing(cellId) == true) pushEditToCommand(landShapeNew, document, landTable, cellId); + } + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + + for(int i = 0; i < landSize; ++i) + { + for(int j = 0; j < landSize; ++j) + { + float v1[3]; + float v2[3]; + float normal[3]; + float hyp; + + v1[0] = 128; + v1[1] = 0; + if (i < landSize - 1) v1[2] = landShapePointer[j*landSize+i+1] - landShapePointer[j*landSize+i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + if (!noLand && !noCell) + v1[2] = landRightShapePointer[j*landSize+1] - landShapePointer[j*landSize+i]; + else + v1[2] = 0; + } + + v2[0] = 0; + v2[1] = 128; + if (j < landSize - 1) v2[2] = landShapePointer[(j+1)*landSize+i] - landShapePointer[j*landSize+i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + if (!noLand && !noCell) + v2[2] = landDownShapePointer[landSize+i] - landShapePointer[j*landSize+i]; + else + v2[2] = 0; + } + + normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + hyp = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]) / 127.0f; + + normal[0] /= hyp; + normal[1] /= hyp; + normal[2] /= hyp; + + landNormalsNew[(j*landSize+i)*3+0] = normal[0]; + landNormalsNew[(j*landSize+i)*3+1] = normal[1]; + landNormalsNew[(j*landSize+i)*3+2] = normal[2]; + } + } + if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); + } + undoStack.endMacro(); + mAlteredCells.clear(); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } + } +} + + +void CSVRender::TerrainShapeMode::dragAborted() +{ + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } +} + +void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) +{ +} + +void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) +{ + int r = mBrushSize / 2; + + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (mShapeEditTool == 0) paged->resetAllAlteredHeights(); + } + + if(allowLandShapeEditing(cellId)==true) + { + if (mBrushShape == 0) + { + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + + if (mBrushShape == 1) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + if (mBrushShape == 2) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + float distancePerRadius = 1.0f * distance / r; + float smoothedByDistance = 0.0f; + if (mShapeEditTool == 0) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == 1 || mShapeEditTool == 2) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (distance < r) + { + if (mShapeEditTool >= 0 && mShapeEditTool < 3) alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + } + if (mBrushShape == 3) + { + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); + if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + } +} + +void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) +{ + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); + std::string cellUpRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() - 1); + std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); + std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); + + if(allowLandShapeEditing(cellId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords); + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (useTool) + { + if (mShapeEditTool == 0) + { + // Get distance from modified land, alter land change based on zoom + osg::Vec3d eye, center, up; + paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d distance = eye - mEditingPos; + alteredHeight = alteredHeight * (distance.length() / 500); + } + if (mShapeEditTool == 1) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == 2) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == 3) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + } + + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + + // Change values of cornering cells + if (inCellX == 0 && inCellY == 0) + { + if(allowLandShapeEditing(cellUpLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, -1)); + paged->setCellAlteredHeight(cellCoords.move(-1, -1), landSize - 1, landSize - 1, alteredHeight); + } + } + else if (inCellX == 0 && inCellY == landSize - 1) + { + if(allowLandShapeEditing(cellDownLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 1)); + paged->setCellAlteredHeight(cellCoords.move(-1, 1), landSize - 1, 0, alteredHeight); + } + } + else if (inCellX == landSize - 1 && inCellY == 0) + { + if(allowLandShapeEditing(cellUpRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, -1)); + paged->setCellAlteredHeight(cellCoords.move(1, -1), 0, landSize - 1, alteredHeight); + } + } + else if (inCellX == landSize - 1 && inCellY == landSize -1) + { + if(allowLandShapeEditing(cellDownRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 1)); + paged->setCellAlteredHeight(cellCoords.move(1, 1), 0, 0, alteredHeight); + } + } + + // Change values of edging cells + if (inCellX == 0) + { + if(allowLandShapeEditing(cellLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 0)); + paged->setCellAlteredHeight(cellCoords.move(-1, 0), landSize - 1, inCellY, alteredHeight); + } + } + if (inCellY == 0) + { + if(allowLandShapeEditing(cellUpId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, -1)); + paged->setCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 1, alteredHeight); + } + } + + if (inCellX == landSize - 1) + { + if(allowLandShapeEditing(cellRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 0)); + paged->setCellAlteredHeight(cellCoords.move(1, 0), 0, inCellY, alteredHeight); + } + } + if (inCellY == landSize - 1) + { + if(allowLandShapeEditing(cellUpId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, 1)); + paged->setCellAlteredHeight(cellCoords.move(0, 1), inCellX, 0, alteredHeight); + } + } + + } + } +} + +void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) +{ + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + // ### Variable naming key ### + // Variables here hold either the real value, or the altered value of current edit. + // this = this Cell + // left = x - 1, up = y - 1, right = x + 1, down = y + 1 + // Altered = transient edit (in current edited) + float thisAlteredHeight = 0.0f; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) + thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + float thisHeight = landShapePointer[inCellY * landSize + inCellX]; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + float upHeight = 0.0f; + + if(allowLandShapeEditing(cellId)==true) + { + //Get key values for calculating average, handle cell edges, check for null pointers + if (inCellX == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + leftHeight = landLeftShapePointer[inCellY * landSize + (landSize - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, landSize - 2)) + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY); + } + if (inCellY == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + upHeight = landUpShapePointer[(landSize - 2) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2)) + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + } + if (inCellX > 0) + { + leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + } + if (inCellY > 0) + { + upHeight = landShapePointer[(inCellY - 1) * landSize + inCellX]; + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + } + if (inCellX == landSize - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + rightHeight = landRightShapePointer[inCellY * landSize + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) + { + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); + } + } + if (inCellY == landSize - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + downHeight = landDownShapePointer[1 * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) + { + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); + } + } + if (inCellX < landSize - 1) + { + rightHeight = landShapePointer[inCellY * landSize + inCellX + 1]; + if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); + } + if (inCellY < landSize - 1) + { + downHeight = landShapePointer[(inCellY + 1) * landSize + inCellX]; + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); + } + + float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + + upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; + if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); + if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight) && toolStrength > 8.0f) toolStrength = + abs(thisHeight + thisAlteredHeight - averageHeight); //Cut down excessive changes + if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, -toolStrength); + if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, +toolStrength); + } + } +} + +void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell && !noLand) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + thisHeight = landShapePointer[inCellY * landSize + inCellX]; + } + } + + if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = + abs(thisHeight - targetHeight); //Cut down excessive changes + if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, -toolStrength); + if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, +toolStrength); +} + +void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + bool noLeftCell = document.getData().getCells().searchId (cellLeftId) == -1; + bool noLeftLand = document.getData().getLand().searchId (cellLeftId) == -1; + bool noUpCell = document.getData().getCells().searchId (cellUpId) == -1; + bool noUpLand = document.getData().getLand().searchId (cellUpId) == -1; + + *thisHeight = 0.0f; // real + altered height + *thisAlteredHeight = 0.0f; // only altered height + *leftHeight = 0.0f; + *leftAlteredHeight = 0.0f; + *upHeight = 0.0f; + *upAlteredHeight = 0.0f; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell && !noLand) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + *thisHeight = landShapePointer[inCellY * landSize + inCellX] + *thisAlteredHeight; + + // In case of cell edge, and touching cell/land is not found, assume that left and up -heights would be the same + // This is to prevent unnecessary action at limitHeightChange() + *leftHeight = *thisHeight; + *upHeight = *thisHeight; + + if (inCellX == 0) + { + if(!noLeftCell && !noLeftLand) + { + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = + landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + *leftHeight = landLeftShapePointer[inCellY * landSize + (landSize - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY)) + { + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY); + *leftHeight += *leftAlteredHeight; + } + } + } + if (inCellY == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + if(!noUpCell && !noUpLand) + { + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = + landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + *upHeight = landUpShapePointer[(landSize - 2) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, landSize - 2)) + { + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + *upHeight += *upAlteredHeight; + } + } + } + if (inCellX != 0) + { + *leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; + if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) + *leftAlteredHeight += *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + *leftHeight += *leftAlteredHeight; + } + if (inCellY != 0) + { + *upHeight = landShapePointer[(inCellY - 1) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + *upHeight += *upAlteredHeight; + } + } + } +} + +void CSVRender::TerrainShapeMode::fixEdges(const CSMWorld::CellCoordinates& cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + if (allowLandShapeEditing(cellId) == true) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < landSize; ++i) + { + if(document.getData().getCells().searchId (cellLeftId) != -1 && document.getData().getLand().searchId (cellLeftId) != -1 && landShapePointer[i * landSize] != landLeftShapePointer[i * landSize + landSize - 1]) landShapeNew[i * landSize] = landLeftShapePointer[i * landSize + landSize - 1]; + if(document.getData().getCells().searchId (cellRightId) != -1 && document.getData().getLand().searchId (cellRightId) != -1 && landShapePointer[i * landSize + landSize - 1] != landRightShapePointer[i * landSize]) landShapeNew[i * landSize + landSize - 1] = landRightShapePointer[i * landSize]; + if(document.getData().getCells().searchId (cellUpId) != -1 && document.getData().getLand().searchId (cellUpId) != -1 && landShapePointer[i] != landUpShapePointer[(landSize - 1) * landSize + i]) landShapeNew[i] = landUpShapePointer[(landSize - 1) * landSize + i]; + if(document.getData().getCells().searchId (cellDownId) != -1 && document.getData().getLand().searchId (cellDownId) != -1 && landShapePointer[(landSize - 1) * landSize + i] != landDownShapePointer[i]) landShapeNew[(landSize - 1) * landSize + i] = landDownShapePointer[i]; + } + pushEditToCommand(landShapeNew, document, landTable, cellId); + } +} + +void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + + int limitHeightChange = 1024.0f; // Limited by save format + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + bool dataAlteredAtThisCell = false; + int maxPasses = 5; //Multiple passes are needed if there are consecutive height differences over the limit + + if (!noCell && !noLand) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + for (int passes = 0; passes < maxPasses; ++passes) + { + for(int inCellX = 0; inCellX < landSize; ++inCellX) + { + for(int inCellY = 0; inCellY < landSize; ++inCellY) + { + // ### Variable naming key ### + // Variables here aim to hold the final value, which is (real_value + altered_value) + // this = this Cell + // left = x - 1, up = y - 1 + // Altered = transient edit (in current edited) + // Binding = the last row or column, the one that binds cell land together + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight); + + bool doChange = false; + + // Check for height limits, prioritize left over up, except in left-right -cell edges + if (thisHeight - upHeight >= limitHeightChange) + { + thisAlteredHeight = upHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - upHeight <= -limitHeightChange) + { + thisAlteredHeight = upHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + + // Apply limits + if (doChange == true) + { + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); + dataAlteredAtThisCell = true; + } + } + } + + //Skip unneeded extra passes + if (dataAlteredAtThisCell == false) + { + passes = maxPasses; + continue; + } + + //Inverse check (implement properly after default check works) + for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) + { + for(int inCellY = landSize - 1; inCellY >= 0; --inCellY) + { + // ### Variable naming key ### + // Variables here aim to hold the final value, which is (real_value + altered_value) + // this = this Cell + // left = x - 1, up = y - 1 + // Altered = transient edit (in current edited) + // Binding = the last row or column, the one that binds cell land together + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight); + + bool doChange = false; + + // Check for height limits, prioritize left over up, except in left-right -cell edges + if (thisHeight - upHeight >= limitHeightChange) + { + thisAlteredHeight = upHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - upHeight <= -limitHeightChange) + { + thisAlteredHeight = upHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + + // Apply limits + if (doChange == true) + { + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); + dataAlteredAtThisCell = true; + } + } + } + } + } +} + +void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) +{ + int r = mBrushSize / 2; + std::vector> selections; + + if (mBrushShape == 0) + { + selections.emplace_back(vertexCoords); + } + + if (mBrushShape == 1) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + selections.emplace_back(std::make_pair(i, j)); + } + } + } + + if (mBrushShape == 2) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + if (distance < r) selections.emplace_back(std::make_pair(i, j)); + } + } + } + + if (mBrushShape == 3) + { + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + selections.emplace_back(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + } + } + } + + if(selectMode == 0) mTerrainShapeSelection->onlySelect(selections); + if(selectMode == 1) mTerrainShapeSelection->toggleSelect(selections, dragOperation); + +} + +void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId) +{ + QVariant changedLand; + changedLand.setValue(newLandGrid); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId) +{ + QVariant changedLand; + changedLand.setValue(newLandGrid); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +bool CSVRender::TerrainShapeMode::allowLandShapeEditing(std::string cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTree& cellTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + bool noCell = document.getData().getCells().searchId (cellId)==-1; + bool noLand = document.getData().getLand().searchId (cellId)==-1; + + if (noCell) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + // target cell does not exist + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit") + { + std::unique_ptr createCommand ( + new CSMWorld::CreateCommand (cellTable, cellId)); + int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue (parentIndex, index, false); + document.getUndoStack().push (createCommand.release()); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + else if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + { + // target cell exists, but is not shown + std::string mode = + CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); + + if (mode=="Discard") + return false; + + if (mode=="Show cell and edit") + { + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + + if (noLand) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + // target cell does not exist + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit") + { + document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + fixEdges(CSMWorld::CellCoordinates::fromId (cellId).first); + } + } + + return true; +} + +void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) +{ +} + +void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; +} + +void CSVRender::TerrainShapeMode::setBrushShape(int brushShape) +{ + mBrushShape = brushShape; + + //Set custom brush shape + if (mBrushShape == 3 && !mTerrainShapeSelection->getTerrainSelection().empty()) + { + auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); + int selectionCenterX = 0; + int selectionCenterY = 0; + int selectionAmount = 0; + + for(auto const& value: terrainSelection) + { + selectionCenterX = selectionCenterX + value.first; + selectionCenterY = selectionCenterY + value.second; + ++selectionAmount; + } + selectionCenterX = selectionCenterX / selectionAmount; + selectionCenterY = selectionCenterY / selectionAmount; + + mCustomBrushShape.clear(); + std::pair differentialPos {}; + for(auto const& value: terrainSelection) + { + differentialPos.first = value.first - selectionCenterX; + differentialPos.second = value.second - selectionCenterY; + mCustomBrushShape.push_back(differentialPos); + } + } +} + +void CSVRender::TerrainShapeMode::setShapeEditTool(int shapeEditTool) +{ + mShapeEditTool = shapeEditTool; +} + +void CSVRender::TerrainShapeMode::setShapeEditToolStrength(int shapeEditToolStrength) +{ + mShapeEditToolStrength = shapeEditToolStrength; +} + +CSVRender::PagedWorldspaceWidget& CSVRender::TerrainShapeMode::getPagedWorldspaceWidget() +{ + return dynamic_cast(getWorldspaceWidget()); +} diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp new file mode 100644 index 000000000..f910b9ecd --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -0,0 +1,157 @@ +#ifndef CSV_RENDER_TERRAINSHAPEMODE_H +#define CSV_RENDER_TERRAINSHAPEMODE_H + +#include "editmode.hpp" + +#include +#include + +#include +#include + +#ifndef Q_MOC_RUN +#include "../../model/world/data.hpp" +#include "../../model/world/land.hpp" + +#include "../../model/doc/document.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/landtexture.hpp" +#endif + +#include "terrainselection.hpp" + +namespace CSVWidget +{ + class SceneToolShapeBrush; +} + +namespace CSVRender +{ + class PagedWorldspaceWidget; + + /// \brief EditMode for handling the terrain shape editing + class TerrainShapeMode : public EditMode + { + Q_OBJECT + + public: + + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; + + /// Editmode for terrain shape grid + TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); + + void primaryOpenPressed (const WorldspaceHitResult& hit); + + /// Create single command for one-click shape editing + void primaryEditPressed (const WorldspaceHitResult& hit); + + /// Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&); + + void secondarySelectPressed(const WorldspaceHitResult&); + + void activate(CSVWidget::SceneToolbar*); + void deactivate(CSVWidget::SceneToolbar*); + + /// Start shape editing command macro + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + virtual bool primarySelectStartDrag (const QPoint& pos); + virtual bool secondarySelectStartDrag (const QPoint& pos); + + /// Handle shape edit behavior during dragging + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + /// End shape editing command macro + virtual void dragCompleted(const QPoint& pos); + + /// Cancel shape editing, and reset all pending changes + virtual void dragAborted(); + + virtual void dragWheel (int diff, double speedFactor); + virtual void dragMoveEvent (QDragMoveEvent *event); + + /// Handle brush mechanics for shape editing + void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + + /// Do a single height alteration for transient shape edit map + void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true); + + /// Do a single smoothing height alteration for transient shape edit map + void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength); + + /// Do a single flattening height alteration for transient shape edit map + void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); + + /// Update the key values used in height calculations + void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight); + + /// Bind edge vertices to next cells + void fixEdges(const CSMWorld::CellCoordinates& cellCoords); + + /// Check that the edit doesn't break save format limits, fix if necessary + void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords); + + /// Handle brush mechanics for terrain shape selection + void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); + + /// Push terrain shape edits to command macro + void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId); + + /// Push land normals edits to command macro + void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId); + + /// Create new cell and land if needed + bool allowLandShapeEditing(std::string textureFileName); + + private: + std::string mCellId; + std::string mBrushTexture; + int mBrushSize; + int mBrushShape; + std::vector> mCustomBrushShape; + CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool; + int mDragMode; + osg::Group* mParentNode; + bool mIsEditing; + std::unique_ptr mTerrainShapeSelection; + int mTotalDiffY; + std::vector mAlteredCells; + osg::Vec3d mEditingPos; + int mShapeEditTool; + int mShapeEditToolStrength; + int mTargetHeight; + + const int cellSize {ESM::Land::REAL_SIZE}; + const int landSize {ESM::Land::LAND_SIZE}; + const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; + + PagedWorldspaceWidget& getPagedWorldspaceWidget(); + + signals: + void passBrushTexture(std::string brushTexture); + + public slots: + //void handleDropEvent(QDropEvent *event); + void setBrushSize(int brushSize); + void setBrushShape(int brushShape); + void setShapeEditTool(int shapeEditTool); + void setShapeEditToolStrength(int shapeEditToolStrength); + }; +} + + +#endif diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index e0edae774..774c204ed 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,15 +1,25 @@ #include "terrainstorage.hpp" +#include +#include + #include "../../model/world/land.hpp" #include "../../model/world/landtexture.hpp" +#include +#include +#include +#include + namespace CSVRender { + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; TerrainStorage::TerrainStorage(const CSMWorld::Data &data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) { + resetHeights(); } osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) @@ -33,6 +43,209 @@ namespace CSVRender return &mData.getLandTextures().getRecord(row).get(); } + void TerrainStorage::setAlteredHeight(int inCellX, int inCellY, float height) + { + mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] = height - fmod(height, 8); //Limit to divisible by 8 to avoid cell seam breakage + } + + void TerrainStorage::resetHeights() + { + for (int x = 0; x < ESM::Land::LAND_SIZE; ++x) + { + for (int y = 0; y < ESM::Land::LAND_SIZE; ++y) + { + mAlteredHeight[y*ESM::Land::LAND_SIZE + x] = 0; + } + } + } + + float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) + { + float height = 0.f; + osg::ref_ptr land = getLand (cellX, cellY); + if (land) + { + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + if (data) height = getVertexHeight(data, inCellX, inCellY); + } + else return height; + return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; + + } + + float* TerrainStorage::getAlteredHeights() + { + return mAlteredHeight; + } + + float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) + { + return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; + } + + void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = static_cast(1) << lodLevel; + + osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); + + int startCellX = static_cast(std::floor(origin.x())); + int startCellY = static_cast(std::floor(origin.y())); + + size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); + + positions->resize(numVerts*numVerts); + normals->resize(numVerts*numVerts); + colours->resize(numVerts*numVerts); + + osg::Vec3f normal; + osg::Vec4ub color; + + float vertY = 0; + float vertX = 0; + + ESMTerrain::LandCache cache; + + float vertY_ = 0; // of current cell corner + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) + { + const ESMTerrain::LandObject* land = ESMTerrain::Storage::getLand(cellX, cellY, cache); + const ESM::Land::LandData *heightData = 0; + const ESM::Land::LandData *normalData = 0; + const ESM::Land::LandData *colourData = 0; + if (land) + { + heightData = land->getData(ESM::Land::DATA_VHGT); + normalData = land->getData(ESM::Land::DATA_VNML); + colourData = land->getData(ESM::Land::DATA_VCLR); + } + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells + if (vertY_ != 0) + colStart += increment; + if (vertX_ != 0) + rowStart += increment; + + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = std::min(static_cast(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); + int colEnd = std::min(static_cast(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); + + vertY = vertY_; + for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); + + float height = defaultHeight; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; + + (*positions)[static_cast(vertX*numVerts + vertY)] + = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, + (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, + height + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]); + + if (normalData) + { + for (int i=0; i<3; ++i) + normal[i] = normalData->mNormals[srcArrayIndex+i]; + + normal.normalize(); + } + else + normal = osg::Vec3f(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row, cache); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row, cache); + + assert(normal.z() > 0); + + (*normals)[static_cast(vertX*numVerts + vertY)] = normal; + + if (colourData) + { + for (int i=0; i<3; ++i) + color[i] = colourData->mColours[srcArrayIndex+i]; + } + else + { + color.r() = 255; + color.g() = 255; + color.b() = 255; + } + + // Highlight broken height changes + if ( ((col > 0 && row > 0) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 ))) + { + color.r() = 255; + color.g() = 0; + color.b() = 0; + } + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixColour(color, cellX, cellY, col, row, cache); + + color.a() = 255; + + (*colours)[static_cast(vertX*numVerts + vertY)] = color; + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area*/ + } + void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) { // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 6c3151e8d..4740f13bb 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -7,6 +7,7 @@ namespace CSVRender { + class LandCache; /** * @brief A bridge between the terrain component and OpenCS's terrain data storage. @@ -15,12 +16,25 @@ namespace CSVRender { public: TerrainStorage(const CSMWorld::Data& data); + float mAlteredHeight[ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE]; + void setAlteredHeight(int inCellX, int inCellY, float heightMap); + void resetHeights(); + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + float* getAlteredHeights(); + float* getAlteredHeight(int inCellX, int inCellY); + private: const CSMWorld::Data& mData; virtual osg::ref_ptr getLand (int cellX, int cellY) override; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; + /// Draws temporarily altered land (transient change support) + void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) override; + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; }; diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index a3f0636be..b1088aa60 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -150,6 +150,11 @@ CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& p return mCell.get(); } +CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const +{ + return mCell.get(); +} + std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 527463990..d169220f9 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -17,6 +17,7 @@ namespace CSMDoc namespace CSMWorld { class IdTable; + class CellCoordinates; } namespace CSVRender @@ -63,6 +64,8 @@ namespace CSVRender virtual Cell* getCell(const osg::Vec3d& point) const; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const; + virtual std::vector > getSelection (unsigned int elementMask) const; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 06c182b0c..a80032b82 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -17,6 +17,7 @@ namespace CSMPrefs namespace CSMWorld { + class CellCoordinates; class UniversalId; } @@ -170,6 +171,8 @@ namespace CSVRender /// \note Returns the cell if it exists, otherwise a null pointer virtual Cell* getCell(const osg::Vec3d& point) const = 0; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0; + virtual std::vector > getSelection (unsigned int elementMask) const = 0; diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp new file mode 100644 index 000000000..a9f494bf3 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -0,0 +1,265 @@ +#include "scenetoolshapebrush.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "scenetool.hpp" + +#include "../../model/doc/document.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idcollection.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/landtexture.hpp" +#include "../../model/world/universalid.hpp" + + +CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) + : QGroupBox(title, parent) +{ + mBrushSizeSlider = new QSlider(Qt::Horizontal); + mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); + mBrushSizeSlider->setTickInterval(10); + mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSlider->setSingleStep(1); + + mBrushSizeSpinBox = new QSpinBox; + mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSpinBox->setSingleStep(1); + + mLayoutSliderSize = new QHBoxLayout; + mLayoutSliderSize->addWidget(mBrushSizeSlider); + mLayoutSliderSize->addWidget(mBrushSizeSpinBox); + + connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); + connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); + + setLayout(mLayoutSliderSize); +} + +CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) + : QFrame(parent, Qt::Popup), + mBrushShape(0), + mBrushSize(0), + mDocument(document) +{ + mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); + mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); + mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); + mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); + + mSizeSliders = new ShapeBrushSizeControls("Brush size", this); + + QVBoxLayout *layoutMain = new QVBoxLayout; + layoutMain->setSpacing(0); + layoutMain->setContentsMargins(4,0,4,4); + + QHBoxLayout *layoutHorizontal = new QHBoxLayout; + layoutHorizontal->setSpacing(0); + layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); + + configureButtonInitialSettings(mButtonPoint); + configureButtonInitialSettings(mButtonSquare); + configureButtonInitialSettings(mButtonCircle); + configureButtonInitialSettings(mButtonCustom); + + mButtonPoint->setToolTip (toolTipPoint); + mButtonSquare->setToolTip (toolTipSquare); + mButtonCircle->setToolTip (toolTipCircle); + mButtonCustom->setToolTip (toolTipCustom); + + QButtonGroup* brushButtonGroup = new QButtonGroup(this); + brushButtonGroup->addButton(mButtonPoint); + brushButtonGroup->addButton(mButtonSquare); + brushButtonGroup->addButton(mButtonCircle); + brushButtonGroup->addButton(mButtonCustom); + + brushButtonGroup->setExclusive(true); + + layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop); + + mHorizontalGroupBox = new QGroupBox(tr("")); + mHorizontalGroupBox->setLayout(layoutHorizontal); + + mToolSelector = new QComboBox(this); + mToolSelector->addItem(tr("Height (drag)")); + mToolSelector->addItem(tr("Height, raise (paint)")); + mToolSelector->addItem(tr("Height, lower (paint)")); + mToolSelector->addItem(tr("Smooth (paint)")); + mToolSelector->addItem(tr("Flatten (paint)")); + + QLabel *brushStrengthLabel = new QLabel(this); + brushStrengthLabel->setText("Brush strength:"); + + mToolStrengthSlider = new QSlider(Qt::Horizontal); + mToolStrengthSlider->setTickPosition(QSlider::TicksBothSides); + mToolStrengthSlider->setTickInterval(8); + mToolStrengthSlider->setRange(8, 128); + mToolStrengthSlider->setSingleStep(8); + + layoutMain->addWidget(mHorizontalGroupBox); + layoutMain->addWidget(mSizeSliders); + layoutMain->addWidget(mToolSelector); + layoutMain->addWidget(brushStrengthLabel); + layoutMain->addWidget(mToolStrengthSlider); + + setLayout(layoutMain); + + connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); +} + +void CSVWidget::ShapeBrushWindow::configureButtonInitialSettings(QPushButton *button) +{ + button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setContentsMargins (QMargins (0, 0, 0, 0)); + button->setIconSize (QSize (48-6, 48-6)); + button->setFixedSize (48, 48); + button->setCheckable(true); +} + +void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; + emit passBrushSize(mBrushSize); +} + +void CSVWidget::ShapeBrushWindow::setBrushShape() +{ + if(mButtonPoint->isChecked()) mBrushShape = 0; + if(mButtonSquare->isChecked()) mBrushShape = 1; + if(mButtonCircle->isChecked()) mBrushShape = 2; + if(mButtonCustom->isChecked()) mBrushShape = 3; + emit passBrushShape(mBrushShape); +} + +void CSVWidget::SceneToolShapeBrush::adjustToolTips() +{ +} + +CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) +: SceneTool (parent, Type_TopAction), + mToolTip (toolTip), + mDocument (document), + mShapeBrushWindow(new ShapeBrushWindow(document, this)) +{ + setAcceptDrops(true); + connect(mShapeBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setButtonIcon(int))); + setButtonIcon(mShapeBrushWindow->mBrushShape); + + mPanel = new QFrame (this, Qt::Popup); + + QHBoxLayout *layout = new QHBoxLayout (mPanel); + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mTable = new QTableWidget (0, 2, this); + + mTable->setShowGrid (true); + mTable->verticalHeader()->hide(); + mTable->horizontalHeader()->hide(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); +#else + mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch); +#endif + mTable->setSelectionMode (QAbstractItemView::NoSelection); + + layout->addWidget (mTable); + + connect (mTable, SIGNAL (clicked (const QModelIndex&)), + this, SLOT (clicked (const QModelIndex&))); + +} + +void CSVWidget::SceneToolShapeBrush::setButtonIcon (int brushShape) +{ + QString tooltip = "Change brush settings

Currently selected: "; + + switch (brushShape) + { + case 0: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + tooltip += mShapeBrushWindow->toolTipPoint; + break; + + case 1: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + tooltip += mShapeBrushWindow->toolTipSquare; + break; + + case 2: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + tooltip += mShapeBrushWindow->toolTipCircle; + break; + + case 3: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); + tooltip += mShapeBrushWindow->toolTipCustom; + break; + } + + setToolTip (tooltip); +} + +void CSVWidget::SceneToolShapeBrush::showPanel (const QPoint& position) +{ +} + +void CSVWidget::SceneToolShapeBrush::updatePanel () +{ +} + +void CSVWidget::SceneToolShapeBrush::clicked (const QModelIndex& index) +{ +} + +void CSVWidget::SceneToolShapeBrush::activate () +{ + QPoint position = QCursor::pos(); + mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->move (position); + mShapeBrushWindow->show(); +} + +void CSVWidget::SceneToolShapeBrush::dragEnterEvent (QDragEnterEvent *event) +{ + emit passEvent(event); + event->accept(); +} +void CSVWidget::SceneToolShapeBrush::dropEvent (QDropEvent *event) +{ + emit passEvent(event); + event->accept(); +} diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp new file mode 100644 index 000000000..9048fa536 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -0,0 +1,130 @@ +#ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H +#define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef Q_MOC_RUN +#include "scenetool.hpp" + +#include "../../model/doc/document.hpp" +#endif + +class QTableWidget; + +namespace CSVRender +{ + class TerrainShapeMode; +} + +namespace CSVWidget +{ + class SceneToolShapeBrush; + + /// \brief Layout-box for some brush button settings + class ShapeBrushSizeControls : public QGroupBox + { + Q_OBJECT + + public: + ShapeBrushSizeControls(const QString &title, QWidget *parent); + + private: + QHBoxLayout *mLayoutSliderSize; + QSlider *mBrushSizeSlider; + QSpinBox *mBrushSizeSpinBox; + + friend class SceneToolShapeBrush; + friend class CSVRender::TerrainShapeMode; + }; + + class SceneToolShapeBrush; + + /// \brief Brush settings window + class ShapeBrushWindow : public QFrame + { + Q_OBJECT + + public: + ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); + void configureButtonInitialSettings(QPushButton *button); + + const QString toolTipPoint = "Paint single point"; + const QString toolTipSquare = "Paint with square brush"; + const QString toolTipCircle = "Paint with circle brush"; + const QString toolTipCustom = "Paint custom selection (not implemented yet)"; + + private: + int mBrushShape; + int mBrushSize; + CSMDoc::Document& mDocument; + QGroupBox *mHorizontalGroupBox; + QComboBox *mToolSelector; + QSlider *mToolStrengthSlider; + QPushButton *mButtonPoint; + QPushButton *mButtonSquare; + QPushButton *mButtonCircle; + QPushButton *mButtonCustom; + ShapeBrushSizeControls* mSizeSliders; + + friend class SceneToolShapeBrush; + friend class CSVRender::TerrainShapeMode; + + public slots: + void setBrushShape(); + void setBrushSize(int brushSize); + + signals: + void passBrushSize (int brushSize); + void passBrushShape(int brushShape); + }; + + class SceneToolShapeBrush : public SceneTool + { + Q_OBJECT + + QString mToolTip; + CSMDoc::Document& mDocument; + QFrame *mPanel; + QTableWidget *mTable; + ShapeBrushWindow *mShapeBrushWindow; + + private: + + void adjustToolTips(); + + public: + + SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); + + virtual void showPanel (const QPoint& position); + void updatePanel (); + + void dropEvent (QDropEvent *event); + void dragEnterEvent (QDragEnterEvent *event); + + friend class CSVRender::TerrainShapeMode; + + public slots: + void setButtonIcon(int brushShape); + void clicked (const QModelIndex& index); + virtual void activate(); + + signals: + void passEvent(QDropEvent *event); + void passEvent(QDragEnterEvent *event); + }; +} + +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index c8d31c41c..151f0ff91 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,13 +16,6 @@ namespace ESMTerrain { - class LandCache - { - public: - typedef std::map, osg::ref_ptr > Map; - Map mMap; - }; - LandObject::LandObject() : mLand(nullptr) , mLoadFlags(0) @@ -98,82 +91,6 @@ namespace ESMTerrain return false; } - void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; - if (data) - { - normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalize(); - } - else - normal = osg::Vec3f(0,0,1); - } - - void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - osg::Vec3f n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row, cache); - fixNormal(n2, cellX, cellY, col-1, row, cache); - fixNormal(n3, cellX, cellY, col, row+1, cache); - fixNormal(n4, cellX, cellY, col, row-1, cache); - normal = (n1+n2+n3+n4); - normal.normalize(); - } - - void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; - if (data) - { - color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; - color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; - color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; - } - else - { - color.r() = 255; - color.g() = 255; - color.b() = 255; - } - } - void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, @@ -521,25 +438,6 @@ namespace ESMTerrain } - float Storage::getVertexHeight(const ESM::Land::LandData* data, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return data->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - - const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) - { - LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); - if (found != cache.mMap.end()) - return found->second; - else - { - found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; - return found->second; - } - } - Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { OpenThreads::ScopedLock lock(mLayerInfoMutex); diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 613d2e218..79c75471f 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,8 +1,13 @@ #ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H +#include + #include +#include +#include + #include #include @@ -13,11 +18,14 @@ namespace VFS class Manager; } +namespace CSVRender +{ + class TerrainStorage; +} + namespace ESMTerrain { - class LandCache; - /// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use class LandObject : public osg::Object { @@ -48,6 +56,13 @@ namespace ESMTerrain ESM::Land::LandData mData; }; + class LandCache + { + public: + typedef std::map, osg::ref_ptr > Map; + Map mMap; + }; + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// into the terrain component, converting it on the fly as needed. class Storage : public Terrain::Storage @@ -110,14 +125,6 @@ namespace ESMTerrain private: const VFS::Manager* mVFS; - inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); - inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - - inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y); - - inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); - // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair @@ -137,6 +144,103 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + protected: + + inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; + if (data) + { + normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalize(); + } + else + normal = osg::Vec3f(0,0,1); + }; + + inline void fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; + if (data) + { + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; + } + else + { + color.r() = 255; + color.g() = 255; + color.b() = 255; + } + }; + + inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + osg::Vec3f n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row, cache); + fixNormal(n2, cellX, cellY, col-1, row, cache); + fixNormal(n3, cellX, cellY, col, row+1, cache); + fixNormal(n4, cellX, cellY, col, row-1, cache); + normal = (n1+n2+n3+n4); + normal.normalize(); + }; + + inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return data->mHeights[y * ESM::Land::LAND_SIZE + x]; + }; + + inline const LandObject* getLand(int cellX, int cellY, LandCache& cache) + { + LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); + if (found != cache.mMap.end()) + return found->second; + else + { + found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; + return found->second; + } + }; }; } From 4a2d8aaf9741dc72ff0a14104f2e282e146e4199 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 12 Sep 2019 02:24:13 +0300 Subject: [PATCH 26/79] Handle mBrushSize 1. --- apps/opencs/view/render/terrainshapemode.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index aa9125696..62aef6570 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -352,6 +352,7 @@ void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; + if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; @@ -409,7 +410,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair float smoothedByDistance = 0.0f; if (mShapeEditTool == 0) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); if (mShapeEditTool == 1 || mShapeEditTool == 2) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); - if (distance < r) + if (distance <= r) { if (mShapeEditTool >= 0 && mShapeEditTool < 3) alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); @@ -978,7 +979,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); - if (distance < r) selections.emplace_back(std::make_pair(i, j)); + if (distance <= r) selections.emplace_back(std::make_pair(i, j)); } } } From c2428bc5fa8d16806af1f6d41230ecdef00106ab Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 18 Sep 2019 12:04:03 +0300 Subject: [PATCH 27/79] Remove unneeded forward declaration --- components/esmterrain/storage.hpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 79c75471f..019bf0bab 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -18,11 +18,6 @@ namespace VFS class Manager; } -namespace CSVRender -{ - class TerrainStorage; -} - namespace ESMTerrain { @@ -62,7 +57,7 @@ namespace ESMTerrain typedef std::map, osg::ref_ptr > Map; Map mMap; }; - + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// into the terrain component, converting it on the fly as needed. class Storage : public Terrain::Storage From 54e13954e8840c5db089be8074b19353ff9ad01b Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 19 Sep 2019 12:38:15 +0300 Subject: [PATCH 28/79] Improve land steepness limiting code --- apps/opencs/view/render/terrainshapemode.cpp | 244 ++++++++++++------- apps/opencs/view/render/terrainshapemode.hpp | 8 +- 2 files changed, 157 insertions(+), 95 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 62aef6570..015c19035 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -238,6 +238,16 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) undoStack.beginMacro ("Edit shape and normal records"); + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + limitAlteredHeights(cellCoordinates); + } + std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + limitAlteredHeights(cellCoordinates, true); + } + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); @@ -689,7 +699,8 @@ void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& } void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, - float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight) + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, + float* rightAlteredHeight, float* downHeight, float* downAlteredHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( @@ -699,13 +710,18 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); - std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); bool noCell = document.getData().getCells().searchId (cellId) == -1; bool noLand = document.getData().getLand().searchId (cellId) == -1; bool noLeftCell = document.getData().getCells().searchId (cellLeftId) == -1; bool noLeftLand = document.getData().getLand().searchId (cellLeftId) == -1; bool noUpCell = document.getData().getCells().searchId (cellUpId) == -1; bool noUpLand = document.getData().getLand().searchId (cellUpId) == -1; + bool noRightCell = document.getData().getCells().searchId (cellRightId) == -1; + bool noRightLand = document.getData().getLand().searchId (cellRightId) == -1; + bool noDownCell = document.getData().getCells().searchId (cellDownId) == -1; + bool noDownLand = document.getData().getLand().searchId (cellDownId) == -1; *thisHeight = 0.0f; // real + altered height *thisAlteredHeight = 0.0f; // only altered height @@ -713,6 +729,10 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor *leftAlteredHeight = 0.0f; *upHeight = 0.0f; *upAlteredHeight = 0.0f; + *rightHeight = 0.0f; + *rightAlteredHeight = 0.0f; + *downHeight = 0.0f; + *downAlteredHeight = 0.0f; if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) @@ -726,11 +746,14 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); *thisHeight = landShapePointer[inCellY * landSize + inCellX] + *thisAlteredHeight; - // In case of cell edge, and touching cell/land is not found, assume that left and up -heights would be the same - // This is to prevent unnecessary action at limitHeightChange() + // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found, + // which is to prevent unnecessary action at limitHeightChange(). *leftHeight = *thisHeight; *upHeight = *thisHeight; + *rightHeight = *thisHeight; + *downHeight = *thisHeight; + //If at edge, get values from neighboring cell if (inCellX == 0) { if(!noLeftCell && !noLeftLand) @@ -760,11 +783,43 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } } } + if (inCellX == landSize - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + if(!noRightCell && !noRightLand) + { + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + *rightHeight = landRightShapePointer[inCellY * landSize + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) + { + *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); + *rightHeight += *rightAlteredHeight; + } + } + } + if (inCellY == landSize - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + if(!noDownCell && !noDownLand) + { + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + *downHeight = landDownShapePointer[landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) + { + *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); + *downHeight += *downAlteredHeight; + } + } + } + + //If not at edge, get values from the same cell if (inCellX != 0) { *leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) - *leftAlteredHeight += *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); *leftHeight += *leftAlteredHeight; } if (inCellY != 0) @@ -774,6 +829,21 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); *upHeight += *upAlteredHeight; } + if (inCellX != landSize - 1) + { + *rightHeight = landShapePointer[inCellY * landSize + inCellX + 1]; + if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); + *rightHeight += *rightAlteredHeight; + } + if (inCellY != landSize - 1) + { + *downHeight = landShapePointer[(inCellY + 1) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); + *downHeight += *downAlteredHeight; + } + } } } @@ -808,7 +878,7 @@ void CSVRender::TerrainShapeMode::fixEdges(const CSMWorld::CellCoordinates& cell } } -void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords) +void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( @@ -817,133 +887,125 @@ void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); - int limitHeightChange = 1024.0f; // Limited by save format + int limitHeightChange = 1016.0f; // Limited by save format bool noCell = document.getData().getCells().searchId (cellId) == -1; bool noLand = document.getData().getLand().searchId (cellId) == -1; - bool dataAlteredAtThisCell = false; - int maxPasses = 5; //Multiple passes are needed if there are consecutive height differences over the limit if (!noCell && !noLand) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - for (int passes = 0; passes < maxPasses; ++passes) + if (reverseMode == false) { - for(int inCellX = 0; inCellX < landSize; ++inCellX) + for(int inCellY = 0; inCellY < landSize; ++inCellY) { - for(int inCellY = 0; inCellY < landSize; ++inCellY) + for(int inCellX = 0; inCellX < landSize; ++inCellX) { - // ### Variable naming key ### - // Variables here aim to hold the final value, which is (real_value + altered_value) - // this = this Cell - // left = x - 1, up = y - 1 - // Altered = transient edit (in current edited) - // Binding = the last row or column, the one that binds cell land together - float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upHeight = 0.0f; float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + float* limitedAlteredHeightXAxis = nullptr; + float* limitedAlteredHeightYAxis = nullptr; updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, - &upHeight, &upAlteredHeight); + &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); - bool doChange = false; + // Check for height limits on x-axis + if (leftHeight - thisHeight > limitHeightChange) + limitedAlteredHeightXAxis = new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + else if (leftHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightXAxis = new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); - // Check for height limits, prioritize left over up, except in left-right -cell edges - if (thisHeight - upHeight >= limitHeightChange) - { - thisAlteredHeight = upHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - upHeight <= -limitHeightChange) - { - thisAlteredHeight = upHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) - { - thisAlteredHeight = leftHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) - { - thisAlteredHeight = leftHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } + // Check for height limits on y-axis + if (upHeight - thisHeight > limitHeightChange) + limitedAlteredHeightYAxis = new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + else if (upHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightYAxis = new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); - // Apply limits - if (doChange == true) + // Limit altered height value based on x or y, whichever is the smallest + if (limitedAlteredHeightXAxis) { - alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); - dataAlteredAtThisCell = true; + if (limitedAlteredHeightYAxis) + { + if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + else + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + } + else + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); } + else if (limitedAlteredHeightYAxis) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + } + delete limitedAlteredHeightXAxis; + delete limitedAlteredHeightYAxis; } } + } - //Skip unneeded extra passes - if (dataAlteredAtThisCell == false) + if (reverseMode == true) + { + for(int inCellY = landSize - 1; inCellY >= 0; --inCellY) { - passes = maxPasses; - continue; - } - - //Inverse check (implement properly after default check works) - for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) - { - for(int inCellY = landSize - 1; inCellY >= 0; --inCellY) + for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) { - // ### Variable naming key ### - // Variables here aim to hold the final value, which is (real_value + altered_value) - // this = this Cell - // left = x - 1, up = y - 1 - // Altered = transient edit (in current edited) - // Binding = the last row or column, the one that binds cell land together - float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upHeight = 0.0f; float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + float* limitedAlteredHeightXAxis = nullptr; + float* limitedAlteredHeightYAxis = nullptr; updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, - &upHeight, &upAlteredHeight); + &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); - bool doChange = false; + // Check for height limits on x-axis + if (rightHeight - thisHeight > limitHeightChange) + limitedAlteredHeightXAxis = new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + else if (rightHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightXAxis = new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); - // Check for height limits, prioritize left over up, except in left-right -cell edges - if (thisHeight - upHeight >= limitHeightChange) - { - thisAlteredHeight = upHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - upHeight <= -limitHeightChange) - { - thisAlteredHeight = upHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) - { - thisAlteredHeight = leftHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } - if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) - { - thisAlteredHeight = leftHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; - doChange = true; - } + // Check for height limits on y-axis + if (downHeight - thisHeight > limitHeightChange) + limitedAlteredHeightYAxis = new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + else if (downHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightYAxis = new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); - // Apply limits - if (doChange == true) + // Limit altered height value based on x or y, whichever is the smallest + if (limitedAlteredHeightXAxis) { - alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); - dataAlteredAtThisCell = true; + if (limitedAlteredHeightYAxis) + { + if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + else + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + } + else + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); } - } + else if (limitedAlteredHeightYAxis) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + } + delete limitedAlteredHeightXAxis; + delete limitedAlteredHeightYAxis; } } } } diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index f910b9ecd..43978eea3 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -93,15 +93,16 @@ namespace CSVRender /// Do a single flattening height alteration for transient shape edit map void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); - /// Update the key values used in height calculations + /// Get altered height values around one vertex void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, - float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight); + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, + float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight); /// Bind edge vertices to next cells void fixEdges(const CSMWorld::CellCoordinates& cellCoords); /// Check that the edit doesn't break save format limits, fix if necessary - void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords); + void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); /// Handle brush mechanics for terrain shape selection void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); @@ -145,7 +146,6 @@ namespace CSVRender void passBrushTexture(std::string brushTexture); public slots: - //void handleDropEvent(QDropEvent *event); void setBrushSize(int brushSize); void setBrushShape(int brushShape); void setShapeEditTool(int shapeEditTool); From 60c0a25004777f21c30400cd1a0c5f5e0478d632 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 19 Sep 2019 14:18:36 +0300 Subject: [PATCH 29/79] Fix smooth tool and flatten tool. Default tool strength to 8. --- apps/opencs/view/render/terrainshapemode.cpp | 13 ++++++------- apps/opencs/view/widget/scenetoolshapebrush.cpp | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 015c19035..cc20339a9 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -50,7 +50,7 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidge mIsEditing(false), mTotalDiffY(0), mShapeEditTool(0), - mShapeEditToolStrength(0), + mShapeEditToolStrength(8), mTargetHeight(0) { } @@ -656,10 +656,9 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); - if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight) && toolStrength > 8.0f) toolStrength = - abs(thisHeight + thisAlteredHeight - averageHeight); //Cut down excessive changes - if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, -toolStrength); - if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, +toolStrength); + if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); + if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, - toolStrength); + if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, + toolStrength); } } } @@ -694,8 +693,8 @@ void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = abs(thisHeight - targetHeight); //Cut down excessive changes - if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, -toolStrength); - if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, +toolStrength); + if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); + if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); } void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index a9f494bf3..3e11dad97 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -118,6 +118,7 @@ CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidge mToolStrengthSlider->setTickInterval(8); mToolStrengthSlider->setRange(8, 128); mToolStrengthSlider->setSingleStep(8); + mToolStrengthSlider->setValue(8); layoutMain->addWidget(mHorizontalGroupBox); layoutMain->addWidget(mSizeSliders); From ff18595a86ef60fdcc2c4ec8ea355797b143a445 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 21 Sep 2019 13:15:42 +0300 Subject: [PATCH 30/79] Reduce code duplification, add bool value to limiting --- apps/opencs/view/render/terrainshapemode.cpp | 104 +++++++++---------- apps/opencs/view/render/terrainshapemode.hpp | 8 +- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index cc20339a9..7c4a68ae5 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -877,7 +877,37 @@ void CSVRender::TerrainShapeMode::fixEdges(const CSMWorld::CellCoordinates& cell } } -void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) +void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) +{ + if (limitedAlteredHeightXAxis) + { + if (limitedAlteredHeightYAxis) + { + if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + *steepnessIsWithinLimits = false; + } + else + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + *steepnessIsWithinLimits = false; + } + } + else + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + *steepnessIsWithinLimits = false; + } + } + else if (limitedAlteredHeightYAxis) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + *steepnessIsWithinLimits = false; + } +} + +bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( @@ -889,31 +919,32 @@ void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi int limitHeightChange = 1016.0f; // Limited by save format bool noCell = document.getData().getCells().searchId (cellId) == -1; bool noLand = document.getData().getLand().searchId (cellId) == -1; + bool steepnessIsWithinLimits = true; if (!noCell && !noLand) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + if (reverseMode == false) { for(int inCellY = 0; inCellY < landSize; ++inCellY) { for(int inCellX = 0; inCellX < landSize; ++inCellX) { - float thisHeight = 0.0f; - float thisAlteredHeight = 0.0f; - float leftHeight = 0.0f; - float leftAlteredHeight = 0.0f; - float upHeight = 0.0f; - float upAlteredHeight = 0.0f; - float rightHeight = 0.0f; - float rightAlteredHeight = 0.0f; - float downHeight = 0.0f; - float downAlteredHeight = 0.0f; float* limitedAlteredHeightXAxis = nullptr; float* limitedAlteredHeightYAxis = nullptr; - updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); @@ -930,25 +961,9 @@ void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi limitedAlteredHeightYAxis = new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest - if (limitedAlteredHeightXAxis) - { - if (limitedAlteredHeightYAxis) - { - if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); - else - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); - } - else - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); - } - else if (limitedAlteredHeightYAxis) - { - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); - } + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); delete limitedAlteredHeightXAxis; - delete limitedAlteredHeightYAxis; - } + delete limitedAlteredHeightYAxis; } } } @@ -958,19 +973,8 @@ void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi { for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) { - float thisHeight = 0.0f; - float thisAlteredHeight = 0.0f; - float leftHeight = 0.0f; - float leftAlteredHeight = 0.0f; - float upHeight = 0.0f; - float upAlteredHeight = 0.0f; - float rightHeight = 0.0f; - float rightAlteredHeight = 0.0f; - float downHeight = 0.0f; - float downAlteredHeight = 0.0f; float* limitedAlteredHeightXAxis = nullptr; float* limitedAlteredHeightYAxis = nullptr; - updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); @@ -987,27 +991,13 @@ void CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi limitedAlteredHeightYAxis = new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest - if (limitedAlteredHeightXAxis) - { - if (limitedAlteredHeightYAxis) - { - if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); - else - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); - } - else - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); - } - else if (limitedAlteredHeightYAxis) - { - alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); - } + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); delete limitedAlteredHeightXAxis; delete limitedAlteredHeightYAxis; } } } } + return steepnessIsWithinLimits; } void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 43978eea3..b5b642b46 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -101,8 +101,12 @@ namespace CSVRender /// Bind edge vertices to next cells void fixEdges(const CSMWorld::CellCoordinates& cellCoords); - /// Check that the edit doesn't break save format limits, fix if necessary - void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); + ///Limit steepness based on either X or Y and return false if steepness is limited + void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, + float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); + + /// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is within limits + bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); /// Handle brush mechanics for terrain shape selection void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); From 1a3fc435b91d39c78cfd7c416b5db8ece5f318e6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 21 Sep 2019 14:48:16 +0300 Subject: [PATCH 31/79] Fix alterHeight bugs, don't let broken land edit pass. --- apps/opencs/view/render/terrainshapemode.cpp | 111 +++++++++++++------ 1 file changed, 76 insertions(+), 35 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 7c4a68ae5..bcf7b2fc5 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -15,6 +15,7 @@ #include #include +#include #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" @@ -236,18 +237,37 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) QUndoStack& undoStack = document.getUndoStack(); - undoStack.beginMacro ("Edit shape and normal records"); + bool passing = false; + int passes = 0; - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + while (passing == false) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { - limitAlteredHeights(cellCoordinates); - } - std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) - { - limitAlteredHeights(cellCoordinates, true); + passing = true; + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + limitAlteredHeights(cellCoordinates); + } + std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + if (!limitAlteredHeights(cellCoordinates, true)) passing = false; + } + ++passes; + if (passes > 2) + { + Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mAlteredCells.clear(); + return; + } + } } + undoStack.beginMacro ("Edit shape and normal records"); + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); @@ -335,12 +355,8 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) undoStack.endMacro(); mAlteredCells.clear(); - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) - { + if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) paged->resetAllAlteredHeights(); - mTotalDiffY = 0; - } } } @@ -464,12 +480,12 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if(allowLandShapeEditing(cellId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (useTool) { + mAlteredCells.emplace_back(cellCoords); if (mShapeEditTool == 0) { // Get distance from modified land, alter land change based on zoom @@ -490,32 +506,44 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c { if(allowLandShapeEditing(cellUpLeftId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, -1)); - paged->setCellAlteredHeight(cellCoords.move(-1, -1), landSize - 1, landSize - 1, alteredHeight); + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); + if (useTool) mAlteredCells.emplace_back(cornerCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, landSize - 1, landSize - 1, alteredHeight); } } else if (inCellX == 0 && inCellY == landSize - 1) { if(allowLandShapeEditing(cellDownLeftId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 1)); - paged->setCellAlteredHeight(cellCoords.move(-1, 1), landSize - 1, 0, alteredHeight); + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); + if (useTool) mAlteredCells.emplace_back(cornerCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, landSize - 1, 0, alteredHeight); } } else if (inCellX == landSize - 1 && inCellY == 0) { if(allowLandShapeEditing(cellUpRightId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, -1)); - paged->setCellAlteredHeight(cellCoords.move(1, -1), 0, landSize - 1, alteredHeight); + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); + if (useTool) mAlteredCells.emplace_back(cornerCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, landSize - 1, alteredHeight); } } else if (inCellX == landSize - 1 && inCellY == landSize -1) { if(allowLandShapeEditing(cellDownRightId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 1)); - paged->setCellAlteredHeight(cellCoords.move(1, 1), 0, 0, alteredHeight); + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); + if (useTool) mAlteredCells.emplace_back(cornerCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); } } @@ -524,16 +552,22 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c { if(allowLandShapeEditing(cellLeftId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 0)); - paged->setCellAlteredHeight(cellCoords.move(-1, 0), landSize - 1, inCellY, alteredHeight); + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); + if (useTool) mAlteredCells.emplace_back(edgeCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(edgeCellCoords, landSize - 1, inCellY, alteredHeight); } } if (inCellY == 0) { if(allowLandShapeEditing(cellUpId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, -1)); - paged->setCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 1, alteredHeight); + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); + if (useTool) mAlteredCells.emplace_back(edgeCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, landSize - 1, alteredHeight); } } @@ -541,16 +575,22 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c { if(allowLandShapeEditing(cellRightId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 0)); - paged->setCellAlteredHeight(cellCoords.move(1, 0), 0, inCellY, alteredHeight); + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); + if (useTool) mAlteredCells.emplace_back(edgeCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } if (inCellY == landSize - 1) { - if(allowLandShapeEditing(cellUpId)==true) + if(allowLandShapeEditing(cellDownId)==true) { - if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, 1)); - paged->setCellAlteredHeight(cellCoords.move(0, 1), inCellX, 0, alteredHeight); + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); + if (useTool) mAlteredCells.emplace_back(edgeCellCoords); + else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); } } @@ -910,8 +950,7 @@ void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinate bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& landTable = dynamic_cast (*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); @@ -963,7 +1002,8 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi // Limit altered height value based on x or y, whichever is the smallest compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); delete limitedAlteredHeightXAxis; - delete limitedAlteredHeightYAxis; } + delete limitedAlteredHeightYAxis; + } } } @@ -993,7 +1033,8 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi // Limit altered height value based on x or y, whichever is the smallest compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); delete limitedAlteredHeightXAxis; - delete limitedAlteredHeightYAxis; } + delete limitedAlteredHeightYAxis; + } } } } From 8f3c22ccc3507b086ee2bbc2f4ab78169b52c101 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 21 Sep 2019 14:51:04 +0300 Subject: [PATCH 32/79] Remove TerrainShapeMode::fixEdges --- apps/opencs/view/render/terrainshapemode.cpp | 31 -------------------- apps/opencs/view/render/terrainshapemode.hpp | 3 -- 2 files changed, 34 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index bcf7b2fc5..c9638faf7 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -887,36 +887,6 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } } -void CSVRender::TerrainShapeMode::fixEdges(const CSMWorld::CellCoordinates& cellCoords) -{ - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); - std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); - std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); - std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); - std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - if (allowLandShapeEditing(cellId) == true) - { - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); - CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - for(int i = 0; i < landSize; ++i) - { - if(document.getData().getCells().searchId (cellLeftId) != -1 && document.getData().getLand().searchId (cellLeftId) != -1 && landShapePointer[i * landSize] != landLeftShapePointer[i * landSize + landSize - 1]) landShapeNew[i * landSize] = landLeftShapePointer[i * landSize + landSize - 1]; - if(document.getData().getCells().searchId (cellRightId) != -1 && document.getData().getLand().searchId (cellRightId) != -1 && landShapePointer[i * landSize + landSize - 1] != landRightShapePointer[i * landSize]) landShapeNew[i * landSize + landSize - 1] = landRightShapePointer[i * landSize]; - if(document.getData().getCells().searchId (cellUpId) != -1 && document.getData().getLand().searchId (cellUpId) != -1 && landShapePointer[i] != landUpShapePointer[(landSize - 1) * landSize + i]) landShapeNew[i] = landUpShapePointer[(landSize - 1) * landSize + i]; - if(document.getData().getCells().searchId (cellDownId) != -1 && document.getData().getLand().searchId (cellDownId) != -1 && landShapePointer[(landSize - 1) * landSize + i] != landDownShapePointer[i]) landShapeNew[(landSize - 1) * landSize + i] = landDownShapePointer[i]; - } - pushEditToCommand(landShapeNew, document, landTable, cellId); - } -} - void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) { if (limitedAlteredHeightXAxis) @@ -1185,7 +1155,6 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(std::string cellId) if (mode=="Create cell and land, then edit") { document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); - fixEdges(CSMWorld::CellCoordinates::fromId (cellId).first); } } diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index b5b642b46..642c9052b 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -98,9 +98,6 @@ namespace CSVRender float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight); - /// Bind edge vertices to next cells - void fixEdges(const CSMWorld::CellCoordinates& cellCoords); - ///Limit steepness based on either X or Y and return false if steepness is limited void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); From 23e7c71a5a9d3f43c32f0b29082aab6eac5f6f5c Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 21 Sep 2019 17:49:32 +0300 Subject: [PATCH 33/79] Tighter corner checking, clean-up, landSize to ESM::Land::LAND_SIZE. --- apps/opencs/view/render/terrainshapemode.cpp | 149 ++++++++++--------- apps/opencs/view/render/terrainshapemode.hpp | 4 - 2 files changed, 77 insertions(+), 76 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index c9638faf7..e5daf7a9f 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -150,7 +150,7 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - mTargetHeight = landShapePointer[inCellY * landSize + inCellX]; + mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } } @@ -274,17 +274,17 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - for(int i = 0; i < landSize; ++i) + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - for(int j = 0; j < landSize; ++j) + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { if (paged->getCellAlteredHeight(cellCoordinates, i, j)) - landShapeNew[j * landSize + i] = landShapePointer[j * landSize + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); else - landShapeNew[j * landSize + i] = 0; + landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; } } } @@ -300,9 +300,9 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); - for(int i = 0; i < landSize; ++i) + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { - for(int j = 0; j < landSize; ++j) + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { float v1[3]; float v2[3]; @@ -311,26 +311,26 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) v1[0] = 128; v1[1] = 0; - if (i < landSize - 1) v1[2] = landShapePointer[j*landSize+i+1] - landShapePointer[j*landSize+i]; + if (i < ESM::Land::LAND_SIZE - 1) v1[2] = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; if (!noLand && !noCell) - v1[2] = landRightShapePointer[j*landSize+1] - landShapePointer[j*landSize+i]; + v1[2] = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else v1[2] = 0; } v2[0] = 0; v2[1] = 128; - if (j < landSize - 1) v2[2] = landShapePointer[(j+1)*landSize+i] - landShapePointer[j*landSize+i]; + if (j < ESM::Land::LAND_SIZE - 1) v2[2] = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; if (!noLand && !noCell) - v2[2] = landDownShapePointer[landSize+i] - landShapePointer[j*landSize+i]; + v2[2] = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else v2[2] = 0; } @@ -345,9 +345,9 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) normal[1] /= hyp; normal[2] /= hyp; - landNormalsNew[(j*landSize+i)*3+0] = normal[0]; - landNormalsNew[(j*landSize+i)*3+1] = normal[1]; - landNormalsNew[(j*landSize+i)*3+2] = normal[2]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal[0]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal[1]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal[2]; } } if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); @@ -499,52 +499,53 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (mShapeEditTool == 3) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; } - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); // Change values of cornering cells if (inCellX == 0 && inCellY == 0) { - if(allowLandShapeEditing(cellUpLeftId)==true) + if(allowLandShapeEditing(cellUpLeftId) && allowLandShapeEditing(cellLeftId) && allowLandShapeEditing(cellUpId)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); if (useTool) mAlteredCells.emplace_back(cornerCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, landSize - 1, landSize - 1, alteredHeight); - } + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; } - else if (inCellX == 0 && inCellY == landSize - 1) + else if (inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) { - if(allowLandShapeEditing(cellDownLeftId)==true) + if(allowLandShapeEditing(cellDownLeftId) && allowLandShapeEditing(cellLeftId) && allowLandShapeEditing(cellDownId)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); if (useTool) mAlteredCells.emplace_back(cornerCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, landSize - 1, 0, alteredHeight); - } + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); + } else return; } - else if (inCellX == landSize - 1 && inCellY == 0) + else if (inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) { - if(allowLandShapeEditing(cellUpRightId)==true) + if(allowLandShapeEditing(cellUpRightId) && allowLandShapeEditing(cellRightId) && allowLandShapeEditing(cellUpId)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); if (useTool) mAlteredCells.emplace_back(cornerCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, 0, landSize - 1, alteredHeight); - } + paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; } - else if (inCellX == landSize - 1 && inCellY == landSize -1) + else if (inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) { - if(allowLandShapeEditing(cellDownRightId)==true) + if(allowLandShapeEditing(cellDownRightId) && allowLandShapeEditing(cellRightId) && allowLandShapeEditing(cellDownId)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); if (useTool) mAlteredCells.emplace_back(cornerCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); - } + } else return; } // Change values of edging cells @@ -556,7 +557,8 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (useTool) mAlteredCells.emplace_back(edgeCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(edgeCellCoords, landSize - 1, inCellY, alteredHeight); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } if (inCellY == 0) @@ -567,11 +569,12 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (useTool) mAlteredCells.emplace_back(edgeCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(edgeCellCoords, inCellX, landSize - 1, alteredHeight); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); } } - if (inCellX == landSize - 1) + if (inCellX == ESM::Land::LAND_SIZE - 1) { if(allowLandShapeEditing(cellRightId)==true) { @@ -579,10 +582,11 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (useTool) mAlteredCells.emplace_back(edgeCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } - if (inCellY == landSize - 1) + if (inCellY == ESM::Land::LAND_SIZE - 1) { if(allowLandShapeEditing(cellDownId)==true) { @@ -590,6 +594,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (useTool) mAlteredCells.emplace_back(edgeCellCoords); else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); } } @@ -619,7 +624,7 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& float thisAlteredHeight = 0.0f; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); - float thisHeight = landShapePointer[inCellY * landSize + inCellX]; + float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upAlteredHeight = 0.0f; @@ -636,59 +641,59 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - leftHeight = landLeftShapePointer[inCellY * landSize + (landSize - 2)]; - if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, landSize - 2)) - leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY); + leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); } if (inCellY == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - upHeight = landUpShapePointer[(landSize - 2) * landSize + inCellX]; - if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2)) - upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); } if (inCellX > 0) { - leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; + leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); } if (inCellY > 0) { - upHeight = landShapePointer[(inCellY - 1) * landSize + inCellX]; + upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); } - if (inCellX == landSize - 1) + if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - rightHeight = landRightShapePointer[inCellY * landSize + 1]; + rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); } } - if (inCellY == landSize - 1) + if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - downHeight = landDownShapePointer[1 * landSize + inCellX]; + downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); } } - if (inCellX < landSize - 1) + if (inCellX < ESM::Land::LAND_SIZE - 1) { - rightHeight = landShapePointer[inCellY * landSize + inCellX + 1]; + rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); } - if (inCellY < landSize - 1) + if (inCellY < ESM::Land::LAND_SIZE - 1) { - downHeight = landShapePointer[(inCellY + 1) * landSize + inCellX]; + downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); } @@ -727,7 +732,7 @@ void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); - thisHeight = landShapePointer[inCellY * landSize + inCellX]; + thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } } @@ -783,7 +788,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); - *thisHeight = landShapePointer[inCellY * landSize + inCellX] + *thisAlteredHeight; + *thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight; // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found, // which is to prevent unnecessary action at limitHeightChange(). @@ -799,10 +804,10 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); - *leftHeight = landLeftShapePointer[inCellY * landSize + (landSize - 2)]; - if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY)) + *leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY)) { - *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY); + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); *leftHeight += *leftAlteredHeight; } } @@ -814,22 +819,22 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); - *upHeight = landUpShapePointer[(landSize - 2) * landSize + inCellX]; - if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, landSize - 2)) + *upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, ESM::Land::LAND_SIZE - 2)) { - *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); *upHeight += *upAlteredHeight; } } } - if (inCellX == landSize - 1) + if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); if(!noRightCell && !noRightLand) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); - *rightHeight = landRightShapePointer[inCellY * landSize + 1]; + *rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); @@ -837,14 +842,14 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } } } - if (inCellY == landSize - 1) + if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); if(!noDownCell && !noDownLand) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); - *downHeight = landDownShapePointer[landSize + inCellX]; + *downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); @@ -856,28 +861,28 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor //If not at edge, get values from the same cell if (inCellX != 0) { - *leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; + *leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); *leftHeight += *leftAlteredHeight; } if (inCellY != 0) { - *upHeight = landShapePointer[(inCellY - 1) * landSize + inCellX]; + *upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); *upHeight += *upAlteredHeight; } - if (inCellX != landSize - 1) + if (inCellX != ESM::Land::LAND_SIZE - 1) { - *rightHeight = landShapePointer[inCellY * landSize + inCellX + 1]; + *rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); *rightHeight += *rightAlteredHeight; } - if (inCellY != landSize - 1) + if (inCellY != ESM::Land::LAND_SIZE - 1) { - *downHeight = landShapePointer[(inCellY + 1) * landSize + inCellX]; + *downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); *downHeight += *downAlteredHeight; @@ -948,9 +953,9 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi if (reverseMode == false) { - for(int inCellY = 0; inCellY < landSize; ++inCellY) + for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) { - for(int inCellX = 0; inCellX < landSize; ++inCellX) + for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) { float* limitedAlteredHeightXAxis = nullptr; float* limitedAlteredHeightYAxis = nullptr; @@ -979,9 +984,9 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi if (reverseMode == true) { - for(int inCellY = landSize - 1; inCellY >= 0; --inCellY) + for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) { - for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) + for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) { float* limitedAlteredHeightXAxis = nullptr; float* limitedAlteredHeightYAxis = nullptr; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 642c9052b..8efe22709 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -137,10 +137,6 @@ namespace CSVRender int mShapeEditToolStrength; int mTargetHeight; - const int cellSize {ESM::Land::REAL_SIZE}; - const int landSize {ESM::Land::LAND_SIZE}; - const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; - PagedWorldspaceWidget& getPagedWorldspaceWidget(); signals: From 62d50a1f47f6ebe443a4fb3e6af61b13cc596784 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 21 Sep 2019 17:52:07 +0300 Subject: [PATCH 34/79] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad8c814ad..34f37451c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -185,7 +185,6 @@ Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch Feature #4840: Editor: Transient terrain change support - Feature #????: Editor: Land shape editing, land selection Feature #4859: Make water reflections more configurable Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed @@ -212,6 +211,7 @@ Feature #5132: Unique animations for different weapon types Feature #5146: Safe Dispose corpse Feature #5147: Show spell magicka cost in spell buying window + Feature #5170: Editor: Land shape editing, land selection Feature #5193: Weapon sheathing Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption From 55b3fd441809fe691b0d803b4c300463b0aa81b0 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 25 Sep 2019 13:01:52 +0300 Subject: [PATCH 35/79] Use float calculations for circle brush, keep tool options in memory. --- apps/opencs/view/render/terrainshapemode.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index e5daf7a9f..3420149a5 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -80,12 +80,6 @@ void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) { - if(mShapeBrushScenetool) - { - toolbar->removeTool (mShapeBrushScenetool); - delete mShapeBrushScenetool; - mShapeBrushScenetool = 0; - } EditMode::deactivate(toolbar); } @@ -429,7 +423,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); - int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); float distancePerRadius = 1.0f * distance / r; From 1046d5719076711ef150a254fe53b7ffeb44665d Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 25 Sep 2019 13:02:05 +0300 Subject: [PATCH 36/79] Remove unneeded forward declarations. --- apps/opencs/view/widget/scenetoolshapebrush.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index 9048fa536..bdb6b1309 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -30,8 +30,6 @@ namespace CSVRender namespace CSVWidget { - class SceneToolShapeBrush; - /// \brief Layout-box for some brush button settings class ShapeBrushSizeControls : public QGroupBox { @@ -49,8 +47,6 @@ namespace CSVWidget friend class CSVRender::TerrainShapeMode; }; - class SceneToolShapeBrush; - /// \brief Brush settings window class ShapeBrushWindow : public QFrame { From dc7dc93320ffe86f260ec09c5059f465b7ef7c73 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 2 Oct 2019 14:04:15 +0300 Subject: [PATCH 37/79] Remove unused functions, improve formatting, handle terrain edit click --- apps/opencs/view/render/cell.cpp | 5 - apps/opencs/view/render/cell.hpp | 2 - .../view/render/pagedworldspacewidget.cpp | 15 +- .../view/render/pagedworldspacewidget.hpp | 2 - apps/opencs/view/render/terrainshapemode.cpp | 322 ++++++++++-------- apps/opencs/view/render/terrainshapemode.hpp | 6 + apps/opencs/view/render/terrainstorage.cpp | 5 - apps/opencs/view/render/terrainstorage.hpp | 5 +- 8 files changed, 189 insertions(+), 173 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 3915ee4fb..94d7bfa51 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -360,11 +360,6 @@ float CSVRender::Cell::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int in return mTerrainStorage->getSumOfAlteredAndTrueHeight(cellX, cellY, inCellX, inCellY); } -float* CSVRender::Cell::getAlteredHeights() -{ - return mTerrainStorage->getAlteredHeights(); -} - float* CSVRender::Cell::getAlteredHeight(int inCellX, int inCellY) { return mTerrainStorage->getAlteredHeight(inCellX, inCellY); diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 36ac7ff81..281ac6735 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -124,8 +124,6 @@ namespace CSVRender float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); - float* getAlteredHeights(); - float* getAlteredHeight(int inCellX, int inCellY); void resetAlteredHeights(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 21624740e..63c7df909 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -796,26 +796,21 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellC if (searchResult != mCells.end()) return searchResult->second; else - return 0; + return nullptr; } void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) { std::map::iterator searchResult = mCells.find(coords); - if (searchResult != mCells.end()) searchResult->second->setAlteredHeight(inCellX, inCellY, height); -} - -float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeights(const CSMWorld::CellCoordinates& coords) -{ - std::map::iterator searchResult = mCells.find(coords); - if (searchResult != mCells.end()) return searchResult->second->getAlteredHeights(); - return nullptr; + if (searchResult != mCells.end()) + searchResult->second->setAlteredHeight(inCellX, inCellY, height); } float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) { std::map::iterator searchResult = mCells.find(coords); - if (searchResult != mCells.end()) return searchResult->second->getAlteredHeight(inCellX, inCellY); + if (searchResult != mCells.end()) + return searchResult->second->getAlteredHeight(inCellX, inCellY); return nullptr; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index db93e8435..fcc55fe7d 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -128,8 +128,6 @@ namespace CSVRender void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); - float* getCellAlteredHeights(const CSMWorld::CellCoordinates& coords); - float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); void resetAllAlteredHeights(); diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 3420149a5..e5a92a5de 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -93,6 +93,24 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& if (hit.hit && hit.tag == 0) { + if (mShapeEditTool == 4) + setFlattenToolTargetHeight(hit); + if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool > 0) + { + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + if (mShapeEditTool > 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + applyTerrainEditChanges(); + } + + if (mDragMode == InteractionType_PrimarySelect) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + } + + if (mDragMode == InteractionType_SecondarySelect) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + } } if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) @@ -131,21 +149,7 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) mEditingPos = hit.worldPos; mIsEditing = true; if (mShapeEditTool == 4) - { - std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); - std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); - int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); - int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); - - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = - landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - - mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; - } + setFlattenToolTargetHeight(hit); } return true; @@ -216,138 +220,7 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) mIsEditing = false; } - std::sort(mAlteredCells.begin(), mAlteredCells.end()); - std::set removeDuplicates(mAlteredCells.begin(), mAlteredCells.end()); - mAlteredCells.assign(removeDuplicates.begin(), removeDuplicates.end()); - - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); - - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); - - QUndoStack& undoStack = document.getUndoStack(); - - bool passing = false; - int passes = 0; - - while (passing == false) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously - { - passing = true; - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) - { - limitAlteredHeights(cellCoordinates); - } - std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) - { - if (!limitAlteredHeights(cellCoordinates, true)) passing = false; - } - ++passes; - if (passes > 2) - { - Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) - { - paged->resetAllAlteredHeights(); - mAlteredCells.clear(); - return; - } - } - } - - undoStack.beginMacro ("Edit shape and normal records"); - - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) - { - std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) - { - for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) - { - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) - { - if (paged->getCellAlteredHeight(cellCoordinates, i, j)) - landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); - else - landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; - } - } - } - if (allowLandShapeEditing(cellId) == true) pushEditToCommand(landShapeNew, document, landTable, cellId); - } - - for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) - { - std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); - const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); - const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); - const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); - CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); - - for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) - { - for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) - { - float v1[3]; - float v2[3]; - float normal[3]; - float hyp; - - v1[0] = 128; - v1[1] = 0; - if (i < ESM::Land::LAND_SIZE - 1) v1[2] = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - { - bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; - bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; - if (!noLand && !noCell) - v1[2] = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - v1[2] = 0; - } - - v2[0] = 0; - v2[1] = 128; - if (j < ESM::Land::LAND_SIZE - 1) v2[2] = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - { - bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; - bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; - if (!noLand && !noCell) - v2[2] = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - v2[2] = 0; - } - - normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; - normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; - normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; - - hyp = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]) / 127.0f; - - normal[0] /= hyp; - normal[1] /= hyp; - normal[2] /= hyp; - - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal[0]; - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal[1]; - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal[2]; - } - } - if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); - } - undoStack.endMacro(); - mAlteredCells.clear(); + applyTerrainEditChanges(); if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) paged->resetAllAlteredHeights(); @@ -369,6 +242,143 @@ void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) { } + +void CSVRender::TerrainShapeMode::applyTerrainEditChanges() +{ + std::sort(mAlteredCells.begin(), mAlteredCells.end()); + std::set removeDuplicates(mAlteredCells.begin(), mAlteredCells.end()); + mAlteredCells.assign(removeDuplicates.begin(), removeDuplicates.end()); + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + QUndoStack& undoStack = document.getUndoStack(); + + bool passing = false; + int passes = 0; + + while (passing == false) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously + { + passing = true; + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + limitAlteredHeights(cellCoordinates); + } + std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + if (!limitAlteredHeights(cellCoordinates, true)) passing = false; + } + ++passes; + if (passes > 2) + { + Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mAlteredCells.clear(); + return; + } + } + } + + undoStack.beginMacro ("Edit shape and normal records"); + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (paged->getCellAlteredHeight(cellCoordinates, i, j)) + landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + else + landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; + } + } + } + if (allowLandShapeEditing(cellId) == true) pushEditToCommand(landShapeNew, document, landTable, cellId); + } + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + float v1[3]; + float v2[3]; + float normal[3]; + float hyp; + + v1[0] = 128; + v1[1] = 0; + if (i < ESM::Land::LAND_SIZE - 1) v1[2] = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + if (!noLand && !noCell) + v1[2] = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + v1[2] = 0; + } + + v2[0] = 0; + v2[1] = 128; + if (j < ESM::Land::LAND_SIZE - 1) v2[2] = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + if (!noLand && !noCell) + v2[2] = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + v2[2] = 0; + } + + normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + hyp = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]) / 127.0f; + + normal[0] /= hyp; + normal[1] /= hyp; + normal[2] /= hyp; + + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal[0]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal[1]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal[2]; + } + } + if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); + } + undoStack.endMacro(); + mAlteredCells.clear(); +} + void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; @@ -460,6 +470,24 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair } } +void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHitResult& hit) +{ + std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; +} + + void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 8efe22709..63e14de31 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -81,9 +81,15 @@ namespace CSVRender virtual void dragWheel (int diff, double speedFactor); virtual void dragMoveEvent (QDragMoveEvent *event); + /// Move pending alteredHeights changes to omwgame/omeaddon -data + void applyTerrainEditChanges(); + /// Handle brush mechanics for shape editing void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + /// set the target height for flatten tool + void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); + /// Do a single height alteration for transient shape edit map void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true); diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 774c204ed..0eb1f9ab3 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -73,11 +73,6 @@ namespace CSVRender } - float* TerrainStorage::getAlteredHeights() - { - return mAlteredHeight; - } - float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) { return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 4740f13bb..5f808d843 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -1,6 +1,8 @@ #ifndef OPENCS_RENDER_TERRAINSTORAGE_H #define OPENCS_RENDER_TERRAINSTORAGE_H +#include + #include #include "../../model/world/data.hpp" @@ -16,11 +18,10 @@ namespace CSVRender { public: TerrainStorage(const CSMWorld::Data& data); - float mAlteredHeight[ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE]; + std::array mAlteredHeight; void setAlteredHeight(int inCellX, int inCellY, float heightMap); void resetHeights(); float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); - float* getAlteredHeights(); float* getAlteredHeight(int inCellX, int inCellY); private: From 3599e804e100ec4604669142212c5b3dd7606046 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 2 Oct 2019 14:36:30 +0300 Subject: [PATCH 38/79] Fix the default value of mBrushSize --- apps/opencs/view/widget/scenetoolshapebrush.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 3e11dad97..3e2f4e99d 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -59,7 +59,7 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), mBrushShape(0), - mBrushSize(0), + mBrushSize(1), mDocument(document) { mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); From 69083369f869c1bae0ea8b71aab81b078cc80502 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 2 Oct 2019 15:00:12 +0300 Subject: [PATCH 39/79] use std::fill instead of for --- apps/opencs/view/render/terrainstorage.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 0eb1f9ab3..5e4ca1365 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -50,13 +50,7 @@ namespace CSVRender void TerrainStorage::resetHeights() { - for (int x = 0; x < ESM::Land::LAND_SIZE; ++x) - { - for (int y = 0; y < ESM::Land::LAND_SIZE; ++y) - { - mAlteredHeight[y*ESM::Land::LAND_SIZE + x] = 0; - } - } + std::fill(std::begin(mAlteredHeight), std::end(mAlteredHeight), 0); } float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) From 0ce971c1bb71e1c3a7b0f03263267819dff1967d Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 3 Oct 2019 12:54:37 +0300 Subject: [PATCH 40/79] Revert back to less aggressive component-level changes. --- apps/opencs/view/render/terrainstorage.cpp | 195 ++++----------------- apps/opencs/view/render/terrainstorage.hpp | 11 +- components/esmterrain/storage.cpp | 108 +++++++++++- components/esmterrain/storage.hpp | 125 ++----------- 4 files changed, 160 insertions(+), 279 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 5e4ca1365..3b314935d 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -72,173 +72,42 @@ namespace CSVRender return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; } - void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, - osg::ref_ptr positions, - osg::ref_ptr normals, - osg::ref_ptr colours) - { - // LOD level n means every 2^n-th vertex is kept - size_t increment = static_cast(1) << lodLevel; - - osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); - - int startCellX = static_cast(std::floor(origin.x())); - int startCellY = static_cast(std::floor(origin.y())); - - size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); - - positions->resize(numVerts*numVerts); - normals->resize(numVerts*numVerts); - colours->resize(numVerts*numVerts); - - osg::Vec3f normal; - osg::Vec4ub color; - - float vertY = 0; - float vertX = 0; - - ESMTerrain::LandCache cache; - - float vertY_ = 0; // of current cell corner - for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) - { - float vertX_ = 0; // of current cell corner - for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) - { - const ESMTerrain::LandObject* land = ESMTerrain::Storage::getLand(cellX, cellY, cache); - const ESM::Land::LandData *heightData = 0; - const ESM::Land::LandData *normalData = 0; - const ESM::Land::LandData *colourData = 0; - if (land) - { - heightData = land->getData(ESM::Land::DATA_VHGT); - normalData = land->getData(ESM::Land::DATA_VNML); - colourData = land->getData(ESM::Land::DATA_VCLR); - } - - int rowStart = 0; - int colStart = 0; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - // This is only relevant if we're creating a chunk spanning multiple cells - if (vertY_ != 0) - colStart += increment; - if (vertX_ != 0) - rowStart += increment; - - // Only relevant for chunks smaller than (contained in) one cell - rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; - colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; - int rowEnd = std::min(static_cast(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); - int colEnd = std::min(static_cast(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); - - vertY = vertY_; - for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); - assert(col >= 0 && col < ESM::Land::LAND_SIZE); - - assert (vertX < numVerts); - assert (vertY < numVerts); - - float height = defaultHeight; - if (heightData) - height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; - - (*positions)[static_cast(vertX*numVerts + vertY)] - = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, - (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, - height + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]); - - if (normalData) - { - for (int i=0; i<3; ++i) - normal[i] = normalData->mNormals[srcArrayIndex+i]; - - normal.normalize(); - } - else - normal = osg::Vec3f(0,0,1); - - // Normals apparently don't connect seamlessly between cells - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixNormal(normal, cellX, cellY, col, row, cache); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) - averageNormal(normal, cellX, cellY, col, row, cache); - - assert(normal.z() > 0); - - (*normals)[static_cast(vertX*numVerts + vertY)] = normal; - - if (colourData) - { - for (int i=0; i<3; ++i) - color[i] = colourData->mColours[srcArrayIndex+i]; - } - else - { - color.r() = 255; - color.g() = 255; - color.b() = 255; - } - - // Highlight broken height changes - if ( ((col > 0 && row > 0) && - ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + - mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) || - abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) || - ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && - ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] + - mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) || - abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 ))) - { - color.r() = 255; - color.g() = 0; - color.b() = 0; - } - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) - fixColour(color, cellX, cellY, col, row, cache); - - color.a() = 255; - - (*colours)[static_cast(vertX*numVerts + vertY)] = color; - - ++vertX; - } - ++vertY; - } - vertX_ = vertX; - } - vertY_ = vertY; - - assert(vertX_ == numVerts); // Ensure we covered whole area - } - assert(vertY_ == numVerts); // Ensure we covered whole area*/ - } - void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) { // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells throw std::runtime_error("getBounds not implemented"); } + void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const + { + // Highlight broken height changes + if ( ((col > 0 && row > 0) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 ))) + { + color.r() = 255; + color.g() = 0; + color.b() = 0; + } + } + + float TerrainStorage::getAlteredHeight(int col, int row) const + { + return mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + } } diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 5f808d843..fe2d94b4a 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -9,8 +9,6 @@ namespace CSVRender { - class LandCache; - /** * @brief A bridge between the terrain component and OpenCS's terrain data storage. */ @@ -30,13 +28,10 @@ namespace CSVRender virtual osg::ref_ptr getLand (int cellX, int cellY) override; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; - /// Draws temporarily altered land (transient change support) - void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, - osg::ref_ptr positions, - osg::ref_ptr normals, - osg::ref_ptr colours) override; - virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + + void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; + float getAlteredHeight(int col, int row) const override; }; } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 151f0ff91..52af530f5 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,6 +16,13 @@ namespace ESMTerrain { + class LandCache + { + public: + typedef std::map, osg::ref_ptr > Map; + Map mMap; + }; + LandObject::LandObject() : mLand(nullptr) , mLoadFlags(0) @@ -91,6 +98,82 @@ namespace ESMTerrain return false; } + void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; + if (data) + { + normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalize(); + } + else + normal = osg::Vec3f(0,0,1); + } + + void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + osg::Vec3f n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row, cache); + fixNormal(n2, cellX, cellY, col-1, row, cache); + fixNormal(n3, cellX, cellY, col, row+1, cache); + fixNormal(n4, cellX, cellY, col, row-1, cache); + normal = (n1+n2+n3+n4); + normal.normalize(); + } + + void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; + if (data) + { + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; + } + else + { + color.r() = 255; + color.g() = 255; + color.b() = 255; + } + } + void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, @@ -172,7 +255,7 @@ namespace ESMTerrain (*positions)[static_cast(vertX*numVerts + vertY)] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, - height); + height + getAlteredHeight(col, row)); if (normalData) { @@ -208,6 +291,8 @@ namespace ESMTerrain color.b() = 255; } + adjustColor(col, row, heightData, color); //Does nothing by default, override in OpenMW-CS + // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixColour(color, cellX, cellY, col, row, cache); @@ -438,6 +523,27 @@ namespace ESMTerrain } + const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) + { + LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); + if (found != cache.mMap.end()) + return found->second; + else + { + found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; + return found->second; + } + } + + void Storage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const + { + } + + float Storage::getAlteredHeight(int col, int row) const + { + return 0; + } + Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { OpenThreads::ScopedLock lock(mLayerInfoMutex); diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 019bf0bab..65e531e5c 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -5,9 +5,6 @@ #include -#include -#include - #include #include @@ -21,6 +18,8 @@ namespace VFS namespace ESMTerrain { + class LandCache; + /// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use class LandObject : public osg::Object { @@ -51,13 +50,6 @@ namespace ESMTerrain ESM::Land::LandData mData; }; - class LandCache - { - public: - typedef std::map, osg::ref_ptr > Map; - Map mMap; - }; - /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// into the terrain component, converting it on the fly as needed. class Storage : public Terrain::Storage @@ -117,9 +109,25 @@ namespace ESMTerrain virtual int getBlendmapScale(float chunkSize); + float getVertexHeight (const ESM::Land::LandData* data, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return data->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + private: const VFS::Manager* mVFS; + inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); + inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); + + inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); + + virtual void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const; + virtual float getAlteredHeight(int col, int row) const; + // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair @@ -139,103 +147,6 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); - - protected: - - inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; - if (data) - { - normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalize(); - } - else - normal = osg::Vec3f(0,0,1); - }; - - inline void fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; - if (data) - { - color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; - color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; - color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; - } - else - { - color.r() = 255; - color.g() = 255; - color.b() = 255; - } - }; - - inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - osg::Vec3f n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row, cache); - fixNormal(n2, cellX, cellY, col-1, row, cache); - fixNormal(n3, cellX, cellY, col, row+1, cache); - fixNormal(n4, cellX, cellY, col, row-1, cache); - normal = (n1+n2+n3+n4); - normal.normalize(); - }; - - inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return data->mHeights[y * ESM::Land::LAND_SIZE + x]; - }; - - inline const LandObject* getLand(int cellX, int cellY, LandCache& cache) - { - LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); - if (found != cache.mMap.end()) - return found->second; - else - { - found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; - return found->second; - } - }; }; } From 45b0f034c3a415180be2625c1fd27e5929d556b1 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Thu, 3 Oct 2019 23:22:19 +0300 Subject: [PATCH 41/79] Split complex if-logic into multiple reasonably named functions --- apps/opencs/view/render/terrainstorage.cpp | 83 +++++++++++++++++----- apps/opencs/view/render/terrainstorage.hpp | 12 ++++ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 3b314935d..8938866e3 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -78,27 +78,74 @@ namespace CSVRender throw std::runtime_error("getBounds not implemented"); } + int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)]; + } + + int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[col*ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row + 1)]; + } + + int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); + } + + bool TerrainStorage::LeftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + { + return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit || + getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; + } + + bool TerrainStorage::RightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + { + return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit || + getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; + } + void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const { // Highlight broken height changes - if ( ((col > 0 && row > 0) && - ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + - mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) || - abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) || - ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && - ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] + - mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) || - abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - - (heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + - mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 ))) + int heightWarningLimit = 1024; + if (((col > 0 && row > 0) && LeftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && RightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) { color.r() = 255; color.g() = 0; diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index fe2d94b4a..703cd9be2 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -30,6 +30,18 @@ namespace CSVRender virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const; + bool LeftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + bool RightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; float getAlteredHeight(int col, int row) const override; }; From bccf36fdbc9eaa3f1e246961d52cc50e742a7820 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 00:30:58 +0300 Subject: [PATCH 42/79] Convert normals calculations mostly to osg::Vec3f --- apps/opencs/view/render/terrainshapemode.cpp | 45 ++++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index e5a92a5de..0ce009a36 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -327,50 +328,48 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { - float v1[3]; - float v2[3]; - float normal[3]; + osg::Vec3f v1; + osg::Vec3f v2; + osg::Vec3f normal; float hyp; - v1[0] = 128; - v1[1] = 0; - if (i < ESM::Land::LAND_SIZE - 1) v1[2] = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v1.x() = 128; + v1.y() = 0; + if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; if (!noLand && !noCell) - v1[2] = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else - v1[2] = 0; + v1.z() = 0; } - v2[0] = 0; - v2[1] = 128; - if (j < ESM::Land::LAND_SIZE - 1) v2[2] = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v2.x() = 0; + v2.y() = 128; + if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; if (!noLand && !noCell) - v2[2] = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else - v2[2] = 0; + v2.z() = 0; } - normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; - normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; - normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + normal.y() = v1.z()*v2.x() - v1.x()*v2.z(); + normal.x() = v1.y()*v2.z() - v1.z()*v2.y(); + normal.z() = v1.x()*v2.y() - v1.y()*v2.x(); - hyp = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]) / 127.0f; + hyp = normal.length() / 127.0f; - normal[0] /= hyp; - normal[1] /= hyp; - normal[2] /= hyp; + normal /= hyp; - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal[0]; - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal[1]; - landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal[2]; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal.x(); + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal.y(); + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal.z(); } } if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); From 6a44cae5724b654a8b5bdec673cfa71ebca9dba6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 00:34:07 +0300 Subject: [PATCH 43/79] white iterator loop to C++11 range-based for loop. --- apps/opencs/view/render/pagedworldspacewidget.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 63c7df909..b5d9234e4 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -816,13 +816,8 @@ float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::Ce void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights() { - std::map::iterator iter (mCells.begin()); - - while (iter!=mCells.end()) - { - iter->second->resetAlteredHeights(); - ++iter; - } + for (const auto& cell : mCells) + cell.second->resetAlteredHeights(); } std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( From da4abcd7c1997806ae6718cf13a4aac42ebe3893 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 00:42:16 +0300 Subject: [PATCH 44/79] std::set to std::unique and erase. --- apps/opencs/view/render/terrainshapemode.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 0ce009a36..bb01247dd 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -247,8 +247,7 @@ void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { std::sort(mAlteredCells.begin(), mAlteredCells.end()); - std::set removeDuplicates(mAlteredCells.begin(), mAlteredCells.end()); - mAlteredCells.assign(removeDuplicates.begin(), removeDuplicates.end()); + mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( From 388edfd8ccbebdb8a5d445bd40fb5180c2d17572 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 01:22:47 +0300 Subject: [PATCH 45/79] change manual memory management to std::unique_ptr --- apps/opencs/view/render/terrainshapemode.cpp | 32 +++++++++----------- 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index bb01247dd..052a5b67e 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -977,27 +977,25 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi { for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) { - float* limitedAlteredHeightXAxis = nullptr; - float* limitedAlteredHeightYAxis = nullptr; + std::unique_ptr limitedAlteredHeightXAxis(nullptr); + std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (leftHeight - thisHeight > limitHeightChange) - limitedAlteredHeightXAxis = new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightXAxis.reset(new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (leftHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightXAxis = new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightXAxis.reset(new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Check for height limits on y-axis if (upHeight - thisHeight > limitHeightChange) - limitedAlteredHeightYAxis = new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightYAxis.reset(new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (upHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightYAxis = new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightYAxis.reset(new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Limit altered height value based on x or y, whichever is the smallest - compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); - delete limitedAlteredHeightXAxis; - delete limitedAlteredHeightYAxis; + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } @@ -1008,27 +1006,25 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi { for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) { - float* limitedAlteredHeightXAxis = nullptr; - float* limitedAlteredHeightYAxis = nullptr; + std::unique_ptr limitedAlteredHeightXAxis(nullptr); + std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (rightHeight - thisHeight > limitHeightChange) - limitedAlteredHeightXAxis = new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightXAxis.reset(new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (rightHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightXAxis = new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightXAxis.reset(new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Check for height limits on y-axis if (downHeight - thisHeight > limitHeightChange) - limitedAlteredHeightYAxis = new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightYAxis.reset(new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); else if (downHeight - thisHeight < -limitHeightChange) - limitedAlteredHeightYAxis = new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); + limitedAlteredHeightYAxis.reset(new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); // Limit altered height value based on x or y, whichever is the smallest - compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis, limitedAlteredHeightYAxis, &steepnessIsWithinLimits); - delete limitedAlteredHeightXAxis; - delete limitedAlteredHeightYAxis; + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } From c031543420c50966045850c070750f2a7acec41e Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 12:07:47 +0300 Subject: [PATCH 46/79] use enum for brush shapes --- apps/opencs/view/render/terrainshapemode.cpp | 18 +++++++++--------- apps/opencs/view/render/terrainshapemode.hpp | 8 ++++++++ 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 052a5b67e..2590a10c5 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -393,7 +393,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if(allowLandShapeEditing(cellId)==true) { - if (mBrushShape == 0) + if (mBrushShape == BrushShape_Point) { int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); @@ -403,7 +403,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } - if (mBrushShape == 1) + if (mBrushShape == BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -421,7 +421,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair } } - if (mBrushShape == 2) + if (mBrushShape == BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -447,7 +447,7 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair } } } - if (mBrushShape == 3) + if (mBrushShape == BrushShape_Custom) { if(!mCustomBrushShape.empty()) { @@ -1037,12 +1037,12 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& int r = mBrushSize / 2; std::vector> selections; - if (mBrushShape == 0) + if (mBrushShape == BrushShape_Point) { selections.emplace_back(vertexCoords); } - if (mBrushShape == 1) + if (mBrushShape == BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -1053,7 +1053,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } } - if (mBrushShape == 2) + if (mBrushShape == BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -1067,7 +1067,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } } - if (mBrushShape == 3) + if (mBrushShape == BrushShape_Custom) { if(!mCustomBrushShape.empty()) { @@ -1196,7 +1196,7 @@ void CSVRender::TerrainShapeMode::setBrushShape(int brushShape) mBrushShape = brushShape; //Set custom brush shape - if (mBrushShape == 3 && !mTerrainShapeSelection->getTerrainSelection().empty()) + if (mBrushShape == BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); int selectionCenterX = 0; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 63e14de31..cec40b136 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -46,6 +46,14 @@ namespace CSVRender InteractionType_None }; + enum BrushShape + { + BrushShape_Point = 1, + BrushShape_Square = 2, + BrushShape_Circle = 3, + BrushShape_Custom = 4 + }; + /// Editmode for terrain shape grid TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); From bae8636ec068944ab8b9424eb453c0e5c225cfb7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 12:43:30 +0300 Subject: [PATCH 47/79] Fix brusshape enum values --- apps/opencs/view/render/terrainshapemode.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index cec40b136..b6d2ffde1 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -48,10 +48,10 @@ namespace CSVRender enum BrushShape { - BrushShape_Point = 1, - BrushShape_Square = 2, - BrushShape_Circle = 3, - BrushShape_Custom = 4 + BrushShape_Point = 0, + BrushShape_Square = 1, + BrushShape_Circle = 2, + BrushShape_Custom = 3 }; /// Editmode for terrain shape grid From 4f9ec24e4154258acf0b43f8b9034b17179a69a6 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 12:51:57 +0300 Subject: [PATCH 48/79] use enum for brushshape at scenetoolshapebrush --- apps/opencs/view/widget/scenetoolshapebrush.cpp | 10 +++++----- apps/opencs/view/widget/scenetoolshapebrush.hpp | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 3e2f4e99d..bc7b04c73 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -58,7 +58,7 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), - mBrushShape(0), + mBrushShape(BrushShape_Point), mBrushSize(1), mDocument(document) { @@ -151,10 +151,10 @@ void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) void CSVWidget::ShapeBrushWindow::setBrushShape() { - if(mButtonPoint->isChecked()) mBrushShape = 0; - if(mButtonSquare->isChecked()) mBrushShape = 1; - if(mButtonCircle->isChecked()) mBrushShape = 2; - if(mButtonCustom->isChecked()) mBrushShape = 3; + if(mButtonPoint->isChecked()) mBrushShape = BrushShape_Point; + if(mButtonSquare->isChecked()) mBrushShape = BrushShape_Square; + if(mButtonCircle->isChecked()) mBrushShape = BrushShape_Circle; + if(mButtonCustom->isChecked()) mBrushShape = BrushShape_Custom; emit passBrushShape(mBrushShape); } diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index bdb6b1309..44fd66df1 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -53,6 +53,15 @@ namespace CSVWidget Q_OBJECT public: + + enum BrushShape + { + BrushShape_Point = 0, + BrushShape_Square = 1, + BrushShape_Circle = 2, + BrushShape_Custom = 3 + }; + ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); void configureButtonInitialSettings(QPushButton *button); From 5b9debc5545d7c85a955c3449ca2c63c58bcc042 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 4 Oct 2019 12:53:54 +0300 Subject: [PATCH 49/79] use enum for mbrushshape initialization --- apps/opencs/view/render/terrainshapemode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 2590a10c5..382bf186c 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -45,7 +45,7 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), mBrushSize(0), - mBrushShape(0), + mBrushShape(BrushShape_Point), mShapeBrushScenetool(0), mDragMode(InteractionType_None), mParentNode(parentNode), From 8acfa2600f30d04f948fd6607711ca5babad3c91 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 7 Oct 2019 02:57:09 +0300 Subject: [PATCH 50/79] Terrain shape editing related fixes --- CHANGELOG_PR.md | 2 + apps/opencs/model/world/columnimp.cpp | 4 +- apps/opencs/view/render/terrainshapemode.cpp | 121 +++++++++++------- apps/opencs/view/render/terrainshapemode.hpp | 27 ++-- apps/opencs/view/render/terrainstorage.cpp | 8 +- apps/opencs/view/render/terrainstorage.hpp | 4 +- apps/opencs/view/widget/brushshapes.hpp | 14 ++ .../view/widget/scenetoolshapebrush.cpp | 15 ++- .../view/widget/scenetoolshapebrush.hpp | 17 +-- 9 files changed, 126 insertions(+), 86 deletions(-) create mode 100644 apps/opencs/view/widget/brushshapes.hpp diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 8862a8448..71677af50 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -40,6 +40,8 @@ New Features: New Editor Features: - "Faction Ranks" table for "Faction" records (#4209) +- Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840) +- Land heightmap/shape editing and vertex selection (#5170) Bug Fixes: - Scripted Items cannot be stacked anymore to avoid multiple script execution (#2969) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index b202a97d9..e610ef223 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -89,10 +89,12 @@ namespace CSMWorld DataType values(Size, 0); - if (land.isDataLoaded(Land::DATA_WNAM)) + if (land.mDataTypes & Land::DATA_WNAM) { for (int i = 0; i < Size; ++i) + { values[i] = land.mWnam[i]; + } } QVariant variant; diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 382bf186c..171ad5a27 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -18,6 +18,7 @@ #include #include +#include "../widget/brushshapes.hpp" #include "../widget/modebutton.hpp" #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" @@ -44,14 +45,14 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), - mBrushSize(0), - mBrushShape(BrushShape_Point), + mBrushSize(1), + mBrushShape(CSVWidget::BrushShape_Point), mShapeBrushScenetool(0), mDragMode(InteractionType_None), mParentNode(parentNode), mIsEditing(false), mTotalDiffY(0), - mShapeEditTool(0), + mShapeEditTool(ShapeEditTool_Drag), mShapeEditToolStrength(8), mTargetHeight(0) { @@ -69,7 +70,7 @@ void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate())); connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); - connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setBrushShape(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int))); connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int))); @@ -94,12 +95,12 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& if (hit.hit && hit.tag == 0) { - if (mShapeEditTool == 4) + if (mShapeEditTool == ShapeEditTool_Flatten) setFlattenToolTargetHeight(hit); - if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool > 0) + if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag) { std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); - if (mShapeEditTool > 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } @@ -149,7 +150,7 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) { mEditingPos = hit.worldPos; mIsEditing = true; - if (mShapeEditTool == 4) + if (mShapeEditTool == ShapeEditTool_Flatten) setFlattenToolTargetHeight(hit); } @@ -194,8 +195,8 @@ void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); mTotalDiffY += diffY; - if (mIsEditing == true && mShapeEditTool == 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); - if (mIsEditing == true && mShapeEditTool > 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + if (mIsEditing == true && mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + if (mIsEditing == true && mShapeEditTool != ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); } if (mDragMode == InteractionType_PrimarySelect) @@ -256,6 +257,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); QUndoStack& undoStack = document.getUndoStack(); @@ -296,7 +298,9 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer); for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) @@ -311,7 +315,15 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() } } } - if (allowLandShapeEditing(cellId) == true) pushEditToCommand(landShapeNew, document, landTable, cellId); + for(int i = 0; i < ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE; ++i) + { + mapLodShapeNew[i] = landMapLodPointer[i]; //TO-DO: generate a new mWnam based on new heights + } + if (allowLandShapeEditing(cellId) == true) + { + pushEditToCommand(landShapeNew, document, landTable, cellId); + pushLodToCommand(mapLodShapeNew, document, landTable, cellId); + } } for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) @@ -358,9 +370,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() v2.z() = 0; } - normal.y() = v1.z()*v2.x() - v1.x()*v2.z(); - normal.x() = v1.y()*v2.z() - v1.z()*v2.y(); - normal.z() = v1.x()*v2.y() - v1.y()*v2.x(); + normal = v1 ^ v2; hyp = normal.length() / 127.0f; @@ -388,22 +398,22 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { - if (mShapeEditTool == 0) paged->resetAllAlteredHeights(); + if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); } if(allowLandShapeEditing(cellId)==true) { - if (mBrushShape == BrushShape_Point) + if (mBrushShape == CSVWidget::BrushShape_Point) { int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); - if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } - if (mBrushShape == BrushShape_Square) + if (mBrushShape == CSVWidget::BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -413,15 +423,15 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); - if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } - if (mBrushShape == BrushShape_Circle) + if (mBrushShape == CSVWidget::BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -436,18 +446,19 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); float distancePerRadius = 1.0f * distance / r; float smoothedByDistance = 0.0f; - if (mShapeEditTool == 0) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); - if (mShapeEditTool == 1 || mShapeEditTool == 2) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); if (distance <= r) { - if (mShapeEditTool >= 0 && mShapeEditTool < 3) alterHeight(cellCoords, x, y, smoothedByDistance); - if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Drag || mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } } - if (mBrushShape == BrushShape_Custom) + if (mBrushShape == CSVWidget::BrushShape_Custom) { if(!mCustomBrushShape.empty()) { @@ -457,10 +468,10 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); - if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } @@ -506,7 +517,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if (useTool) { mAlteredCells.emplace_back(cellCoords); - if (mShapeEditTool == 0) + if (mShapeEditTool == ShapeEditTool_Drag) { // Get distance from modified land, alter land change based on zoom osg::Vec3d eye, center, up; @@ -514,9 +525,9 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c osg::Vec3d distance = eye - mEditingPos; alteredHeight = alteredHeight * (distance.length() / 500); } - if (mShapeEditTool == 1) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; - if (mShapeEditTool == 2) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; - if (mShapeEditTool == 3) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; } if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) @@ -1037,12 +1048,12 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& int r = mBrushSize / 2; std::vector> selections; - if (mBrushShape == BrushShape_Point) + if (mBrushShape == CSVWidget::BrushShape_Point) { selections.emplace_back(vertexCoords); } - if (mBrushShape == BrushShape_Square) + if (mBrushShape == CSVWidget::BrushShape_Square) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -1053,7 +1064,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } } - if (mBrushShape == BrushShape_Circle) + if (mBrushShape == CSVWidget::BrushShape_Circle) { for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { @@ -1067,7 +1078,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } } - if (mBrushShape == BrushShape_Custom) + if (mBrushShape == CSVWidget::BrushShape_Custom) { if(!mCustomBrushShape.empty()) { @@ -1084,7 +1095,7 @@ void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& } void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId) + CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); @@ -1096,7 +1107,7 @@ void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsC } void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId) + CSMWorld::IdTable& landTable, const std::string& cellId) { QVariant changedLand; changedLand.setValue(newLandGrid); @@ -1107,7 +1118,19 @@ void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandN undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); } -bool CSVRender::TerrainShapeMode::allowLandShapeEditing(std::string cellId) +void CSVRender::TerrainShapeMode::pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId) +{ + QVariant changedLod; + changedLod.setValue(newLandMapLod); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandMapLodIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLod)); +} + +bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( @@ -1191,12 +1214,12 @@ void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) mBrushSize = brushSize; } -void CSVRender::TerrainShapeMode::setBrushShape(int brushShape) +void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape) { mBrushShape = brushShape; //Set custom brush shape - if (mBrushShape == BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) + if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) { auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); int selectionCenterX = 0; diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index b6d2ffde1..912aec2ce 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -12,11 +12,11 @@ #ifndef Q_MOC_RUN #include "../../model/world/data.hpp" #include "../../model/world/land.hpp" - #include "../../model/doc/document.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtable.hpp" #include "../../model/world/landtexture.hpp" +#include "../widget/brushshapes.hpp" #endif #include "terrainselection.hpp" @@ -46,12 +46,13 @@ namespace CSVRender InteractionType_None }; - enum BrushShape + enum ShapeEditTool { - BrushShape_Point = 0, - BrushShape_Square = 1, - BrushShape_Circle = 2, - BrushShape_Custom = 3 + ShapeEditTool_Drag = 0, + ShapeEditTool_PaintToRaise = 1, + ShapeEditTool_PaintToLower = 2, + ShapeEditTool_Smooth = 3, + ShapeEditTool_Flatten = 4 }; /// Editmode for terrain shape grid @@ -124,20 +125,24 @@ namespace CSVRender /// Push terrain shape edits to command macro void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId); + CSMWorld::IdTable& landTable, const std::string& cellId); /// Push land normals edits to command macro void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, - CSMWorld::IdTable& landTable, std::string cellId); + CSMWorld::IdTable& landTable, const std::string& cellId); + + /// Generate new land map LOD + void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId); /// Create new cell and land if needed - bool allowLandShapeEditing(std::string textureFileName); + bool allowLandShapeEditing(const std::string& textureFileName); private: std::string mCellId; std::string mBrushTexture; int mBrushSize; - int mBrushShape; + CSVWidget::BrushShape mBrushShape; std::vector> mCustomBrushShape; CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool; int mDragMode; @@ -158,7 +163,7 @@ namespace CSVRender public slots: void setBrushSize(int brushSize); - void setBrushShape(int brushShape); + void setBrushShape(CSVWidget::BrushShape brushShape); void setShapeEditTool(int shapeEditTool); void setShapeEditToolStrength(int shapeEditToolStrength); }; diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 8938866e3..7f5dc1208 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -128,13 +128,13 @@ namespace CSVRender return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); } - bool TerrainStorage::LeftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + bool TerrainStorage::leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const { return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit || getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; } - bool TerrainStorage::RightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + bool TerrainStorage::rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const { return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit || getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; @@ -144,8 +144,8 @@ namespace CSVRender { // Highlight broken height changes int heightWarningLimit = 1024; - if (((col > 0 && row > 0) && LeftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || - ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && RightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) + if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) { color.r() = 255; color.g() = 0; diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 703cd9be2..9e5226d5f 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -39,8 +39,8 @@ namespace CSVRender int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const; int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const; - bool LeftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; - bool RightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; float getAlteredHeight(int col, int row) const override; diff --git a/apps/opencs/view/widget/brushshapes.hpp b/apps/opencs/view/widget/brushshapes.hpp new file mode 100644 index 000000000..2e931157c --- /dev/null +++ b/apps/opencs/view/widget/brushshapes.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_WIDGET_BRUSHSHAPES_H +#define CSV_WIDGET_BRUSHSHAPES_H + +namespace CSVWidget +{ + enum BrushShape + { + BrushShape_Point, + BrushShape_Square, + BrushShape_Circle, + BrushShape_Custom + }; +} +#endif diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index bc7b04c73..3b9d908ac 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -21,6 +21,7 @@ #include #include +#include "brushshapes.hpp" #include "scenetool.hpp" #include "../../model/doc/document.hpp" @@ -58,7 +59,7 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), - mBrushShape(BrushShape_Point), + mBrushShape(CSVWidget::BrushShape_Point), mBrushSize(1), mDocument(document) { @@ -169,7 +170,7 @@ CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const mShapeBrushWindow(new ShapeBrushWindow(document, this)) { setAcceptDrops(true); - connect(mShapeBrushWindow, SIGNAL(passBrushShape(int)), this, SLOT(setButtonIcon(int))); + connect(mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); setButtonIcon(mShapeBrushWindow->mBrushShape); mPanel = new QFrame (this, Qt::Popup); @@ -199,31 +200,31 @@ CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const } -void CSVWidget::SceneToolShapeBrush::setButtonIcon (int brushShape) +void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushShape) { QString tooltip = "Change brush settings

Currently selected: "; switch (brushShape) { - case 0: + case BrushShape_Point: setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); tooltip += mShapeBrushWindow->toolTipPoint; break; - case 1: + case BrushShape_Square: setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); tooltip += mShapeBrushWindow->toolTipSquare; break; - case 2: + case BrushShape_Circle: setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); tooltip += mShapeBrushWindow->toolTipCircle; break; - case 3: + case BrushShape_Custom: setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); tooltip += mShapeBrushWindow->toolTipCustom; diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index 44fd66df1..19d24111d 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -16,6 +16,7 @@ #include #ifndef Q_MOC_RUN +#include "brushshapes.hpp" #include "scenetool.hpp" #include "../../model/doc/document.hpp" @@ -54,24 +55,16 @@ namespace CSVWidget public: - enum BrushShape - { - BrushShape_Point = 0, - BrushShape_Square = 1, - BrushShape_Circle = 2, - BrushShape_Custom = 3 - }; - ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); void configureButtonInitialSettings(QPushButton *button); const QString toolTipPoint = "Paint single point"; const QString toolTipSquare = "Paint with square brush"; const QString toolTipCircle = "Paint with circle brush"; - const QString toolTipCustom = "Paint custom selection (not implemented yet)"; + const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; private: - int mBrushShape; + CSVWidget::BrushShape mBrushShape; int mBrushSize; CSMDoc::Document& mDocument; QGroupBox *mHorizontalGroupBox; @@ -92,7 +85,7 @@ namespace CSVWidget signals: void passBrushSize (int brushSize); - void passBrushShape(int brushShape); + void passBrushShape(CSVWidget::BrushShape brushShape); }; class SceneToolShapeBrush : public SceneTool @@ -122,7 +115,7 @@ namespace CSVWidget friend class CSVRender::TerrainShapeMode; public slots: - void setButtonIcon(int brushShape); + void setButtonIcon(CSVWidget::BrushShape brushShape); void clicked (const QModelIndex& index); virtual void activate(); From 96be82a04719478217789628fcf19292cf8720c2 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 7 Oct 2019 22:32:50 +0300 Subject: [PATCH 51/79] Remove terrain vertex selection and brush button when exiting editMode --- apps/opencs/view/render/terrainshapemode.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 171ad5a27..8da50ada3 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -82,6 +82,16 @@ void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) { + if(mShapeBrushScenetool) + { + toolbar->removeTool (mShapeBrushScenetool); + } + + if (mTerrainShapeSelection) + { + mTerrainShapeSelection.reset(); + } + EditMode::deactivate(toolbar); } From 8a9ca0b3ec1793a81773bbc74ef16570c6ec15b7 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 7 Oct 2019 23:12:21 +0300 Subject: [PATCH 52/79] Generate new WNAM record based on new land heights, add comments --- apps/opencs/view/render/terrainshapemode.cpp | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 8da50ada3..419d222c0 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -311,6 +311,8 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer); + + // Generate land height record for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) @@ -325,9 +327,24 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() } } } - for(int i = 0; i < ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE; ++i) + + // Generate WNAM record + int sqrtLandGlobalMapLodSize = sqrt(ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE); + for(int i = 0; i < sqrtLandGlobalMapLodSize; ++i) { - mapLodShapeNew[i] = landMapLodPointer[i]; //TO-DO: generate a new mWnam based on new heights + for(int j = 0; j < sqrtLandGlobalMapLodSize; ++j) + { + int col = (static_cast(j) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); + int row = (static_cast(i) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); + signed char lodHeight = 0; + float floatLodHeight = 0; + if (landShapeNew[col * ESM::Land::LAND_SIZE + row] > 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 128; + if (landShapeNew[col * ESM::Land::LAND_SIZE + row] <= 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 16; + if (floatLodHeight > std::numeric_limits::max()) lodHeight = std::numeric_limits::max(); + else if (floatLodHeight < std::numeric_limits::min()) lodHeight = std::numeric_limits::min(); + else lodHeight = static_cast(floatLodHeight); + mapLodShapeNew[j * sqrtLandGlobalMapLodSize + i] = lodHeight; + } } if (allowLandShapeEditing(cellId) == true) { @@ -345,6 +362,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + // Generate land normals record for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) From 6a3070e68073b9a44e57f35fc9b00d7a842e48eb Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 7 Oct 2019 23:48:02 +0300 Subject: [PATCH 53/79] Fix shape brush setting description --- apps/opencs/model/prefs/state.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index 35a8ef2df..b67285513 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -248,7 +248,7 @@ void CSMPrefs::State::declare() addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); - declareInt ("shapebrush-maximumsize", "Maximum texture brush size", 100). + declareInt ("shapebrush-maximumsize", "Maximum shape brush size", 100). setMin (1); declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" From c2de645c8cb8aa4790317ff61f5ee4f714072bdd Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 9 Oct 2019 01:31:13 +0300 Subject: [PATCH 54/79] override, virtual -> final, remove unused variables, public to private. --- apps/opencs/view/render/terrainshapemode.cpp | 2 +- apps/opencs/view/render/terrainshapemode.hpp | 20 ++++++++++---------- apps/opencs/view/render/terrainstorage.cpp | 2 -- apps/opencs/view/render/terrainstorage.hpp | 12 ++++++------ 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 419d222c0..934388acf 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -47,7 +47,7 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidge : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), mBrushSize(1), mBrushShape(CSVWidget::BrushShape_Point), - mShapeBrushScenetool(0), + mShapeBrushScenetool(nullptr), mDragMode(InteractionType_None), mParentNode(parentNode), mIsEditing(false), diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 912aec2ce..8af81dff9 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -72,24 +72,25 @@ namespace CSVRender void deactivate(CSVWidget::SceneToolbar*); /// Start shape editing command macro - virtual bool primaryEditStartDrag (const QPoint& pos); + bool primaryEditStartDrag (const QPoint& pos) final; - virtual bool secondaryEditStartDrag (const QPoint& pos); - virtual bool primarySelectStartDrag (const QPoint& pos); - virtual bool secondarySelectStartDrag (const QPoint& pos); + bool secondaryEditStartDrag (const QPoint& pos) final; + bool primarySelectStartDrag (const QPoint& pos) final; + bool secondarySelectStartDrag (const QPoint& pos) final; /// Handle shape edit behavior during dragging - virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) final; /// End shape editing command macro - virtual void dragCompleted(const QPoint& pos); + void dragCompleted(const QPoint& pos) final; /// Cancel shape editing, and reset all pending changes - virtual void dragAborted(); + void dragAborted() final; - virtual void dragWheel (int diff, double speedFactor); - virtual void dragMoveEvent (QDragMoveEvent *event); + void dragWheel (int diff, double speedFactor) final; + void dragMoveEvent (QDragMoveEvent *event) final; + private: /// Move pending alteredHeights changes to omwgame/omeaddon -data void applyTerrainEditChanges(); @@ -138,7 +139,6 @@ namespace CSVRender /// Create new cell and land if needed bool allowLandShapeEditing(const std::string& textureFileName); - private: std::string mCellId; std::string mBrushTexture; int mBrushSize; diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 7f5dc1208..65c89ae5c 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -13,8 +13,6 @@ namespace CSVRender { - const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; - TerrainStorage::TerrainStorage(const CSMWorld::Data &data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 9e5226d5f..032261ad4 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -16,7 +16,6 @@ namespace CSVRender { public: TerrainStorage(const CSMWorld::Data& data); - std::array mAlteredHeight; void setAlteredHeight(int inCellX, int inCellY, float heightMap); void resetHeights(); float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); @@ -24,11 +23,12 @@ namespace CSVRender private: const CSMWorld::Data& mData; + std::array mAlteredHeight; - virtual osg::ref_ptr getLand (int cellX, int cellY) override; - virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; + osg::ref_ptr getLand (int cellX, int cellY) final; + const ESM::LandTexture* getLandTexture(int index, short plugin) final; - virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + void getBounds(float& minX, float& maxX, float& minY, float& maxY) final; int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const; int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const; @@ -42,8 +42,8 @@ namespace CSVRender bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; - void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const override; - float getAlteredHeight(int col, int row) const override; + void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const final; + float getAlteredHeight(int col, int row) const final; }; } From 16ba32ffd5ee17ef9346180c5b9da990f6880819 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 9 Oct 2019 01:46:28 +0300 Subject: [PATCH 55/79] assignments to member initialization list --- apps/opencs/view/widget/scenetoolshapebrush.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index 3b9d908ac..fcafc4cf0 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -35,19 +35,19 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) - : QGroupBox(title, parent) + : QGroupBox(title, parent), + mLayoutSliderSize(new QHBoxLayout), + mBrushSizeSlider(new QSlider(Qt::Horizontal)), + mBrushSizeSpinBox(new QSpinBox) { - mBrushSizeSlider = new QSlider(Qt::Horizontal); mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); mBrushSizeSlider->setTickInterval(10); mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSlider->setSingleStep(1); - mBrushSizeSpinBox = new QSpinBox; mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSpinBox->setSingleStep(1); - mLayoutSliderSize = new QHBoxLayout; mLayoutSliderSize->addWidget(mBrushSizeSlider); mLayoutSliderSize->addWidget(mBrushSizeSpinBox); From a1348d94f9185324b890dfd3e29dade0f4352b2d Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 11 Oct 2019 00:15:32 +0300 Subject: [PATCH 56/79] add missing final specifiers --- apps/opencs/view/render/terrainshapemode.hpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 8af81dff9..95ffa572e 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -58,18 +58,18 @@ namespace CSVRender /// Editmode for terrain shape grid TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); - void primaryOpenPressed (const WorldspaceHitResult& hit); + void primaryOpenPressed (const WorldspaceHitResult& hit) final; /// Create single command for one-click shape editing - void primaryEditPressed (const WorldspaceHitResult& hit); + void primaryEditPressed (const WorldspaceHitResult& hit) final; /// Open brush settings window - void primarySelectPressed(const WorldspaceHitResult&); + void primarySelectPressed(const WorldspaceHitResult&) final; - void secondarySelectPressed(const WorldspaceHitResult&); + void secondarySelectPressed(const WorldspaceHitResult&) final; - void activate(CSVWidget::SceneToolbar*); - void deactivate(CSVWidget::SceneToolbar*); + void activate(CSVWidget::SceneToolbar*) final; + void deactivate(CSVWidget::SceneToolbar*) final; /// Start shape editing command macro bool primaryEditStartDrag (const QPoint& pos) final; From 71eff60d22c2465ab048fd52de624e273c276224 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Fri, 11 Oct 2019 00:41:33 +0300 Subject: [PATCH 57/79] Do in-class init. if possible, mLayoutSliderSize -> layoutSliderSize --- apps/opencs/view/render/terrainshapemode.cpp | 11 +---------- apps/opencs/view/render/terrainshapemode.hpp | 18 +++++++++--------- .../opencs/view/widget/scenetoolshapebrush.cpp | 14 +++++--------- .../opencs/view/widget/scenetoolshapebrush.hpp | 9 ++++----- 4 files changed, 19 insertions(+), 33 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 934388acf..701e704bb 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -45,16 +45,7 @@ CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), - mBrushSize(1), - mBrushShape(CSVWidget::BrushShape_Point), - mShapeBrushScenetool(nullptr), - mDragMode(InteractionType_None), - mParentNode(parentNode), - mIsEditing(false), - mTotalDiffY(0), - mShapeEditTool(ShapeEditTool_Drag), - mShapeEditToolStrength(8), - mTargetHeight(0) + mParentNode(parentNode) { } diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 95ffa572e..8ee9625ff 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -141,20 +141,20 @@ namespace CSVRender std::string mCellId; std::string mBrushTexture; - int mBrushSize; - CSVWidget::BrushShape mBrushShape; + int mBrushSize = 1; + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; std::vector> mCustomBrushShape; - CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool; - int mDragMode; + CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr; + int mDragMode = InteractionType_None; osg::Group* mParentNode; - bool mIsEditing; + bool mIsEditing = false; std::unique_ptr mTerrainShapeSelection; - int mTotalDiffY; + int mTotalDiffY = 0; std::vector mAlteredCells; osg::Vec3d mEditingPos; - int mShapeEditTool; - int mShapeEditToolStrength; - int mTargetHeight; + int mShapeEditTool = ShapeEditTool_Drag; + int mShapeEditToolStrength = 8; + int mTargetHeight = 0; PagedWorldspaceWidget& getPagedWorldspaceWidget(); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp index fcafc4cf0..e4647d600 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.cpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -35,10 +35,7 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) - : QGroupBox(title, parent), - mLayoutSliderSize(new QHBoxLayout), - mBrushSizeSlider(new QSlider(Qt::Horizontal)), - mBrushSizeSpinBox(new QSpinBox) + : QGroupBox(title, parent) { mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); mBrushSizeSlider->setTickInterval(10); @@ -48,19 +45,18 @@ CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); mBrushSizeSpinBox->setSingleStep(1); - mLayoutSliderSize->addWidget(mBrushSizeSlider); - mLayoutSliderSize->addWidget(mBrushSizeSpinBox); + QHBoxLayout *layoutSliderSize = new QHBoxLayout; + layoutSliderSize->addWidget(mBrushSizeSlider); + layoutSliderSize->addWidget(mBrushSizeSpinBox); connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); - setLayout(mLayoutSliderSize); + setLayout(layoutSliderSize); } CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) : QFrame(parent, Qt::Popup), - mBrushShape(CSVWidget::BrushShape_Point), - mBrushSize(1), mDocument(document) { mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp index 19d24111d..2c027baf0 100644 --- a/apps/opencs/view/widget/scenetoolshapebrush.hpp +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -40,9 +40,8 @@ namespace CSVWidget ShapeBrushSizeControls(const QString &title, QWidget *parent); private: - QHBoxLayout *mLayoutSliderSize; - QSlider *mBrushSizeSlider; - QSpinBox *mBrushSizeSpinBox; + QSlider *mBrushSizeSlider = new QSlider(Qt::Horizontal); + QSpinBox *mBrushSizeSpinBox = new QSpinBox; friend class SceneToolShapeBrush; friend class CSVRender::TerrainShapeMode; @@ -64,8 +63,8 @@ namespace CSVWidget const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; private: - CSVWidget::BrushShape mBrushShape; - int mBrushSize; + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + int mBrushSize = 1; CSMDoc::Document& mDocument; QGroupBox *mHorizontalGroupBox; QComboBox *mToolSelector; From 2d34b63b0b15b0aa040eed0aaf01eacca8ad26fe Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 12 Oct 2019 15:25:34 +0300 Subject: [PATCH 58/79] Fix logic of land loading when no cell, land or landdata. Fix also draw. --- apps/opencs/view/render/cell.cpp | 1 - apps/opencs/view/render/terrainshapemode.cpp | 475 ++++++++++++------- apps/opencs/view/render/terrainshapemode.hpp | 25 +- 3 files changed, 336 insertions(+), 165 deletions(-) diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 94d7bfa51..056c50e45 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -149,7 +149,6 @@ void CSVRender::Cell::updateLand() } // No land data - mLandDeleted = true; unloadLand(); } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 701e704bb..d43b126e0 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -92,8 +92,6 @@ void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) { - mCellId = getWorldspaceWidget().getCellId (hit.worldPos); - if (hit.hit && hit.tag == 0) { if (mShapeEditTool == ShapeEditTool_Flatten) @@ -143,8 +141,6 @@ bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - mCellId = getWorldspaceWidget().getCellId (hit.worldPos); - mDragMode = InteractionType_PrimaryEdit; if (hit.hit && hit.tag == 0) @@ -245,27 +241,14 @@ void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) { } - -void CSVRender::TerrainShapeMode::applyTerrainEditChanges() +void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() { - std::sort(mAlteredCells.begin(), mAlteredCells.end()); - mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); - - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdTable& landTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); - CSMWorld::IdTable& ltexTable = dynamic_cast ( - *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); - - int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); - int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex); - int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); - - QUndoStack& undoStack = document.getUndoStack(); - bool passing = false; int passes = 0; + std::sort(mAlteredCells.begin(), mAlteredCells.end()); + mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); + while (passing == false) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { passing = true; @@ -291,6 +274,23 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() } } } +} + +void CSVRender::TerrainShapeMode::applyTerrainEditChanges() +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + QUndoStack& undoStack = document.getUndoStack(); + + sortAndLimitAlteredCells(); undoStack.beginMacro ("Edit shape and normal records"); @@ -337,11 +337,8 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() mapLodShapeNew[j * sqrtLandGlobalMapLodSize + i] = lodHeight; } } - if (allowLandShapeEditing(cellId) == true) - { - pushEditToCommand(landShapeNew, document, landTable, cellId); - pushLodToCommand(mapLodShapeNew, document, landTable, cellId); - } + pushEditToCommand(landShapeNew, document, landTable, cellId); + pushLodToCommand(mapLodShapeNew, document, landTable, cellId); } for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) @@ -368,9 +365,8 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; - bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; - if (!noLand && !noCell) + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); + if (!noCell(cellId) && !noLand(cellId) && noLandLoaded(cellId)) v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else v1.z() = 0; @@ -381,9 +377,8 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; - bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; - if (!noLand && !noCell) + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); + if (!noCell(cellId) && !noLand(cellId) && noLandLoaded(cellId)) v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else v2.z() = 0; @@ -400,7 +395,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal.z(); } } - if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); + pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); } undoStack.endMacro(); mAlteredCells.clear(); @@ -420,81 +415,77 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); } - if(allowLandShapeEditing(cellId)==true) + if (mBrushShape == CSVWidget::BrushShape_Point) { - if (mBrushShape == CSVWidget::BrushShape_Point) - { - int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); - int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); - } + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } - if (mBrushShape == CSVWidget::BrushShape_Square) + if (mBrushShape == CSVWidget::BrushShape_Square) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + if (mBrushShape == CSVWidget::BrushShape_Circle) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + float distancePerRadius = 1.0f * distance / r; + float smoothedByDistance = 0.0f; + if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (distance <= r) { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; - int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); - int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Drag || mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } } - - if (mBrushShape == CSVWidget::BrushShape_Circle) + } + if (mBrushShape == CSVWidget::BrushShape_Custom) + { + if(!mCustomBrushShape.empty()) { - for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + for(auto const& value: mCustomBrushShape) { - for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) - { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; - int distanceX = abs(i - vertexCoords.first); - int distanceY = abs(j - vertexCoords.second); - float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); - int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); - int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); - float distancePerRadius = 1.0f * distance / r; - float smoothedByDistance = 0.0f; - if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); - if (distance <= r) - { - if (mShapeEditTool == ShapeEditTool_Drag || mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) - alterHeight(cellCoords, x, y, smoothedByDistance); - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); - } - } + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } } - if (mBrushShape == CSVWidget::BrushShape_Custom) - { - if(!mCustomBrushShape.empty()) - { - for(auto const& value: mCustomBrushShape) - { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; - int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); - int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); - if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); - if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); - } - } - } - } } @@ -528,7 +519,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); - if(allowLandShapeEditing(cellId)==true) + if ((allowLandShapeEditing(cellId, useTool)==true) && (useTool || (isLandLoaded(cellId)))) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) @@ -553,96 +544,88 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); // Change values of cornering cells - if (inCellX == 0 && inCellY == 0) + if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) { - if(allowLandShapeEditing(cellUpLeftId) && allowLandShapeEditing(cellLeftId) && allowLandShapeEditing(cellUpId)) + if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); - if (useTool) mAlteredCells.emplace_back(cornerCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } - else if (inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) + else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) { - if(allowLandShapeEditing(cellDownLeftId) && allowLandShapeEditing(cellLeftId) && allowLandShapeEditing(cellDownId)) + if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); - if (useTool) mAlteredCells.emplace_back(cornerCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); } else return; } - else if (inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) { - if(allowLandShapeEditing(cellUpRightId) && allowLandShapeEditing(cellRightId) && allowLandShapeEditing(cellUpId)) + if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); - if (useTool) mAlteredCells.emplace_back(cornerCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } - else if (inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) { - if(allowLandShapeEditing(cellDownRightId) && allowLandShapeEditing(cellRightId) && allowLandShapeEditing(cellDownId)) + if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); - if (useTool) mAlteredCells.emplace_back(cornerCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); } else return; } // Change values of edging cells - if (inCellX == 0) + if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) { - if(allowLandShapeEditing(cellLeftId)==true) + if(allowLandShapeEditing(cellLeftId, useTool)==true) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); - if (useTool) mAlteredCells.emplace_back(edgeCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } - if (inCellY == 0) + if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) { - if(allowLandShapeEditing(cellUpId)==true) + if(allowLandShapeEditing(cellUpId, useTool)==true) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); - if (useTool) mAlteredCells.emplace_back(edgeCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); } } - if (inCellX == ESM::Land::LAND_SIZE - 1) + if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) { - if(allowLandShapeEditing(cellRightId)==true) + if(allowLandShapeEditing(cellRightId, useTool)==true) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); - if (useTool) mAlteredCells.emplace_back(edgeCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } - if (inCellY == ESM::Land::LAND_SIZE - 1) + if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) { - if(allowLandShapeEditing(cellDownId)==true) + if(allowLandShapeEditing(cellDownId, useTool)==true) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); - if (useTool) mAlteredCells.emplace_back(edgeCellCoords); - else if (!(std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) != mAlteredCells.end())) + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); @@ -769,13 +752,11 @@ void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& float thisAlteredHeight = 0.0f; std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); - bool noCell = document.getData().getCells().searchId (cellId) == -1; - bool noLand = document.getData().getLand().searchId (cellId) == -1; if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { - if (!noCell && !noLand) + if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); @@ -806,16 +787,6 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - bool noCell = document.getData().getCells().searchId (cellId) == -1; - bool noLand = document.getData().getLand().searchId (cellId) == -1; - bool noLeftCell = document.getData().getCells().searchId (cellLeftId) == -1; - bool noLeftLand = document.getData().getLand().searchId (cellLeftId) == -1; - bool noUpCell = document.getData().getCells().searchId (cellUpId) == -1; - bool noUpLand = document.getData().getLand().searchId (cellUpId) == -1; - bool noRightCell = document.getData().getCells().searchId (cellRightId) == -1; - bool noRightLand = document.getData().getLand().searchId (cellRightId) == -1; - bool noDownCell = document.getData().getCells().searchId (cellDownId) == -1; - bool noDownLand = document.getData().getLand().searchId (cellDownId) == -1; *thisHeight = 0.0f; // real + altered height *thisAlteredHeight = 0.0f; // only altered height @@ -831,7 +802,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { - if (!noCell && !noLand) + if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); @@ -850,7 +821,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor //If at edge, get values from neighboring cell if (inCellX == 0) { - if(!noLeftCell && !noLeftLand) + if(isLandLoaded(cellLeftId)) { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); @@ -864,8 +835,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } if (inCellY == 0) { - cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); - if(!noUpCell && !noUpLand) + if(isLandLoaded(cellUpId)) { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); @@ -879,8 +849,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } if (inCellX == ESM::Land::LAND_SIZE - 1) { - cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); - if(!noRightCell && !noRightLand) + if(isLandLoaded(cellRightId)) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); @@ -894,8 +863,7 @@ void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoor } if (inCellY == ESM::Land::LAND_SIZE - 1) { - cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); - if(!noDownCell && !noDownLand) + if(isLandLoaded(cellDownId)) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); @@ -981,11 +949,9 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); int limitHeightChange = 1016.0f; // Limited by save format - bool noCell = document.getData().getCells().searchId (cellId) == -1; - bool noLand = document.getData().getLand().searchId (cellId) == -1; bool steepnessIsWithinLimits = true; - if (!noCell && !noLand) + if (isLandLoaded(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); @@ -1149,7 +1115,134 @@ void CSVRender::TerrainShapeMode::pushLodToCommand(const CSMWorld::LandMapLodCol undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLod)); } -bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId) +bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); + return cellCollection.searchId (cellId) == -1; +} + +bool CSVRender::TerrainShapeMode::noLand(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& landCollection = document.getData().getLand(); + return landCollection.searchId (cellId) == -1; +} + +bool CSVRender::TerrainShapeMode::noLandLoaded(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& landCollection = document.getData().getLand(); + return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); +} + +bool CSVRender::TerrainShapeMode::isLandLoaded(const std::string& cellId) +{ + if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; + return false; +} + +void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordinates& cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + float defaultHeight = 0.f; + int averageDivider = 0; + CSMWorld::CellCoordinates cellLeftCoords = cellCoords.move(-1, 0); + CSMWorld::CellCoordinates cellRightCoords = cellCoords.move(1, 0); + CSMWorld::CellCoordinates cellUpCoords = cellCoords.move(0, -1); + CSMWorld::CellCoordinates cellDownCoords = cellCoords.move(0, 1); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellLeftCoords.getX(), cellLeftCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellRightCoords.getX(), cellRightCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellUpCoords.getX(), cellUpCoords.getY()); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellDownCoords.getX(), cellDownCoords.getY()); + + float leftCellSampleHeight = 0.0f; + float rightCellSampleHeight = 0.0f; + float upCellSampleHeight = 0.0f; + float downCellSampleHeight = 0.0f; + + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (isLandLoaded(cellLeftId)) + { + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = + landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + + ++averageDivider; + leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) + leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); + } + if (isLandLoaded(cellRightId)) + { + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + + ++averageDivider; + rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE]; + if(paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) + rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2); + } + if (isLandLoaded(cellUpId)) + { + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = + landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + + ++averageDivider; + upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; + if(paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) + upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); + } + if (isLandLoaded(cellDownId)) + { + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + + ++averageDivider; + downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2]; + if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE / 2, 0)) + downCellSampleHeight += *paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0); + } + } + if (averageDivider > 0) defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) / averageDivider; + + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + landShapeNew[j * ESM::Land::LAND_SIZE + i] = defaultHeight; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = 0; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = 0; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = 127; + } + } + QVariant changedShape; + changedShape.setValue(landShapeNew); + QVariant changedNormals; + changedNormals.setValue(landNormalsNew); + QModelIndex indexShape(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QModelIndex indexNormal(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); + document.getUndoStack().push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexShape, changedShape)); + document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals)); +} + +bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId, bool useTool) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast ( @@ -1157,10 +1250,7 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); - bool noCell = document.getData().getCells().searchId (cellId)==-1; - bool noLand = document.getData().getLand().searchId (cellId)==-1; - - if (noCell) + if (noCell(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); @@ -1168,7 +1258,7 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI if (mode=="Discard") return false; - if (mode=="Create cell and land, then edit") + if (mode=="Create cell and land, then edit" && useTool) { std::unique_ptr createCommand ( new CSMWorld::CreateCommand (cellTable, cellId)); @@ -1199,7 +1289,7 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI if (mode=="Discard") return false; - if (mode=="Show cell and edit") + if (mode=="Show cell and edit" && useTool) { selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); paged->setCellSelection (selection); @@ -1207,7 +1297,7 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI } } - if (noLand) + if (noLand(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); @@ -1215,15 +1305,80 @@ bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellI if (mode=="Discard") return false; - if (mode=="Create cell and land, then edit") + if (mode=="Create cell and land, then edit" && useTool) { document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); + fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); + sortAndLimitAlteredCells(); + } + } + else if (noLandLoaded(cellId)) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit" && useTool) + { + createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); + fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); + sortAndLimitAlteredCells(); } } + if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId))) + { + Log(Debug::Warning) << "Land creation failed at cell id: " << cellId; + return false; + } return true; } +void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + if (isLandLoaded(cellLeftId) && + landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) + landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if (isLandLoaded(cellRightId) && + landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE]) + landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE]; + if (isLandLoaded(cellUpId) && + landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) + landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]; + if (isLandLoaded(cellDownId) && + landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) + landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i]; + } + + QVariant changedLand; + changedLand.setValue(landShapeNew); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) { } diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 8ee9625ff..8c07aa0ce 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -91,7 +91,11 @@ namespace CSVRender void dragMoveEvent (QDragMoveEvent *event) final; private: - /// Move pending alteredHeights changes to omwgame/omeaddon -data + + /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse + void sortAndLimitAlteredCells(); + + /// Move pending alteredHeights changes to omwgame/omwaddon -data void applyTerrainEditChanges(); /// Handle brush mechanics for shape editing @@ -136,10 +140,23 @@ namespace CSVRender void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId); - /// Create new cell and land if needed - bool allowLandShapeEditing(const std::string& textureFileName); + bool noCell(const std::string& cellId); + + bool noLand(const std::string& cellId); + + bool noLandLoaded(const std::string& cellId); + + bool isLandLoaded(const std::string& cellId); + + /// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and set the average height based on that + void createNewLandData(const CSMWorld::CellCoordinates& cellCoords); + + /// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for automated land changes) + bool allowLandShapeEditing(const std::string& textureFileName, bool useTool = true); + + /// Bind the edging vertice to the values of the adjancent cells + void fixEdges(CSMWorld::CellCoordinates cellCoords); - std::string mCellId; std::string mBrushTexture; int mBrushSize = 1; CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; From deb122ffdb121c1e1cf577eecffda2a358d0e262 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sat, 12 Oct 2019 16:20:57 +0300 Subject: [PATCH 59/79] Add optional post-processing (smooth/rough), add/fix tooltips. --- apps/opencs/model/prefs/state.cpp | 16 +++++++++-- apps/opencs/view/render/terrainshapemode.cpp | 30 ++++++++++++++++---- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index b67285513..1bf5752f0 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -242,14 +242,24 @@ void CSMPrefs::State::declare() addValues (insertOutsideCell); declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). addValues (insertOutsideVisibleCell); - declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit). + declareEnum ("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit). + setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."). addValues (landeditOutsideCell); - declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit). + declareEnum ("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit). + setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."). addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); - declareInt ("shapebrush-maximumsize", "Maximum shape brush size", 100). + declareInt ("shapebrush-maximumsize", "Maximum height edit brush size", 100). + setTooltip("Setting for the slider range of brush size in terrain height editing."). setMin (1); + declareBool ("landedit-post-smoothpainting", "Smooth land after painting height", false). + setTooltip("Raise and lower tools will leave bumpy finish without this option"); + declareDouble ("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25). + setTooltip("If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " + "Negative values may be used to roughen instead of smooth."). + setMin (-1). + setMax (1); declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index d43b126e0..7f3ccf04f 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -420,7 +420,12 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } @@ -436,7 +441,12 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } @@ -462,8 +472,13 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); if (distance <= r) { - if (mShapeEditTool == ShapeEditTool_Drag || mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) - alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, smoothedByDistance); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } @@ -481,7 +496,12 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); } From c556885d715e4bfb177ddfd76b8a40b40f178d4f Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 13 Oct 2019 23:09:13 +0300 Subject: [PATCH 60/79] remove unused Qt signal --- apps/opencs/view/render/terrainshapemode.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 8c07aa0ce..0f987c88b 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -175,9 +175,6 @@ namespace CSVRender PagedWorldspaceWidget& getPagedWorldspaceWidget(); - signals: - void passBrushTexture(std::string brushTexture); - public slots: void setBrushSize(int brushSize); void setBrushShape(CSVWidget::BrushShape brushShape); From 12349f4e48efc7baade19d19b78338c71747c7dc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Sun, 13 Oct 2019 23:20:27 +0300 Subject: [PATCH 61/79] cleanup --- apps/opencs/view/render/terrainshapemode.cpp | 23 +++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 7f3ccf04f..e723e18df 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -192,8 +192,11 @@ void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); mTotalDiffY += diffY; - if (mIsEditing == true && mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); - if (mIsEditing == true && mShapeEditTool != ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + if (mIsEditing) + { + if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + if (mShapeEditTool != ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + } } if (mDragMode == InteractionType_PrimarySelect) @@ -213,7 +216,7 @@ void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) { if (mDragMode == InteractionType_PrimaryEdit) { - if (mIsEditing == true) + if (mIsEditing) { mTotalDiffY = 0; mIsEditing = false; @@ -539,7 +542,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); - if ((allowLandShapeEditing(cellId, useTool)==true) && (useTool || (isLandLoaded(cellId)))) + if (allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId)))) { if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) @@ -608,7 +611,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c // Change values of edging cells if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) { - if(allowLandShapeEditing(cellLeftId, useTool)==true) + if(allowLandShapeEditing(cellLeftId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) @@ -619,7 +622,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c } if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) { - if(allowLandShapeEditing(cellUpId, useTool)==true) + if(allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) @@ -631,7 +634,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) { - if(allowLandShapeEditing(cellRightId, useTool)==true) + if(allowLandShapeEditing(cellRightId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) @@ -642,7 +645,7 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c } if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) { - if(allowLandShapeEditing(cellDownId, useTool)==true) + if(allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) @@ -687,7 +690,7 @@ void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& float downAlteredHeight = 0.0f; float upHeight = 0.0f; - if(allowLandShapeEditing(cellId)==true) + if(allowLandShapeEditing(cellId)) { //Get key values for calculating average, handle cell edges, check for null pointers if (inCellX == 0) @@ -1016,7 +1019,7 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi } } - if (reverseMode == true) + if (reverseMode) { for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) { From 0f6ddfe8e69aba38b9798e104d095215b75075a1 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 16 Oct 2019 03:02:29 +0300 Subject: [PATCH 62/79] change include, cleanup --- apps/opencs/view/render/terrainshapemode.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index e723e18df..54fddd76b 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -39,7 +39,7 @@ #include "editmode.hpp" #include "pagedworldspacewidget.hpp" #include "mask.hpp" -#include "object.hpp" // Something small needed regarding pointers from here () +#include "tagbase.hpp" #include "terrainselection.hpp" #include "worldspacewidget.hpp" @@ -252,7 +252,7 @@ void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() std::sort(mAlteredCells.begin(), mAlteredCells.end()); mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); - while (passing == false) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously + while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { passing = true; for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) @@ -990,7 +990,7 @@ bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordi float downHeight = 0.0f; float downAlteredHeight = 0.0f; - if (reverseMode == false) + if (!reverseMode) { for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) { From 8d3e7f281554d90f839b1f96f53f42a72fb72507 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Mon, 21 Oct 2019 01:49:15 +0300 Subject: [PATCH 63/79] fix normals at cell edges, better coding standards --- apps/opencs/view/render/terrainshapemode.cpp | 247 +++++++++---------- 1 file changed, 117 insertions(+), 130 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 54fddd76b..174a7beb2 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -98,7 +98,6 @@ void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& setFlattenToolTargetHeight(hit); if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag) { - std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } @@ -190,12 +189,11 @@ void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, if (mDragMode == InteractionType_PrimaryEdit) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); mTotalDiffY += diffY; if (mIsEditing) { if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); - if (mShapeEditTool != ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); } } @@ -305,14 +303,14 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer); + CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); // Generate land height record for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + if (paged) { if (paged->getCellAlteredHeight(cellCoordinates, i, j)) landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); @@ -358,38 +356,27 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { - osg::Vec3f v1; - osg::Vec3f v2; - osg::Vec3f normal; - float hyp; + osg::Vec3f v1(128, 0, 0); + osg::Vec3f v2(0, 128, 0); - v1.x() = 128; - v1.y() = 0; if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); - if (!noCell(cellId) && !noLand(cellId) && noLandLoaded(cellId)) + if (isLandLoaded(cellId)) v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - v1.z() = 0; } - v2.x() = 0; - v2.y() = 128; if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); - if (!noCell(cellId) && !noLand(cellId) && noLandLoaded(cellId)) + if (isLandLoaded(cellId)) v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; - else - v2.z() = 0; } - normal = v1 ^ v2; - - hyp = normal.length() / 127.0f; + osg::Vec3f normal = v1 ^ v2; + const float hyp = normal.length() / 127.0f; normal /= hyp; @@ -533,6 +520,13 @@ void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHit void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + + if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId))))) + return; + CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); + if (!paged) + return; + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); @@ -542,121 +536,114 @@ void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& c std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); - if (allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId)))) + if (useTool) { - if (CSVRender::PagedWorldspaceWidget *paged = - dynamic_cast (&getWorldspaceWidget())) + mAlteredCells.emplace_back(cellCoords); + if (mShapeEditTool == ShapeEditTool_Drag) { - if (useTool) - { - mAlteredCells.emplace_back(cellCoords); - if (mShapeEditTool == ShapeEditTool_Drag) - { - // Get distance from modified land, alter land change based on zoom - osg::Vec3d eye, center, up; - paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); - osg::Vec3d distance = eye - mEditingPos; - alteredHeight = alteredHeight * (distance.length() / 500); - } - if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; - if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; - if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; - } + // Get distance from modified land, alter land change based on zoom + osg::Vec3d eye, center, up; + paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d distance = eye - mEditingPos; + alteredHeight = alteredHeight * (distance.length() / 500); + } + if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + } - if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); - // Change values of cornering cells - if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) - { - if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) - { - CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); - } else return; - } - else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) - { - if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) - { - CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); - } else return; - } - else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) - { - if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) - { - CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); - } else return; - } - else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) - { - if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) - { - CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(cornerCellCoords); - paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); - } else return; - } - - // Change values of edging cells - if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) - { - if(allowLandShapeEditing(cellLeftId, useTool)) - { - CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); - paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); - } - } - if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) - { - if(allowLandShapeEditing(cellUpId, useTool)) - { - CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); - paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); - } - } - - if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) - { - if(allowLandShapeEditing(cellRightId, useTool)) - { - CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); - paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); - } - } - if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) - { - if(allowLandShapeEditing(cellDownId, useTool)) - { - CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); - if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) - mAlteredCells.emplace_back(edgeCellCoords); - paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); - paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); - } - } + // Change values of cornering cells + if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) + { + if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; + } + else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) + { + if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); + } else return; + } + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) + { + if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; + } + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) + { + if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); + } else return; + } + // Change values of edging cells + if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) + { + if(allowLandShapeEditing(cellLeftId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } + if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) + { + if(allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); + } + } + + if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) + { + if(allowLandShapeEditing(cellRightId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); + } + } + if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) + { + if(allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); + } + } + } void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) From a13edbdb428b26d9cb79e698a66e71ca262307fd Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 22 Oct 2019 20:58:23 +0300 Subject: [PATCH 64/79] Bump shape calculation to function --- apps/opencs/view/render/terrainshapemode.cpp | 15 ++++++++++----- apps/opencs/view/render/terrainshapemode.hpp | 3 +++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 174a7beb2..8bdd96ad8 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -391,6 +391,12 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() mAlteredCells.clear(); } +float CSVRender::TerrainShapeMode::calculateBumpShape(const float& distance, int radius, const float& height) +{ + float distancePerRadius = distance / radius; + return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); +} + void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; @@ -451,15 +457,14 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair { cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); - int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); - int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); - float distancePerRadius = 1.0f * distance / r; float smoothedByDistance = 0.0f; - if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); - if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); if (distance <= r) { if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 0f987c88b..6e6589c86 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -101,6 +101,9 @@ namespace CSVRender /// Handle brush mechanics for shape editing void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + /// Calculate height, when aiming for bump-shaped terrain change + float calculateBumpShape(const float& distance, int radius, const float& height); + /// set the target height for flatten tool void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); From fdcc7fa1d7435ca52228ade9049e38b15969c87f Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 22 Oct 2019 20:58:34 +0300 Subject: [PATCH 65/79] 0 to nullptr --- apps/opencs/view/render/terrainstorage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 65c89ae5c..847a57404 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -57,7 +57,7 @@ namespace CSVRender osg::ref_ptr land = getLand (cellX, cellY); if (land) { - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : 0; + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; if (data) height = getVertexHeight(data, inCellX, inCellY); } else return height; From 6f9f59dd580f327e024677dff3e396d49b0b6c17 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Tue, 22 Oct 2019 21:03:37 +0300 Subject: [PATCH 66/79] declare cellId and cellCoords in smallest possible scope --- apps/opencs/view/render/terrainshapemode.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 8bdd96ad8..559b8a47c 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -402,9 +402,6 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair int r = mBrushSize / 2; if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 - std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); - CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; - if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) { @@ -413,6 +410,8 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair if (mBrushShape == CSVWidget::BrushShape_Point) { + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); @@ -432,8 +431,8 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); @@ -455,8 +454,8 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair { for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); int distanceX = abs(i - vertexCoords.first); @@ -486,8 +485,8 @@ void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair { for(auto const& value: mCustomBrushShape) { - cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); - cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); From ac5b356e8da64e4a0e60b2e327fd3425d234c57b Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 23 Oct 2019 00:01:23 +0300 Subject: [PATCH 67/79] Always initialize landShapeNew --- apps/opencs/view/render/terrainshapemode.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 559b8a47c..bc0a2650f 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -310,13 +310,10 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) { - if (paged) - { - if (paged->getCellAlteredHeight(cellCoordinates, i, j)) - landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); - else - landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; - } + if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j)) + landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + else + landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; } } From 9e5db10288ff1240b38d3be6cce06d3a92b94433 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 23 Oct 2019 00:02:24 +0300 Subject: [PATCH 68/79] const ref float -> float --- apps/opencs/view/render/terrainshapemode.cpp | 2 +- apps/opencs/view/render/terrainshapemode.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index bc0a2650f..5f5a60da2 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -388,7 +388,7 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() mAlteredCells.clear(); } -float CSVRender::TerrainShapeMode::calculateBumpShape(const float& distance, int radius, const float& height) +float CSVRender::TerrainShapeMode::calculateBumpShape(float distance, int radius, float height) { float distancePerRadius = distance / radius; return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp index 6e6589c86..8e2a616c9 100644 --- a/apps/opencs/view/render/terrainshapemode.hpp +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -102,7 +102,7 @@ namespace CSVRender void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); /// Calculate height, when aiming for bump-shaped terrain change - float calculateBumpShape(const float& distance, int radius, const float& height); + float calculateBumpShape(float distance, int radius, float height); /// set the target height for flatten tool void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); From fdc73b87ba1b01c9599e73fbcfedf49c9ddd2901 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 23 Oct 2019 00:07:41 +0300 Subject: [PATCH 69/79] Remove includes that aren't needed any more --- apps/opencs/view/render/terrainstorage.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 847a57404..d9cc3015e 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,15 +1,9 @@ #include "terrainstorage.hpp" -#include -#include - #include "../../model/world/land.hpp" #include "../../model/world/landtexture.hpp" #include -#include -#include -#include namespace CSVRender { From c957f0544c6aaa6fb9f55f44b4d882abf3043478 Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 23 Oct 2019 00:34:54 +0300 Subject: [PATCH 70/79] Remove unneeded brackets --- apps/opencs/model/world/columnimp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index e610ef223..948174b30 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -92,9 +92,7 @@ namespace CSMWorld if (land.mDataTypes & Land::DATA_WNAM) { for (int i = 0; i < Size; ++i) - { values[i] = land.mWnam[i]; - } } QVariant variant; From f7d2cdb782cf3ee19e2da4d1069849a13401efe7 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 30 Sep 2019 18:51:11 +0300 Subject: [PATCH 71/79] Revert to 0.45.0 comments-in-the-middle settings behavior --- components/fallback/fallback.cpp | 34 +++++++++-------------------- components/settings/settings.cpp | 37 +++++++++++--------------------- 2 files changed, 22 insertions(+), 49 deletions(-) diff --git a/components/fallback/fallback.cpp b/components/fallback/fallback.cpp index d5c6c4e97..a151bd40b 100644 --- a/components/fallback/fallback.cpp +++ b/components/fallback/fallback.cpp @@ -1,8 +1,8 @@ #include "fallback.hpp" -#include +#include -#include +#include namespace Fallback { @@ -28,16 +28,10 @@ namespace Fallback const std::string& fallback = getString(fall); if (!fallback.empty()) { - try - { - // We have to rely on Boost because std::stof from C++11 uses the current locale - // for separators (which is undesired) and it often silently ignores parsing errors. - return boost::lexical_cast(fallback); - } - catch (boost::bad_lexical_cast&) - { - Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; - } + std::stringstream stream(fallback); + float number = 0.f; + stream >> number; + return number; } return 0; @@ -48,18 +42,10 @@ namespace Fallback const std::string& fallback = getString(fall); if (!fallback.empty()) { - try - { - return std::stoi(fallback); - } - catch (const std::invalid_argument&) - { - Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is not a valid number, using 0 as a fallback"; - } - catch (const std::out_of_range&) - { - Log(Debug::Error) << "Error: '" << fall << "' setting value (" << fallback << ") is out of range, using 0 as a fallback"; - } + std::stringstream stream(fallback); + int number = 0; + stream >> number; + return number; } return 0; diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 8bbde7a64..540af4d19 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,10 +1,9 @@ #include "settings.hpp" #include "parser.hpp" -#include -#include +#include -#include +#include namespace Settings { @@ -55,32 +54,20 @@ std::string Manager::getString(const std::string &setting, const std::string &ca float Manager::getFloat (const std::string& setting, const std::string& category) { - const std::string value = getString(setting, category); - try - { - // We have to rely on Boost because std::stof from C++11 uses the current locale - // for separators (which is undesired) and it often silently ignores parsing errors. - return boost::lexical_cast(value); - } - catch (boost::bad_lexical_cast&) - { - Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ")."; - return 0.f; - } + const std::string& value = getString(setting, category); + std::stringstream stream(value); + float number = 0.f; + stream >> number; + return number; } int Manager::getInt (const std::string& setting, const std::string& category) { - const std::string value = getString(setting, category); - try - { - return std::stoi(value); - } - catch(const std::exception& e) - { - Log(Debug::Warning) << "Cannot parse setting '" << setting << "' (invalid setting value: " << value << ")."; - return 0; - } + const std::string& value = getString(setting, category); + std::stringstream stream(value); + int number = 0; + stream >> number; + return number; } bool Manager::getBool (const std::string& setting, const std::string& category) From 7cd4fa4706acdaae7e1783ffbde23d7402a5c4f7 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 26 Oct 2019 12:41:16 +0400 Subject: [PATCH 72/79] Ignore shields sheathing for creatures without sheathing bone --- apps/openmw/mwrender/actoranimation.cpp | 35 ++++++++++++++----------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/apps/openmw/mwrender/actoranimation.cpp b/apps/openmw/mwrender/actoranimation.cpp index eac6c7a44..6d7da5d66 100644 --- a/apps/openmw/mwrender/actoranimation.cpp +++ b/apps/openmw/mwrender/actoranimation.cpp @@ -111,25 +111,30 @@ bool ActorAnimation::updateCarriedLeftVisible(const int weaptype) const MWMechanics::CreatureStats &stats = cls.getCreatureStats(mPtr); if (cls.hasInventoryStore(mPtr) && weaptype != ESM::Weapon::Spell) { - const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); - const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) + SceneUtil::FindByNameVisitor findVisitor ("Bip01 AttachShield"); + mObjectRoot->accept(findVisitor); + if (findVisitor.mFoundNode) { - if(stats.getDrawState() != MWMechanics::DrawState_Weapon) - return false; - - if (weapon != inv.end()) + const MWWorld::InventoryStore& inv = cls.getInventoryStore(mPtr); + const MWWorld::ConstContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); + const MWWorld::ConstContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if (shield != inv.end() && shield->getTypeName() == typeid(ESM::Armor).name() && !getShieldMesh(*shield).empty()) { - const std::string &type = weapon->getTypeName(); - if(type == typeid(ESM::Weapon).name()) + if(stats.getDrawState() != MWMechanics::DrawState_Weapon) + return false; + + if (weapon != inv.end()) { - const MWWorld::LiveCellRef *ref = weapon->get(); - ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; - return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); + const std::string &type = weapon->getTypeName(); + if(type == typeid(ESM::Weapon).name()) + { + const MWWorld::LiveCellRef *ref = weapon->get(); + ESM::Weapon::Type weaponType = (ESM::Weapon::Type)ref->mBase->mData.mType; + return !(MWMechanics::getWeaponType(weaponType)->mFlags & ESM::WeaponType::TwoHanded); + } + else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) + return true; } - else if (type == typeid(ESM::Lockpick).name() || type == typeid(ESM::Probe).name()) - return true; } } } From 15cd305253656e5e3153330f77dcc9bea862629f Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 14 May 2019 17:34:41 +0400 Subject: [PATCH 73/79] Reset idle animation only when play movement animation (bug #5196) --- CHANGELOG.md | 1 + apps/openmw/mwmechanics/character.cpp | 34 +++++++++++++++++---------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fd166c72..c1755c42d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -167,6 +167,7 @@ Bug #5182: OnPCEquip doesn't trigger on skipped beast race attempts to equip something not equippable by beasts Bug #5186: Equipped item enchantments don't affect creatures Bug #5190: On-strike enchantments can be applied to and used with non-projectile ranged weapons + Bug #5196: Dwarven ghosts do not use idle animations Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4b9287f31..60da8b27d 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -477,6 +477,11 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup if (movement == mMovementState && idle == mIdleState && !force) return; + // Reset idle if we actually play movement animations excepts of these cases: + // 1. When we play turning animations + // 2. When we use a fallback animation for lower body since movement animation for given weapon is missing (e.g. for crossbows and spellcasting) + bool resetIdle = (movement != CharState_None && !isTurning()); + std::string movementAnimName; MWRender::Animation::BlendMask movemask; const StateInfo *movestate; @@ -508,14 +513,9 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup // For upper body there will be idle animation. if (movemask == MWRender::Animation::BlendMask_LowerBody && idle == CharState_None) idle = CharState_Idle; - } - else if (idle == CharState_None) - { - // In the 1st-person mode use ground idle animations as fallback - if (mPtr == getPlayer() && MWBase::Environment::get().getWorld()->isFirstPerson()) - idle = CharState_Idle; - else - idle = CharState_IdleSwim; + + if (movemask == MWRender::Animation::BlendMask_LowerBody) + resetIdle = false; } } } @@ -538,7 +538,11 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup if(mAnimation->hasAnimation(weapMovementAnimName)) movementAnimName = weapMovementAnimName; else + { movementAnimName = fallbackShortWeaponGroup(movementAnimName, &movemask); + if (movemask == MWRender::Animation::BlendMask_LowerBody) + resetIdle = false; + } } } @@ -565,6 +569,10 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup mMovementAnimationControlled = true; mAnimation->disable(mCurrentMovement); + + if (!mAnimation->hasAnimation(movementAnimName)) + movementAnimName.clear(); + mCurrentMovement = movementAnimName; if(!mCurrentMovement.empty()) { @@ -607,7 +615,12 @@ void CharacterController::refreshMovementAnims(const std::string& weapShortGroup mAnimation->play(mCurrentMovement, Priority_Movement, movemask, false, 1.f, "start", "stop", startpoint, ~0ul, true); + + if (resetIdle) + mAnimation->disable(mCurrentIdle); } + else + mMovementState = CharState_None; } } @@ -2217,10 +2230,7 @@ void CharacterController::update(float duration, bool animationOnly) if(mAnimQueue.empty() || inwater || sneak) { - // Note: turning animations should not interrupt idle ones - if (movestate != CharState_None && !isTurning()) - idlestate = CharState_None; - else if (inwater) + if (inwater) idlestate = CharState_IdleSwim; else if (sneak && !inJump) idlestate = CharState_IdleSneak; From ee4fa93bd4397c812b336e23e6f14e9793fba363 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 28 Oct 2019 01:58:23 +0300 Subject: [PATCH 74/79] Rework prevent merchant equipping setting again --- apps/openmw/mwgui/companionitemmodel.cpp | 4 +-- apps/openmw/mwgui/companionitemmodel.hpp | 2 +- apps/openmw/mwgui/containeritemmodel.cpp | 4 +-- apps/openmw/mwgui/containeritemmodel.hpp | 2 +- apps/openmw/mwgui/hud.cpp | 2 +- apps/openmw/mwgui/inventoryitemmodel.cpp | 4 +-- apps/openmw/mwgui/inventoryitemmodel.hpp | 2 +- apps/openmw/mwgui/itemmodel.cpp | 4 +-- apps/openmw/mwgui/itemmodel.hpp | 6 ++-- apps/openmw/mwgui/tradeitemmodel.cpp | 4 ++- apps/openmw/mwmechanics/actors.cpp | 4 +-- apps/openmw/mwworld/containerstore.cpp | 2 +- apps/openmw/mwworld/containerstore.hpp | 4 +-- apps/openmw/mwworld/inventorystore.cpp | 37 ++---------------------- apps/openmw/mwworld/inventorystore.hpp | 8 ++--- 15 files changed, 26 insertions(+), 63 deletions(-) diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index 87adc94c0..3a272aa86 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -25,12 +25,12 @@ namespace MWGui { } - MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count) + MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { if (hasProfit(mActor)) modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count); - return InventoryItemModel::copyItem(item, count); + return InventoryItemModel::copyItem(item, count, allowAutoEquip); } void CompanionItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index 5de5910fb..b30a98142 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -13,7 +13,7 @@ namespace MWGui public: CompanionItemModel (const MWWorld::Ptr& actor); - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true); virtual void removeItem (const ItemStack& item, size_t count); bool hasProfit(const MWWorld::Ptr& actor); diff --git a/apps/openmw/mwgui/containeritemmodel.cpp b/apps/openmw/mwgui/containeritemmodel.cpp index afb0e2488..0cfa6ebf5 100644 --- a/apps/openmw/mwgui/containeritemmodel.cpp +++ b/apps/openmw/mwgui/containeritemmodel.cpp @@ -91,12 +91,12 @@ ItemModel::ModelIndex ContainerItemModel::getIndex (ItemStack item) return -1; } -MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count) +MWWorld::Ptr ContainerItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { const MWWorld::Ptr& source = mItemSources[mItemSources.size()-1]; if (item.mBase.getContainerStore() == &source.getClass().getContainerStore(source)) throw std::runtime_error("Item to copy needs to be from a different container!"); - return *source.getClass().getContainerStore(source).add(item.mBase, count, source); + return *source.getClass().getContainerStore(source).add(item.mBase, count, source, allowAutoEquip); } void ContainerItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/containeritemmodel.hpp b/apps/openmw/mwgui/containeritemmodel.hpp index a2b14c46f..806cc0a73 100644 --- a/apps/openmw/mwgui/containeritemmodel.hpp +++ b/apps/openmw/mwgui/containeritemmodel.hpp @@ -26,7 +26,7 @@ namespace MWGui virtual ModelIndex getIndex (ItemStack item); virtual size_t getItemCount(); - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true); virtual void removeItem (const ItemStack& item, size_t count); virtual void update(); diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 996ac4b73..4f51dac04 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -38,7 +38,7 @@ namespace MWGui public: WorldItemModel(float left, float top) : mLeft(left), mTop(top) {} virtual ~WorldItemModel() {} - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count) + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool /*allowAutoEquip*/) { MWBase::World* world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index 46094a6b2..f2ff64aa1 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -46,11 +46,11 @@ ItemModel::ModelIndex InventoryItemModel::getIndex (ItemStack item) return -1; } -MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count) +MWWorld::Ptr InventoryItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { if (item.mBase.getContainerStore() == &mActor.getClass().getContainerStore(mActor)) throw std::runtime_error("Item to copy needs to be from a different container!"); - return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor); + return *mActor.getClass().getContainerStore(mActor).add(item.mBase, count, mActor, allowAutoEquip); } void InventoryItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/inventoryitemmodel.hpp b/apps/openmw/mwgui/inventoryitemmodel.hpp index f13bf44d2..d1fb88b6e 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.hpp +++ b/apps/openmw/mwgui/inventoryitemmodel.hpp @@ -17,7 +17,7 @@ namespace MWGui virtual bool onTakeItem(const MWWorld::Ptr &item, int count); - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true); virtual void removeItem (const ItemStack& item, size_t count); /// Move items from this model to \a otherModel. diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index 16a0b0748..5bbf74e26 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -116,9 +116,9 @@ namespace MWGui return mSourceModel->allowedToUseItems(); } - MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count) + MWWorld::Ptr ProxyItemModel::copyItem (const ItemStack& item, size_t count, bool allowAutoEquip) { - return mSourceModel->copyItem (item, count); + return mSourceModel->copyItem (item, count, allowAutoEquip); } void ProxyItemModel::removeItem (const ItemStack& item, size_t count) diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index 01bc1afb2..36432d479 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -65,9 +65,7 @@ namespace MWGui /// @note Derived implementations may return an empty Ptr if the move was unsuccessful. virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); - /// @param setNewOwner If true, set the copied item's owner to the actor we are copying to, - /// otherwise reset owner to "" - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count) = 0; + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; /// Is the player allowed to use items from this item model? (default true) @@ -97,7 +95,7 @@ namespace MWGui virtual bool onDropItem(const MWWorld::Ptr &item, int count); virtual bool onTakeItem(const MWWorld::Ptr &item, int count); - virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count); + virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool allowAutoEquip = true); virtual void removeItem (const ItemStack& item, size_t count); virtual ModelIndex getIndex (ItemStack item); diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 84b23ee2b..b1bab32dc 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -1,6 +1,7 @@ #include "tradeitemmodel.hpp" #include +#include #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -139,8 +140,9 @@ namespace MWGui throw std::runtime_error("The borrowed item disappeared"); const ItemStack& item = sourceModel->getItem(i); + static const bool prevent = Settings::Manager::getBool("prevent merchant equipping", "Game"); // copy the borrowed items to our model - copyItem(item, itemStack.mCount); + copyItem(item, itemStack.mCount, !prevent); // then remove them from the source model sourceModel->removeItem(item, itemStack.mCount); } diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index 7a0c0c754..21a24f457 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -313,7 +313,7 @@ namespace MWMechanics if (actor.getClass().getCreatureStats(actor).isDead()) return; - if (!actor.getClass().hasInventoryStore(actor) || !actor.getClass().getInventoryStore(actor).canActorAutoEquip(actor)) + if (!actor.getClass().hasInventoryStore(actor)) return; if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) @@ -1220,7 +1220,7 @@ namespace MWMechanics heldIter = inventoryStore.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); // If we have a torch and can equip it, then equip it now. - if (heldIter == inventoryStore.end() && inventoryStore.canActorAutoEquip(ptr)) + if (heldIter == inventoryStore.end()) { inventoryStore.equip(MWWorld::InventoryStore::Slot_CarriedLeft, torch, ptr); } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 8c5861e73..5dd380be0 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -266,7 +266,7 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add(const std::string & return add(ref.getPtr(), count, actorPtr); } -MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr) +MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool /*allowAutoEquip*/) { Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index ac6126176..1c690dddf 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -138,7 +138,7 @@ namespace MWWorld bool hasVisibleItems() const; - virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) /// /// \note The item pointed to is not required to exist beyond this function call. @@ -146,8 +146,6 @@ namespace MWWorld /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// - /// @param setOwner Set the owner of the added item to \a actorPtr? If false, the owner is reset to "". - /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. ContainerStoreIterator add(const std::string& id, int count, const Ptr& actorPtr); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index d8c7e25bd..04947335f 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -131,12 +130,12 @@ MWWorld::InventoryStore& MWWorld::InventoryStore::operator= (const InventoryStor return *this; } -MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr) +MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip) { - const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr); + const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, allowAutoEquip); // Auto-equip items if an armor/clothing item is added, but not for the player nor werewolves - if (actorPtr != MWMechanics::getPlayer() + if (allowAutoEquip && actorPtr != MWMechanics::getPlayer() && actorPtr.getClass().isNpc() && !actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) { std::string type = itemPtr.getTypeName(); @@ -208,33 +207,6 @@ MWWorld::ConstContainerStoreIterator MWWorld::InventoryStore::getSlot (int slot) return findSlot (slot); } -bool MWWorld::InventoryStore::canActorAutoEquip(const MWWorld::Ptr& actor) -{ - // Treat player as non-trader indifferently from service flags. - if (actor == MWMechanics::getPlayer()) - return true; - - static const bool prevent = Settings::Manager::getBool("prevent merchant equipping", "Game"); - if (!prevent) - return true; - - // Corpses can be dressed up by the player as desired. - if (actor.getClass().getCreatureStats(actor).isDead()) - return true; - - // Companions can autoequip items. - if (!actor.getClass().getScript(actor).empty() && - actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion")) - return true; - - // If the actor is trader, he can auto-equip items only during initial auto-equipping - int services = actor.getClass().getServices(actor); - if (services & ESM::NPC::AllItems) - return mFirstAutoEquip; - - return true; -} - MWWorld::ContainerStoreIterator MWWorld::InventoryStore::findSlot (int slot) const { if (slot<0 || slot>=static_cast (mSlots.size())) @@ -552,9 +524,6 @@ void MWWorld::InventoryStore::autoEquipShield(const MWWorld::Ptr& actor, TSlots& void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { - if (!canActorAutoEquip(actor)) - return; - TSlots slots_; initSlots (slots_); diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index faa829ef8..2293f96af 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -124,17 +124,15 @@ namespace MWWorld virtual InventoryStore* clone() { return new InventoryStore(*this); } - virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr); + virtual ContainerStoreIterator add (const Ptr& itemPtr, int count, const Ptr& actorPtr, bool allowAutoEquip = true); ///< Add the item pointed to by \a ptr to this container. (Stacks automatically if needed) - /// Auto-equip items if specific conditions are fulfilled (see the implementation). + /// Auto-equip items if specific conditions are fulfilled and allowAutoEquip is true (see the implementation). /// /// \note The item pointed to is not required to exist beyond this function call. /// /// \attention Do not add items to an existing stack by increasing the count instead of /// calling this function! /// - /// @param setOwner Set the owner of the added item to \a actorPtr? - /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); @@ -160,8 +158,6 @@ namespace MWWorld void autoEquip (const MWWorld::Ptr& actor); ///< Auto equip items according to stats and item value. - bool canActorAutoEquip(const MWWorld::Ptr& actor); - const MWMechanics::MagicEffects& getMagicEffects() const; ///< Return magic effects from worn items. From acbdd0391c899818ecbb64a9a61eef8f88583f2f Mon Sep 17 00:00:00 2001 From: Icecream95 Date: Mon, 28 Oct 2019 05:28:12 +0000 Subject: [PATCH 75/79] Install Debian packages again in CI The gcc docker image was updated to a newer version of Debian, so downloading packages from Ubuntu is no longer required. --- .gitlab-ci.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f55e124e8..18535596d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -14,13 +14,7 @@ Debian: - export APT_CACHE_DIR=`pwd`/apt-cache && mkdir -pv $APT_CACHE_DIR - apt-get update -yq - apt-get -o dir::cache::archives="$APT_CACHE_DIR" install -y cmake libboost-filesystem-dev libboost-program-options-dev libboost-system-dev libboost-iostreams-dev libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libswresample-dev libsdl2-dev libqt4-dev libopenal-dev libopenscenegraph-3.4-dev libunshield-dev libtinyxml-dev - # - apt-get install -y libmygui-dev libbullet-dev # to be updated to latest below because stretch is too old - - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet-dev_2.87+dfsg-2_amd64.deb -o libbullet-dev_2.87+dfsg-2_amd64.deb - - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/b/bullet/libbullet2.87_2.87+dfsg-2_amd64.deb -o libbullet2.87_2.87+dfsg-2_amd64.deb - - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb -o libmygui.openglplatform0debian1v5_3.2.2+dfsg-1_amd64.deb - - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb -o libmyguiengine3debian1v5_3.2.2+dfsg-1_amd64.deb - - curl -L http://archive.ubuntu.com/ubuntu/pool/universe/m/mygui/libmygui-dev_3.2.2+dfsg-1_amd64.deb -o libmygui-dev_3.2.2+dfsg-1_amd64.deb - - dpkg --ignore-depends=libmygui.ogreplatform0debian1v5 -i *.deb + - apt-get install -y libmygui-dev libbullet-dev stage: build script: - cores_to_use=$((`nproc`-2)); if (( $cores_to_use < 1 )); then cores_to_use=1; fi From b2e12f0a737ef35969f3a94cac8f03f7bc772dc0 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Mon, 28 Oct 2019 18:58:16 +0400 Subject: [PATCH 76/79] Attempt to fix a regression - crash on ARM --- components/esm/loadregn.hpp | 4 ++-- components/esm/loadscpt.hpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/esm/loadregn.hpp b/components/esm/loadregn.hpp index 4adda5197..6f39dc0bf 100644 --- a/components/esm/loadregn.hpp +++ b/components/esm/loadregn.hpp @@ -33,14 +33,14 @@ struct Region // the engine uses mA as "snow" and mB as "blizard" mA, mB; }; // 10 bytes +#pragma pack(pop) // Reference to a sound that is played randomly in this region struct SoundRef { std::string mSound; unsigned char mChance; - }; // 33 bytes -#pragma pack(pop) + }; WEATstruct mData; int mMapColor; // RGBA diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 64cedb095..e1ffe1b86 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -33,7 +33,7 @@ public: { std::string mName; Script::SCHDstruct mData; - }; // 52 bytes + }; std::string mId; From c6e431d86201f1054b2faa6dfd7327e5abc699da Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Mon, 28 Oct 2019 19:28:14 +0300 Subject: [PATCH 77/79] Avoid copying strings in SearchVisitor and readReferenceCollection --- apps/openmw/mwworld/cellref.cpp | 5 +++++ apps/openmw/mwworld/cellref.hpp | 3 +++ apps/openmw/mwworld/cellstore.cpp | 4 ++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index fb9fd592a..188a80ae1 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -25,6 +25,11 @@ namespace MWWorld return mCellRef.mRefID; } + const std::string* CellRef::getRefIdPtr() const + { + return &mCellRef.mRefID; + } + bool CellRef::getTeleport() const { return mCellRef.mTeleport; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index 04e807ce5..f9f6dbdda 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -34,6 +34,9 @@ namespace MWWorld // Id of object being referenced std::string getRefId() const; + // Pointer to ID of the object being referenced + const std::string* getRefIdPtr() const; + // For doors - true if this door teleports to somewhere else, false // if it should open through animation. bool getTeleport() const; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 3d9c24009..607551f7d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -137,7 +137,7 @@ namespace { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) - if (iter->mRef.getRefNum()==state.mRef.mRefNum && iter->mRef.getRefId() == state.mRef.mRefID) + if (iter->mRef.getRefNum()==state.mRef.mRefNum && *iter->mRef.getRefIdPtr() == state.mRef.mRefID) { // overwrite existing reference iter->load (state); @@ -390,7 +390,7 @@ namespace MWWorld const std::string *mIdToFind; bool operator()(const PtrType& ptr) { - if (ptr.getCellRef().getRefId() == *mIdToFind) + if (*ptr.getCellRef().getRefIdPtr() == *mIdToFind) { mFound = ptr; return false; From d278e5d4aff91d520b089d869a2a75a9ced40a04 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 29 Oct 2019 13:48:08 +0400 Subject: [PATCH 78/79] Fix warnings about variable re-declaration --- apps/opencs/view/render/terrainshapemode.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp index 5f5a60da2..f4f5322e5 100644 --- a/apps/opencs/view/render/terrainshapemode.cpp +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -359,16 +359,16 @@ void CSVRender::TerrainShapeMode::applyTerrainEditChanges() if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); - if (isLandLoaded(cellId)) + std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); + if (isLandLoaded(shiftedCellId)) v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; else { - std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); - if (isLandLoaded(cellId)) + std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); + if (isLandLoaded(shiftedCellId)) v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; } From 8bc2c1ac34f5a159fea31612f2ce48ab3ba23fa6 Mon Sep 17 00:00:00 2001 From: Alexei Dobrohotov Date: Tue, 29 Oct 2019 14:39:21 +0300 Subject: [PATCH 79/79] Make ForceGreeting ignore any extra argument --- components/compiler/extensions0.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 7ccfb9285..51732bed3 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -180,7 +180,7 @@ namespace Compiler extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); - extensions.registerInstruction("forcegreeting","X",opcodeForceGreeting, + extensions.registerInstruction("forcegreeting","z",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); extensions.registerInstruction("setreputation", "l", opcodeSetReputation,