From 3a31468af07548a17d2a0cd45020bb9aa552c349 Mon Sep 17 00:00:00 2001 From: cc9cii Date: Wed, 27 May 2015 22:12:11 +1000 Subject: [PATCH] Pathgrid record verifier. Feature #2000. --- apps/opencs/CMakeLists.txt | 2 +- apps/opencs/model/tools/pathgridcheck.cpp | 158 ++++++++++++++++++++++ apps/opencs/model/tools/pathgridcheck.hpp | 31 +++++ apps/opencs/model/tools/tools.cpp | 3 + 4 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 apps/opencs/model/tools/pathgridcheck.cpp create mode 100644 apps/opencs/model/tools/pathgridcheck.hpp diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 438f3c694..78b2deb7a 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -40,7 +40,7 @@ opencs_units (model/tools opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck - startscriptcheck search searchoperation searchstage + startscriptcheck search searchoperation searchstage pathgridcheck ) diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp new file mode 100644 index 000000000..71f596f4f --- /dev/null +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -0,0 +1,158 @@ +#include "pathgridcheck.hpp" + +#include +#include + +#include "../world/universalid.hpp" +#include "../world/idcollection.hpp" +#include "../world/subcellcollection.hpp" +#include "../world/pathgrid.hpp" + + +CSMTools::PathgridCheckStage::PathgridCheckStage (const CSMWorld::SubCellCollection& pathgrids) +: mPathgrids (pathgrids) +{} + +int CSMTools::PathgridCheckStage::setup() +{ + return mPathgrids.getSize(); +} + +void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& messages) +{ + const CSMWorld::Record& record = mPathgrids.getRecord (stage); + + if (record.isDeleted()) + return; + + const CSMWorld::Pathgrid& pathgrid = record.get(); + + CSMWorld::UniversalId id (CSMWorld::UniversalId::Type_Pathgrid, pathgrid.mId); + + // check the number of pathgrid points + if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) + messages.push_back (std::make_pair (id, pathgrid.mId + " has less points than expected")); + else if (pathgrid.mData.mS2 > static_cast(pathgrid.mPoints.size())) + messages.push_back (std::make_pair (id, pathgrid.mId + " has more points than expected")); + + struct Point + { + unsigned char mConnectionNum; + std::vector mOtherIndex; + Point() : mConnectionNum(0), mOtherIndex(0) {} + }; + std::vector pointList(pathgrid.mPoints.size()); + std::vector duplList; + + for (unsigned int i = 0; i < pathgrid.mEdges.size(); ++i) + { + if (pathgrid.mEdges[i].mV0 < static_cast(pathgrid.mPoints.size()) && pathgrid.mEdges[i].mV0 >= 0) + { + pointList[pathgrid.mEdges[i].mV0].mConnectionNum++; + // first check for duplicate edges + unsigned int j = 0; + for (; j < pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size(); ++j) + { + if (pointList[pathgrid.mEdges[i].mV0].mOtherIndex[j] == pathgrid.mEdges[i].mV1) + { + std::ostringstream ss; + ss << "has a duplicate edge between points" << pathgrid.mEdges[i].mV0 + << " and " << pathgrid.mEdges[i].mV1; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + break; + } + } + + // only add if not a duplicate + if (j == pointList[pathgrid.mEdges[i].mV0].mOtherIndex.size()) + pointList[pathgrid.mEdges[i].mV0].mOtherIndex.push_back(pathgrid.mEdges[i].mV1); + } + else + { + std::ostringstream ss; + ss << " has an edge connecting a non-existent point " << pathgrid.mEdges[i].mV0; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + } + } + + for (unsigned int i = 0; i < pathgrid.mPoints.size(); ++i) + { + // check the connection number for each point matches the edge connections + if (pathgrid.mPoints[i].mConnectionNum > pointList[i].mConnectionNum) + { + std::ostringstream ss; + ss << " has has less edges than expected for point " << i; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + } + else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) + { + std::ostringstream ss; + ss << " has has more edges than expected for point " << i; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + } + + // check that edges are bidirectional + bool foundReverse = false; + for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) + { + for (unsigned int k = 0; k < pointList[pointList[i].mOtherIndex[j]].mOtherIndex.size(); ++k) + { + if (pointList[pointList[i].mOtherIndex[j]].mOtherIndex[k] == static_cast(i)) + { + foundReverse = true; + break; + } + } + + if (!foundReverse) + { + std::ostringstream ss; + ss << " has a missing edge between points " << i << " and " << pointList[i].mOtherIndex[j]; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + } + } + + // check duplicate points + // FIXME: how to do this efficiently? + for (unsigned int j = 0; j < pathgrid.mPoints.size(); ++j) + { + if (j == i) + continue; + + if (pathgrid.mPoints[i].mX == pathgrid.mPoints[j].mX && + pathgrid.mPoints[i].mY == pathgrid.mPoints[j].mY && + pathgrid.mPoints[i].mZ == pathgrid.mPoints[j].mZ) + { + std::vector::const_iterator it = find(duplList.begin(), duplList.end(), i); + if (it == duplList.end()) + { + std::ostringstream ss; + ss << " has a duplicated point (" << i + << ") x=" << pathgrid.mPoints[i].mX + << ", y=" << pathgrid.mPoints[i].mY + << ", z=" << pathgrid.mPoints[i].mZ; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + + duplList.push_back(i); + break; + } + } + } + } + + // check pathgrid points that are not connected to anything + for (unsigned int i = 0; i < pointList.size(); ++i) + { + if (pointList[i].mConnectionNum == 0) + { + std::ostringstream ss; + ss << " has an orphaned point (" << i + << ") x=" << pathgrid.mPoints[i].mX + << ", y=" << pathgrid.mPoints[i].mY + << ", z=" << pathgrid.mPoints[i].mZ; + messages.push_back (std::make_pair (id, pathgrid.mId + ss.str())); + } + } + + // TODO: check whether there are disconnected graphs +} diff --git a/apps/opencs/model/tools/pathgridcheck.hpp b/apps/opencs/model/tools/pathgridcheck.hpp new file mode 100644 index 000000000..c90dbc8ed --- /dev/null +++ b/apps/opencs/model/tools/pathgridcheck.hpp @@ -0,0 +1,31 @@ +#ifndef CSM_TOOLS_PATHGRIDCHECK_H +#define CSM_TOOLS_PATHGRIDCHECK_H + +#include "../world/collection.hpp" + +#include "../doc/stage.hpp" + +namespace CSMWorld +{ + struct Pathgrid; + template + class SubCellCollection; +} + +namespace CSMTools +{ + class PathgridCheckStage : public CSMDoc::Stage + { + const CSMWorld::SubCellCollection >& mPathgrids; + + public: + + PathgridCheckStage (const CSMWorld::SubCellCollection >& pathgrids); + + virtual int setup(); + + virtual void perform (int stage, CSMDoc::Messages& messages); + }; +} + +#endif // CSM_TOOLS_PATHGRIDCHECK_H diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 99e462b1d..fd1569697 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -26,6 +26,7 @@ #include "referencecheck.hpp" #include "startscriptcheck.hpp" #include "searchoperation.hpp" +#include "pathgridcheck.hpp" CSMDoc::OperationHolder *CSMTools::Tools::get (int type) { @@ -96,6 +97,8 @@ CSMDoc::OperationHolder *CSMTools::Tools::getVerifier() CSMWorld::UniversalId( CSMWorld::UniversalId::Type_Meshes )), mData.getRaces() )); + mVerifierOperation->appendStage (new PathgridCheckStage (mData.getPathgrids())); + mVerifier.setOperation (mVerifierOperation); }