Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714#include "pathgrid.hpp"
#include <algorithm>#include <functional>#include <memory>#include <set>#include <stddef.h>
#include <osg/Array>#include <osg/Callback>#include <osg/GL>#include <osg/Geometry>#include <osg/Group>#include <osg/MixinVector>#include <osg/Node>#include <osg/PositionAttitudeTransform>#include <osg/PrimitiveSet>#include <osg/StateAttribute>#include <osg/StateSet>#include <osg/Vec3f>#include <osg/Vec4f>
#include <apps/opencs/model/world/cell.hpp>#include <apps/opencs/model/world/cellcoordinates.hpp>#include <apps/opencs/model/world/columns.hpp>#include <apps/opencs/model/world/idcollection.hpp>#include <apps/opencs/model/world/pathgrid.hpp>#include <apps/opencs/model/world/record.hpp>#include <apps/opencs/model/world/subcellcollection.hpp>#include <apps/opencs/model/world/universalid.hpp>#include <apps/opencs/view/render/mask.hpp>#include <apps/opencs/view/render/tagbase.hpp>
#include <components/esm3/loadcell.hpp>#include <components/esm3/loadland.hpp>#include <components/esm3/loadpgrd.hpp>#include <components/sceneutil/pathgridutil.hpp>
#include "../../model/world/commandmacro.hpp"#include "../../model/world/commands.hpp"#include "../../model/world/data.hpp"#include "../../model/world/idtree.hpp"#include "worldspacewidget.hpp"
namespace osg{ class NodeVisitor;}
namespace CSVRender{ class PathgridNodeCallback : public osg::NodeCallback { public: void operator()(osg::Node* node, osg::NodeVisitor* nv) override { PathgridTag* tag = static_cast<PathgridTag*>(node->getUserData()); tag->getPathgrid()->update(); } };
PathgridTag::PathgridTag(Pathgrid* pathgrid) : TagBase(Mask_Pathgrid) , mPathgrid(pathgrid) { }
Pathgrid* PathgridTag::getPathgrid() const { return mPathgrid; }
QString PathgridTag::getToolTip(bool /*hideBasics*/, const WorldspaceHitResult& hit) const { QString text("Pathgrid: "); text += mPathgrid->getId().c_str(); text += " ("; text += QString::number(SceneUtil::getPathgridNode(hit.index0)); text += ")";
return text; }
Pathgrid::Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, const CSMWorld::CellCoordinates& coordinates) : mData(data) , mPathgridCollection(mData.getPathgrids()) , mId(ESM::RefId::stringRefId(pathgridId)) , mCoords(coordinates) , mInterior(false) , mDragOrigin(0) , mChangeGeometry(true) , mRemoveGeometry(false) , mUseOffset(true) , mParent(parent) , mPathgridGeometry(nullptr) , mDragGeometry(nullptr) , mTag(new PathgridTag(this)) { const float coordScalar = ESM::Land::REAL_SIZE;
mBaseNode = new osg::PositionAttitudeTransform(); mBaseNode->setPosition(osg::Vec3f(mCoords.getX() * coordScalar, mCoords.getY() * coordScalar, 0.f)); mBaseNode->setUserData(mTag); mBaseNode->setUpdateCallback(new PathgridNodeCallback()); mBaseNode->setNodeMask(Mask_Pathgrid); mParent->addChild(mBaseNode);
mPathgridGroup = new osg::Group(); mBaseNode->addChild(mPathgridGroup);
recreateGeometry();
int index = mData.getCells().searchId(mId); if (index != -1) { const CSMWorld::Cell& cell = mData.getCells().getRecord(index).get(); mInterior = cell.mData.mFlags & ESM::Cell::Interior; } }
Pathgrid::~Pathgrid() { mParent->removeChild(mBaseNode); }
const CSMWorld::CellCoordinates& Pathgrid::getCoordinates() const { return mCoords; }
const std::string& Pathgrid::getId() const { return mId.getRefIdString(); }
bool Pathgrid::isSelected() const { return !mSelected.empty(); }
const Pathgrid::NodeList& Pathgrid::getSelected() const { return mSelected; }
void Pathgrid::selectAll() { mSelected.clear();
const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (unsigned short i = 0; i < static_cast<unsigned short>(source->mPoints.size()); ++i) mSelected.push_back(i);
createSelectedGeometry(*source); } else { removeSelectedGeometry(); } }
void Pathgrid::toggleSelected(unsigned short node) { NodeList::iterator searchResult = std::find(mSelected.begin(), mSelected.end(), node); if (searchResult != mSelected.end()) { mSelected.erase(searchResult); } else { mSelected.push_back(node); }
createSelectedGeometry(); }
void Pathgrid::invertSelected() { NodeList temp = NodeList(mSelected); mSelected.clear();
const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (unsigned short i = 0; i < static_cast<unsigned short>(source->mPoints.size()); ++i) { if (std::find(temp.begin(), temp.end(), i) == temp.end()) mSelected.push_back(i); }
createSelectedGeometry(*source); } else { removeSelectedGeometry(); } }
void Pathgrid::clearSelected() { mSelected.clear(); removeSelectedGeometry(); }
void Pathgrid::moveSelected(const osg::Vec3d& offset) { mUseOffset = true; mMoveOffset += offset;
recreateGeometry(); }
void Pathgrid::setDragOrigin(unsigned short node) { mDragOrigin = node; }
void Pathgrid::setDragEndpoint(unsigned short node) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { const CSMWorld::Pathgrid::Point& pointA = source->mPoints[mDragOrigin]; const CSMWorld::Pathgrid::Point& pointB = source->mPoints[node];
osg::Vec3f start = osg::Vec3f(pointA.mX, pointA.mY, pointA.mZ + SceneUtil::DiamondHalfHeight); osg::Vec3f end = osg::Vec3f(pointB.mX, pointB.mY, pointB.mZ + SceneUtil::DiamondHalfHeight);
createDragGeometry(start, end, true); } }
void Pathgrid::setDragEndpoint(const osg::Vec3d& pos) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { const CSMWorld::Pathgrid::Point& point = source->mPoints[mDragOrigin];
osg::Vec3f start = osg::Vec3f(point.mX, point.mY, point.mZ + SceneUtil::DiamondHalfHeight); osg::Vec3f end = pos - mBaseNode->getPosition(); createDragGeometry(start, end, false); } }
void Pathgrid::resetIndicators() { mUseOffset = false; mMoveOffset.set(0, 0, 0);
mPathgridGroup->removeChild(mDragGeometry); mDragGeometry = nullptr; }
void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) { CSMWorld::IdTree* model = &dynamic_cast<CSMWorld::IdTree&>(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); const std::string& idString = mId.getRefIdString(); const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { osg::Vec3d localCoords = worldPos - mBaseNode->getPosition();
int posX = clampToCell(static_cast<int>(localCoords.x())); int posY = clampToCell(static_cast<int>(localCoords.y())); int posZ = clampToCell(static_cast<int>(localCoords.z()));
int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints);
int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX);
int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY);
int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ);
QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast<int>(source->mPoints.size());
// Add node to end of list commands.push(new CSMWorld::AddNestedCommand(*model, idString, row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posXColumn, parent), posX)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posYColumn, parent), posY)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, posZColumn, parent), posZ)); } else { int index = mPathgridCollection.searchId(mId); if (index == -1) { // Does not exist commands.push(new CSMWorld::CreatePathgridCommand(*model, idString)); } else { source = &mPathgridCollection.getRecord(index).get();
// Deleted, so revert and remove all data commands.push(new CSMWorld::RevertCommand(*model, idString));
int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); for (size_t row = source->mPoints.size(); row > 0; --row) { commands.push( new CSMWorld::DeleteNestedCommand(*model, idString, static_cast<int>(row - 1), parentColumn)); }
parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); for (size_t row = source->mEdges.size(); row > 0; --row) { commands.push( new CSMWorld::DeleteNestedCommand(*model, idString, static_cast<int>(row - 1), parentColumn)); } } } }
void Pathgrid::applyPosition(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { osg::Vec3d localCoords = mMoveOffset;
int offsetX = static_cast<int>(localCoords.x()); int offsetY = static_cast<int>(localCoords.y()); int offsetZ = static_cast<int>(localCoords.z());
QAbstractItemModel* model = mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids);
int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints);
int posXColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosX);
int posYColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosY);
int posZColumn = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridPosZ);
QModelIndex parent = model->index(recordIndex, parentColumn);
for (const auto& selected : mSelected) { const CSMWorld::Pathgrid::Point& point = source->mPoints[selected]; int row = static_cast<int>(selected);
commands.push(new CSMWorld::ModifyCommand( *model, model->index(row, posXColumn, parent), clampToCell(point.mX + offsetX)));
commands.push(new CSMWorld::ModifyCommand( *model, model->index(row, posYColumn, parent), clampToCell(point.mY + offsetY)));
commands.push(new CSMWorld::ModifyCommand( *model, model->index(row, posZColumn, parent), clampToCell(point.mZ + offsetZ))); } } }
void Pathgrid::applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { addEdge(commands, *source, node1, node2); } }
void Pathgrid::applyEdges(CSMWorld::CommandMacro& commands, unsigned short node) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { for (const auto& selected : mSelected) { addEdge(commands, *source, node, selected); } } }
void Pathgrid::applyRemoveNodes(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { CSMWorld::IdTree* model = &dynamic_cast<CSMWorld::IdTree&>(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids));
// Want to remove nodes from end of list first std::sort(mSelected.begin(), mSelected.end(), std::greater<int>());
int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints);
for (std::vector<unsigned short>::iterator row = mSelected.begin(); row != mSelected.end(); ++row) { commands.push(new CSMWorld::DeleteNestedCommand( *model, mId.getRefIdString(), static_cast<int>(*row), parentColumn)); }
// Fix/remove edges std::set<int, std::greater<int>> edgeRowsToRemove;
parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges);
int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0);
int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1);
QModelIndex parent = model->index(recordIndex, parentColumn);
for (size_t edge = 0; edge < source->mEdges.size(); ++edge) { size_t adjustment0 = 0; size_t adjustment1 = 0;
// Determine necessary adjustment for (const auto point : mSelected) { if (source->mEdges[edge].mV0 == point || source->mEdges[edge].mV1 == point) { edgeRowsToRemove.insert(static_cast<int>(edge));
adjustment0 = 0; // No need to adjust, its getting removed adjustment1 = 0; break; }
if (source->mEdges[edge].mV0 > point) ++adjustment0;
if (source->mEdges[edge].mV1 > point) ++adjustment1; }
if (adjustment0 != 0) { int adjustedEdge = static_cast<int>(source->mEdges[edge].mV0 - adjustment0); commands.push(new CSMWorld::ModifyCommand( *model, model->index(static_cast<int>(edge), edge0Column, parent), adjustedEdge)); }
if (adjustment1 != 0) { int adjustedEdge = static_cast<int>(source->mEdges[edge].mV1 - adjustment1); commands.push(new CSMWorld::ModifyCommand( *model, model->index(static_cast<int>(edge), edge1Column, parent), adjustedEdge)); } }
for (const auto row : edgeRowsToRemove) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); } }
clearSelected(); }
void Pathgrid::applyRemoveEdges(CSMWorld::CommandMacro& commands) { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { // Want to remove from end of row first std::set<int, std::greater<int>> rowsToRemove; for (size_t i = 0; i <= mSelected.size(); ++i) { for (size_t j = i + 1; j < mSelected.size(); ++j) { int row = edgeExists(*source, mSelected[i], mSelected[j]); if (row != -1) { rowsToRemove.insert(row); }
row = edgeExists(*source, mSelected[j], mSelected[i]); if (row != -1) { rowsToRemove.insert(row); } } }
CSMWorld::IdTree* model = &dynamic_cast<CSMWorld::IdTree&>(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids)); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges);
std::set<int, std::greater<int>>::iterator row; for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) { commands.push(new CSMWorld::DeleteNestedCommand(*model, mId.getRefIdString(), *row, parentColumn)); } } }
osg::ref_ptr<PathgridTag> Pathgrid::getTag() const { return mTag; }
void Pathgrid::recreateGeometry() { mChangeGeometry = true; }
void Pathgrid::removeGeometry() { mRemoveGeometry = true; }
void Pathgrid::update() { if (mRemoveGeometry) { removePathgridGeometry(); removeSelectedGeometry(); } else if (mChangeGeometry) { createGeometry(); }
mChangeGeometry = false; mRemoveGeometry = false; }
void Pathgrid::createGeometry() { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { CSMWorld::Pathgrid temp; if (mUseOffset) { temp = *source;
for (NodeList::iterator it = mSelected.begin(); it != mSelected.end(); ++it) { temp.mPoints[*it].mX += mMoveOffset.x(); temp.mPoints[*it].mY += mMoveOffset.y(); temp.mPoints[*it].mZ += mMoveOffset.z(); }
source = &temp; }
removePathgridGeometry(); mPathgridGeometry = SceneUtil::createPathgridGeometry(*source); mPathgridGroup->addChild(mPathgridGeometry);
createSelectedGeometry(*source); } else { removePathgridGeometry(); removeSelectedGeometry(); } }
void Pathgrid::createSelectedGeometry() { const CSMWorld::Pathgrid* source = getPathgridSource(); if (source) { createSelectedGeometry(*source); } else { removeSelectedGeometry(); } }
void Pathgrid::createSelectedGeometry(const CSMWorld::Pathgrid& source) { removeSelectedGeometry();
mSelectedGeometry = SceneUtil::createPathgridSelectedWireframe(source, mSelected); mPathgridGroup->addChild(mSelectedGeometry); }
void Pathgrid::removePathgridGeometry() { if (mPathgridGeometry) { mPathgridGroup->removeChild(mPathgridGeometry); mPathgridGeometry = nullptr; } }
void Pathgrid::removeSelectedGeometry() { if (mSelectedGeometry) { mPathgridGroup->removeChild(mSelectedGeometry); mSelectedGeometry = nullptr; } }
void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) { if (mDragGeometry) mPathgridGroup->removeChild(mDragGeometry);
mDragGeometry = new osg::Geometry();
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array(2); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array(1); osg::ref_ptr<osg::DrawElementsUShort> indices = new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, 2);
(*vertices)[0] = start; (*vertices)[1] = end;
if (valid) { (*colors)[0] = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); } else { (*colors)[0] = osg::Vec4f(1.f, 0.f, 0.f, 1.f); }
indices->setElement(0, 0); indices->setElement(1, 1);
mDragGeometry->setVertexArray(vertices); mDragGeometry->setColorArray(colors, osg::Array::BIND_OVERALL); mDragGeometry->addPrimitiveSet(indices);
mPathgridGroup->addChild(mDragGeometry); }
const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() { int index = mPathgridCollection.searchId(mId); if (index != -1 && !mPathgridCollection.getRecord(index).isDeleted()) { return &mPathgridCollection.getRecord(index).get(); }
return nullptr; }
int Pathgrid::edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) { for (size_t i = 0; i < source.mEdges.size(); ++i) { if (source.mEdges[i].mV0 == node1 && source.mEdges[i].mV1 == node2) return static_cast<int>(i); }
return -1; }
void Pathgrid::addEdge( CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2) { CSMWorld::IdTree* model = &dynamic_cast<CSMWorld::IdTree&>(*mData.getTableModel(CSMWorld::UniversalId::Type_Pathgrids));
int recordIndex = mPathgridCollection.getIndex(mId); int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges);
int edge0Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge0);
int edge1Column = mPathgridCollection.searchNestedColumnIndex(parentColumn, CSMWorld::Columns::ColumnId_PathgridEdge1);
QModelIndex parent = model->index(recordIndex, parentColumn); int row = static_cast<int>(source.mEdges.size());
if (edgeExists(source, node1, node2) == -1) { commands.push(new CSMWorld::AddNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node1)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node2)); ++row; }
if (edgeExists(source, node2, node1) == -1) { commands.push(new CSMWorld::AddNestedCommand(*model, mId.getRefIdString(), row, parentColumn)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge0Column, parent), node2)); commands.push(new CSMWorld::ModifyCommand(*model, model->index(row, edge1Column, parent), node1)); } }
int Pathgrid::clampToCell(int v) { const int cellExtent = ESM::Land::REAL_SIZE;
if (mInterior) return v; else if (v > cellExtent) return cellExtent; else if (v < 0) return 0; else return v; }}