Merge branch 'mergetool'

Conflicts:
	apps/opencs/CMakeLists.txt
	apps/opencs/model/tools/tools.cpp
sceneinput
Marc Zinnschlag 9 years ago
commit a445683312

@ -841,19 +841,13 @@ void Record<ESM::Land>::print()
std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl;
std::cout << " DataTypes: " << mData.mDataTypes << std::endl;
// Seems like this should done with reference counting in the
// loader to me. But I'm not really knowledgable about this
// record type yet. --Cory
bool wasLoaded = (mData.mDataLoaded != 0);
if (mData.mDataTypes) mData.loadData(mData.mDataTypes);
if (mData.mDataLoaded)
{
std::cout << " Height Offset: " << mData.mLandData->mHeightOffset << std::endl;
if (const ESM::Land::LandData *data = mData.getLandData (mData.mDataTypes))
{
std::cout << " Height Offset: " << data->mHeightOffset << std::endl;
// Lots of missing members.
std::cout << " Unknown1: " << mData.mLandData->mUnk1 << std::endl;
std::cout << " Unknown2: " << mData.mLandData->mUnk2 << std::endl;
std::cout << " Unknown1: " << data->mUnk1 << std::endl;
std::cout << " Unknown2: " << data->mUnk2 << std::endl;
}
if (!wasLoaded) mData.unloadData();
}
template<>

@ -35,13 +35,18 @@ opencs_hdrs_noqt (model/world
opencs_units (model/tools
tools reportmodel
tools reportmodel mergeoperation
)
opencs_units_noqt (model/tools
mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck
birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck
startscriptcheck search searchoperation searchstage pathgridcheck soundgencheck magiceffectcheck
mergestages
)
opencs_hdrs_noqt (model/tools
mergestate
)
@ -94,7 +99,7 @@ opencs_hdrs_noqt (view/render
opencs_units (view/tools
reportsubview reporttable searchsubview searchbox
reportsubview reporttable searchsubview searchbox merge
)
opencs_units_noqt (view/tools

@ -20,7 +20,8 @@
CS::Editor::Editor ()
: mUserSettings (mCfgMgr), mDocumentManager (mCfgMgr),
mViewManager (mDocumentManager), mPid(""),
mLock(), mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
mLock(), mMerge (mDocumentManager),
mIpcServerName ("org.openmw.OpenCS"), mServer(NULL), mClientSocket(NULL)
{
std::pair<Files::PathContainer, std::vector<std::string> > config = readConfig();
@ -39,9 +40,12 @@ CS::Editor::Editor ()
mNewGame.setLocalData (mLocal);
mFileDialog.setLocalData (mLocal);
mMerge.setLocalData (mLocal);
connect (&mDocumentManager, SIGNAL (documentAdded (CSMDoc::Document *)),
this, SLOT (documentAdded (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (documentAboutToBeRemoved (CSMDoc::Document *)),
this, SLOT (documentAboutToBeRemoved (CSMDoc::Document *)));
connect (&mDocumentManager, SIGNAL (lastDocumentDeleted()),
this, SLOT (lastDocumentDeleted()));
@ -49,6 +53,7 @@ CS::Editor::Editor ()
connect (&mViewManager, SIGNAL (newAddonRequest ()), this, SLOT (createAddon ()));
connect (&mViewManager, SIGNAL (loadDocumentRequest ()), this, SLOT (loadDocument ()));
connect (&mViewManager, SIGNAL (editSettingsRequest()), this, SLOT (showSettings ()));
connect (&mViewManager, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SLOT (mergeDocument (CSMDoc::Document *)));
connect (&mStartup, SIGNAL (createGame()), this, SLOT (createGame ()));
connect (&mStartup, SIGNAL (createAddon()), this, SLOT (createAddon ()));
@ -359,7 +364,21 @@ void CS::Editor::documentAdded (CSMDoc::Document *document)
mViewManager.addView (document);
}
void CS::Editor::documentAboutToBeRemoved (CSMDoc::Document *document)
{
if (mMerge.getDocument()==document)
mMerge.cancel();
}
void CS::Editor::lastDocumentDeleted()
{
QApplication::quit();
}
void CS::Editor::mergeDocument (CSMDoc::Document *document)
{
mMerge.configure (document);
mMerge.show();
mMerge.raise();
mMerge.activateWindow();
}

@ -27,11 +27,18 @@
#include "view/settings/dialog.hpp"
#include "view/tools/merge.hpp"
namespace VFS
{
class Manager;
}
namespace CSMDoc
{
class Document;
}
namespace CS
{
class Editor : public QObject
@ -55,6 +62,7 @@ namespace CS
boost::interprocess::file_lock mLock;
boost::filesystem::ofstream mPidFile;
bool mFsStrict;
CSVTools::Merge mMerge;
void setupDataFiles (const Files::PathContainer& dataDirs);
@ -94,8 +102,12 @@ namespace CS
void documentAdded (CSMDoc::Document *document);
void documentAboutToBeRemoved (CSMDoc::Document *document);
void lastDocumentDeleted();
void mergeDocument (CSMDoc::Document *document);
private:
QString mIpcServerName;

@ -2250,13 +2250,13 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM
ToUTF8::FromType encoding, const CSMWorld::ResourcesManager& resourcesManager,
const std::vector<std::string>& blacklistedScripts)
: mVFS(vfs), mSavePath (savePath), mContentFiles (files), mNew (new_), mData (encoding, resourcesManager),
mTools (*this),
mTools (*this, encoding),
mProjectPath ((configuration.getUserDataPath() / "projects") /
(savePath.filename().string() + ".project")),
mSavingOperation (*this, mProjectPath, encoding),
mSaving (&mSavingOperation),
mResDir(resDir),
mRunner (mProjectPath), mIdCompletionManager(mData)
mRunner (mProjectPath), mDirty (false), mIdCompletionManager(mData)
{
if (mContentFiles.empty())
throw std::runtime_error ("Empty content file sequence");
@ -2294,6 +2294,8 @@ CSMDoc::Document::Document (const VFS::Manager* vfs, const Files::ConfigurationM
connect (&mTools, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mTools, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
connect (&mTools, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SIGNAL (mergeDone (CSMDoc::Document*)));
connect (&mSaving, SIGNAL (progress (int, int, int)), this, SLOT (progress (int, int, int)));
connect (&mSaving, SIGNAL (done (int, bool)), this, SLOT (operationDone (int, bool)));
@ -2323,7 +2325,7 @@ int CSMDoc::Document::getState() const
{
int state = 0;
if (!mUndoStack.isClean())
if (!mUndoStack.isClean() || mDirty)
state |= State_Modified;
if (mSaving.isRunning())
@ -2388,6 +2390,12 @@ void CSMDoc::Document::runSearch (const CSMWorld::UniversalId& searchId, const C
emit stateChanged (getState(), this);
}
void CSMDoc::Document::runMerge (std::auto_ptr<CSMDoc::Document> target)
{
mTools.runMerge (target);
emit stateChanged (getState(), this);
}
void CSMDoc::Document::abortOperation (int type)
{
if (type==State_Saving)
@ -2409,6 +2417,9 @@ void CSMDoc::Document::reportMessage (const CSMDoc::Message& message, int type)
void CSMDoc::Document::operationDone (int type, bool failed)
{
if (type==CSMDoc::State_Saving && !failed)
mDirty = false;
emit stateChanged (getState(), this);
}
@ -2485,3 +2496,8 @@ CSMWorld::IdCompletionManager &CSMDoc::Document::getIdCompletionManager()
{
return mIdCompletionManager;
}
void CSMDoc::Document::flagAsDirty()
{
mDirty = true;
}

@ -68,6 +68,7 @@ namespace CSMDoc
boost::filesystem::path mResDir;
Blacklist mBlacklist;
Runner mRunner;
bool mDirty;
CSMWorld::IdCompletionManager mIdCompletionManager;
@ -129,7 +130,9 @@ namespace CSMDoc
CSMWorld::UniversalId newSearch();
void runSearch (const CSMWorld::UniversalId& searchId, const CSMTools::Search& search);
void runMerge (std::auto_ptr<CSMDoc::Document> target);
void abortOperation (int type);
const CSMWorld::Data& getData() const;
@ -150,12 +153,18 @@ namespace CSMDoc
CSMWorld::IdCompletionManager &getIdCompletionManager();
void flagAsDirty();
signals:
void stateChanged (int state, CSMDoc::Document *document);
void progress (int current, int max, int type, int threads, CSMDoc::Document *document);
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
private slots:
void modificationStateChanged (bool clean);
@ -173,4 +182,3 @@ namespace CSMDoc
}
#endif

@ -56,10 +56,24 @@ bool CSMDoc::DocumentManager::isEmpty()
void CSMDoc::DocumentManager::addDocument (const std::vector<boost::filesystem::path>& files, const boost::filesystem::path& savePath,
bool new_)
{
Document *document = new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts);
Document *document = makeDocument (files, savePath, new_);
insertDocument (document);
}
CSMDoc::Document *CSMDoc::DocumentManager::makeDocument (
const std::vector< boost::filesystem::path >& files,
const boost::filesystem::path& savePath, bool new_)
{
return new Document (mVFS, mConfiguration, files, new_, savePath, mResDir, mEncoding, mResourcesManager, mBlacklistedScripts);
}
void CSMDoc::DocumentManager::insertDocument (CSMDoc::Document *document)
{
mDocuments.push_back (document);
connect (document, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SLOT (insertDocument (CSMDoc::Document*)));
emit loadRequest (document);
mLoader.hasThingsToDo().wakeAll();
@ -72,6 +86,8 @@ void CSMDoc::DocumentManager::removeDocument (CSMDoc::Document *document)
if (iter==mDocuments.end())
throw std::runtime_error ("removing invalid document");
emit documentAboutToBeRemoved (document);
mDocuments.erase (iter);
document->deleteLater();

@ -56,6 +56,15 @@ namespace CSMDoc
///< \param new_ Do not load the last content file in \a files and instead create in an
/// appropriate way.
/// Create a new document. The ownership of the created document is transferred to
/// the calling function. The DocumentManager does not manage it. Loading has not
/// taken place at the point when the document is returned.
///
/// \param new_ Do not load the last content file in \a files and instead create in an
/// appropriate way.
Document *makeDocument (const std::vector< boost::filesystem::path >& files,
const boost::filesystem::path& savePath, bool new_);
void setResourceDir (const boost::filesystem::path& parResDir);
void setEncoding (ToUTF8::FromType encoding);
@ -84,10 +93,16 @@ namespace CSMDoc
void removeDocument (CSMDoc::Document *document);
///< Emits the lastDocumentDeleted signal, if applicable.
/// Hand over document to *this. The ownership is transferred. The DocumentManager
/// will initiate the load procedure, if necessary
void insertDocument (CSMDoc::Document *document);
signals:
void documentAdded (CSMDoc::Document *document);
void documentAboutToBeRemoved (CSMDoc::Document *document);
void loadRequest (CSMDoc::Document *document);
void lastDocumentDeleted();

@ -83,7 +83,9 @@ namespace CSMDoc
void executeStage();
void operationDone();
protected slots:
virtual void operationDone();
};
}

@ -415,15 +415,16 @@ void CSMDoc::WriteLandCollectionStage::perform (int stage, Messages& messages)
if (land.mState==CSMWorld::RecordBase::State_Modified ||
land.mState==CSMWorld::RecordBase::State_ModifiedOnly)
{
CSMWorld::Land record = land.get();
const CSMWorld::Land& record = land.get();
mState.getWriter().startRecord (record.mLand->sRecordId);
mState.getWriter().startRecord (record.sRecordId);
record.save (mState.getWriter());
record.mLand->save (mState.getWriter());
if(record.mLand->mLandData)
record.mLand->mLandData->save (mState.getWriter());
if (const ESM::Land::LandData *data = record.getLandData (record.mDataTypes))
data->save (mState.getWriter());
mState.getWriter().endRecord (record.mLand->sRecordId);
mState.getWriter().endRecord (record.sRecordId);
}
else if (land.mState==CSMWorld::RecordBase::State_Deleted)
{

@ -12,7 +12,7 @@ namespace CSMDoc
State_Saving = 16,
State_Verifying = 32,
State_Compiling = 64, // not implemented yet
State_Merging = 64,
State_Searching = 128,
State_Loading = 256 // pseudo-state; can not be encountered in a loaded document
};

@ -0,0 +1,59 @@
#include "mergeoperation.hpp"
#include "../doc/state.hpp"
#include "../doc/document.hpp"
#include "mergestages.hpp"
CSMTools::MergeOperation::MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding)
: CSMDoc::Operation (CSMDoc::State_Merging, true), mState (document)
{
appendStage (new StartMergeStage (mState));
appendStage (new MergeIdCollectionStage<ESM::Global> (mState, &CSMWorld::Data::getGlobals));
appendStage (new MergeIdCollectionStage<ESM::GameSetting> (mState, &CSMWorld::Data::getGmsts));
appendStage (new MergeIdCollectionStage<ESM::Skill> (mState, &CSMWorld::Data::getSkills));
appendStage (new MergeIdCollectionStage<ESM::Class> (mState, &CSMWorld::Data::getClasses));
appendStage (new MergeIdCollectionStage<ESM::Faction> (mState, &CSMWorld::Data::getFactions));
appendStage (new MergeIdCollectionStage<ESM::Race> (mState, &CSMWorld::Data::getRaces));
appendStage (new MergeIdCollectionStage<ESM::Sound> (mState, &CSMWorld::Data::getSounds));
appendStage (new MergeIdCollectionStage<ESM::Script> (mState, &CSMWorld::Data::getScripts));
appendStage (new MergeIdCollectionStage<ESM::Region> (mState, &CSMWorld::Data::getRegions));
appendStage (new MergeIdCollectionStage<ESM::BirthSign> (mState, &CSMWorld::Data::getBirthsigns));
appendStage (new MergeIdCollectionStage<ESM::Spell> (mState, &CSMWorld::Data::getSpells));
appendStage (new MergeIdCollectionStage<ESM::Dialogue> (mState, &CSMWorld::Data::getTopics));
appendStage (new MergeIdCollectionStage<ESM::Dialogue> (mState, &CSMWorld::Data::getJournals));
appendStage (new MergeIdCollectionStage<CSMWorld::Cell> (mState, &CSMWorld::Data::getCells));
appendStage (new MergeIdCollectionStage<ESM::Filter> (mState, &CSMWorld::Data::getFilters));
appendStage (new MergeIdCollectionStage<ESM::Enchantment> (mState, &CSMWorld::Data::getEnchantments));
appendStage (new MergeIdCollectionStage<ESM::BodyPart> (mState, &CSMWorld::Data::getBodyParts));
appendStage (new MergeIdCollectionStage<ESM::DebugProfile> (mState, &CSMWorld::Data::getDebugProfiles));
appendStage (new MergeIdCollectionStage<ESM::SoundGenerator> (mState, &CSMWorld::Data::getSoundGens));
appendStage (new MergeIdCollectionStage<ESM::MagicEffect> (mState, &CSMWorld::Data::getMagicEffects));
appendStage (new MergeIdCollectionStage<ESM::StartScript> (mState, &CSMWorld::Data::getStartScripts));
appendStage (new MergeIdCollectionStage<CSMWorld::Pathgrid, CSMWorld::SubCellCollection<CSMWorld::Pathgrid> > (mState, &CSMWorld::Data::getPathgrids));
appendStage (new MergeIdCollectionStage<CSMWorld::Info, CSMWorld::InfoCollection> (mState, &CSMWorld::Data::getTopicInfos));
appendStage (new MergeIdCollectionStage<CSMWorld::Info, CSMWorld::InfoCollection> (mState, &CSMWorld::Data::getJournalInfos));
appendStage (new MergeRefIdsStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new MergeReferencesStage (mState));
appendStage (new ListLandTexturesMergeStage (mState));
appendStage (new MergeLandTexturesStage (mState));
appendStage (new MergeLandStage (mState));
appendStage (new FinishMergedDocumentStage (mState, encoding));
}
void CSMTools::MergeOperation::setTarget (std::auto_ptr<CSMDoc::Document> document)
{
mState.mTarget = document;
}
void CSMTools::MergeOperation::operationDone()
{
CSMDoc::Operation::operationDone();
if (mState.mCompleted)
emit mergeDone (mState.mTarget.release());
}

@ -0,0 +1,45 @@
#ifndef CSM_TOOLS_MERGEOPERATION_H
#define CSM_TOOLS_MERGEOPERATION_H
#include <memory>
#include <components/to_utf8/to_utf8.hpp>
#include "../doc/operation.hpp"
#include "mergestate.hpp"
namespace CSMDoc
{
class Document;
}
namespace CSMTools
{
class MergeOperation : public CSMDoc::Operation
{
Q_OBJECT
MergeState mState;
public:
MergeOperation (CSMDoc::Document& document, ToUTF8::FromType encoding);
/// \attention Do not call this function while a merge is running.
void setTarget (std::auto_ptr<CSMDoc::Document> document);
protected slots:
virtual void operationDone();
signals:
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
};
}
#endif

@ -0,0 +1,258 @@
#include "mergestages.hpp"
#include <sstream>
#include <components/misc/stringops.hpp>
#include "mergestate.hpp"
#include "../doc/document.hpp"
#include "../world/data.hpp"
CSMTools::StartMergeStage::StartMergeStage (MergeState& state)
: mState (state)
{}
int CSMTools::StartMergeStage::setup()
{
return 1;
}
void CSMTools::StartMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
mState.mCompleted = false;
mState.mTextureIndices.clear();
}
CSMTools::FinishMergedDocumentStage::FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding)
: mState (state), mEncoder (encoding)
{}
int CSMTools::FinishMergedDocumentStage::setup()
{
return 1;
}
void CSMTools::FinishMergedDocumentStage::perform (int stage, CSMDoc::Messages& messages)
{
// We know that the content file list contains at least two entries and that the first one
// does exist on disc (otherwise it would have been impossible to initiate a merge on that
// document).
boost::filesystem::path path = mState.mSource.getContentFiles()[0];
ESM::ESMReader reader;
reader.setEncoder (&mEncoder);
reader.open (path.string());
CSMWorld::MetaData source;
source.mId = "sys::meta";
source.load (reader);
CSMWorld::MetaData target = mState.mTarget->getData().getMetaData();
target.mAuthor = source.mAuthor;
target.mDescription = source.mDescription;
mState.mTarget->getData().setMetaData (target);
mState.mCompleted = true;
}
CSMTools::MergeRefIdsStage::MergeRefIdsStage (MergeState& state) : mState (state) {}
int CSMTools::MergeRefIdsStage::setup()
{
return mState.mSource.getData().getReferenceables().getSize();
}
void CSMTools::MergeRefIdsStage::perform (int stage, CSMDoc::Messages& messages)
{
mState.mSource.getData().getReferenceables().copyTo (
stage, mState.mTarget->getData().getReferenceables());
}
CSMTools::MergeReferencesStage::MergeReferencesStage (MergeState& state)
: mState (state)
{}
int CSMTools::MergeReferencesStage::setup()
{
mIndex.clear();
return mState.mSource.getData().getReferences().getSize();
}
void CSMTools::MergeReferencesStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::CellRef>& record =
mState.mSource.getData().getReferences().getRecord (stage);
if (!record.isDeleted())
{
CSMWorld::CellRef ref = record.get();
ref.mOriginalCell = ref.mCell;
ref.mRefNum.mIndex = mIndex[Misc::StringUtils::lowerCase (ref.mCell)]++;
ref.mRefNum.mContentFile = 0;
CSMWorld::Record<CSMWorld::CellRef> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &ref);
mState.mTarget->getData().getReferences().appendRecord (newRecord);
}
}
CSMTools::ListLandTexturesMergeStage::ListLandTexturesMergeStage (MergeState& state)
: mState (state)
{}
int CSMTools::ListLandTexturesMergeStage::setup()
{
return mState.mSource.getData().getLand().getSize();
}
void CSMTools::ListLandTexturesMergeStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
if (!record.isDeleted())
{
const CSMWorld::Land& land = record.get();
// make sure record is loaded
land.loadData (ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);
if (const ESM::Land::LandData *data = land.getLandData (ESM::Land::DATA_VTEX))
{
// list texture indices
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
mState.mTextureIndices[key] = -1;
}
}
}
}
CSMTools::MergeLandTexturesStage::MergeLandTexturesStage (MergeState& state)
: mState (state), mNext (mState.mTextureIndices.end())
{}
int CSMTools::MergeLandTexturesStage::setup()
{
// Should use the size of mState.mTextureIndices instead, but that is not available at this
// point. Unless there are any errors in the land and land texture records this will not
// make a difference.
return mState.mSource.getData().getLandTextures().getSize();
}
void CSMTools::MergeLandTexturesStage::perform (int stage, CSMDoc::Messages& messages)
{
if (stage==0)
mNext = mState.mTextureIndices.begin();
bool found = false;
do
{
if (mNext==mState.mTextureIndices.end())
return;
mNext->second = stage+1;
std::ostringstream stream;
stream << mNext->first.first-1 << "_" << mNext->first.second;
int index = mState.mSource.getData().getLandTextures().searchId (stream.str());
if (index!=-1)
{
CSMWorld::LandTexture texture =
mState.mSource.getData().getLandTextures().getRecord (index).get();
std::ostringstream stream;
stream << mNext->second-1 << "_0";
texture.mIndex = mNext->second-1;
texture.mId = stream.str();
CSMWorld::Record<CSMWorld::LandTexture> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &texture);
mState.mTarget->getData().getLandTextures().appendRecord (newRecord);
found = true;
}
++mNext;
}
while (!found);
}
CSMTools::MergeLandStage::MergeLandStage (MergeState& state) : mState (state) {}
int CSMTools::MergeLandStage::setup()
{
return mState.mSource.getData().getLand().getSize();
}
void CSMTools::MergeLandStage::perform (int stage, CSMDoc::Messages& messages)
{
const CSMWorld::Record<CSMWorld::Land>& record =
mState.mSource.getData().getLand().getRecord (stage);
if (!record.isDeleted())
{
const CSMWorld::Land& land = record.get();
land.loadData (ESM::Land::DATA_VCLR | ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML |
ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);
CSMWorld::Land newLand (land);
newLand.mEsm = 0; // avoid potential dangling pointer (ESMReader isn't needed anyway,
// because record is already fully loaded)
newLand.mPlugin = 0;
if (land.mDataTypes & ESM::Land::DATA_VTEX)
{
// adjust land texture references
if (ESM::Land::LandData *data = newLand.getLandData())
{
std::pair<uint16_t, int> key;
key.second = land.mPlugin;
for (int i=0; i<ESM::Land::LAND_NUM_TEXTURES; ++i)
{
key.first = data->mTextures[i];
std::map<std::pair<uint16_t, int>, int>::const_iterator iter =
mState.mTextureIndices.find (key);
if (iter!=mState.mTextureIndices.end())
data->mTextures[i] = iter->second;
else
data->mTextures[i] = 0;
}
}
}
CSMWorld::Record<CSMWorld::Land> newRecord (
CSMWorld::RecordBase::State_ModifiedOnly, 0, &newLand);
mState.mTarget->getData().getLand().appendRecord (newRecord);
}
}

@ -0,0 +1,166 @@
#ifndef CSM_TOOLS_MERGESTAGES_H
#define CSM_TOOLS_MERGESTAGES_H
#include <algorithm>
#include <map>
#include <components/to_utf8/to_utf8.hpp>
#include "../doc/stage.hpp"
#include "../world/data.hpp"
#include "mergestate.hpp"
namespace CSMTools
{
class StartMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
StartMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class FinishMergedDocumentStage : public CSMDoc::Stage
{
MergeState& mState;
ToUTF8::Utf8Encoder mEncoder;
public:
FinishMergedDocumentStage (MergeState& state, ToUTF8::FromType encoding);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<typename RecordType, typename Collection = CSMWorld::IdCollection<RecordType> >
class MergeIdCollectionStage : public CSMDoc::Stage
{
MergeState& mState;
Collection& (CSMWorld::Data::*mAccessor)();
public:
MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)());
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
template<typename RecordType, typename Collection>
MergeIdCollectionStage<RecordType, Collection>::MergeIdCollectionStage (MergeState& state, Collection& (CSMWorld::Data::*accessor)())
: mState (state), mAccessor (accessor)
{}
template<typename RecordType, typename Collection>
int MergeIdCollectionStage<RecordType, Collection>::setup()
{
return (mState.mSource.getData().*mAccessor)().getSize();
}
template<typename RecordType, typename Collection>
void MergeIdCollectionStage<RecordType, Collection>::perform (int stage, CSMDoc::Messages& messages)
{
const Collection& source = (mState.mSource.getData().*mAccessor)();
Collection& target = (mState.mTarget->getData().*mAccessor)();
const CSMWorld::Record<RecordType>& record = source.getRecord (stage);
if (!record.isDeleted())
target.appendRecord (CSMWorld::Record<RecordType> (CSMWorld::RecordBase::State_ModifiedOnly, 0, &record.get()));
}
class MergeRefIdsStage : public CSMDoc::Stage
{
MergeState& mState;
public:
MergeRefIdsStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeReferencesStage : public CSMDoc::Stage
{
MergeState& mState;
std::map<std::string, int> mIndex;
public:
MergeReferencesStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class ListLandTexturesMergeStage : public CSMDoc::Stage
{
MergeState& mState;
public:
ListLandTexturesMergeStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandTexturesStage : public CSMDoc::Stage
{
MergeState& mState;
std::map<std::pair<uint16_t, int>, int>::iterator mNext;
public:
MergeLandTexturesStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
class MergeLandStage : public CSMDoc::Stage
{
MergeState& mState;
public:
MergeLandStage (MergeState& state);
virtual int setup();
///< \return number of steps
virtual void perform (int stage, CSMDoc::Messages& messages);
///< Messages resulting from this stage will be appended to \a messages.
};
}
#endif

@ -0,0 +1,24 @@
#ifndef CSM_TOOLS_MERGESTATE_H
#define CSM_TOOLS_MERGESTATE_H
#include <stdint.h>
#include <memory>
#include <map>
#include "../doc/document.hpp"
namespace CSMTools
{
struct MergeState
{
std::auto_ptr<CSMDoc::Document> mTarget;
CSMDoc::Document& mSource;
bool mCompleted;
std::map<std::pair<uint16_t, int>, int> mTextureIndices; // (texture, content file) -> new texture
MergeState (CSMDoc::Document& source) : mSource (source), mCompleted (false) {}
};
}
#endif

@ -28,6 +28,7 @@
#include "pathgridcheck.hpp"
#include "soundgencheck.hpp"
#include "magiceffectcheck.hpp"
#include "mergeoperation.hpp"
CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{
@ -35,6 +36,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::get (int type)
{
case CSMDoc::State_Verifying: return &mVerifier;
case CSMDoc::State_Searching: return &mSearch;
case CSMDoc::State_Merging: return &mMerge;
}
return 0;
@ -53,7 +55,7 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
std::vector<QString> settings;
settings.push_back ("script-editor/warnings");
mVerifierOperation->configureSettings (settings);
connect (&mVerifier, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int)));
@ -120,9 +122,9 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier()
return &mVerifier;
}
CSMTools::Tools::Tools (CSMDoc::Document& document)
CSMTools::Tools::Tools (CSMDoc::Document& document, ToUTF8::FromType encoding)
: mDocument (document), mData (document.getData()), mVerifierOperation (0),
mSearchOperation (0), mNextReportNumber (0)
mSearchOperation (0), mMergeOperation (0), mNextReportNumber (0), mEncoding (encoding)
{
// index 0: load error log
mReports.insert (std::make_pair (mNextReportNumber++, new ReportModel));
@ -132,6 +134,10 @@ CSMTools::Tools::Tools (CSMDoc::Document& document)
connect (&mSearch, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool)));
connect (&mSearch, SIGNAL (reportMessage (const CSMDoc::Message&, int)),
this, SLOT (verifierMessage (const CSMDoc::Message&, int)));
connect (&mMerge, SIGNAL (progress (int, int, int)), this, SIGNAL (progress (int, int, int)));
connect (&mMerge, SIGNAL (done (int, bool)), this, SIGNAL (done (int, bool)));
// don't need to connect report message, since there are no messages for merge
}
CSMTools::Tools::~Tools()
@ -148,6 +154,12 @@ CSMTools::Tools::~Tools()
delete mSearchOperation;
}
if (mMergeOperation)
{
mMerge.abortAndWait();
delete mMergeOperation;
}
for (std::map<int, ReportModel *>::iterator iter (mReports.begin()); iter!=mReports.end(); ++iter)
delete iter->second;
}
@ -159,7 +171,7 @@ CSMWorld::UniversalId CSMTools::Tools::runVerifier (const CSMWorld::UniversalId&
if (mReports.find (reportNumber)==mReports.end())
mReports.insert (std::make_pair (reportNumber, new ReportModel));
mActiveReports[CSMDoc::State_Verifying] = reportNumber;
getVerifier()->start();
@ -189,6 +201,25 @@ void CSMTools::Tools::runSearch (const CSMWorld::UniversalId& searchId, const Se
mSearch.start();
}
void CSMTools::Tools::runMerge (std::auto_ptr<CSMDoc::Document> target)
{
// not setting an active report, because merge does not produce messages
if (!mMergeOperation)
{
mMergeOperation = new MergeOperation (mDocument, mEncoding);
mMerge.setOperation (mMergeOperation);
connect (mMergeOperation, SIGNAL (mergeDone (CSMDoc::Document*)),
this, SIGNAL (mergeDone (CSMDoc::Document*)));
}
target->flagAsDirty();
mMergeOperation->setTarget (target);
mMerge.start();
}
void CSMTools::Tools::abortOperation (int type)
{
if (CSMDoc::OperationHolder *operation = get (type))
@ -201,6 +232,7 @@ int CSMTools::Tools::getRunningOperations() const
{
CSMDoc::State_Verifying,
CSMDoc::State_Searching,
CSMDoc::State_Merging,
-1
};
@ -231,4 +263,3 @@ void CSMTools::Tools::verifierMessage (const CSMDoc::Message& message, int type)
if (iter!=mActiveReports.end())
mReports[iter->second]->add (message);
}

@ -1,9 +1,14 @@
#ifndef CSM_TOOLS_TOOLS_H
#define CSM_TOOLS_TOOLS_H
#include <memory>
#include <map>
#include <components/to_utf8/to_utf8.hpp>
#include <QObject>
#include <map>
#include <boost/filesystem/path.hpp>
#include "../doc/operationholder.hpp"
@ -24,6 +29,7 @@ namespace CSMTools
class ReportModel;
class Search;
class SearchOperation;
class MergeOperation;
class Tools : public QObject
{
@ -35,9 +41,12 @@ namespace CSMTools
CSMDoc::OperationHolder mVerifier;
SearchOperation *mSearchOperation;
CSMDoc::OperationHolder mSearch;
MergeOperation *mMergeOperation;
CSMDoc::OperationHolder mMerge;
std::map<int, ReportModel *> mReports;
int mNextReportNumber;
std::map<int, int> mActiveReports; // type, report number
ToUTF8::FromType mEncoding;
// not implemented
Tools (const Tools&);
@ -53,7 +62,7 @@ namespace CSMTools
public:
Tools (CSMDoc::Document& document);
Tools (CSMDoc::Document& document, ToUTF8::FromType encoding);
virtual ~Tools();
@ -67,7 +76,9 @@ namespace CSMTools
CSMWorld::UniversalId newSearch();
void runSearch (const CSMWorld::UniversalId& searchId, const Search& search);
void runMerge (std::auto_ptr<CSMDoc::Document> target);
void abortOperation (int type);
///< \attention The operation is not aborted immediately.
@ -85,6 +96,10 @@ namespace CSMTools
void progress (int current, int max, int type);
void done (int type, bool failed);
/// \attention When this signal is emitted, *this hands over the ownership of the
/// document. This signal must be handled to avoid a leak.
void mergeDone (CSMDoc::Document *document);
};
}

@ -485,7 +485,7 @@ CSMWorld::Data::Data (ToUTF8::FromType encoding, const ResourcesManager& resourc
mMetaData.addColumn (new FormatColumn<MetaData>);
mMetaData.addColumn (new AuthorColumn<MetaData>);
mMetaData.addColumn (new FileDescriptionColumn<MetaData>);
addModel (new IdTable (&mGlobals), UniversalId::Type_Global);
addModel (new IdTable (&mGmsts), UniversalId::Type_Gmst);
addModel (new IdTable (&mSkills), UniversalId::Type_Skill);
@ -775,11 +775,21 @@ const CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand() const
return mLand;
}
CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand()
{
return mLand;
}
const CSMWorld::IdCollection<CSMWorld::LandTexture>& CSMWorld::Data::getLandTextures() const
{
return mLandTextures;
}
CSMWorld::IdCollection<CSMWorld::LandTexture>& CSMWorld::Data::getLandTextures()
{
return mLandTextures;
}
const CSMWorld::IdCollection<ESM::SoundGenerator>& CSMWorld::Data::getSoundGens() const
{
return mSoundGens;
@ -830,6 +840,12 @@ const CSMWorld::MetaData& CSMWorld::Data::getMetaData() const
return mMetaData.getRecord (0).get();
}
void CSMWorld::Data::setMetaData (const MetaData& metaData)
{
Record<MetaData> record (RecordBase::State_ModifiedOnly, 0, &metaData);
mMetaData.setRecord (0, record);
}
QAbstractItemModel *CSMWorld::Data::getTableModel (const CSMWorld::UniversalId& id)
{
std::map<UniversalId::Type, QAbstractItemModel *>::iterator iter = mModelIndex.find (id.getType());
@ -882,7 +898,7 @@ int CSMWorld::Data::startLoading (const boost::filesystem::path& path, bool base
mMetaData.setRecord (0, Record<MetaData> (RecordBase::State_ModifiedOnly, 0, &metaData));
}
return mReader->getRecordCount();
}
@ -941,8 +957,10 @@ bool CSMWorld::Data::continueLoading (CSMDoc::Messages& messages)
{
int index = mLand.load(*mReader, mBase);
if (index!=-1 && !mBase)
mLand.getRecord (index).mModified.mLand->loadData (
// Load all land data for now. A future optimisation may only load non-base data
// if a suitable mechanism for avoiding race conditions can be established.
if (index!=-1/* && !mBase*/)
mLand.getRecord (index).get().loadData (
ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR |
ESM::Land::DATA_VTEX | ESM::Land::DATA_WNAM);

@ -232,8 +232,12 @@ namespace CSMWorld
const IdCollection<CSMWorld::Land>& getLand() const;
IdCollection<CSMWorld::Land>& getLand();
const IdCollection<CSMWorld::LandTexture>& getLandTextures() const;
IdCollection<CSMWorld::LandTexture>& getLandTextures();
const IdCollection<ESM::SoundGenerator>& getSoundGens() const;
IdCollection<ESM::SoundGenerator>& getSoundGens();
@ -255,6 +259,8 @@ namespace CSMWorld
const MetaData& getMetaData() const;
void setMetaData (const MetaData& metaData);
QAbstractItemModel *getTableModel (const UniversalId& id);
///< If no table model is available for \a id, an exception is thrown.
///

@ -4,25 +4,13 @@
namespace CSMWorld
{
Land::Land()
{
mLand.reset(new ESM::Land());
}
void Land::load(ESM::ESMReader &esm)
{
mLand->load(esm);
ESM::Land::load(esm);
std::ostringstream stream;
stream << "#" << mLand->mX << " " << mLand->mY;
stream << "#" << mX << " " << mY;
mId = stream.str();
}
void Land::blank()
{
/// \todo
}
}

@ -2,7 +2,7 @@
#define CSM_WORLD_LAND_H
#include <string>
#include <boost/shared_ptr.hpp>
#include <components/esm/loadland.hpp>
namespace CSMWorld
@ -11,18 +11,12 @@ namespace CSMWorld
///
/// \todo Add worldspace support to the Land record.
/// \todo Add a proper copy constructor (currently worked around using shared_ptr)
struct Land
struct Land : public ESM::Land
{
Land();
boost::shared_ptr<ESM::Land> mLand;
std::string mId;
/// Loads the metadata and ID
void load (ESM::ESMReader &esm);
void blank();
};
}

@ -913,3 +913,8 @@ const CSMWorld::NestedRefIdAdapterBase& CSMWorld::RefIdCollection::getNestedAdap
}
throw std::runtime_error("No such column in the nestedadapters");
}
void CSMWorld::RefIdCollection::copyTo (int index, RefIdCollection& target) const
{
mData.copyTo (index, target.mData);
}

@ -138,6 +138,7 @@ namespace CSMWorld
void save (int index, ESM::ESMWriter& writer) const;
const RefIdData& getDataSet() const; //I can't figure out a better name for this one :(
void copyTo (int index, RefIdCollection& target) const;
};
}

@ -1,6 +1,7 @@
#include "refiddata.hpp"
#include <cassert>
#include <memory>
#include <components/misc/stringops.hpp>
@ -344,7 +345,7 @@ const CSMWorld::RefIdDataContainer< ESM::Static >& CSMWorld::RefIdData::getStati
return mStatics;
}
void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id)
void CSMWorld::RefIdData::insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id)
{
std::map<UniversalId::Type, RefIdDataContainerBase *>::iterator iter =
mRecordContainers.find (type);
@ -357,3 +358,16 @@ void CSMWorld::RefIdData::insertRecord(CSMWorld::RecordBase& record, CSMWorld::U
mIndex.insert (std::make_pair (Misc::StringUtils::lowerCase (id),
LocalIndex (iter->second->getSize()-1, type)));
}
void CSMWorld::RefIdData::copyTo (int index, RefIdData& target) const
{
LocalIndex localIndex = globalToLocalIndex (index);
RefIdDataContainerBase *source = mRecordContainers.find (localIndex.second)->second;
std::string id = source->getId (localIndex.first);
std::auto_ptr<CSMWorld::RecordBase> newRecord (source->getRecord (localIndex.first).modifiedCopy());
target.insertRecord (*newRecord, localIndex.second, id);
}

@ -210,7 +210,8 @@ namespace CSMWorld
void erase (int index, int count);
void insertRecord(CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type, const std::string& id);
void insertRecord (CSMWorld::RecordBase& record, CSMWorld::UniversalId::Type type,
const std::string& id);
const RecordBase& getRecord (const LocalIndex& index) const;
@ -252,11 +253,9 @@ namespace CSMWorld
const RefIdDataContainer<ESM::Probe >& getProbes() const;
const RefIdDataContainer<ESM::Repair>& getRepairs() const;
const RefIdDataContainer<ESM::Static>& getStatics() const;
void copyTo (int index, RefIdData& target) const;
};
}
#endif

@ -63,6 +63,7 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon)
QString message;
mValid = (!name.isEmpty());
bool warning = false;
if (!mValid)
{
@ -105,13 +106,14 @@ void CSVDoc::AdjusterWidget::setName (const QString& name, bool addon)
{
/// \todo add an user setting to make this an error.
message += "<p>A file with the same name already exists. If you continue, it will be overwritten.";
warning = true;
}
}
}
mMessage->setText (message);
mIcon->setPixmap (style()->standardIcon (
mValid ? QStyle::SP_MessageBoxInformation : QStyle::SP_MessageBoxWarning).
mValid ? (warning ? QStyle::SP_MessageBoxWarning : QStyle::SP_MessageBoxInformation) : QStyle::SP_MessageBoxCritical).
pixmap (QSize (16, 16)));
emit stateChanged (mValid);

@ -55,3 +55,11 @@ void CSVDoc::FileWidget::extensionLabelIsVisible(bool visible)
{
mType->setVisible(visible);
}
void CSVDoc::FileWidget::setName (const std::string& text)
{
QString text2 = QString::fromUtf8 (text.c_str());
mInput->setText (text2);
textChanged (text2);
}

@ -3,6 +3,8 @@
#include <QWidget>
#include <string>
class QLabel;
class QString;
class QLineEdit;
@ -29,6 +31,8 @@ namespace CSVDoc
void extensionLabelIsVisible(bool visible);
void setName (const std::string& text);
private slots:
void textChanged (const QString& text);

@ -19,6 +19,7 @@ void CSVDoc::Operation::updateLabel (int threads)
case CSMDoc::State_Saving: name = "saving"; break;
case CSMDoc::State_Verifying: name = "verifying"; break;
case CSMDoc::State_Searching: name = "searching"; break;
case CSMDoc::State_Merging: name = "merging"; break;
}
std::ostringstream stream;
@ -122,7 +123,7 @@ void CSVDoc::Operation::setBarColor (int type)
bottomColor = "#9ECB2D"; //green gloss
break;
case CSMDoc::State_Compiling:
case CSMDoc::State_Merging:
topColor = "#F3E2C7";
midTopColor = "#C19E67";

@ -66,6 +66,10 @@ void CSVDoc::View::setupFileMenu()
connect (mVerify, SIGNAL (triggered()), this, SLOT (verify()));
file->addAction (mVerify);
mMerge = new QAction (tr ("Merge"), this);
connect (mMerge, SIGNAL (triggered()), this, SLOT (merge()));
file->addAction (mMerge);
QAction *loadErrors = new QAction (tr ("Load Error Log"), this);
connect (loadErrors, SIGNAL (triggered()), this, SLOT (loadErrorLog()));
file->addAction (loadErrors);
@ -393,6 +397,9 @@ void CSVDoc::View::updateActions()
mGlobalDebugProfileMenu->updateActions (running);
mStopDebug->setEnabled (running);
mMerge->setEnabled (mDocument->getContentFiles().size()>1 &&
!(mDocument->getState() & CSMDoc::State_Merging));
}
CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int totalViews)
@ -471,6 +478,7 @@ void CSVDoc::View::updateDocumentState()
static const int operations[] =
{
CSMDoc::State_Saving, CSMDoc::State_Verifying, CSMDoc::State_Searching,
CSMDoc::State_Merging,
-1 // end marker
};
@ -968,3 +976,8 @@ void CSVDoc::View::updateScrollbar()
else
mSubViewWindow.setMinimumWidth(0);
}
void CSVDoc::View::merge()
{
emit mergeDocument (mDocument);
}

@ -43,6 +43,7 @@ namespace CSVDoc
QAction *mVerify;
QAction *mShowStatusBar;
QAction *mStopDebug;
QAction *mMerge;
std::vector<QAction *> mEditingActions;
Operations *mOperations;
SubViewFactoryManager mSubViewFactory;
@ -129,6 +130,8 @@ namespace CSVDoc
void editSettingsRequest();
void mergeDocument (CSMDoc::Document *document);
public slots:
void addSubView (const CSMWorld::UniversalId& id, const std::string& hint = "");
@ -237,6 +240,8 @@ namespace CSVDoc
void closeRequest (SubView *subView);
void moveScrollBarToEnd(int min, int max);
void merge();
};
}

@ -173,6 +173,7 @@ CSVDoc::View *CSVDoc::ViewManager::addView (CSMDoc::Document *document)
connect (view, SIGNAL (newAddonRequest ()), this, SIGNAL (newAddonRequest()));
connect (view, SIGNAL (loadDocumentRequest ()), this, SIGNAL (loadDocumentRequest()));
connect (view, SIGNAL (editSettingsRequest()), this, SIGNAL (editSettingsRequest()));
connect (view, SIGNAL (mergeDocument (CSMDoc::Document *)), this, SIGNAL (mergeDocument (CSMDoc::Document *)));
connect (&CSMSettings::UserSettings::instance(),
SIGNAL (userSettingUpdated(const QString &, const QStringList &)),

@ -77,6 +77,8 @@ namespace CSVDoc
void editSettingsRequest();
void mergeDocument (CSMDoc::Document *document);
public slots:
void exitApplication (CSVDoc::View *view);

@ -31,7 +31,7 @@ bool CSVRender::Cell::addObjects (int start, int end)
bool modified = false;
const CSMWorld::RefCollection& collection = mData.getReferences();
for (int i=start; i<=end; ++i)
{
std::string cell = Misc::StringUtils::lowerCase (collection.getRecord (i).get().mCell);
@ -67,15 +67,16 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
int landIndex = land.searchId(mId);
if (landIndex != -1)
{
const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get();
if(esmLand && esmLand->mDataTypes&ESM::Land::DATA_VHGT)
const ESM::Land& esmLand = land.getRecord(mId).get();
if (esmLand.getLandData (ESM::Land::DATA_VHGT))
{
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, data.getResourceSystem().get(), NULL, new TerrainStorage(mData), Element_Terrain<<1));
mTerrain->loadCell(esmLand->mX,
esmLand->mY);
mTerrain->loadCell(esmLand.mX,
esmLand.mY);
mX = esmLand->mX;
mY = esmLand->mY;
mX = esmLand.mX;
mY = esmLand.mY;
}
}
}

@ -9,7 +9,7 @@ namespace CSVRender
{
}
ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
const ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
{
std::ostringstream stream;
stream << "#" << cellX << " " << cellY;
@ -20,11 +20,10 @@ namespace CSVRender
if (index == -1)
return NULL;
ESM::Land* land = mData.getLand().getRecord(index).get().mLand.get();
const ESM::Land& land = mData.getLand().getRecord(index).get();
int mask = ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX;
if (!land->isDataLoaded(mask))
land->loadData(mask);
return land;
land.loadData (mask);
return &land;
}
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)

@ -18,7 +18,7 @@ namespace CSVRender
private:
const CSMWorld::Data& mData;
virtual ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY);

@ -0,0 +1,142 @@
#include "merge.hpp"
#include <QVBoxLayout>
#include <QDialogButtonBox>
#include <QSplitter>
#include <QPushButton>
#include <QListWidget>
#include <QLabel>
#include <QKeyEvent>
#include "../../model/doc/document.hpp"
#include "../../model/doc/documentmanager.hpp"
#include "../doc/filewidget.hpp"
#include "../doc/adjusterwidget.hpp"
void CSVTools::Merge::keyPressEvent (QKeyEvent *event)
{
if (event->key()==Qt::Key_Escape)
{
event->accept();
cancel();
}
else
QWidget::keyPressEvent (event);
}
CSVTools::Merge::Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent)
: QWidget (parent), mDocument (0), mDocumentManager (documentManager)
{
setWindowTitle ("Merge Content Files into a new Game File");
QVBoxLayout *mainLayout = new QVBoxLayout;
setLayout (mainLayout);
QSplitter *splitter = new QSplitter (Qt::Horizontal, this);
mainLayout->addWidget (splitter, 1);
// left panel (files to be merged)
QWidget *left = new QWidget (this);
left->setContentsMargins (0, 0, 0, 0);
splitter->addWidget (left);
QVBoxLayout *leftLayout = new QVBoxLayout;
left->setLayout (leftLayout);
leftLayout->addWidget (new QLabel ("Files to be merged", this));
mFiles = new QListWidget (this);
leftLayout->addWidget (mFiles, 1);
// right panel (new game file)
QWidget *right = new QWidget (this);
right->setContentsMargins (0, 0, 0, 0);
splitter->addWidget (right);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->setAlignment (Qt::AlignTop);
right->setLayout (rightLayout);
rightLayout->addWidget (new QLabel ("New game file", this));
mNewFile = new CSVDoc::FileWidget (this);
mNewFile->setType (false);
mNewFile->extensionLabelIsVisible (true);
rightLayout->addWidget (mNewFile);
mAdjuster = new CSVDoc::AdjusterWidget (this);
rightLayout->addWidget (mAdjuster);
connect (mNewFile, SIGNAL (nameChanged (const QString&, bool)),
mAdjuster, SLOT (setName (const QString&, bool)));
connect (mAdjuster, SIGNAL (stateChanged (bool)), this, SLOT (stateChanged (bool)));
// buttons
QDialogButtonBox *buttons = new QDialogButtonBox (QDialogButtonBox::Cancel, Qt::Horizontal, this);
connect (buttons->button (QDialogButtonBox::Cancel), SIGNAL (clicked()), this, SLOT (cancel()));
mOkay = new QPushButton ("Merge", this);
connect (mOkay, SIGNAL (clicked()), this, SLOT (accept()));
mOkay->setDefault (true);
buttons->addButton (mOkay, QDialogButtonBox::AcceptRole);
mainLayout->addWidget (buttons);
}
void CSVTools::Merge::configure (CSMDoc::Document *document)
{
mDocument = document;
mNewFile->setName ("");
// content files
while (mFiles->count())
delete mFiles->takeItem (0);
std::vector<boost::filesystem::path> files = document->getContentFiles();
for (std::vector<boost::filesystem::path>::const_iterator iter (files.begin());
iter!=files.end(); ++iter)
mFiles->addItem (QString::fromUtf8 (iter->filename().string().c_str()));
}
void CSVTools::Merge::setLocalData (const boost::filesystem::path& localData)
{
mAdjuster->setLocalData (localData);
}
CSMDoc::Document *CSVTools::Merge::getDocument() const
{
return mDocument;
}
void CSVTools::Merge::cancel()
{
mDocument = 0;
hide();
}
void CSVTools::Merge::accept()
{
if ((mDocument->getState() & CSMDoc::State_Merging)==0)
{
std::vector< boost::filesystem::path > files (1, mAdjuster->getPath());
std::auto_ptr<CSMDoc::Document> target (
mDocumentManager.makeDocument (files, files[0], true));
mDocument->runMerge (target);
hide();
}
}
void CSVTools::Merge::stateChanged (bool valid)
{
mOkay->setEnabled (valid);
}

@ -0,0 +1,61 @@
#ifndef CSV_TOOLS_REPORTTABLE_H
#define CSV_TOOLS_REPORTTABLE_H
#include <QWidget>
#include <boost/filesystem/path.hpp>
class QPushButton;
class QListWidget;
namespace CSMDoc
{
class Document;
class DocumentManager;
}
namespace CSVDoc
{
class FileWidget;
class AdjusterWidget;
}
namespace CSVTools
{
class Merge : public QWidget
{
Q_OBJECT
CSMDoc::Document *mDocument;
QPushButton *mOkay;
QListWidget *mFiles;
CSVDoc::FileWidget *mNewFile;
CSVDoc::AdjusterWidget *mAdjuster;
CSMDoc::DocumentManager& mDocumentManager;
void keyPressEvent (QKeyEvent *event);
public:
Merge (CSMDoc::DocumentManager& documentManager, QWidget *parent = 0);
/// Configure dialogue for a new merge
void configure (CSMDoc::Document *document);
void setLocalData (const boost::filesystem::path& localData);
CSMDoc::Document *getDocument() const;
public slots:
void cancel();
private slots:
void accept();
void stateChanged (bool valid);
};
}
#endif

@ -461,7 +461,7 @@ namespace MWPhysics
class HeightField
{
public:
HeightField(float* heights, int x, int y, float triSize, float sqrtVerts)
HeightField(const float* heights, int x, int y, float triSize, float sqrtVerts)
{
// find the minimum and maximum heights (needed for bullet)
float minh = heights[0];
@ -928,7 +928,7 @@ namespace MWPhysics
return MovementSolver::traceDown(ptr, found->second, mCollisionWorld, maxHeight);
}
void PhysicsSystem::addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts)
void PhysicsSystem::addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts)
{
HeightField *heightfield = new HeightField(heights, x, y, triSize, sqrtVerts);
mHeightFields[std::make_pair(x,y)] = heightfield;

@ -71,7 +71,7 @@ namespace MWPhysics
void updatePosition (const MWWorld::Ptr& ptr);
void addHeightField (float* heights, int x, int y, float triSize, float sqrtVerts);
void addHeightField (const float* heights, int x, int y, float triSize, float sqrtVerts);
void removeHeightField (int x, int y);

@ -156,6 +156,9 @@ namespace MWRender
land->loadData(mask);
}
const ESM::Land::LandData *landData =
land ? land->getLandData (ESM::Land::DATA_WNAM) : 0;
for (int cellY=0; cellY<mCellSize; ++cellY)
{
for (int cellX=0; cellX<mCellSize; ++cellX)
@ -163,15 +166,14 @@ namespace MWRender
int vertexX = static_cast<int>(float(cellX)/float(mCellSize) * 9);
int vertexY = static_cast<int>(float(cellY) / float(mCellSize) * 9);
int texelX = (x-mMinX) * mCellSize + cellX;
int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY);
unsigned char r,g,b;
float y = 0;
if (land && land->mDataTypes & ESM::Land::DATA_WNAM)
y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f;
if (landData)
y = (landData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f;
else
y = (SCHAR_MIN << 4) / 2048.f;
if (y < 0)

@ -51,7 +51,7 @@ namespace MWRender
maxY += 1;
}
ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
const ESM::Land* TerrainStorage::getLand(int cellX, int cellY)
{
const MWWorld::ESMStore &esmStore =
MWBase::Environment::get().getWorld()->getStore();

@ -10,7 +10,7 @@ namespace MWRender
class TerrainStorage : public ESMTerrain::Storage
{
private:
virtual ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::Land* getLand (int cellX, int cellY);
virtual const ESM::LandTexture* getLandTexture(int index, short plugin);
public:

@ -250,9 +250,9 @@ namespace MWWorld
// Actually only VHGT is needed here, but we'll need the rest for rendering anyway.
// Load everything now to reduce IO overhead.
const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX;
if (!land->isDataLoaded(flags))
land->loadData(flags);
mPhysics->addHeightField (land->mLandData->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(),
const ESM::Land::LandData *data = land->getLandData (flags);
mPhysics->addHeightField (data->mHeights, cell->getCell()->getGridX(), cell->getCell()->getGridY(),
worldsize / (verts-1), verts);
}
}

@ -1,5 +1,7 @@
#include "loadland.hpp"
#include <utility>
#include "esmreader.hpp"
#include "esmwriter.hpp"
#include "defs.hpp"
@ -8,7 +10,7 @@ namespace ESM
{
unsigned int Land::sRecordId = REC_LAND;
void Land::LandData::save(ESMWriter &esm)
void Land::LandData::save(ESMWriter &esm) const
{
if (mDataTypes & Land::DATA_VNML) {
esm.writeHNT("VNML", mNormals, sizeof(mNormals));
@ -53,7 +55,7 @@ void Land::LandData::save(ESMWriter &esm)
}
}
void Land::LandData::transposeTextureData(uint16_t *in, uint16_t *out)
void Land::LandData::transposeTextureData(const uint16_t *in, uint16_t *out)
{
int readPos = 0; //bit ugly, but it works
for ( int y1 = 0; y1 < 4; y1++ )
@ -137,7 +139,7 @@ void Land::save(ESMWriter &esm) const
esm.writeHNT("DATA", mFlags);
}
void Land::loadData(int flags)
void Land::loadData(int flags) const
{
// Try to load only available data
flags = flags & mDataTypes;
@ -199,7 +201,7 @@ void Land::unloadData()
}
}
bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size)
bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const
{
if ((mDataLoaded & dataFlag) == 0 && (flags & dataFlag) != 0) {
mEsm->getHExact(ptr, size);
@ -215,4 +217,69 @@ bool Land::isDataLoaded(int flags) const
return (mDataLoaded & flags) == (flags & mDataTypes);
}
Land::Land (const Land& land)
: mFlags (land.mFlags), mX (land.mX), mY (land.mY), mPlugin (land.mPlugin),
mEsm (land.mEsm), mContext (land.mContext), mDataTypes (land.mDataTypes),
mDataLoaded (land.mDataLoaded),
mLandData (land.mLandData ? new LandData (*land.mLandData) : 0)
{}
Land& Land::operator= (Land land)
{
swap (land);
return *this;
}
void Land::swap (Land& land)
{
std::swap (mFlags, land.mFlags);
std::swap (mX, land.mX);
std::swap (mY, land.mY);
std::swap (mPlugin, land.mPlugin);
std::swap (mEsm, land.mEsm);
std::swap (mContext, land.mContext);
std::swap (mDataTypes, land.mDataTypes);
std::swap (mDataLoaded, land.mDataLoaded);
std::swap (mLandData, land.mLandData);
}
const Land::LandData *Land::getLandData (int flags) const
{
if (!(flags & mDataTypes))
return 0;
loadData (flags);
return mLandData;
}
const Land::LandData *Land::getLandData() const
{
return mLandData;
}
Land::LandData *Land::getLandData()
{
return mLandData;
}
void Land::add (int flags)
{
if (!mLandData)
mLandData = new LandData;
mDataTypes |= flags;
mDataLoaded |= flags;
}
void Land::remove (int flags)
{
mDataTypes &= ~flags;
mDataLoaded &= ~flags;
if (!mDataLoaded)
{
delete mLandData;
mLandData = 0;
}
}
}

@ -35,7 +35,6 @@ struct Land
ESM_Context mContext;
int mDataTypes;
int mDataLoaded;
enum
{
@ -91,12 +90,10 @@ struct Land
short mUnk1;
uint8_t mUnk2;
void save(ESMWriter &esm);
static void transposeTextureData(uint16_t *in, uint16_t *out);
void save(ESMWriter &esm) const;
static void transposeTextureData(const uint16_t *in, uint16_t *out);
};
LandData *mLandData;
void load(ESMReader &esm);
void save(ESMWriter &esm) const;
@ -105,7 +102,7 @@ struct Land
/**
* Actually loads data
*/
void loadData(int flags);
void loadData(int flags) const;
/**
* Frees memory allocated for land data
@ -116,14 +113,41 @@ struct Land
/// @note We only check data types that *can* be loaded (present in mDataTypes)
bool isDataLoaded(int flags) const;
Land (const Land& land);
Land& operator= (Land land);
void swap (Land& land);
/// Return land data with at least the data types specified in \a flags loaded (if they
/// are available). Will return a 0-pointer if there is no data for any of the
/// specified types.
const LandData *getLandData (int flags) const;
/// Return land data without loading first anything. Can return a 0-pointer.
const LandData *getLandData() const;
/// Return land data without loading first anything. Can return a 0-pointer.
LandData *getLandData();
/// \attention Must not be called on objects that aren't fully loaded.
///
/// \note Added data fields will be uninitialised
void add (int flags);
/// \attention Must not be called on objects that aren't fully loaded.
void remove (int flags);
private:
Land(const Land& land);
Land& operator=(const Land& land);
/// Loads data and marks it as loaded
/// \return true if data is actually loaded from file, false otherwise
/// including the case when data is already loaded
bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size);
bool condLoad(int flags, int dataFlag, void *ptr, unsigned int size) const;
mutable int mDataLoaded;
mutable LandData *mLandData;
};
}

@ -18,6 +18,14 @@ namespace ESMTerrain
{
}
const ESM::Land::LandData *Storage::getLandData (int cellX, int cellY, int flags)
{
if (const ESM::Land *land = getLand (cellX, cellY))
return land->getLandData (flags);
return 0;
}
bool Storage::getMinMaxHeights(float size, const osg::Vec2f &center, float &min, float &max)
{
assert (size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
@ -32,24 +40,25 @@ namespace ESMTerrain
int cellX = static_cast<int>(origin.x());
int cellY = static_cast<int>(origin.y());
const ESM::Land* land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
return false;
min = std::numeric_limits<float>::max();
max = -std::numeric_limits<float>::max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VHGT))
{
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
min = std::numeric_limits<float>::max();
max = -std::numeric_limits<float>::max();
for (int row=0; row<ESM::Land::LAND_SIZE; ++row)
{
float h = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
for (int col=0; col<ESM::Land::LAND_SIZE; ++col)
{
float h = data->mHeights[col*ESM::Land::LAND_SIZE+row];
if (h > max)
max = h;
if (h < min)
min = h;
}
}
return true;
}
return true;
return false;
}
void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row)
@ -74,12 +83,12 @@ namespace ESMTerrain
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mDataTypes&ESM::Land::DATA_VNML)
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VNML))
{
normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
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
@ -109,12 +118,12 @@ namespace ESMTerrain
++cellX;
row = 0;
}
ESM::Land* land = getLand(cellX, cellY);
if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VCLR))
{
color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
}
else
{
@ -158,9 +167,9 @@ namespace ESMTerrain
float vertX_ = 0; // of current cell corner
for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX)
{
ESM::Land* land = getLand(cellX, cellY);
if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT))
land = NULL;
const ESM::Land::LandData *heightData = getLandData (cellX, cellY, ESM::Land::DATA_VHGT);
const ESM::Land::LandData *normalData = getLandData (cellX, cellY, ESM::Land::DATA_VNML);
const ESM::Land::LandData *colourData = getLandData (cellX, cellY, ESM::Land::DATA_VCLR);
int rowStart = 0;
int colStart = 0;
@ -177,20 +186,22 @@ namespace ESMTerrain
vertX = vertX_;
for (int row=rowStart; row<ESM::Land::LAND_SIZE; row += increment)
{
int arrayIndex = col*ESM::Land::LAND_SIZE*3+row*3;
float height = -2048;
if (land)
height = land->mLandData->mHeights[col*ESM::Land::LAND_SIZE + row];
if (heightData)
height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row];
(*positions)[static_cast<unsigned int>(vertX*numVerts + vertY)]
= osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * 8192,
(vertY / float(numVerts - 1) - 0.5f) * size * 8192,
height);
if (land && land->mDataTypes&ESM::Land::DATA_VNML)
if (normalData)
{
normal.x() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z() = land->mLandData->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
for (int i=0; i<3; ++i)
normal[i] = normalData->mNormals[arrayIndex+i];
normal.normalize();
}
else
@ -208,11 +219,10 @@ namespace ESMTerrain
(*normals)[static_cast<unsigned int>(vertX*numVerts + vertY)] = normal;
if (land && land->mDataTypes&ESM::Land::DATA_VCLR)
if (colourData)
{
color.r() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3] / 255.f;
color.g() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1] / 255.f;
color.b() = land->mLandData->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2] / 255.f;
for (int i=0; i<3; ++i)
color[i] = colourData->mColours[arrayIndex+i] / 255.f;
}
else
{
@ -262,13 +272,12 @@ namespace ESMTerrain
assert(x<ESM::Land::LAND_TEXTURE_SIZE);
assert(y<ESM::Land::LAND_TEXTURE_SIZE);
ESM::Land* land = getLand(cellX, cellY);
if (land && (land->mDataTypes&ESM::Land::DATA_VTEX))
if (const ESM::Land::LandData *data = getLandData (cellX, cellY, ESM::Land::DATA_VTEX))
{
int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0,0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->mPlugin);
return std::make_pair(tex, getLand (cellX, cellY)->mPlugin);
}
else
return std::make_pair(0,0);
@ -368,7 +377,7 @@ namespace ESMTerrain
int cellX = static_cast<int>(std::floor(worldPos.x() / 8192.f));
int cellY = static_cast<int>(std::floor(worldPos.y() / 8192.f));
ESM::Land* land = getLand(cellX, cellY);
const ESM::Land* land = getLand(cellX, cellY);
if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT))
return -2048;
@ -447,7 +456,7 @@ namespace ESMTerrain
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x];
return land->getLandData()->mHeights[y * ESM::Land::LAND_SIZE + x];
}
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)

@ -21,12 +21,17 @@ namespace ESMTerrain
private:
// Not implemented in this class, because we need different Store implementations for game and editor
virtual ESM::Land* getLand (int cellX, int cellY) = 0;
virtual const ESM::Land* getLand (int cellX, int cellY)= 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
public:
Storage(const VFS::Manager* vfs);
/// Data is loaded first, if necessary. Will return a 0-pointer if there is no data for
/// any of the data types specified via \a flags. Will also return a 0-pointer if there
/// is no land record for the coordinates \a cellX / \a cellY.
const ESM::Land::LandData *getLandData (int cellX, int cellY, int flags);
// Not implemented in this class, because we need different Store implementations for game and editor
/// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0;

Loading…
Cancel
Save