1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-01 03:21:41 +00:00
OpenMW/apps/opencs/view/render/pathgrid.cpp
2024-10-02 21:10:51 +02:00

713 lines
23 KiB
C++

#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 (int row = source->mPoints.size() - 1; row >= 0; --row)
{
commands.push(new CSMWorld::DeleteNestedCommand(*model, idString, row, parentColumn));
}
parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges);
for (int row = source->mEdges.size() - 1; row >= 0; --row)
{
commands.push(new CSMWorld::DeleteNestedCommand(*model, idString, row, 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)
{
int adjustment0 = 0;
int 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 = source->mEdges[edge].mV0 + adjustment0;
commands.push(
new CSMWorld::ModifyCommand(*model, model->index(edge, edge0Column, parent), adjustedEdge));
}
if (adjustment1 != 0)
{
int adjustedEdge = source->mEdges[edge].mV1 + adjustment1;
commands.push(
new CSMWorld::ModifyCommand(*model, model->index(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);
mDragGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
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;
}
}