Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519#include "regionmap.hpp"
#include <QApplication>#include <QBrush>#include <QModelIndex>#include <QPalette>#include <QSize>#include <QVariant>
#include <algorithm>#include <sstream>#include <stdexcept>#include <type_traits>
#include <apps/opencs/model/world/cell.hpp>#include <apps/opencs/model/world/cellcoordinates.hpp>#include <apps/opencs/model/world/idcollection.hpp>#include <apps/opencs/model/world/record.hpp>
#include <components/esm3/loadregn.hpp>#include <components/misc/strings/lower.hpp>
#include "data.hpp"#include "universalid.hpp"
namespace CSMWorld{ float getLandHeight(const CSMWorld::Cell& cell, CSMWorld::Data& data) { const IdCollection<Land>& lands = data.getLand(); int landIndex = lands.searchId(cell.mId); if (landIndex == -1) return 0.0f;
// If any part of land is above water, returns > 0 - otherwise returns < 0 const Land& land = lands.getRecord(landIndex).get(); if (land.getLandData()) return land.getLandData()->mMaxHeight - cell.mWater;
return 0.0f; }}
CSMWorld::RegionMap::CellDescription::CellDescription(const Record<Cell>& cell, float landHeight){ const Cell& cell2 = cell.get();
if (!cell2.isExterior()) throw std::logic_error("Interior cell in region map");
mMaxLandHeight = landHeight; mDeleted = cell.isDeleted(); mRegion = cell2.mRegion; mName = cell2.mName;}
CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const QModelIndex& index) const{ return mMin.move(index.column(), mMax.getY() - mMin.getY() - index.row() - 1);}
QModelIndex CSMWorld::RegionMap::getIndex(const CellCoordinates& index) const{ // I hate you, Qt API naming scheme! return QAbstractTableModel::index( mMax.getY() - mMin.getY() - (index.getY() - mMin.getY()) - 1, index.getX() - mMin.getX());}
CSMWorld::CellCoordinates CSMWorld::RegionMap::getIndex(const Cell& cell) const{ std::istringstream stream(cell.mId.getRefIdString());
char ignore; int x = 0; int y = 0; stream >> ignore >> x >> y;
return CellCoordinates(x, y);}
void CSMWorld::RegionMap::buildRegions(){ const IdCollection<ESM::Region>& regions = mData.getRegions();
int size = regions.getSize();
for (int i = 0; i < size; ++i) { const Record<ESM::Region>& region = regions.getRecord(i);
if (!region.isDeleted()) mColours.insert(std::make_pair(region.get().mId, region.get().mMapColor)); }}
void CSMWorld::RegionMap::buildMap(){ const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize();
for (int i = 0; i < size; ++i) { const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior()) { CellDescription description(cell, getLandHeight(cell2, mData));
CellCoordinates index = getIndex(cell2);
mMap.insert(std::make_pair(index, description)); } }
std::pair<CellCoordinates, CellCoordinates> mapSize = getSize();
mMin = mapSize.first; mMax = mapSize.second;}
void CSMWorld::RegionMap::addCell(const CellCoordinates& index, const CellDescription& description){ std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find(index);
if (cell != mMap.end()) { cell->second = description; } else { updateSize();
mMap.insert(std::make_pair(index, description)); }
QModelIndex index2 = getIndex(index);
dataChanged(index2, index2);}
void CSMWorld::RegionMap::addCells(int start, int end){ const IdCollection<Cell>& cells = mData.getCells();
for (int i = start; i <= end; ++i) { const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior()) { CellCoordinates index = getIndex(cell2);
CellDescription description(cell, getLandHeight(cell.get(), mData));
addCell(index, description); } }}
void CSMWorld::RegionMap::removeCell(const CellCoordinates& index){ std::map<CellCoordinates, CellDescription>::iterator cell = mMap.find(index);
if (cell != mMap.end()) { mMap.erase(cell);
QModelIndex index2 = getIndex(index);
dataChanged(index2, index2);
updateSize(); }}
void CSMWorld::RegionMap::addRegion(const ESM::RefId& region, unsigned int colour){ mColours[region] = colour;}
void CSMWorld::RegionMap::removeRegion(const ESM::RefId& region){ auto iter(mColours.find(region));
if (iter != mColours.end()) mColours.erase(iter);}
void CSMWorld::RegionMap::updateRegions(const std::vector<ESM::RefId>& regions){ std::vector<ESM::RefId> regions2(regions);
std::sort(regions2.begin(), regions2.end());
for (std::map<CellCoordinates, CellDescription>::const_iterator iter(mMap.begin()); iter != mMap.end(); ++iter) { if (!iter->second.mRegion.empty() && std::find(regions2.begin(), regions2.end(), iter->second.mRegion) != regions2.end()) { QModelIndex index = getIndex(iter->first);
dataChanged(index, index); } }}
void CSMWorld::RegionMap::updateSize(){ std::pair<CellCoordinates, CellCoordinates> size = getSize();
if (int diff = size.first.getX() - mMin.getX()) { beginInsertColumns(QModelIndex(), 0, std::abs(diff) - 1); mMin = CellCoordinates(size.first.getX(), mMin.getY()); endInsertColumns(); }
if (int diff = size.first.getY() - mMin.getY()) { beginInsertRows(QModelIndex(), 0, std::abs(diff) - 1); mMin = CellCoordinates(mMin.getX(), size.first.getY()); endInsertRows(); }
if (int diff = size.second.getX() - mMax.getX()) { int columns = columnCount();
if (diff > 0) beginInsertColumns(QModelIndex(), columns, columns + diff - 1); else beginRemoveColumns(QModelIndex(), columns + diff, columns - 1);
mMax = CellCoordinates(size.second.getX(), mMax.getY()); endInsertColumns(); }
if (int diff = size.second.getY() - mMax.getY()) { int rows = rowCount();
if (diff > 0) beginInsertRows(QModelIndex(), rows, rows + diff - 1); else beginRemoveRows(QModelIndex(), rows + diff, rows - 1);
mMax = CellCoordinates(mMax.getX(), size.second.getY()); endInsertRows(); }}
std::pair<CSMWorld::CellCoordinates, CSMWorld::CellCoordinates> CSMWorld::RegionMap::getSize() const{ const IdCollection<Cell>& cells = mData.getCells();
int size = cells.getSize();
CellCoordinates min(0, 0); CellCoordinates max(0, 0);
for (int i = 0; i < size; ++i) { const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior()) { CellCoordinates index = getIndex(cell2);
if (min == max) { min = index; max = min.move(1, 1); } else { if (index.getX() < min.getX()) min = CellCoordinates(index.getX(), min.getY()); else if (index.getX() >= max.getX()) max = CellCoordinates(index.getX() + 1, max.getY());
if (index.getY() < min.getY()) min = CellCoordinates(min.getX(), index.getY()); else if (index.getY() >= max.getY()) max = CellCoordinates(max.getX(), index.getY() + 1); } } }
return std::make_pair(min, max);}
CSMWorld::RegionMap::RegionMap(Data& data) : mData(data){ buildRegions(); buildMap();
QAbstractItemModel* regions = data.getTableModel(UniversalId(UniversalId::Type_Regions));
connect(regions, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::regionsAboutToBeRemoved); connect(regions, &QAbstractItemModel::rowsInserted, this, &RegionMap::regionsInserted); connect(regions, &QAbstractItemModel::dataChanged, this, &RegionMap::regionsChanged);
QAbstractItemModel* cells = data.getTableModel(UniversalId(UniversalId::Type_Cells));
connect(cells, &QAbstractItemModel::rowsAboutToBeRemoved, this, &RegionMap::cellsAboutToBeRemoved); connect(cells, &QAbstractItemModel::rowsInserted, this, &RegionMap::cellsInserted); connect(cells, &QAbstractItemModel::dataChanged, this, &RegionMap::cellsChanged);}
int CSMWorld::RegionMap::rowCount(const QModelIndex& parent) const{ if (parent.isValid()) return 0;
return mMax.getY() - mMin.getY();}
int CSMWorld::RegionMap::columnCount(const QModelIndex& parent) const{ if (parent.isValid()) return 0;
return mMax.getX() - mMin.getX();}
QVariant CSMWorld::RegionMap::data(const QModelIndex& index, int role) const{ if (role == Qt::SizeHintRole) return QSize(16, 16);
if (role == Qt::BackgroundRole) { /// \todo GUI class in non-GUI code. Needs to be addressed eventually.
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(getIndex(index));
if (cell != mMap.end()) { if (cell->second.mDeleted) return QBrush(Qt::red, Qt::DiagCrossPattern);
auto iter = mColours.find(cell->second.mRegion);
if (iter != mColours.end()) return QBrush(QColor(iter->second & 0xff, (iter->second >> 8) & 0xff, (iter->second >> 16) & 0xff), cell->second.mMaxLandHeight > 0 ? Qt::SolidPattern : Qt::CrossPattern);
if (cell->second.mRegion.empty()) // no region return QBrush(cell->second.mMaxLandHeight > 0 ? Qt::Dense3Pattern : Qt::Dense6Pattern);
return QBrush(Qt::red, Qt::Dense6Pattern); // invalid region }
return QBrush(Qt::DiagCrossPattern); }
if (role == Qt::ToolTipRole) { CellCoordinates cellIndex = getIndex(index);
std::ostringstream stream;
stream << cellIndex;
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(cellIndex);
if (cell != mMap.end()) { if (!cell->second.mName.empty()) stream << " " << cell->second.mName;
if (cell->second.mDeleted) stream << " (deleted)";
if (!cell->second.mRegion.empty()) { stream << "<br>";
auto iter = mColours.find(cell->second.mRegion);
if (iter != mColours.end()) stream << cell->second.mRegion; else stream << "<font color=red>" << cell->second.mRegion << "</font>"; } } else stream << " (no cell)";
return QString::fromUtf8(stream.str().c_str()); }
if (role == Role_Region) { CellCoordinates cellIndex = getIndex(index);
std::map<CellCoordinates, CellDescription>::const_iterator cell = mMap.find(cellIndex);
if (cell != mMap.end() && !cell->second.mRegion.empty()) return QString::fromUtf8(cell->second.mRegion.getRefIdString().c_str()); }
if (role == Role_CellId) { CellCoordinates cellIndex = getIndex(index);
std::ostringstream stream; stream << "#" << cellIndex.getX() << " " << cellIndex.getY();
return QString::fromUtf8(stream.str().c_str()); }
return QVariant();}
Qt::ItemFlags CSMWorld::RegionMap::flags(const QModelIndex& index) const{ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;}
void CSMWorld::RegionMap::regionsAboutToBeRemoved(const QModelIndex& parent, int start, int end){ std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = start; i <= end; ++i) { const Record<ESM::Region>& region = regions.getRecord(i);
update.push_back(region.get().mId);
removeRegion(region.get().mId); }
updateRegions(update);}
void CSMWorld::RegionMap::regionsInserted(const QModelIndex& parent, int start, int end){ std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = start; i <= end; ++i) { const Record<ESM::Region>& region = regions.getRecord(i);
if (!region.isDeleted()) { update.push_back(region.get().mId);
addRegion(region.get().mId, region.get().mMapColor); } }
updateRegions(update);}
void CSMWorld::RegionMap::regionsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight){ // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating.
std::vector<ESM::RefId> update;
const IdCollection<ESM::Region>& regions = mData.getRegions();
for (int i = topLeft.row(); i <= bottomRight.column(); ++i) { const Record<ESM::Region>& region = regions.getRecord(i);
update.push_back(region.get().mId);
if (!region.isDeleted()) addRegion(region.get().mId, region.get().mMapColor); else removeRegion(region.get().mId); }
updateRegions(update);}
void CSMWorld::RegionMap::cellsAboutToBeRemoved(const QModelIndex& parent, int start, int end){ const IdCollection<Cell>& cells = mData.getCells();
for (int i = start; i <= end; ++i) { const Record<Cell>& cell = cells.getRecord(i);
const Cell& cell2 = cell.get();
if (cell2.isExterior()) removeCell(getIndex(cell2)); }}
void CSMWorld::RegionMap::cellsInserted(const QModelIndex& parent, int start, int end){ addCells(start, end);}
void CSMWorld::RegionMap::cellsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight){ // Note: At this point an additional check could be inserted to see if there is any change to the // columns we are interested in. If not we can exit the function here and avoid all updating.
addCells(topLeft.row(), bottomRight.row());}