From 97d0fd756a2dff3ac73bd308a1ad7f88c3e4e13d Mon Sep 17 00:00:00 2001 From: Kyle Cooley Date: Mon, 4 Sep 2017 19:31:09 -0400 Subject: [PATCH] LTEX importing --- apps/opencs/model/world/commands.cpp | 85 +++++++++++++++++++++++++ apps/opencs/model/world/commands.hpp | 25 +++++++- apps/opencs/model/world/idtable.cpp | 57 +++++++++++++++++ apps/opencs/model/world/idtable.hpp | 16 ++++- apps/opencs/model/world/land.cpp | 2 +- apps/opencs/model/world/landtexture.cpp | 2 +- apps/opencs/view/world/landcreator.cpp | 20 ++++++ apps/opencs/view/world/landcreator.hpp | 2 + 8 files changed, 204 insertions(+), 5 deletions(-) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index c6a56f7b8..c98f52777 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -2,6 +2,7 @@ #include #include +#include #include @@ -40,6 +41,90 @@ void CSMWorld::TouchCommand::undo() } } +CSMWorld::TouchLandCommand::TouchLandCommand(IdTable& landTable, IdTable& ltexTable, const std::string& id, QUndoCommand* parent) + : QUndoCommand(parent) + , mLands(landTable) + , mLtexs(ltexTable) + , mId(id) + , mOld(nullptr) + , mChanged(false) +{ + setText(("Touch " + mId).c_str()); + mOld.reset(mLands.getRecord(mId).clone()); +} + +void CSMWorld::TouchLandCommand::redo() +{ + int pluginColumn = mLands.findColumnIndex(Columns::ColumnId_PluginIndex); + int oldPlugin = mLands.data(mLands.getModelIndex(mId, pluginColumn)).toInt(); + + mChanged = mLands.touchRecord(mId); + if (mChanged) + { + // Original data + int textureColumn = mLands.findColumnIndex(Columns::ColumnId_LandTexturesIndex); + QByteArray textureByteArray = mLands.data(mLands.getModelIndex(mId, textureColumn)).toByteArray(); + const uint16_t* textureData = reinterpret_cast(textureByteArray.data()); + + // Need to make a copy so the old values can be looked up + QByteArray newTextureByteArray(textureByteArray.data(), textureByteArray.size()); + uint16_t* newTextureData = reinterpret_cast(newTextureByteArray.data()); + + // Find all indices used + std::unordered_set texIndices; + for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i) + { + // All indices are offset by 1 for a default texture + if (textureData[i] > 0) + texIndices.insert(textureData[i] - 1); + } + + std::vector oldTextures; + for (int index : texIndices) + { + oldTextures.push_back(LandTexture::createUniqueRecordId(oldPlugin, index)); + } + + // Import the textures, replace old values + LandTextureIdTable::ImportResults results = dynamic_cast(mLtexs).importTextures(oldTextures); + mCreatedTextures = std::move(results.createdRecords); + for (const auto& it : results.recordMapping) + { + int plugin = 0, newIndex = 0, oldIndex = 0; + LandTexture::parseUniqueRecordId(it.first, plugin, oldIndex); + LandTexture::parseUniqueRecordId(it.second, plugin, newIndex); + + if (newIndex != oldIndex) + { + for (int i = 0; i < Land::LAND_NUM_TEXTURES; ++i) + { + // All indices are offset by 1 for a default texture + if (textureData[i] == oldIndex) + newTextureData[i] = newIndex + 1; + } + } + } + + mLands.setData(mLands.getModelIndex(mId, textureColumn), newTextureByteArray); + } +} + +void CSMWorld::TouchLandCommand::undo() +{ + if (mChanged) + { + mLands.setRecord(mId, *mOld); + mChanged = false; + + for (const std::string& id : mCreatedTextures) + { + int row = mLtexs.getModelIndex(id, 0).row(); + mLtexs.removeRows(row, 1); + } + mCreatedTextures.clear(); + } +} + CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) : QUndoCommand (parent), mModel (&model), mIndex (index), mNew (new_), mHasRecordState(false), mOldRecordState(CSMWorld::RecordBase::State_BaseOnly) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 4c9be7e8a..5f4259cb8 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -31,8 +31,8 @@ namespace CSMWorld TouchCommand(IdTable& model, const std::string& id, QUndoCommand* parent=nullptr); - virtual void redo(); - virtual void undo(); + void redo() override; + void undo() override; private: @@ -43,6 +43,27 @@ namespace CSMWorld bool mChanged; }; + class TouchLandCommand : public QUndoCommand + { + public: + + TouchLandCommand(IdTable& landTable, IdTable& ltexTable, const std::string& id, + QUndoCommand* parent = nullptr); + + void redo() override; + void undo() override; + + private: + + IdTable& mLands; + IdTable& mLtexs; + std::string mId; + std::unique_ptr mOld; + std::vector mCreatedTextures; + + bool mChanged; + }; + class ModifyCommand : public QUndoCommand { QAbstractItemModel *mModel; diff --git a/apps/opencs/model/world/idtable.cpp b/apps/opencs/model/world/idtable.cpp index 94eab3fc3..5543fc13f 100644 --- a/apps/opencs/model/world/idtable.cpp +++ b/apps/opencs/model/world/idtable.cpp @@ -1,11 +1,13 @@ #include "idtable.hpp" +#include #include #include #include "collectionbase.hpp" #include "columnbase.hpp" +#include "landtexture.hpp" CSMWorld::IdTable::IdTable (CollectionBase *idCollection, unsigned int features) : IdTableBase (features), mIdCollection (idCollection) @@ -332,3 +334,58 @@ Qt::ItemFlags CSMWorld::LandTextureIdTable::flags(const QModelIndex& index) cons return flags; } + +CSMWorld::LandTextureIdTable::ImportResults CSMWorld::LandTextureIdTable::importTextures(const std::vector& ids) +{ + ImportResults results; + + for (const std::string& id : ids) + { + int plugin, index; + + LandTexture::parseUniqueRecordId(id, plugin, index); + int oldRow = idCollection()->searchId(id); + + // If it does not exist or it is in the current plugin, it can be skipped. + if (oldRow <= 0 || plugin == 0) + { + results.recordMapping.push_back(std::make_pair(id, id)); + continue; + } + + // Try a direct mapping to the current plugin first. Otherwise iterate until one is found. + // Iteration is deterministic to avoid duplicates. + do { + std::string newId = LandTexture::createUniqueRecordId(0, index); + int newRow = idCollection()->searchId(newId); + + if (newRow < 0) + { + // Id not taken, clone it + cloneRecord(id, newId, UniversalId::Type_LandTexture); + results.createdRecords.push_back(newId); + results.recordMapping.push_back(std::make_pair(id, newId)); + break; + } + + // Id is taken, check if same handle and texture. Note that mId is the handle. + const LandTexture& oldLtex = dynamic_cast&>(idCollection()->getRecord(oldRow)).get(); + const LandTexture& newLtex = dynamic_cast&>(idCollection()->getRecord(newRow)).get(); + if (oldLtex.mId == newLtex.mId && oldLtex.mTexture == newLtex.mTexture) + { + // It's a match + results.recordMapping.push_back(std::make_pair(id, newId)); + break; + } + + // Determine next index. Spread out the indices to reduce conflicts. + size_t MaxIndex = std::numeric_limits::max(); + size_t Prime = (1 << 19) - 1; // A mersenne prime + size_t K = 2154; + + index = ((K * index) % Prime) % MaxIndex; + } while (true); + } + + return results; +} diff --git a/apps/opencs/model/world/idtable.hpp b/apps/opencs/model/world/idtable.hpp index 5e5f9da4a..ccc5ac404 100644 --- a/apps/opencs/model/world/idtable.hpp +++ b/apps/opencs/model/world/idtable.hpp @@ -100,11 +100,22 @@ namespace CSMWorld }; /// An IdTable customized to handle the more unique needs of LandTextureId's which behave - /// differently from other records. + /// differently from other records. The major difference is that base records cannot be + /// modified. class LandTextureIdTable : public IdTable { public: + struct ImportResults + { + using StringPair = std::pair; + + /// The newly added records + std::vector createdRecords; + /// The 1st string is the original id, the 2nd is the mapped id + std::vector recordMapping; + }; + LandTextureIdTable(CollectionBase* idCollection, unsigned int features=0); QVariant data(const QModelIndex& index, int role=Qt::DisplayRole) const override; @@ -112,6 +123,9 @@ namespace CSMWorld bool setData(const QModelIndex& index, const QVariant& value, int role) override; Qt::ItemFlags flags (const QModelIndex & index) const override; + + /// Finds and maps/recreates the specified ids. + ImportResults importTextures(const std::vector& ids); }; } diff --git a/apps/opencs/model/world/land.cpp b/apps/opencs/model/world/land.cpp index 2b12b55fe..74833d7ef 100644 --- a/apps/opencs/model/world/land.cpp +++ b/apps/opencs/model/world/land.cpp @@ -1,7 +1,7 @@ #include "land.hpp" #include -#include +#include namespace CSMWorld { diff --git a/apps/opencs/model/world/landtexture.cpp b/apps/opencs/model/world/landtexture.cpp index 63ebbc11f..32bab1e6d 100644 --- a/apps/opencs/model/world/landtexture.cpp +++ b/apps/opencs/model/world/landtexture.cpp @@ -1,7 +1,7 @@ #include "landtexture.hpp" #include -#include +#include #include diff --git a/apps/opencs/view/world/landcreator.cpp b/apps/opencs/view/world/landcreator.cpp index 34599b2c4..08b8b3620 100644 --- a/apps/opencs/view/world/landcreator.cpp +++ b/apps/opencs/view/world/landcreator.cpp @@ -2,6 +2,8 @@ #include +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" #include "../../model/world/land.hpp" namespace CSVWorld @@ -47,6 +49,24 @@ namespace CSVWorld mY->setValue(y); } + void LandCreator::touch(const std::vector& ids) + { + // Combine multiple touch commands into one "macro" command + std::unique_ptr macro(new QUndoCommand()); + macro->setText("Touch records"); + + CSMWorld::IdTable& lands = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_Lands)); + CSMWorld::IdTable& ltexs = dynamic_cast(*getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); + for (const CSMWorld::UniversalId& uid : ids) + { + // This is not leaked, touchCmd is a child of macro and managed by Qt + CSMWorld::TouchLandCommand* touchCmd = new CSMWorld::TouchLandCommand(lands, ltexs, uid.getId(), macro.get()); + } + + // Execute + getUndoStack().push(macro.release()); + } + void LandCreator::focus() { mX->setFocus(); diff --git a/apps/opencs/view/world/landcreator.hpp b/apps/opencs/view/world/landcreator.hpp index aea202dcd..ef8cf60d5 100644 --- a/apps/opencs/view/world/landcreator.hpp +++ b/apps/opencs/view/world/landcreator.hpp @@ -23,6 +23,8 @@ namespace CSVWorld void cloneMode(const std::string& originId, const CSMWorld::UniversalId::Type type) override; + void touch(const std::vector& ids) override; + void focus() override; void reset() override;