Merge remote-tracking branch 'upstream/master' into wizard
commit
a2c129f655
@ -0,0 +1,60 @@
|
||||
|
||||
#include "cellcoordinates.hpp"
|
||||
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
|
||||
CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {}
|
||||
|
||||
CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {}
|
||||
|
||||
int CSMWorld::CellCoordinates::getX() const
|
||||
{
|
||||
return mX;
|
||||
}
|
||||
|
||||
int CSMWorld::CellCoordinates::getY() const
|
||||
{
|
||||
return mY;
|
||||
}
|
||||
|
||||
CSMWorld::CellCoordinates CSMWorld::CellCoordinates::move (int x, int y) const
|
||||
{
|
||||
return CellCoordinates (mX + x, mY + y);
|
||||
}
|
||||
|
||||
std::string CSMWorld::CellCoordinates::getId (const std::string& worldspace) const
|
||||
{
|
||||
// we ignore the worldspace for now, since there is only one (will change in 1.1)
|
||||
std::ostringstream stream;
|
||||
|
||||
stream << "#" << mX << " " << mY;
|
||||
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
bool CSMWorld::operator== (const CellCoordinates& left, const CellCoordinates& right)
|
||||
{
|
||||
return left.getX()==right.getX() && left.getY()==right.getY();
|
||||
}
|
||||
|
||||
bool CSMWorld::operator!= (const CellCoordinates& left, const CellCoordinates& right)
|
||||
{
|
||||
return !(left==right);
|
||||
}
|
||||
|
||||
bool CSMWorld::operator< (const CellCoordinates& left, const CellCoordinates& right)
|
||||
{
|
||||
if (left.getX()<right.getX())
|
||||
return true;
|
||||
|
||||
if (left.getX()>right.getX())
|
||||
return false;
|
||||
|
||||
return left.getY()<right.getY();
|
||||
}
|
||||
|
||||
std::ostream& CSMWorld::operator<< (std::ostream& stream, const CellCoordinates& coordiantes)
|
||||
{
|
||||
return stream << coordiantes.getX() << ", " << coordiantes.getY();
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
#ifndef CSM_WOLRD_CELLCOORDINATES_H
|
||||
#define CSM_WOLRD_CELLCOORDINATES_H
|
||||
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
class CellCoordinates
|
||||
{
|
||||
int mX;
|
||||
int mY;
|
||||
|
||||
public:
|
||||
|
||||
CellCoordinates();
|
||||
|
||||
CellCoordinates (int x, int y);
|
||||
|
||||
int getX() const;
|
||||
|
||||
int getY() const;
|
||||
|
||||
CellCoordinates move (int x, int y) const;
|
||||
///< Return a copy of *this, moved by the given offset.
|
||||
|
||||
std::string getId (const std::string& worldspace) const;
|
||||
///< Return the ID for the cell at these coordinates.
|
||||
};
|
||||
|
||||
bool operator== (const CellCoordinates& left, const CellCoordinates& right);
|
||||
bool operator!= (const CellCoordinates& left, const CellCoordinates& right);
|
||||
bool operator< (const CellCoordinates& left, const CellCoordinates& right);
|
||||
|
||||
std::ostream& operator<< (std::ostream& stream, const CellCoordinates& coordiantes);
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE (CSMWorld::CellCoordinates)
|
||||
|
||||
#endif
|
@ -0,0 +1,83 @@
|
||||
|
||||
#include "cellselection.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <stdexcept>
|
||||
#include <limits>
|
||||
|
||||
CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::begin() const
|
||||
{
|
||||
return mCells.begin();
|
||||
}
|
||||
|
||||
CSMWorld::CellSelection::Iterator CSMWorld::CellSelection::end() const
|
||||
{
|
||||
return mCells.end();
|
||||
}
|
||||
|
||||
bool CSMWorld::CellSelection::add (const CellCoordinates& coordinates)
|
||||
{
|
||||
return mCells.insert (coordinates).second;
|
||||
}
|
||||
|
||||
void CSMWorld::CellSelection::remove (const CellCoordinates& coordinates)
|
||||
{
|
||||
mCells.erase (coordinates);
|
||||
}
|
||||
|
||||
bool CSMWorld::CellSelection::has (const CellCoordinates& coordinates) const
|
||||
{
|
||||
return mCells.find (coordinates)!=end();
|
||||
}
|
||||
|
||||
int CSMWorld::CellSelection::getSize() const
|
||||
{
|
||||
return mCells.size();
|
||||
}
|
||||
|
||||
CSMWorld::CellCoordinates CSMWorld::CellSelection::getCentre() const
|
||||
{
|
||||
if (mCells.empty())
|
||||
throw std::logic_error ("call of getCentre on empty cell selection");
|
||||
|
||||
double x = 0;
|
||||
double y = 0;
|
||||
|
||||
for (Iterator iter = begin(); iter!=end(); ++iter)
|
||||
{
|
||||
x += iter->getX();
|
||||
y += iter->getY();
|
||||
}
|
||||
|
||||
x /= mCells.size();
|
||||
y /= mCells.size();
|
||||
|
||||
Iterator closest = begin();
|
||||
double distance = std::numeric_limits<double>::max();
|
||||
|
||||
for (Iterator iter (begin()); iter!=end(); ++iter)
|
||||
{
|
||||
double deltaX = x - iter->getX();
|
||||
double deltaY = y - iter->getY();
|
||||
|
||||
double delta = std::sqrt (deltaX * deltaX + deltaY * deltaY);
|
||||
|
||||
if (delta<distance)
|
||||
{
|
||||
distance = delta;
|
||||
closest = iter;
|
||||
}
|
||||
}
|
||||
|
||||
return *closest;
|
||||
}
|
||||
|
||||
void CSMWorld::CellSelection::move (int x, int y)
|
||||
{
|
||||
Container moved;
|
||||
|
||||
for (Iterator iter = begin(); iter!=end(); ++iter)
|
||||
moved.insert (iter->move (x, y));
|
||||
|
||||
mCells.swap (moved);
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
#ifndef CSM_WOLRD_CELLSELECTION_H
|
||||
#define CSM_WOLRD_CELLSELECTION_H
|
||||
|
||||
#include <set>
|
||||
|
||||
#include <QMetaType>
|
||||
|
||||
#include "cellcoordinates.hpp"
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
/// \brief Selection of cells in a paged worldspace
|
||||
///
|
||||
/// \note The CellSelection does not specify the worldspace it applies to.
|
||||
class CellSelection
|
||||
{
|
||||
public:
|
||||
|
||||
typedef std::set<CellCoordinates> Container;
|
||||
typedef Container::const_iterator Iterator;
|
||||
|
||||
private:
|
||||
|
||||
Container mCells;
|
||||
|
||||
public:
|
||||
|
||||
Iterator begin() const;
|
||||
|
||||
Iterator end() const;
|
||||
|
||||
bool add (const CellCoordinates& coordinates);
|
||||
///< Ignored if the cell specified by \a coordinates is already part of the selection.
|
||||
///
|
||||
/// \return Was a cell added to the collection?
|
||||
|
||||
void remove (const CellCoordinates& coordinates);
|
||||
///< ignored if the cell specified by \a coordinates is not part of the selection.
|
||||
|
||||
bool has (const CellCoordinates& coordinates) const;
|
||||
///< \return Is the cell specified by \a coordinates part of the selection?
|
||||
|
||||
int getSize() const;
|
||||
///< Return number of cells.
|
||||
|
||||
CellCoordinates getCentre() const;
|
||||
///< Return the selected cell that is closest to the geometric centre of the selection.
|
||||
///
|
||||
/// \attention This function must not be called on selections that are empty.
|
||||
|
||||
void move (int x, int y);
|
||||
};
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE (CSMWorld::CellSelection)
|
||||
|
||||
#endif
|
@ -1,6 +1,47 @@
|
||||
|
||||
#include "pagedworldspacewidget.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
CSVRender::PagedWorldspaceWidget::PagedWorldspaceWidget (QWidget *parent)
|
||||
: WorldspaceWidget (parent)
|
||||
{}
|
||||
{}
|
||||
|
||||
void CSVRender::PagedWorldspaceWidget::useViewHint (const std::string& hint)
|
||||
{
|
||||
if (!hint.empty())
|
||||
{
|
||||
CSMWorld::CellSelection selection;
|
||||
|
||||
if (hint[0]=='c')
|
||||
{
|
||||
// syntax: c:#x1 y1; #x2 y2 (number of coordinate pairs can be 0 or larger)
|
||||
char ignore;
|
||||
|
||||
std::istringstream stream (hint.c_str());
|
||||
if (stream >> ignore)
|
||||
{
|
||||
char ignore1; // : or ;
|
||||
char ignore2; // #
|
||||
int x, y;
|
||||
|
||||
while (stream >> ignore1 >> ignore2 >> x >> y)
|
||||
selection.add (CSMWorld::CellCoordinates (x, y));
|
||||
|
||||
/// \todo adjust camera position
|
||||
}
|
||||
}
|
||||
else if (hint[0]=='r')
|
||||
{
|
||||
/// \todo implement 'r' type hints
|
||||
}
|
||||
|
||||
setCellSelection (selection);
|
||||
}
|
||||
}
|
||||
|
||||
void CSVRender::PagedWorldspaceWidget::setCellSelection (const CSMWorld::CellSelection& selection)
|
||||
{
|
||||
mSelection = selection;
|
||||
emit cellSelectionChanged (mSelection);
|
||||
}
|
@ -0,0 +1,346 @@
|
||||
|
||||
#include "regionmap.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QContextMenuEvent>
|
||||
#include <QMenu>
|
||||
|
||||
#include "../../model/doc/document.hpp"
|
||||
|
||||
#include "../../model/world/regionmap.hpp"
|
||||
#include "../../model/world/universalid.hpp"
|
||||
#include "../../model/world/data.hpp"
|
||||
#include "../../model/world/idtable.hpp"
|
||||
#include "../../model/world/commands.hpp"
|
||||
#include "../../model/world/columns.hpp"
|
||||
|
||||
void CSVWorld::RegionMap::contextMenuEvent (QContextMenuEvent *event)
|
||||
{
|
||||
QMenu menu (this);
|
||||
|
||||
if (getUnselectedCells().size()>0)
|
||||
menu.addAction (mSelectAllAction);
|
||||
|
||||
if (selectionModel()->selectedIndexes().size()>0)
|
||||
menu.addAction (mClearSelectionAction);
|
||||
|
||||
if (getMissingRegionCells().size()>0)
|
||||
menu.addAction (mSelectRegionsAction);
|
||||
|
||||
int selectedNonExistentCells = getSelectedCells (false, true).size();
|
||||
|
||||
if (selectedNonExistentCells>0)
|
||||
{
|
||||
if (selectedNonExistentCells==1)
|
||||
mCreateCellsAction->setText ("Create one Cell");
|
||||
else
|
||||
{
|
||||
std::ostringstream stream;
|
||||
stream << "Create " << selectedNonExistentCells << " cells";
|
||||
mCreateCellsAction->setText (QString::fromUtf8 (stream.str().c_str()));
|
||||
}
|
||||
|
||||
menu.addAction (mCreateCellsAction);
|
||||
}
|
||||
|
||||
if (getSelectedCells().size()>0)
|
||||
{
|
||||
if (!mRegionId.empty())
|
||||
{
|
||||
mSetRegionAction->setText (QString::fromUtf8 (("Set Region to " + mRegionId).c_str()));
|
||||
menu.addAction (mSetRegionAction);
|
||||
}
|
||||
|
||||
menu.addAction (mUnsetRegionAction);
|
||||
|
||||
menu.addAction (mViewInTableAction);
|
||||
}
|
||||
|
||||
if (selectionModel()->selectedIndexes().size()>0)
|
||||
menu.addAction (mViewAction);
|
||||
|
||||
menu.exec (event->globalPos());
|
||||
}
|
||||
|
||||
QModelIndexList CSVWorld::RegionMap::getUnselectedCells() const
|
||||
{
|
||||
const QAbstractItemModel *model = QTableView::model();
|
||||
|
||||
int rows = model->rowCount();
|
||||
int columns = model->columnCount();
|
||||
|
||||
QModelIndexList selected = selectionModel()->selectedIndexes();
|
||||
std::sort (selected.begin(), selected.end());
|
||||
|
||||
QModelIndexList all;
|
||||
|
||||
for (int y=0; y<rows; ++y)
|
||||
for (int x=0; x<columns; ++x)
|
||||
{
|
||||
QModelIndex index = model->index (y, x);
|
||||
if (model->data (index, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern))
|
||||
all.push_back (index);
|
||||
}
|
||||
|
||||
std::sort (all.begin(), all.end());
|
||||
|
||||
QModelIndexList list;
|
||||
|
||||
std::set_difference (all.begin(), all.end(), selected.begin(), selected.end(),
|
||||
std::back_inserter (list));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QModelIndexList CSVWorld::RegionMap::getSelectedCells (bool existent, bool nonExistent) const
|
||||
{
|
||||
const QAbstractItemModel *model = QTableView::model();
|
||||
|
||||
QModelIndexList selected = selectionModel()->selectedIndexes();
|
||||
|
||||
QModelIndexList list;
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
bool exists = model->data (*iter, Qt::BackgroundRole)!=QBrush (Qt::DiagCrossPattern);
|
||||
|
||||
if ((exists && existent) || (!exists && nonExistent))
|
||||
list.push_back (*iter);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
QModelIndexList CSVWorld::RegionMap::getMissingRegionCells() const
|
||||
{
|
||||
const QAbstractItemModel *model = QTableView::model();
|
||||
|
||||
QModelIndexList selected = selectionModel()->selectedIndexes();
|
||||
|
||||
std::set<std::string> regions;
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
std::string region =
|
||||
model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData();
|
||||
|
||||
if (!region.empty())
|
||||
regions.insert (region);
|
||||
}
|
||||
|
||||
QModelIndexList list;
|
||||
|
||||
QModelIndexList unselected = getUnselectedCells();
|
||||
|
||||
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
|
||||
{
|
||||
std::string region =
|
||||
model->data (*iter, CSMWorld::RegionMap::Role_Region).toString().toUtf8().constData();
|
||||
|
||||
if (!region.empty() && regions.find (region)!=regions.end())
|
||||
list.push_back (*iter);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::setRegion (const std::string& regionId)
|
||||
{
|
||||
QModelIndexList selected = getSelectedCells();
|
||||
|
||||
QAbstractItemModel *regionModel = model();
|
||||
|
||||
CSMWorld::IdTable *cellsModel = &dynamic_cast<CSMWorld::IdTable&> (*
|
||||
mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
|
||||
|
||||
QString regionId2 = QString::fromUtf8 (regionId.c_str());
|
||||
|
||||
if (selected.size()>1)
|
||||
mDocument.getUndoStack().beginMacro (tr ("Set Region"));
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
std::string cellId = regionModel->data (*iter, CSMWorld::RegionMap::Role_CellId).
|
||||
toString().toUtf8().constData();
|
||||
|
||||
QModelIndex index = cellsModel->getModelIndex (cellId,
|
||||
cellsModel->findColumnIndex (CSMWorld::Columns::ColumnId_Region));
|
||||
|
||||
mDocument.getUndoStack().push (
|
||||
new CSMWorld::ModifyCommand (*cellsModel, index, regionId2));
|
||||
}
|
||||
|
||||
if (selected.size()>1)
|
||||
mDocument.getUndoStack().endMacro();
|
||||
}
|
||||
|
||||
CSVWorld::RegionMap::RegionMap (const CSMWorld::UniversalId& universalId,
|
||||
CSMDoc::Document& document, QWidget *parent)
|
||||
: QTableView (parent), mEditLock (false), mDocument (document)
|
||||
{
|
||||
verticalHeader()->hide();
|
||||
horizontalHeader()->hide();
|
||||
|
||||
setSelectionMode (QAbstractItemView::ExtendedSelection);
|
||||
|
||||
setModel (document.getData().getTableModel (universalId));
|
||||
|
||||
resizeColumnsToContents();
|
||||
resizeRowsToContents();
|
||||
|
||||
mSelectAllAction = new QAction (tr ("Select All"), this);
|
||||
connect (mSelectAllAction, SIGNAL (triggered()), this, SLOT (selectAll()));
|
||||
addAction (mSelectAllAction);
|
||||
|
||||
mClearSelectionAction = new QAction (tr ("Clear Selection"), this);
|
||||
connect (mClearSelectionAction, SIGNAL (triggered()), this, SLOT (clearSelection()));
|
||||
addAction (mClearSelectionAction);
|
||||
|
||||
mSelectRegionsAction = new QAction (tr ("Select Regions"), this);
|
||||
connect (mSelectRegionsAction, SIGNAL (triggered()), this, SLOT (selectRegions()));
|
||||
addAction (mSelectRegionsAction);
|
||||
|
||||
mCreateCellsAction = new QAction (tr ("Create Cells Action"), this);
|
||||
connect (mCreateCellsAction, SIGNAL (triggered()), this, SLOT (createCells()));
|
||||
addAction (mCreateCellsAction);
|
||||
|
||||
mSetRegionAction = new QAction (tr ("Set Region"), this);
|
||||
connect (mSetRegionAction, SIGNAL (triggered()), this, SLOT (setRegion()));
|
||||
addAction (mSetRegionAction);
|
||||
|
||||
mUnsetRegionAction = new QAction (tr ("Unset Region"), this);
|
||||
connect (mUnsetRegionAction, SIGNAL (triggered()), this, SLOT (unsetRegion()));
|
||||
addAction (mUnsetRegionAction);
|
||||
|
||||
mViewAction = new QAction (tr ("View Cells"), this);
|
||||
connect (mViewAction, SIGNAL (triggered()), this, SLOT (view()));
|
||||
addAction (mViewAction);
|
||||
|
||||
mViewInTableAction = new QAction (tr ("View Cells in Table"), this);
|
||||
connect (mViewInTableAction, SIGNAL (triggered()), this, SLOT (viewInTable()));
|
||||
addAction (mViewInTableAction);
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::setEditLock (bool locked)
|
||||
{
|
||||
mEditLock = locked;
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::selectAll()
|
||||
{
|
||||
QModelIndexList unselected = getUnselectedCells();
|
||||
|
||||
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
|
||||
selectionModel()->select (*iter, QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::clearSelection()
|
||||
{
|
||||
selectionModel()->clearSelection();
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::selectRegions()
|
||||
{
|
||||
QModelIndexList unselected = getMissingRegionCells();
|
||||
|
||||
for (QModelIndexList::const_iterator iter (unselected.begin()); iter!=unselected.end(); ++iter)
|
||||
selectionModel()->select (*iter, QItemSelectionModel::Select);
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::createCells()
|
||||
{
|
||||
if (mEditLock)
|
||||
return;
|
||||
|
||||
QModelIndexList selected = getSelectedCells (false, true);
|
||||
|
||||
CSMWorld::IdTable *cellsModel = &dynamic_cast<CSMWorld::IdTable&> (*
|
||||
mDocument.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
|
||||
|
||||
if (selected.size()>1)
|
||||
mDocument.getUndoStack().beginMacro (tr ("Create cells"));
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
|
||||
toString().toUtf8().constData();
|
||||
|
||||
mDocument.getUndoStack().push (new CSMWorld::CreateCommand (*cellsModel, cellId));
|
||||
}
|
||||
|
||||
if (selected.size()>1)
|
||||
mDocument.getUndoStack().endMacro();
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::setRegion()
|
||||
{
|
||||
if (mEditLock)
|
||||
return;
|
||||
|
||||
setRegion (mRegionId);
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::unsetRegion()
|
||||
{
|
||||
if (mEditLock)
|
||||
return;
|
||||
|
||||
setRegion ("");
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::view()
|
||||
{
|
||||
std::ostringstream hint;
|
||||
hint << "c:";
|
||||
|
||||
QModelIndexList selected = selectionModel()->selectedIndexes();
|
||||
|
||||
bool first = true;
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
|
||||
toString().toUtf8().constData();
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
hint << "; ";
|
||||
|
||||
hint << cellId;
|
||||
}
|
||||
|
||||
emit editRequest (CSMWorld::UniversalId (CSMWorld::UniversalId::Type_Scene, "sys::default"),
|
||||
hint.str());
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMap::viewInTable()
|
||||
{
|
||||
std::ostringstream hint;
|
||||
hint << "f:!or(";
|
||||
|
||||
QModelIndexList selected = getSelectedCells();
|
||||
|
||||
bool first = true;
|
||||
|
||||
for (QModelIndexList::const_iterator iter (selected.begin()); iter!=selected.end(); ++iter)
|
||||
{
|
||||
std::string cellId = model()->data (*iter, CSMWorld::RegionMap::Role_CellId).
|
||||
toString().toUtf8().constData();
|
||||
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
hint << ",";
|
||||
|
||||
hint << "string(ID,\"" << cellId << "\")";
|
||||
}
|
||||
|
||||
hint << ")";
|
||||
|
||||
emit editRequest (CSMWorld::UniversalId::Type_Cells, hint.str());
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
#ifndef CSV_WORLD_REGIONMAP_H
|
||||
#define CSV_WORLD_REGIONMAP_H
|
||||
|
||||
#include <QTableView>
|
||||
|
||||
class QAction;
|
||||
|
||||
namespace CSMDoc
|
||||
{
|
||||
class Document;
|
||||
}
|
||||
|
||||
namespace CSMWorld
|
||||
{
|
||||
class UniversalId;
|
||||
}
|
||||
|
||||
namespace CSVWorld
|
||||
{
|
||||
class RegionMap : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QAction *mSelectAllAction;
|
||||
QAction *mClearSelectionAction;
|
||||
QAction *mSelectRegionsAction;
|
||||
QAction *mCreateCellsAction;
|
||||
QAction *mSetRegionAction;
|
||||
QAction *mUnsetRegionAction;
|
||||
QAction *mViewAction;
|
||||
QAction *mViewInTableAction;
|
||||
bool mEditLock;
|
||||
CSMDoc::Document& mDocument;
|
||||
std::string mRegionId;
|
||||
|
||||
private:
|
||||
|
||||
void contextMenuEvent (QContextMenuEvent *event);
|
||||
|
||||
QModelIndexList getUnselectedCells() const;
|
||||
///< \note Non-existent cells are not listed.
|
||||
|
||||
QModelIndexList getSelectedCells (bool existent = true, bool nonExistent = false) const;
|
||||
///< \param existant Include existant cells.
|
||||
/// \param nonExistant Include non-existant cells.
|
||||
|
||||
QModelIndexList getMissingRegionCells() const;
|
||||
///< Unselected cells within all regions that have at least one selected cell.
|
||||
|
||||
void setRegion (const std::string& regionId);
|
||||
///< Set region Id of selected cells.
|
||||
|
||||
public:
|
||||
|
||||
RegionMap (const CSMWorld::UniversalId& universalId, CSMDoc::Document& document,
|
||||
QWidget *parent = 0);
|
||||
|
||||
void setEditLock (bool locked);
|
||||
|
||||
signals:
|
||||
|
||||
void editRequest (const CSMWorld::UniversalId& id, const std::string& hint);
|
||||
|
||||
private slots:
|
||||
|
||||
void selectAll();
|
||||
|
||||
void clearSelection();
|
||||
|
||||
void selectRegions();
|
||||
|
||||
void createCells();
|
||||
|
||||
void setRegion();
|
||||
|
||||
void unsetRegion();
|
||||
|
||||
void view();
|
||||
|
||||
void viewInTable();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
@ -1,29 +1,27 @@
|
||||
|
||||
#include "regionmapsubview.hpp"
|
||||
|
||||
#include <QTableView>
|
||||
#include <QHeaderView>
|
||||
#include "regionmap.hpp"
|
||||
|
||||
CSVWorld::RegionMapSubView::RegionMapSubView (CSMWorld::UniversalId universalId,
|
||||
CSMDoc::Document& document)
|
||||
: CSVDoc::SubView (universalId)
|
||||
{
|
||||
mTable = new QTableView (this);
|
||||
mRegionMap = new RegionMap (universalId, document, this);
|
||||
|
||||
mTable->verticalHeader()->hide();
|
||||
mTable->horizontalHeader()->hide();
|
||||
setWidget (mRegionMap);
|
||||
|
||||
mTable->setSelectionMode (QAbstractItemView::ExtendedSelection);
|
||||
|
||||
mTable->setModel (document.getData().getTableModel (universalId));
|
||||
|
||||
mTable->resizeColumnsToContents();
|
||||
mTable->resizeRowsToContents();
|
||||
|
||||
setWidget (mTable);
|
||||
connect (mRegionMap, SIGNAL (editRequest (const CSMWorld::UniversalId&, const std::string&)),
|
||||
this, SLOT (editRequest (const CSMWorld::UniversalId&, const std::string&)));
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMapSubView::setEditLock (bool locked)
|
||||
{
|
||||
mRegionMap->setEditLock (locked);
|
||||
}
|
||||
|
||||
void CSVWorld::RegionMapSubView::editRequest (const CSMWorld::UniversalId& id,
|
||||
const std::string& hint)
|
||||
{
|
||||
focusId (id, hint);
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
#include "backgroundimage.hpp"
|
||||
|
||||
#include <MyGUI_Gui.h>
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool correct)
|
||||
{
|
||||
if (mChild)
|
||||
{
|
||||
MyGUI::Gui::getInstance().destroyWidget(mChild);
|
||||
mChild = NULL;
|
||||
}
|
||||
if (correct)
|
||||
{
|
||||
setImageTexture("black.png");
|
||||
|
||||
if (fixedRatio)
|
||||
mAspect = 4.0/3.0;
|
||||
else
|
||||
mAspect = 0; // TODO
|
||||
|
||||
mChild = createWidgetReal<MyGUI::ImageBox>("ImageBox",
|
||||
MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default);
|
||||
mChild->setImageTexture(image);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
else
|
||||
{
|
||||
mAspect = 0;
|
||||
setImageTexture(image);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundImage::adjustSize()
|
||||
{
|
||||
if (mAspect == 0)
|
||||
return;
|
||||
|
||||
MyGUI::IntSize screenSize = getSize();
|
||||
|
||||
int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * mAspect) / 2);
|
||||
int topPadding = std::max(0.0, (screenSize.height - screenSize.width / mAspect) / 2);
|
||||
|
||||
mChild->setCoord(leftPadding, topPadding, screenSize.width - leftPadding*2, screenSize.height - topPadding*2);
|
||||
}
|
||||
|
||||
void BackgroundImage::setSize (const MyGUI::IntSize& _value)
|
||||
{
|
||||
MyGUI::Widget::setSize (_value);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void BackgroundImage::setCoord (const MyGUI::IntCoord& _value)
|
||||
{
|
||||
MyGUI::Widget::setCoord (_value);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
#ifndef OPENMW_MWGUI_BACKGROUNDIMAGE_H
|
||||
#define OPENMW_MWGUI_BACKGROUNDIMAGE_H
|
||||
|
||||
#include <MyGUI_ImageBox.h>
|
||||
|
||||
namespace MWGui
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief A variant of MyGUI::ImageBox with aspect ratio correction using black bars
|
||||
*/
|
||||
class BackgroundImage : public MyGUI::ImageBox
|
||||
{
|
||||
MYGUI_RTTI_DERIVED(BackgroundImage)
|
||||
|
||||
public:
|
||||
BackgroundImage() : mChild(NULL), mAspect(0) {}
|
||||
|
||||
/**
|
||||
* @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions
|
||||
* @param correct Add black bars?
|
||||
*/
|
||||
void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool correct=true);
|
||||
|
||||
virtual void setSize (const MyGUI::IntSize &_value);
|
||||
virtual void setCoord (const MyGUI::IntCoord &_value);
|
||||
|
||||
private:
|
||||
MyGUI::ImageBox* mChild;
|
||||
double mAspect;
|
||||
|
||||
void adjustSize();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -0,0 +1,106 @@
|
||||
#include "aipersue.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwworld/class.hpp"
|
||||
#include "../mwworld/action.hpp"
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
#include "steering.hpp"
|
||||
#include "movement.hpp"
|
||||
#include "creaturestats.hpp"
|
||||
|
||||
MWMechanics::AiPersue::AiPersue(const std::string &objectId)
|
||||
: mObjectId(objectId)
|
||||
{
|
||||
}
|
||||
MWMechanics::AiPersue *MWMechanics::AiPersue::clone() const
|
||||
{
|
||||
return new AiPersue(*this);
|
||||
}
|
||||
bool MWMechanics::AiPersue::execute (const MWWorld::Ptr& actor, float duration)
|
||||
{
|
||||
MWBase::World *world = MWBase::Environment::get().getWorld();
|
||||
ESM::Position pos = actor.getRefData().getPosition();
|
||||
Movement &movement = actor.getClass().getMovementSettings(actor);
|
||||
const ESM::Cell *cell = actor.getCell()->getCell();
|
||||
|
||||
actor.getClass().getCreatureStats(actor).setMovementFlag(CreatureStats::Flag_Run, true);
|
||||
|
||||
MWWorld::Ptr player = world->getPlayerPtr();
|
||||
if(cell->mData.mX != player.getCell()->getCell()->mData.mX)
|
||||
{
|
||||
int sideX = PathFinder::sgn(cell->mData.mX - player.getCell()->getCell()->mData.mX);
|
||||
//check if actor is near the border of an inactive cell. If so, stop walking.
|
||||
if(sideX * (pos.pos[0] - cell->mData.mX*ESM::Land::REAL_SIZE) >
|
||||
sideX * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
|
||||
{
|
||||
movement.mPosition[1] = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if(cell->mData.mY != player.getCell()->getCell()->mData.mY)
|
||||
{
|
||||
int sideY = PathFinder::sgn(cell->mData.mY - player.getCell()->getCell()->mData.mY);
|
||||
//check if actor is near the border of an inactive cell. If so, stop walking.
|
||||
if(sideY * (pos.pos[1] - cell->mData.mY*ESM::Land::REAL_SIZE) >
|
||||
sideY * (ESM::Land::REAL_SIZE/2.0f - 200.0f))
|
||||
{
|
||||
movement.mPosition[1] = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MWWorld::Ptr target = world->getPtr(mObjectId,false);
|
||||
ESM::Position targetPos = target.getRefData().getPosition();
|
||||
|
||||
bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY;
|
||||
if(!mPathFinder.isPathConstructed() || cellChange)
|
||||
{
|
||||
mCellX = cell->mData.mX;
|
||||
mCellY = cell->mData.mY;
|
||||
|
||||
ESM::Pathgrid::Point dest;
|
||||
dest.mX = targetPos.pos[0];
|
||||
dest.mY = targetPos.pos[1];
|
||||
dest.mZ = targetPos.pos[2];
|
||||
|
||||
ESM::Pathgrid::Point start;
|
||||
start.mX = pos.pos[0];
|
||||
start.mY = pos.pos[1];
|
||||
start.mZ = pos.pos[2];
|
||||
|
||||
mPathFinder.buildPath(start, dest, actor.getCell(), true);
|
||||
}
|
||||
|
||||
if((pos.pos[0]-targetPos.pos[0])*(pos.pos[0]-targetPos.pos[0])+
|
||||
(pos.pos[1]-targetPos.pos[1])*(pos.pos[1]-targetPos.pos[1])+
|
||||
(pos.pos[2]-targetPos.pos[2])*(pos.pos[2]-targetPos.pos[2]) < 200*200)
|
||||
{
|
||||
movement.mPosition[1] = 0;
|
||||
MWWorld::Ptr target = world->getPtr(mObjectId,false);
|
||||
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2]))
|
||||
{
|
||||
movement.mPosition[1] = 0;
|
||||
MWWorld::Ptr target = world->getPtr(mObjectId,false);
|
||||
MWWorld::Class::get(target).activate(target,actor).get()->execute(actor);
|
||||
return true;
|
||||
}
|
||||
|
||||
float zAngle = mPathFinder.getZAngleToNext(pos.pos[0], pos.pos[1]);
|
||||
zTurn(actor, Ogre::Degree(zAngle));
|
||||
MWWorld::Class::get(actor).getMovementSettings(actor).mPosition[1] = 1;
|
||||
movement.mPosition[1] = 1;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int MWMechanics::AiPersue::getTypeId() const
|
||||
{
|
||||
return TypeIdPersue;
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
#ifndef GAME_MWMECHANICS_AIPERSUE_H
|
||||
#define GAME_MWMECHANICS_AIPERSUE_H
|
||||
|
||||
#include "aipackage.hpp"
|
||||
#include <string>
|
||||
|
||||
#include "pathfinding.hpp"
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
|
||||
class AiPersue : public AiPackage
|
||||
{
|
||||
public:
|
||||
AiPersue(const std::string &objectId);
|
||||
virtual AiPersue *clone() const;
|
||||
virtual bool execute (const MWWorld::Ptr& actor,float duration);
|
||||
///< \return Package completed?
|
||||
virtual int getTypeId() const;
|
||||
|
||||
private:
|
||||
std::string mObjectId;
|
||||
|
||||
PathFinder mPathFinder;
|
||||
int mCellX;
|
||||
int mCellY;
|
||||
};
|
||||
}
|
||||
#endif
|
@ -0,0 +1,334 @@
|
||||
#include "pathgrid.hpp"
|
||||
|
||||
#include "../mwbase/world.hpp"
|
||||
#include "../mwbase/environment.hpp"
|
||||
|
||||
#include "../mwworld/cellstore.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// See http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
|
||||
//
|
||||
// One of the smallest cost in Seyda Neen is between points 77 & 78:
|
||||
// pt x y
|
||||
// 77 = 8026, 4480
|
||||
// 78 = 7986, 4218
|
||||
//
|
||||
// Euclidean distance is about 262 (ignoring z) and Manhattan distance is 300
|
||||
// (again ignoring z). Using a value of about 300 for D seems like a reasonable
|
||||
// starting point for experiments. If in doubt, just use value 1.
|
||||
//
|
||||
// The distance between 3 & 4 are pretty small, too.
|
||||
// 3 = 5435, 223
|
||||
// 4 = 5948, 193
|
||||
//
|
||||
// Approx. 514 Euclidean distance and 533 Manhattan distance.
|
||||
//
|
||||
float manhattan(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
|
||||
{
|
||||
return 300 * (abs(a.mX - b.mX) + abs(a.mY - b.mY) + abs(a.mZ - b.mZ));
|
||||
}
|
||||
|
||||
// Choose a heuristics - Note that these may not be the best for directed
|
||||
// graphs with non-uniform edge costs.
|
||||
//
|
||||
// distance:
|
||||
// - sqrt((curr.x - goal.x)^2 + (curr.y - goal.y)^2 + (curr.z - goal.z)^2)
|
||||
// - slower but more accurate
|
||||
//
|
||||
// Manhattan:
|
||||
// - |curr.x - goal.x| + |curr.y - goal.y| + |curr.z - goal.z|
|
||||
// - faster but not the shortest path
|
||||
float costAStar(const ESM::Pathgrid::Point& a, const ESM::Pathgrid::Point& b)
|
||||
{
|
||||
//return distance(a, b);
|
||||
return manhattan(a, b);
|
||||
}
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
PathgridGraph::PathgridGraph()
|
||||
: mCell(NULL)
|
||||
, mIsGraphConstructed(false)
|
||||
, mPathgrid(NULL)
|
||||
, mGraph(0)
|
||||
, mSCCId(0)
|
||||
, mSCCIndex(0)
|
||||
, mIsExterior(0)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* mGraph is populated with the cost of each allowed edge.
|
||||
*
|
||||
* The data structure is based on the code in buildPath2() but modified.
|
||||
* Please check git history if interested.
|
||||
*
|
||||
* mGraph[v].edges[i].index = w
|
||||
*
|
||||
* v = point index of location "from"
|
||||
* i = index of edges from point v
|
||||
* w = point index of location "to"
|
||||
*
|
||||
*
|
||||
* Example: (notice from p(0) to p(2) is not allowed in this example)
|
||||
*
|
||||
* mGraph[0].edges[0].index = 1
|
||||
* .edges[1].index = 3
|
||||
*
|
||||
* mGraph[1].edges[0].index = 0
|
||||
* .edges[1].index = 2
|
||||
* .edges[2].index = 3
|
||||
*
|
||||
* mGraph[2].edges[0].index = 1
|
||||
*
|
||||
* (etc, etc)
|
||||
*
|
||||
*
|
||||
* low
|
||||
* cost
|
||||
* p(0) <---> p(1) <------------> p(2)
|
||||
* ^ ^
|
||||
* | |
|
||||
* | +-----> p(3)
|
||||
* +---------------->
|
||||
* high cost
|
||||
*/
|
||||
bool PathgridGraph::load(const ESM::Cell* cell)
|
||||
{
|
||||
if(!cell)
|
||||
return false;
|
||||
|
||||
mCell = cell;
|
||||
mIsExterior = cell->isExterior();
|
||||
mPathgrid = MWBase::Environment::get().getWorld()->getStore().get<ESM::Pathgrid>().search(*cell);
|
||||
|
||||
if(!mPathgrid)
|
||||
return false;
|
||||
|
||||
if(mIsGraphConstructed)
|
||||
return true;
|
||||
|
||||
mGraph.resize(mPathgrid->mPoints.size());
|
||||
for(int i = 0; i < static_cast<int> (mPathgrid->mEdges.size()); i++)
|
||||
{
|
||||
ConnectedPoint neighbour;
|
||||
neighbour.cost = costAStar(mPathgrid->mPoints[mPathgrid->mEdges[i].mV0],
|
||||
mPathgrid->mPoints[mPathgrid->mEdges[i].mV1]);
|
||||
// forward path of the edge
|
||||
neighbour.index = mPathgrid->mEdges[i].mV1;
|
||||
mGraph[mPathgrid->mEdges[i].mV0].edges.push_back(neighbour);
|
||||
// reverse path of the edge
|
||||
// NOTE: These are redundant, ESM already contains the required reverse paths
|
||||
//neighbour.index = mPathgrid->mEdges[i].mV0;
|
||||
//mGraph[mPathgrid->mEdges[i].mV1].edges.push_back(neighbour);
|
||||
}
|
||||
buildConnectedPoints();
|
||||
mIsGraphConstructed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
// v is the pathgrid point index (some call them vertices)
|
||||
void PathgridGraph::recursiveStrongConnect(int v)
|
||||
{
|
||||
mSCCPoint[v].first = mSCCIndex; // index
|
||||
mSCCPoint[v].second = mSCCIndex; // lowlink
|
||||
mSCCIndex++;
|
||||
mSCCStack.push_back(v);
|
||||
int w;
|
||||
|
||||
for(int i = 0; i < static_cast<int> (mGraph[v].edges.size()); i++)
|
||||
{
|
||||
w = mGraph[v].edges[i].index;
|
||||
if(mSCCPoint[w].first == -1) // not visited
|
||||
{
|
||||
recursiveStrongConnect(w); // recurse
|
||||
mSCCPoint[v].second = std::min(mSCCPoint[v].second,
|
||||
mSCCPoint[w].second);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(find(mSCCStack.begin(), mSCCStack.end(), w) != mSCCStack.end())
|
||||
mSCCPoint[v].second = std::min(mSCCPoint[v].second,
|
||||
mSCCPoint[w].first);
|
||||
}
|
||||
}
|
||||
|
||||
if(mSCCPoint[v].second == mSCCPoint[v].first)
|
||||
{ // new component
|
||||
do
|
||||
{
|
||||
w = mSCCStack.back();
|
||||
mSCCStack.pop_back();
|
||||
mGraph[w].componentId = mSCCId;
|
||||
}
|
||||
while(w != v);
|
||||
mSCCId++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* mGraph contains the strongly connected component group id's along
|
||||
* with pre-calculated edge costs.
|
||||
*
|
||||
* A cell can have disjointed pathgrids, e.g. Seyda Neen has 3
|
||||
*
|
||||
* mGraph for Seyda Neen will therefore have 3 different values. When
|
||||
* selecting a random pathgrid point for AiWander, mGraph can be checked
|
||||
* for quickly finding whether the destination is reachable.
|
||||
*
|
||||
* Otherwise, buildPath can automatically select a closest reachable end
|
||||
* pathgrid point (reachable from the closest start point).
|
||||
*
|
||||
* Using Tarjan's algorithm:
|
||||
*
|
||||
* mGraph | graph G |
|
||||
* mSCCPoint | V | derived from mPoints
|
||||
* mGraph[v].edges | E (for v) |
|
||||
* mSCCIndex | index | tracking smallest unused index
|
||||
* mSCCStack | S |
|
||||
* mGraph[v].edges[i].index | w |
|
||||
*
|
||||
*/
|
||||
void PathgridGraph::buildConnectedPoints()
|
||||
{
|
||||
// both of these are set to zero in the constructor
|
||||
//mSCCId = 0; // how many strongly connected components in this cell
|
||||
//mSCCIndex = 0;
|
||||
int pointsSize = mPathgrid->mPoints.size();
|
||||
mSCCPoint.resize(pointsSize, std::pair<int, int> (-1, -1));
|
||||
mSCCStack.reserve(pointsSize);
|
||||
|
||||
for(int v = 0; v < static_cast<int> (pointsSize); v++)
|
||||
{
|
||||
if(mSCCPoint[v].first == -1) // undefined (haven't visited)
|
||||
recursiveStrongConnect(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool PathgridGraph::isPointConnected(const int start, const int end) const
|
||||
{
|
||||
return (mGraph[start].componentId == mGraph[end].componentId);
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Based on buildPath2(), please check git history if interested
|
||||
* Should consider using a 3rd party library version (e.g. boost)
|
||||
*
|
||||
* Find the shortest path to the target goal using a well known algorithm.
|
||||
* Uses mGraph which has pre-computed costs for allowed edges. It is assumed
|
||||
* that mGraph is already constructed.
|
||||
*
|
||||
* Should be possible to make this MT safe.
|
||||
*
|
||||
* Returns path which may be empty. path contains pathgrid points in local
|
||||
* cell co-ordinates (indoors) or world co-ordinates (external).
|
||||
*
|
||||
* Input params:
|
||||
* start, goal - pathgrid point indexes (for this cell)
|
||||
*
|
||||
* Variables:
|
||||
* openset - point indexes to be traversed, lowest cost at the front
|
||||
* closedset - point indexes already traversed
|
||||
* gScore - past accumulated costs vector indexed by point index
|
||||
* fScore - future estimated costs vector indexed by point index
|
||||
*
|
||||
* TODO: An intersting exercise might be to cache the paths created for a
|
||||
* start/goal pair. To cache the results the paths need to be in
|
||||
* pathgrid points form (currently they are converted to world
|
||||
* co-ordinates). Essentially trading speed w/ memory.
|
||||
*/
|
||||
std::list<ESM::Pathgrid::Point> PathgridGraph::aStarSearch(const int start,
|
||||
const int goal) const
|
||||
{
|
||||
std::list<ESM::Pathgrid::Point> path;
|
||||
if(!isPointConnected(start, goal))
|
||||
{
|
||||
return path; // there is no path, return an empty path
|
||||
}
|
||||
|
||||
int graphSize = mGraph.size();
|
||||
std::vector<float> gScore (graphSize, -1);
|
||||
std::vector<float> fScore (graphSize, -1);
|
||||
std::vector<int> graphParent (graphSize, -1);
|
||||
|
||||
// gScore & fScore keep costs for each pathgrid point in mPoints
|
||||
gScore[start] = 0;
|
||||
fScore[start] = costAStar(mPathgrid->mPoints[start], mPathgrid->mPoints[goal]);
|
||||
|
||||
std::list<int> openset;
|
||||
std::list<int> closedset;
|
||||
openset.push_back(start);
|
||||
|
||||
int current = -1;
|
||||
|
||||
while(!openset.empty())
|
||||
{
|
||||
current = openset.front(); // front has the lowest cost
|
||||
openset.pop_front();
|
||||
|
||||
if(current == goal)
|
||||
break;
|
||||
|
||||
closedset.push_back(current); // remember we've been here
|
||||
|
||||
// check all edges for the current point index
|
||||
for(int j = 0; j < static_cast<int> (mGraph[current].edges.size()); j++)
|
||||
{
|
||||
if(std::find(closedset.begin(), closedset.end(), mGraph[current].edges[j].index) ==
|
||||
closedset.end())
|
||||
{
|
||||
// not in closedset - i.e. have not traversed this edge destination
|
||||
int dest = mGraph[current].edges[j].index;
|
||||
float tentative_g = gScore[current] + mGraph[current].edges[j].cost;
|
||||
bool isInOpenSet = std::find(openset.begin(), openset.end(), dest) != openset.end();
|
||||
if(!isInOpenSet
|
||||
|| tentative_g < gScore[dest])
|
||||
{
|
||||
graphParent[dest] = current;
|
||||
gScore[dest] = tentative_g;
|
||||
fScore[dest] = tentative_g + costAStar(mPathgrid->mPoints[dest],
|
||||
mPathgrid->mPoints[goal]);
|
||||
if(!isInOpenSet)
|
||||
{
|
||||
// add this edge to openset, lowest cost goes to the front
|
||||
// TODO: if this causes performance problems a hash table may help
|
||||
std::list<int>::iterator it = openset.begin();
|
||||
for(it = openset.begin(); it!= openset.end(); it++)
|
||||
{
|
||||
if(fScore[*it] > fScore[dest])
|
||||
break;
|
||||
}
|
||||
openset.insert(it, dest);
|
||||
}
|
||||
}
|
||||
} // if in closedset, i.e. traversed this edge already, try the next edge
|
||||
}
|
||||
}
|
||||
|
||||
if(current != goal)
|
||||
return path; // for some reason couldn't build a path
|
||||
|
||||
// reconstruct path to return, using world co-ordinates
|
||||
float xCell = 0;
|
||||
float yCell = 0;
|
||||
if (mIsExterior)
|
||||
{
|
||||
xCell = mPathgrid->mData.mX * ESM::Land::REAL_SIZE;
|
||||
yCell = mPathgrid->mData.mY * ESM::Land::REAL_SIZE;
|
||||
}
|
||||
|
||||
while(graphParent[current] != -1)
|
||||
{
|
||||
ESM::Pathgrid::Point pt = mPathgrid->mPoints[current];
|
||||
pt.mX += xCell;
|
||||
pt.mY += yCell;
|
||||
path.push_front(pt);
|
||||
current = graphParent[current];
|
||||
}
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,77 @@
|
||||
#ifndef GAME_MWMECHANICS_PATHGRID_H
|
||||
#define GAME_MWMECHANICS_PATHGRID_H
|
||||
|
||||
#include <components/esm/loadpgrd.hpp>
|
||||
#include <list>
|
||||
|
||||
namespace ESM
|
||||
{
|
||||
class Cell;
|
||||
}
|
||||
|
||||
namespace MWWorld
|
||||
{
|
||||
class CellStore;
|
||||
}
|
||||
|
||||
namespace MWMechanics
|
||||
{
|
||||
class PathgridGraph
|
||||
{
|
||||
public:
|
||||
PathgridGraph();
|
||||
|
||||
bool load(const ESM::Cell *cell);
|
||||
|
||||
// returns true if end point is strongly connected (i.e. reachable
|
||||
// from start point) both start and end are pathgrid point indexes
|
||||
bool isPointConnected(const int start, const int end) const;
|
||||
|
||||
// the input parameters are pathgrid point indexes
|
||||
// the output list is in local (internal cells) or world (external
|
||||
// cells) co-ordinates
|
||||
std::list<ESM::Pathgrid::Point> aStarSearch(const int start,
|
||||
const int end) const;
|
||||
private:
|
||||
|
||||
const ESM::Cell *mCell;
|
||||
const ESM::Pathgrid *mPathgrid;
|
||||
bool mIsExterior;
|
||||
|
||||
struct ConnectedPoint // edge
|
||||
{
|
||||
int index; // pathgrid point index of neighbour
|
||||
float cost;
|
||||
};
|
||||
|
||||
struct Node // point
|
||||
{
|
||||
int componentId;
|
||||
std::vector<ConnectedPoint> edges; // neighbours
|
||||
};
|
||||
|
||||
// componentId is an integer indicating the groups of connected
|
||||
// pathgrid points (all connected points will have the same value)
|
||||
//
|
||||
// In Seyda Neen there are 3:
|
||||
//
|
||||
// 52, 53 and 54 are one set (enclosed yard)
|
||||
// 48, 49, 50, 51, 84, 85, 86, 87, 88, 89, 90 (ship & office)
|
||||
// all other pathgrid points are the third set
|
||||
//
|
||||
std::vector<Node> mGraph;
|
||||
bool mIsGraphConstructed;
|
||||
|
||||
// variables used to calculate connected components
|
||||
int mSCCId;
|
||||
int mSCCIndex;
|
||||
std::vector<int> mSCCStack;
|
||||
typedef std::pair<int, int> VPair; // first is index, second is lowlink
|
||||
std::vector<VPair> mSCCPoint;
|
||||
// methods used to calculate connected components
|
||||
void recursiveStrongConnect(int v);
|
||||
void buildConnectedPoints();
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue