diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index e7312d0ba..f8cf1e2d8 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -86,12 +86,12 @@ opencs_units (view/widget opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode - orbitcameramode + orbitcameramode pathgridmode selectionmode pathgridselectionmode ) opencs_units_noqt (view/render lighting lightingday lightingnight lightingbright object cell terrainstorage tagbase - cellarrow cellmarker cellborder cameracontroller + cellarrow cellmarker cellborder cameracontroller pathgrid ) opencs_hdrs_noqt (view/render diff --git a/apps/opencs/model/tools/pathgridcheck.cpp b/apps/opencs/model/tools/pathgridcheck.cpp index 69ee5a809..3cd4a1b09 100644 --- a/apps/opencs/model/tools/pathgridcheck.cpp +++ b/apps/opencs/model/tools/pathgridcheck.cpp @@ -70,20 +70,6 @@ void CSMTools::PathgridCheckStage::perform (int stage, CSMDoc::Messages& message 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.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - else if (pathgrid.mPoints[i].mConnectionNum < pointList[i].mConnectionNum) - { - std::ostringstream ss; - ss << " has has more edges than expected for point " << i; - messages.add (id, pathgrid.mId + ss.str(), "", CSMDoc::Message::Severity_Error); - } - // check that edges are bidirectional bool foundReverse = false; for (unsigned int j = 0; j < pointList[i].mOtherIndex.size(); ++j) diff --git a/apps/opencs/model/world/commands.cpp b/apps/opencs/model/world/commands.cpp index f8c72a390..5f9422376 100644 --- a/apps/opencs/model/world/commands.cpp +++ b/apps/opencs/model/world/commands.cpp @@ -8,9 +8,12 @@ #include #include +#include "cellcoordinates.hpp" +#include "idcollection.hpp" #include "idtable.hpp" #include "idtree.hpp" #include "nestedtablewrapper.hpp" +#include "pathgrid.hpp" CSMWorld::ModifyCommand::ModifyCommand (QAbstractItemModel& model, const QModelIndex& index, const QVariant& new_, QUndoCommand* parent) @@ -235,6 +238,29 @@ void CSMWorld::CloneCommand::undo() mModel.removeRow (mModel.getModelIndex (mId, 0).row()); } +CSMWorld::CreatePathgridCommand::CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent) + : CreateCommand(model, id, parent) +{ + setType(UniversalId::Type_Pathgrid); +} + +void CSMWorld::CreatePathgridCommand::redo() +{ + CreateCommand::redo(); + + Record record = static_cast& >(mModel.getRecord(mId)); + record.get().blank(); + record.get().mCell = mId; + + std::pair coords = CellCoordinates::fromId(mId); + if (coords.second) + { + record.get().mData.mX = coords.first.getX(); + record.get().mData.mY = coords.first.getY(); + } + + mModel.setRecord(mId, record, mType); +} CSMWorld::UpdateCellCommand::UpdateCellCommand (IdTable& model, int row, QUndoCommand *parent) : QUndoCommand (parent), mModel (model), mRow (row) diff --git a/apps/opencs/model/world/commands.hpp b/apps/opencs/model/world/commands.hpp index 23ffccbd7..b54a1d5ac 100644 --- a/apps/opencs/model/world/commands.hpp +++ b/apps/opencs/model/world/commands.hpp @@ -153,6 +153,15 @@ namespace CSMWorld virtual void undo(); }; + class CreatePathgridCommand : public CreateCommand + { + public: + + CreatePathgridCommand(IdTable& model, const std::string& id, QUndoCommand *parent = 0); + + virtual void redo(); + }; + /// \brief Update cell ID according to x/y-coordinates /// /// \note The new value will be calculated in the first call to redo instead of the diff --git a/apps/opencs/model/world/nestedcoladapterimp.cpp b/apps/opencs/model/world/nestedcoladapterimp.cpp index 3189f76ed..464757cd4 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.cpp +++ b/apps/opencs/model/world/nestedcoladapterimp.cpp @@ -27,20 +27,8 @@ namespace CSMWorld point.mConnectionNum = 0; point.mUnknown = 0; - // inserting a point should trigger re-indexing of the edges - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (;iter != pathgrid.mEdges.end(); ++iter) - { - if ((*iter).mV0 >= position) - (*iter).mV0++; - if ((*iter).mV1 >= position) - (*iter).mV1++; - } - points.insert(points.begin()+position, point); - pathgrid.mData.mS2 += 1; // increment the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -54,28 +42,10 @@ namespace CSMWorld if (rowToRemove < 0 || rowToRemove >= static_cast (points.size())) throw std::runtime_error ("index out of range"); - // deleting a point should trigger re-indexing of the edges - // dangling edges are not allowed and hence removed - // - // FIXME: does not auto refresh edges table view - std::vector::iterator iter = pathgrid.mEdges.begin(); - for (; iter != pathgrid.mEdges.end();) - { - if (((*iter).mV0 == rowToRemove) || ((*iter).mV1 == rowToRemove)) - iter = pathgrid.mEdges.erase(iter); - else - { - if ((*iter).mV0 > rowToRemove) - (*iter).mV0--; - - if ((*iter).mV1 > rowToRemove) - (*iter).mV1--; - - ++iter; - } - } + // Do not remove dangling edges, does not work with current undo mechanism + // Do not automatically adjust indices, what would be done with dangling edges? points.erase(points.begin()+rowToRemove); - pathgrid.mData.mS2 -= 1; // decrement the number of points + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -84,14 +54,8 @@ namespace CSMWorld const NestedTableWrapperBase& nestedTable) const { Pathgrid pathgrid = record.get(); - - pathgrid.mPoints = - static_cast(nestedTable).mRecord.mPoints; - pathgrid.mData.mS2 = - static_cast(nestedTable).mRecord.mData.mS2; - // also update edges in case points were added/removed - pathgrid.mEdges = - static_cast(nestedTable).mRecord.mEdges; + pathgrid.mPoints = static_cast &>(nestedTable).mNestedTable; + pathgrid.mData.mS2 = pathgrid.mPoints.size(); record.setModified (pathgrid); } @@ -99,7 +63,7 @@ namespace CSMWorld NestedTableWrapperBase* PathgridPointListAdapter::table(const Record& record) const { // deleted by dtor of NestedTableStoring - return new PathgridPointsWrap(record.get()); + return new NestedTableWrapper(record.get().mPoints); } QVariant PathgridPointListAdapter::getData(const Record& record, @@ -147,7 +111,6 @@ namespace CSMWorld PathgridEdgeListAdapter::PathgridEdgeListAdapter () {} - // ToDo: seems to be auto-sorted in the dialog table display after insertion void PathgridEdgeListAdapter::addRow(Record& record, int position) const { Pathgrid pathgrid = record.get(); @@ -218,7 +181,6 @@ namespace CSMWorld } } - // ToDo: detect duplicates in mEdges void PathgridEdgeListAdapter::setData(Record& record, const QVariant& value, int subRowIndex, int subColIndex) const { diff --git a/apps/opencs/model/world/nestedcoladapterimp.hpp b/apps/opencs/model/world/nestedcoladapterimp.hpp index e24bcb134..131f547a5 100644 --- a/apps/opencs/model/world/nestedcoladapterimp.hpp +++ b/apps/opencs/model/world/nestedcoladapterimp.hpp @@ -25,21 +25,6 @@ namespace CSMWorld struct Pathgrid; struct Info; - struct PathgridPointsWrap : public NestedTableWrapperBase - { - ESM::Pathgrid mRecord; - - PathgridPointsWrap(ESM::Pathgrid pathgrid) - : mRecord(pathgrid) {} - - virtual ~PathgridPointsWrap() {} - - virtual int size() const - { - return mRecord.mPoints.size(); // used in IdTree::setNestedTable() - } - }; - class PathgridPointListAdapter : public NestedColumnAdapter { public: diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 691b909bb..dc22fd511 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -17,6 +17,7 @@ #include "../../model/world/cellcoordinates.hpp" #include "mask.hpp" +#include "pathgrid.hpp" #include "terrainstorage.hpp" bool CSVRender::Cell::removeObject (const std::string& id) @@ -68,18 +69,6 @@ bool CSVRender::Cell::addObjects (int start, int end) return modified; } -void CSVRender::Cell::recreatePathgrid() -{ - const CSMWorld::SubCellCollection& pathgrids = mData.getPathgrids(); - int pathgridIndex = pathgrids.searchId(mId); - if (pathgridIndex != -1) - { - mPathgridGeode->removeDrawable(mPathgridGeometry); - mPathgridGeometry = SceneUtil::createPathgridGeometry(pathgrids.getRecord(pathgridIndex).get()); - mPathgridGeode->addDrawable(mPathgridGeometry); - } -} - CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::string& id, bool deleted) : mData (data), mId (Misc::StringUtils::lowerCase (id)), mDeleted (deleted), mSubMode (0), @@ -93,17 +82,6 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st mCellNode = new osg::Group; rootNode->addChild(mCellNode); - osg::ref_ptr pathgridTransform = new osg::PositionAttitudeTransform(); - pathgridTransform->setPosition(osg::Vec3f(mCoordinates.getX() * ESM::Land::REAL_SIZE, - mCoordinates.getY() * ESM::Land::REAL_SIZE, 0)); - pathgridTransform->setNodeMask(Mask_Pathgrid); - mCellNode->addChild(pathgridTransform); - - mPathgridGeode = new osg::Geode(); - pathgridTransform->addChild(mPathgridGeode); - - mPathgridGeometry = 0; - setCellMarker(); if (!mDeleted) @@ -132,7 +110,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st } } - recreatePathgrid(); + mPathgrid.reset(new Pathgrid(mData, mCellNode, mId, mCoordinates)); } } @@ -145,6 +123,11 @@ CSVRender::Cell::~Cell() mCellNode->getParent(0)->removeChild(mCellNode); } +CSVRender::Pathgrid* CSVRender::Cell::getPathgrid() const +{ + return mPathgrid.get(); +} + bool CSVRender::Cell::referenceableDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) { @@ -283,29 +266,14 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int return addObjects (start, end); } -void CSVRender::Cell::pathgridAdded(const CSMWorld::Pathgrid& pathgrid) +void CSVRender::Cell::pathgridModified() { - recreatePathgrid(); + mPathgrid->recreateGeometry(); } void CSVRender::Cell::pathgridRemoved() { - mPathgridGeode->removeDrawable(mPathgridGeometry); -} - -void CSVRender::Cell::pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) -{ - recreatePathgrid(); -} - -void CSVRender::Cell::pathgridRowRemoved(const QModelIndex& parent, int start, int end) -{ - recreatePathgrid(); -} - -void CSVRender::Cell::pathgridRowAdded(const QModelIndex& parent, int start, int end) -{ - recreatePathgrid(); + mPathgrid->removeGeometry(); } void CSVRender::Cell::setSelection (int elementMask, Selection mode) @@ -327,6 +295,27 @@ void CSVRender::Cell::setSelection (int elementMask, Selection mode) iter->second->setSelected (selected); } } + if (elementMask & Mask_Pathgrid) + { + // Only one pathgrid may be selected, so some operations will only have an effect + // if the pathgrid is already focused + switch (mode) + { + case Selection_Clear: + mPathgrid->clearSelected(); + break; + + case Selection_All: + if (mPathgrid->isSelected()) + mPathgrid->selectAll(); + break; + + case Selection_Invert: + if (mPathgrid->isSelected()) + mPathgrid->invertSelected(); + break; + } + } } void CSVRender::Cell::selectAllWithSameParentId (int elementMask) @@ -406,6 +395,9 @@ std::vector > CSVRender::Cell::getSelection (un iter!=mObjects.end(); ++iter) if (iter->second->getSelected()) result.push_back (iter->second->getTag()); + if (elementMask & Mask_Pathgrid) + if (mPathgrid->isSelected()) + result.push_back(mPathgrid->getTag()); return result; } @@ -440,4 +432,6 @@ void CSVRender::Cell::reset (unsigned int elementMask) for (std::map::const_iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) iter->second->reset(); + if (elementMask & Mask_Pathgrid) + mPathgrid->resetIndicators(); } diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index e265fc21c..a5b581d24 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -31,11 +31,11 @@ namespace CSMWorld { class Data; class CellCoordinates; - class Pathgrid; } namespace CSVRender { + class Pathgrid; class TagBase; class Cell @@ -43,14 +43,13 @@ namespace CSVRender CSMWorld::Data& mData; std::string mId; osg::ref_ptr mCellNode; - osg::ref_ptr mPathgridGeode; - osg::ref_ptr mPathgridGeometry; std::map mObjects; std::auto_ptr mTerrain; CSMWorld::CellCoordinates mCoordinates; std::auto_ptr mCellArrows[4]; std::auto_ptr mCellMarker; std::auto_ptr mCellBorder; + std::auto_ptr mPathgrid; bool mDeleted; int mSubMode; unsigned int mSubModeElementMask; @@ -69,8 +68,6 @@ namespace CSVRender /// \return Have any objects been added? bool addObjects (int start, int end); - void recreatePathgrid(); - public: enum Selection @@ -89,6 +86,9 @@ namespace CSVRender ~Cell(); + /// \note Returns the pathgrid representation which will exist as long as the cell exists + Pathgrid* getPathgrid() const; + /// \return Did this call result in a modification of the visual representation of /// this cell? bool referenceableDataChanged (const QModelIndex& topLeft, @@ -110,16 +110,10 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); - void pathgridAdded(const CSMWorld::Pathgrid& pathgrid); + void pathgridModified(); void pathgridRemoved(); - void pathgridDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); - - void pathgridRowRemoved(const QModelIndex& parent, int start, int end); - - void pathgridRowAdded(const QModelIndex& parent, int start, int end); - void setSelection (int elementMask, Selection mode); // Select everything that references the same ID as at least one of the elements diff --git a/apps/opencs/view/render/editmode.cpp b/apps/opencs/view/render/editmode.cpp index be264afc7..5f0852c90 100644 --- a/apps/opencs/view/render/editmode.cpp +++ b/apps/opencs/view/render/editmode.cpp @@ -29,37 +29,37 @@ void CSVRender::EditMode::setEditLock (bool locked) } -void CSVRender::EditMode::primaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondaryEditPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondaryEditPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::primarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::primarySelectPressed (const WorldspaceHitResult& hit) {} -void CSVRender::EditMode::secondarySelectPressed (osg::ref_ptr tag) {} +void CSVRender::EditMode::secondarySelectPressed (const WorldspaceHitResult& hit) {} -bool CSVRender::EditMode::primaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondaryEditStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::primarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::primarySelectStartDrag (const QPoint& pos) { return false; } -bool CSVRender::EditMode::secondarySelectStartDrag (osg::ref_ptr tag) +bool CSVRender::EditMode::secondarySelectStartDrag (const QPoint& pos) { return false; } -void CSVRender::EditMode::drag (int diffX, int diffY, double speedFactor) {} +void CSVRender::EditMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {} -void CSVRender::EditMode::dragCompleted() {} +void CSVRender::EditMode::dragCompleted(const QPoint& pos) {} void CSVRender::EditMode::dragAborted() {} @@ -67,7 +67,7 @@ void CSVRender::EditMode::dragWheel (int diff, double speedFactor) {} void CSVRender::EditMode::dragEnterEvent (QDragEnterEvent *event) {} -void CSVRender::EditMode::dropEvent (QDropEvent* event) {} +void CSVRender::EditMode::dropEvent (QDropEvent *event) {} void CSVRender::EditMode::dragMoveEvent (QDragMoveEvent *event) {} diff --git a/apps/opencs/view/render/editmode.hpp b/apps/opencs/view/render/editmode.hpp index f5cef1be2..f9b7027f9 100644 --- a/apps/opencs/view/render/editmode.hpp +++ b/apps/opencs/view/render/editmode.hpp @@ -8,10 +8,12 @@ class QDragEnterEvent; class QDropEvent; class QDragMoveEvent; +class QPoint; namespace CSVRender { class WorldspaceWidget; + struct WorldspaceHitResult; class TagBase; class EditMode : public CSVWidget::ModeButton @@ -38,42 +40,42 @@ namespace CSVRender virtual void setEditLock (bool locked); /// Default-implementation: Ignored. - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void primaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: Ignored. - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primaryEditStartDrag (osg::ref_ptr tag); + virtual bool primaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + virtual bool secondaryEditStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool primarySelectStartDrag (osg::ref_ptr tag); + virtual bool primarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignore and return false /// /// \return Drag accepted? - virtual bool secondarySelectStartDrag (osg::ref_ptr tag); + virtual bool secondarySelectStartDrag (const QPoint& pos); /// Default-implementation: ignored - virtual void drag (int diffX, int diffY, double speedFactor); + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); /// Default-implementation: ignored - virtual void dragCompleted(); + virtual void dragCompleted(const QPoint& pos); /// Default-implementation: ignored /// @@ -88,7 +90,7 @@ namespace CSVRender virtual void dragEnterEvent (QDragEnterEvent *event); /// Default-implementation: ignored - virtual void dropEvent (QDropEvent* event); + virtual void dropEvent (QDropEvent *event); /// Default-implementation: ignored virtual void dragMoveEvent (QDragMoveEvent *event); diff --git a/apps/opencs/view/render/instancemode.cpp b/apps/opencs/view/render/instancemode.cpp index 4c708242a..df5ba7621 100644 --- a/apps/opencs/view/render/instancemode.cpp +++ b/apps/opencs/view/render/instancemode.cpp @@ -2,6 +2,7 @@ #include "instancemode.hpp" #include +#include #include "../../model/prefs/state.hpp" @@ -103,25 +104,25 @@ void CSVRender::InstanceMode::setEditLock (bool locked) getWorldspaceWidget().abortDrag(); } -void CSVRender::InstanceMode::primaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::primaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - primarySelectPressed (tag); + primarySelectPressed (hit); } -void CSVRender::InstanceMode::secondaryEditPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondaryEditPressed (const WorldspaceHitResult& hit) { if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) - secondarySelectPressed (tag); + secondarySelectPressed (hit); } -void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::primarySelectPressed (const WorldspaceHitResult& hit) { getWorldspaceWidget().clearSelection (Mask_Reference); - if (tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, select it CSVRender::Object* object = objectTag->mObject; @@ -131,11 +132,11 @@ void CSVRender::InstanceMode::primarySelectPressed (osg::ref_ptr tag) } } -void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) +void CSVRender::InstanceMode::secondarySelectPressed (const WorldspaceHitResult& hit) { - if (tag) + if (hit.tag) { - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { // hit an Object, toggle its selection state CSVRender::Object* object = objectTag->mObject; @@ -145,15 +146,16 @@ void CSVRender::InstanceMode::secondarySelectPressed (osg::ref_ptr tag) } } -bool CSVRender::InstanceMode::primaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::InstanceMode::primaryEditStartDrag (const QPoint& pos) { if (mDragMode!=DragMode_None || mLocked) return false; - if (tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.tag && CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) { getWorldspaceWidget().clearSelection (Mask_Reference); - if (CSVRender::ObjectTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectTag *objectTag = dynamic_cast (hit.tag.get())) { CSVRender::Object* object = objectTag->mObject; object->setSelected (true); @@ -177,7 +179,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (osg::ref_ptr tag) // \todo check for sub-mode - if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (tag.get())) + if (CSVRender::ObjectMarkerTag *objectTag = dynamic_cast (hit.tag.get())) { mDragAxis = objectTag->mAxis; } @@ -189,7 +191,7 @@ bool CSVRender::InstanceMode::primaryEditStartDrag (osg::ref_ptr tag) return true; } -bool CSVRender::InstanceMode::secondaryEditStartDrag (osg::ref_ptr tag) +bool CSVRender::InstanceMode::secondaryEditStartDrag (const QPoint& pos) { if (mLocked) return false; @@ -197,7 +199,7 @@ bool CSVRender::InstanceMode::secondaryEditStartDrag (osg::ref_ptr tag) return false; } -void CSVRender::InstanceMode::drag (int diffX, int diffY, double speedFactor) +void CSVRender::InstanceMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { osg::Vec3f eye; osg::Vec3f centre; @@ -244,7 +246,7 @@ void CSVRender::InstanceMode::drag (int diffX, int diffY, double speedFactor) } } -void CSVRender::InstanceMode::dragCompleted() +void CSVRender::InstanceMode::dragCompleted(const QPoint& pos) { std::vector > selection = getWorldspaceWidget().getEdited (Mask_Reference); @@ -333,9 +335,9 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) if (!mime->fromDocument (document)) return; - osg::Vec3f insertPoint = getWorldspaceWidget().getIntersectionPoint (event->pos()); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (event->pos(), getWorldspaceWidget().getInteractionMask()); - std::string cellId = getWorldspaceWidget().getCellId (insertPoint); + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); CSMWorld::IdTree& cellTable = dynamic_cast ( *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); @@ -412,11 +414,11 @@ void CSVRender::InstanceMode::dropEvent (QDropEvent* event) createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_Cell), QString::fromUtf8 (cellId.c_str())); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionXPos), insertPoint.x()); + CSMWorld::Columns::ColumnId_PositionXPos), hit.worldPos.x()); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionYPos), insertPoint.y()); + CSMWorld::Columns::ColumnId_PositionYPos), hit.worldPos.y()); createCommand->addValue (referencesTable.findColumnIndex ( - CSMWorld::Columns::ColumnId_PositionZPos), insertPoint.z()); + CSMWorld::Columns::ColumnId_PositionZPos), hit.worldPos.z()); createCommand->addValue (referencesTable.findColumnIndex ( CSMWorld::Columns::ColumnId_ReferenceableId), QString::fromUtf8 (iter->getId().c_str())); diff --git a/apps/opencs/view/render/instancemode.hpp b/apps/opencs/view/render/instancemode.hpp index db7fdaa96..4f7937b51 100644 --- a/apps/opencs/view/render/instancemode.hpp +++ b/apps/opencs/view/render/instancemode.hpp @@ -41,21 +41,21 @@ namespace CSVRender virtual void setEditLock (bool locked); - virtual void primaryEditPressed (osg::ref_ptr tag); + virtual void primaryEditPressed (const WorldspaceHitResult& hit); - virtual void secondaryEditPressed (osg::ref_ptr tag); + virtual void secondaryEditPressed (const WorldspaceHitResult& hit); - virtual void primarySelectPressed (osg::ref_ptr tag); + virtual void primarySelectPressed (const WorldspaceHitResult& hit); - virtual void secondarySelectPressed (osg::ref_ptr tag); + virtual void secondarySelectPressed (const WorldspaceHitResult& hit); - virtual bool primaryEditStartDrag (osg::ref_ptr tag); + virtual bool primaryEditStartDrag (const QPoint& pos); - virtual bool secondaryEditStartDrag (osg::ref_ptr tag); + virtual bool secondaryEditStartDrag (const QPoint& pos); - virtual void drag (int diffX, int diffY, double speedFactor); + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); - virtual void dragCompleted(); + virtual void dragCompleted(const QPoint& pos); /// \note dragAborted will not be called, if the drag is aborted via changing /// editing mode @@ -65,7 +65,7 @@ namespace CSVRender virtual void dragEnterEvent (QDragEnterEvent *event); - virtual void dropEvent (QDropEvent* event); + virtual void dropEvent (QDropEvent *event); virtual int getSubMode() const; diff --git a/apps/opencs/view/render/instanceselectionmode.cpp b/apps/opencs/view/render/instanceselectionmode.cpp index 5c3aaa8d1..bf8ede0eb 100644 --- a/apps/opencs/view/render/instanceselectionmode.cpp +++ b/apps/opencs/view/render/instanceselectionmode.cpp @@ -1,4 +1,3 @@ - #include "instanceselectionmode.hpp" #include @@ -10,84 +9,49 @@ #include "worldspacewidget.hpp" #include "object.hpp" -bool CSVRender::InstanceSelectionMode::createContextMenu (QMenu *menu) +namespace CSVRender { - if (menu) + InstanceSelectionMode::InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Reference) { - menu->addAction (mSelectAll); - menu->addAction (mDeselectAll); - menu->addAction (mSelectSame); - menu->addAction (mDeleteSelection); - } + mSelectSame = new QAction("Extend selection to instances with same object ID", this); + mDeleteSelection = new QAction("Delete selected instances", this); - return true; -} + connect(mSelectSame, SIGNAL(triggered()), this, SLOT(selectSame())); + connect(mDeleteSelection, SIGNAL(triggered()), this, SLOT(deleteSelection())); + } -CSVRender::InstanceSelectionMode::InstanceSelectionMode (CSVWidget::SceneToolbar *parent, - WorldspaceWidget& worldspaceWidget) -: CSVWidget::SceneToolMode (parent, "Selection Mode"), mWorldspaceWidget (worldspaceWidget) -{ - addButton (":placeholder", "cube-centre", - "Centred cube" - "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection cube outwards
  • " - "
  • The selection cube is aligned to the word space axis
  • " - "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " - "
" - "Not implemented yet"); - addButton (":placeholder", "cube-corner", - "Cube corner to corner" - "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite corner
  • " - "
  • The selection cube is aligned to the word space axis
  • " - "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " - "
" - "Not implemented yet"); - addButton (":placeholder", "sphere", - "Centred sphere" - "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection sphere outwards
  • " - "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " - "
" - "Not implemented yet"); + bool InstanceSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); - mSelectAll = new QAction ("Select all instances", this); - mDeselectAll = new QAction ("Clear selection", this); - mDeleteSelection = new QAction ("Delete selected instances", this); - mSelectSame = new QAction ("Extend selection to instances with same object ID", this); - connect (mSelectAll, SIGNAL (triggered ()), this, SLOT (selectAll())); - connect (mDeselectAll, SIGNAL (triggered ()), this, SLOT (clearSelection())); - connect (mDeleteSelection, SIGNAL (triggered ()), this, SLOT (deleteSelection())); - connect (mSelectSame, SIGNAL (triggered ()), this, SLOT (selectSame())); -} + menu->addAction(mSelectSame); + menu->addAction(mDeleteSelection); + } -void CSVRender::InstanceSelectionMode::selectAll() -{ - mWorldspaceWidget.selectAll (Mask_Reference); -} + return true; + } -void CSVRender::InstanceSelectionMode::clearSelection() -{ - mWorldspaceWidget.clearSelection (Mask_Reference); -} + void InstanceSelectionMode::selectSame() + { + getWorldspaceWidget().selectAllWithSameParentId(Mask_Reference); + } -void CSVRender::InstanceSelectionMode::deleteSelection() -{ - std::vector > selection = - mWorldspaceWidget.getSelection (Mask_Reference); + void InstanceSelectionMode::deleteSelection() + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Reference); - CSMWorld::IdTable& referencesTable = - dynamic_cast (*mWorldspaceWidget.getDocument().getData(). - getTableModel (CSMWorld::UniversalId::Type_References)); + CSMWorld::IdTable& referencesTable = dynamic_cast( + *getWorldspaceWidget().getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_References)); - for (std::vector >::iterator iter (selection.begin()); - iter!=selection.end(); ++iter) - { - CSMWorld::DeleteCommand *command = new CSMWorld::DeleteCommand (referencesTable, - static_cast (iter->get())->mObject->getReferenceId()); + for (std::vector >::iterator iter = selection.begin(); iter != selection.end(); ++iter) + { + CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable, + static_cast(iter->get())->mObject->getReferenceId()); - mWorldspaceWidget.getDocument().getUndoStack().push (command); + getWorldspaceWidget().getDocument().getUndoStack().push(command); + } } } - -void CSVRender::InstanceSelectionMode::selectSame() -{ - mWorldspaceWidget.selectAllWithSameParentId (Mask_Reference); -} diff --git a/apps/opencs/view/render/instanceselectionmode.hpp b/apps/opencs/view/render/instanceselectionmode.hpp index cac2ca8c2..a590240d9 100644 --- a/apps/opencs/view/render/instanceselectionmode.hpp +++ b/apps/opencs/view/render/instanceselectionmode.hpp @@ -1,23 +1,19 @@ #ifndef CSV_RENDER_INSTANCE_SELECTION_MODE_H #define CSV_RENDER_INSTANCE_SELECTION_MODE_H -#include "../widget/scenetoolmode.hpp" - -class QAction; +#include "selectionmode.hpp" namespace CSVRender { - class WorldspaceWidget; - - class InstanceSelectionMode : public CSVWidget::SceneToolMode + class InstanceSelectionMode : public SelectionMode { Q_OBJECT - WorldspaceWidget& mWorldspaceWidget; - QAction *mSelectAll; - QAction *mDeselectAll; - QAction *mDeleteSelection; - QAction *mSelectSame; + public: + + InstanceSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: /// Add context menu items to \a menu. /// @@ -25,20 +21,16 @@ namespace CSVRender /// /// \return Have there been any menu items to be added (if menu is 0 and there /// items to be added, the function must return true anyway. - virtual bool createContextMenu (QMenu *menu); + bool createContextMenu(QMenu* menu); - public: + private: - InstanceSelectionMode (CSVWidget::SceneToolbar *parent, WorldspaceWidget& worldspaceWidget); + QAction* mDeleteSelection; + QAction* mSelectSame; private slots: - void selectAll(); - - void clearSelection(); - void deleteSelection(); - void selectSame(); }; } diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 1d9b22216..a01df4392 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -142,14 +142,15 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( "terrain-move"); } -void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +void CSVRender::PagedWorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, + bool shift) { - if (tag && tag->getMask()==Mask_CellArrow) + if (hit.tag && hit.tag->getMask()==Mask_CellArrow) { if (button=="p-edit" || button=="s-edit") { if (CellArrowTag *cellArrowTag = - dynamic_cast (tag.get())) + dynamic_cast (hit.tag.get())) { CellArrow *arrow = cellArrowTag->getCellArrow(); @@ -209,7 +210,7 @@ void CSVRender::PagedWorldspaceWidget::handleMouseClick (osg::ref_ptr t } } - WorldspaceWidget::handleMouseClick (tag, button, shift); + WorldspaceWidget::handleMouseClick (hit, button, shift); } void CSVRender::PagedWorldspaceWidget::referenceableDataChanged (const QModelIndex& topLeft, @@ -279,38 +280,29 @@ void CSVRender::PagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& t { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + int rowStart = -1; + int rowEnd = -1; + if (topLeft.parent().isValid()) { - int row = topLeft.parent().row(); - - const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); - - std::map::iterator searchResult = mCells.find(coords); - if (searchResult != mCells.end()) - { - searchResult->second->pathgridDataChanged(topLeft, bottomRight); - flagAsModified(); - } + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); } -} - -void CSVRender::PagedWorldspaceWidget::pathgridRemoved (const QModelIndex& parent, int start, int end) -{ - const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); - - if (parent.isValid()) + else { - // Pathgrid data was modified - int row = parent.row(); + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); + } + for (int row = rowStart; row <= rowEnd; ++row) + { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) { - searchResult->second->pathgridRowRemoved(parent, start, end); + searchResult->second->pathgridModified(); flagAsModified(); } } @@ -340,11 +332,10 @@ void CSVRender::PagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelInd void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, int start, int end) { - const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); if (!parent.isValid()) { - // Pathgrid added theoretically, unable to test until it is possible to add pathgrids for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); @@ -353,26 +344,11 @@ void CSVRender::PagedWorldspaceWidget::pathgridAdded(const QModelIndex& parent, std::map::iterator searchResult = mCells.find(coords); if (searchResult != mCells.end()) { - searchResult->second->pathgridAdded(pathgrid); + searchResult->second->pathgridModified(); flagAsModified(); } } } - else - { - // Pathgrid data was modified - int row = parent.row(); - - const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - CSMWorld::CellCoordinates coords = CSMWorld::CellCoordinates(pathgrid.mData.mX, pathgrid.mData.mY); - - std::map::iterator searchResult = mCells.find(coords); - if (searchResult != mCells.end()) - { - searchResult->second->pathgridRowAdded(parent, start, end); - flagAsModified(); - } - } } @@ -605,6 +581,15 @@ void CSVRender::PagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::PagedWorldspaceWidget::invertSelection (int elementMask) +{ + for (std::map::iterator iter = mCells.begin(); + iter!=mCells.end(); ++iter) + iter->second->setSelection (elementMask, Cell::Selection_Invert); + + flagAsModified(); +} + void CSVRender::PagedWorldspaceWidget::selectAll (int elementMask) { for (std::map::iterator iter = mCells.begin(); @@ -634,6 +619,21 @@ std::string CSVRender::PagedWorldspaceWidget::getCellId (const osg::Vec3f& point return cellCoordinates.getId (mWorldspace); } +CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + const int cellSize = 8192; + + CSMWorld::CellCoordinates coords( + static_cast (std::floor (point.x()/cellSize)), + static_cast (std::floor (point.y()/cellSize))); + + std::map::const_iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second; + else + return 0; +} + std::vector > CSVRender::PagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/pagedworldspacewidget.hpp b/apps/opencs/view/render/pagedworldspacewidget.hpp index 3cd628df0..692000708 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -54,8 +54,6 @@ namespace CSVRender virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - virtual void pathgridRemoved (const QModelIndex& parent, int start, int end); - virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); virtual void pathgridAdded (const QModelIndex& parent, int start, int end); @@ -107,6 +105,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); @@ -118,6 +119,8 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual Cell* getCell(const osg::Vec3d& point) const; + virtual std::vector > getSelection (unsigned int elementMask) const; @@ -135,7 +138,7 @@ namespace CSVRender virtual void addEditModeSelectorButtons (CSVWidget::SceneToolMode *tool); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift); + virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift); signals: diff --git a/apps/opencs/view/render/pathgrid.cpp b/apps/opencs/view/render/pathgrid.cpp new file mode 100644 index 000000000..9eb2765d3 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.cpp @@ -0,0 +1,680 @@ +#include "pathgrid.hpp" + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "../../model/world/cell.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtree.hpp" + +namespace CSVRender +{ + class PathgridNodeCallback : public osg::NodeCallback + { + public: + + virtual void operator()(osg::Node* node, osg::NodeVisitor* nv) + { + PathgridTag* tag = static_cast(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 + { + QString text("Pathgrid: "); + text += mPathgrid->getId().c_str(); + + return text; + } + + Pathgrid::Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates) + : mData(data) + , mPathgridCollection(mData.getPathgrids()) + , mId(pathgridId) + , mCoords(coordinates) + , mInterior(false) + , mDragOrigin(0) + , mChangeGeometry(true) + , mRemoveGeometry(false) + , mUseOffset(true) + , mParent(parent) + , mPathgridGeometry(0) + , mDragGeometry(0) + , 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); + + mPathgridGeode = new osg::Geode(); + mBaseNode->addChild(mPathgridGeode); + + 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; + } + + 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(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(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); + + mPathgridGeode->removeDrawable(mDragGeometry); + mDragGeometry = 0; + } + + void Pathgrid::applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = worldPos - mBaseNode->getPosition(); + + int posX = clampToCell(static_cast(localCoords.x())); + int posY = clampToCell(static_cast(localCoords.y())); + int posZ = clampToCell(static_cast(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(source->mPoints.size()); + + // Add node to end of list + commands.push(new CSMWorld::AddNestedCommand(*model, mId, 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, mId)); + } + else + { + source = &mPathgridCollection.getRecord(index).get(); + + // Deleted, so revert and remove all data + commands.push(new CSMWorld::RevertCommand(*model, mId)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + for (int row = source->mPoints.size() - 1; row >= 0; --row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, 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, mId, row, parentColumn)); + } + } + } + } + + void Pathgrid::applyPosition(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + osg::Vec3d localCoords = mMoveOffset; + + int offsetX = static_cast(localCoords.x()); + int offsetY = static_cast(localCoords.y()); + int offsetZ = static_cast(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 (size_t i = 0; i < mSelected.size(); ++i) + { + const CSMWorld::Pathgrid::Point& point = source->mPoints[mSelected[i]]; + int row = static_cast(mSelected[i]); + + 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 (size_t i = 0; i < mSelected.size(); ++i) + { + addEdge(commands, *source, node, mSelected[i]); + } + } + } + + void Pathgrid::applyRemoveNodes(CSMWorld::CommandMacro& commands) + { + const CSMWorld::Pathgrid* source = getPathgridSource(); + if (source) + { + CSMWorld::IdTree* model = dynamic_cast(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + // Want to remove nodes from end of list first + std::sort(mSelected.begin(), mSelected.end(), std::greater()); + + int recordIndex = mPathgridCollection.getIndex(mId); + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridPoints); + + for (std::vector::iterator row = mSelected.begin(); row != mSelected.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, static_cast(*row), parentColumn)); + } + + // Fix/remove edges + std::set > 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 (std::vector::iterator point = mSelected.begin(); point != mSelected.end(); ++point) + { + if (source->mEdges[edge].mV0 == *point || source->mEdges[edge].mV1 == *point) + { + edgeRowsToRemove.insert(static_cast(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)); + } + } + + std::set >::iterator row; + for (row = edgeRowsToRemove.begin(); row != edgeRowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *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 > 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(mData.getTableModel( + CSMWorld::UniversalId::Type_Pathgrids)); + + int parentColumn = mPathgridCollection.findColumnIndex(CSMWorld::Columns::ColumnId_PathgridEdges); + + std::set >::iterator row; + for (row = rowsToRemove.begin(); row != rowsToRemove.end(); ++row) + { + commands.push(new CSMWorld::DeleteNestedCommand(*model, mId, *row, parentColumn)); + } + } + } + + osg::ref_ptr 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); + mPathgridGeode->addDrawable(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); + mPathgridGeode->addDrawable(mSelectedGeometry); + } + + void Pathgrid::removePathgridGeometry() + { + if (mPathgridGeometry) + { + mPathgridGeode->removeDrawable(mPathgridGeometry); + mPathgridGeometry = 0; + } + } + + void Pathgrid::removeSelectedGeometry() + { + if (mSelectedGeometry) + { + mPathgridGeode->removeDrawable(mSelectedGeometry); + mSelectedGeometry = 0; + } + } + + void Pathgrid::createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid) + { + if (mDragGeometry) + mPathgridGeode->removeDrawable(mDragGeometry); + + mDragGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(2); + osg::ref_ptr colors = new osg::Vec4Array(1); + osg::ref_ptr 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); + + mPathgridGeode->addDrawable(mDragGeometry); + } + + const CSMWorld::Pathgrid* Pathgrid::getPathgridSource() + { + int index = mPathgridCollection.searchId(mId); + if (index != -1 && !mPathgridCollection.getRecord(index).isDeleted()) + { + return &mPathgridCollection.getRecord(index).get(); + } + + return 0; + } + + 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(i); + } + + return -1; + } + + void Pathgrid::addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2) + { + CSMWorld::IdTree* model = dynamic_cast(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(source.mEdges.size()); + + if (edgeExists(source, node1, node2) == -1) + { + commands.push(new CSMWorld::AddNestedCommand(*model, mId, 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, 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; + } +} diff --git a/apps/opencs/view/render/pathgrid.hpp b/apps/opencs/view/render/pathgrid.hpp new file mode 100644 index 000000000..181a62b44 --- /dev/null +++ b/apps/opencs/view/render/pathgrid.hpp @@ -0,0 +1,137 @@ +#ifndef CSV_RENDER_PATHGRID_H +#define CSV_RENDER_PATHGRID_H + +#include + +#include +#include +#include + +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/idcollection.hpp" +#include "../../model/world/subcellcollection.hpp" + +#include "tagbase.hpp" + +namespace osg +{ + class Geode; + class Geometry; + class Group; + class PositionAttitudeTransform; +} + +namespace CSMWorld +{ + class CommandMacro; + class Data; + struct Pathgrid; +} + +namespace CSVRender +{ + class Pathgrid; + + class PathgridTag : public TagBase + { + public: + + PathgridTag (Pathgrid* pathgrid); + + Pathgrid* getPathgrid () const; + + virtual QString getToolTip (bool hideBasics) const; + + private: + + Pathgrid* mPathgrid; + }; + + class Pathgrid + { + public: + + typedef std::vector NodeList; + + Pathgrid(CSMWorld::Data& data, osg::Group* parent, const std::string& pathgridId, + const CSMWorld::CellCoordinates& coordinates); + + ~Pathgrid(); + + const CSMWorld::CellCoordinates& getCoordinates() const; + const std::string& getId() const; + + bool isSelected() const; + const NodeList& getSelected() const; + void selectAll(); + void toggleSelected(unsigned short node); // Adds to end of vector + void invertSelected(); + void clearSelected(); + + void moveSelected(const osg::Vec3d& offset); + void setDragOrigin(unsigned short node); + void setDragEndpoint(unsigned short node); + void setDragEndpoint(const osg::Vec3d& pos); + + void resetIndicators(); + + void applyPoint(CSMWorld::CommandMacro& commands, const osg::Vec3d& worldPos); + void applyPosition(CSMWorld::CommandMacro& commands); + void applyEdge(CSMWorld::CommandMacro& commands, unsigned short node1, unsigned short node2); + void applyEdges(CSMWorld::CommandMacro& commands, unsigned short node); + void applyRemoveNodes(CSMWorld::CommandMacro& commands); + void applyRemoveEdges(CSMWorld::CommandMacro& commands); + + osg::ref_ptr getTag() const; + + void recreateGeometry(); + void removeGeometry(); + + void update(); + + private: + + CSMWorld::Data& mData; + CSMWorld::SubCellCollection& mPathgridCollection; + std::string mId; + CSMWorld::CellCoordinates mCoords; + bool mInterior; + + NodeList mSelected; + osg::Vec3d mMoveOffset; + unsigned short mDragOrigin; + + bool mChangeGeometry; + bool mRemoveGeometry; + bool mUseOffset; + + osg::Group* mParent; + osg::ref_ptr mBaseNode; + osg::ref_ptr mPathgridGeode; + osg::ref_ptr mPathgridGeometry; + osg::ref_ptr mSelectedGeometry; + osg::ref_ptr mDragGeometry; + + osg::ref_ptr mTag; + + void createGeometry(); + void createSelectedGeometry(); + void createSelectedGeometry(const CSMWorld::Pathgrid& source); + void removePathgridGeometry(); + void removeSelectedGeometry(); + + void createDragGeometry(const osg::Vec3f& start, const osg::Vec3f& end, bool valid); + + const CSMWorld::Pathgrid* getPathgridSource(); + + int edgeExists(const CSMWorld::Pathgrid& source, unsigned short node1, unsigned short node2); + void addEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + void removeEdge(CSMWorld::CommandMacro& commands, const CSMWorld::Pathgrid& source, unsigned short node1, + unsigned short node2); + + int clampToCell(int v); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridmode.cpp b/apps/opencs/view/render/pathgridmode.cpp new file mode 100644 index 000000000..87ec80556 --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.cpp @@ -0,0 +1,265 @@ +#include "pathgridmode.hpp" + +#include +#include + +#include + +#include "../../model/prefs/state.hpp" + +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" + +#include "../widget/scenetoolbar.hpp" + +#include "cell.hpp" +#include "mask.hpp" +#include "pathgrid.hpp" +#include "pathgridselectionmode.hpp" +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + PathgridMode::PathgridMode(WorldspaceWidget* worldspaceWidget, QWidget* parent) + : EditMode(worldspaceWidget, QIcon(":placeholder"), Mask_Pathgrid | Mask_Terrain | Mask_Reference, + getTooltip(), parent) + , mDragMode(DragMode_None) + , mFromNode(0) + , mSelectionMode(0) + { + } + + QString PathgridMode::getTooltip() + { + return QString( + "Pathgrid editing" + "
  • Primary edit: Add node to scene
  • " + "
  • Secondary edit: Connect selected nodes to node
  • " + "
  • Primary drag: Move selected nodes
  • " + "
  • Secondary drag: Connect one node to another
  • " + "

Note: Only a single cell's pathgrid may be edited at a time"); + } + + void PathgridMode::activate(CSVWidget::SceneToolbar* toolbar) + { + if (!mSelectionMode) + { + mSelectionMode = new PathgridSelectionMode(toolbar, getWorldspaceWidget()); + } + + EditMode::activate(toolbar); + toolbar->addTool(mSelectionMode); + } + + void PathgridMode::primaryEditPressed(const WorldspaceHitResult& hitResult) + { + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue() && + dynamic_cast(hitResult.tag.get())) + { + primarySelectPressed(hitResult); + } + else if (Cell* cell = getWorldspaceWidget().getCell (hitResult.worldPos)) + { + // Add node + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add node"; + + CSMWorld::CommandMacro macro(undoStack, description); + cell->getPathgrid()->applyPoint(macro, hitResult.worldPos); + } + } + + void PathgridMode::secondaryEditPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->isSelected()) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Connect node to selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdges(macro, node); + } + } + } + } + + void PathgridMode::primarySelectPressed(const WorldspaceHitResult& hit) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mLastId = tag->getPathgrid()->getId(); + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + } + } + } + + void PathgridMode::secondarySelectPressed(const WorldspaceHitResult& hit) + { + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() != mLastId) + { + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + mLastId = tag->getPathgrid()->getId(); + } + + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + tag->getPathgrid()->toggleSelected(node); + + return; + } + } + + getWorldspaceWidget().clearSelection(Mask_Pathgrid); + } + + bool PathgridMode::primaryEditStartDrag(const QPoint& pos) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + if (CSMPrefs::get()["3D Scene Input"]["context-select"].isTrue()) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + if (dynamic_cast(hit.tag.get())) + { + primarySelectPressed(hit); + selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + } + } + + if (!selection.empty()) + { + mDragMode = DragMode_Move; + return true; + } + + return false; + } + + bool PathgridMode::secondaryEditStartDrag(const QPoint& pos) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + mDragMode = DragMode_Edge; + mEdgeId = tag->getPathgrid()->getId(); + mFromNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + tag->getPathgrid()->setDragOrigin(mFromNode); + return true; + } + } + + return false; + } + + void PathgridMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection(Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + osg::Vec3d eye, center, up, offset; + getWorldspaceWidget().getCamera()->getViewMatrix().getLookAt (eye, center, up); + + offset = (up * diffY * speedFactor) + (((center - eye) ^ up) * diffX * speedFactor); + + tag->getPathgrid()->moveSelected(offset); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + Cell* cell = getWorldspaceWidget().getCell(hit.worldPos); + if (cell) + { + PathgridTag* tag = 0; + if (hit.tag && (tag = dynamic_cast(hit.tag.get())) && tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short node = SceneUtil::getPathgridNode(static_cast(hit.index0)); + cell->getPathgrid()->setDragEndpoint(node); + } + else + { + cell->getPathgrid()->setDragEndpoint(hit.worldPos); + } + + } + } + } + + void PathgridMode::dragCompleted(const QPoint& pos) + { + if (mDragMode == DragMode_Move) + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Move pathgrid node(s)"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyPosition(macro); + } + } + } + else if (mDragMode == DragMode_Edge) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); + + if (hit.tag) + { + if (PathgridTag* tag = dynamic_cast(hit.tag.get())) + { + if (tag->getPathgrid()->getId() == mEdgeId) + { + unsigned short toNode = SceneUtil::getPathgridNode(static_cast(hit.index0)); + + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Add edge between nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyEdge(macro, mFromNode, toNode); + } + } + } + + mEdgeId.clear(); + mFromNode = 0; + } + + mDragMode = DragMode_None; + getWorldspaceWidget().reset(Mask_Pathgrid); + } + + void PathgridMode::dragAborted() + { + getWorldspaceWidget().reset(Mask_Pathgrid); + } +} diff --git a/apps/opencs/view/render/pathgridmode.hpp b/apps/opencs/view/render/pathgridmode.hpp new file mode 100644 index 000000000..908eefa5b --- /dev/null +++ b/apps/opencs/view/render/pathgridmode.hpp @@ -0,0 +1,61 @@ +#ifndef CSV_RENDER_PATHGRIDMODE_H +#define CSV_RENDER_PATHGRIDMODE_H + +#include + +#include "editmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode; + + class PathgridMode : public EditMode + { + Q_OBJECT + + public: + + PathgridMode(WorldspaceWidget* worldspace, QWidget* parent=0); + + virtual void activate(CSVWidget::SceneToolbar* toolbar); + + virtual void primaryEditPressed(const WorldspaceHitResult& hit); + + virtual void secondaryEditPressed(const WorldspaceHitResult& hit); + + virtual void primarySelectPressed(const WorldspaceHitResult& hit); + + virtual void secondarySelectPressed(const WorldspaceHitResult& hit); + + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + virtual void dragCompleted(const QPoint& pos); + + /// \note dragAborted will not be called, if the drag is aborted via changing + /// editing mode + virtual void dragAborted(); + + private: + + enum DragMode + { + DragMode_None, + DragMode_Move, + DragMode_Edge + }; + + DragMode mDragMode; + std::string mLastId, mEdgeId; + unsigned short mFromNode; + + PathgridSelectionMode* mSelectionMode; + + QString getTooltip(); + }; +} + +#endif diff --git a/apps/opencs/view/render/pathgridselectionmode.cpp b/apps/opencs/view/render/pathgridselectionmode.cpp new file mode 100644 index 000000000..db41faf50 --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.cpp @@ -0,0 +1,71 @@ +#include "pathgridselectionmode.hpp" + +#include +#include + +#include "../../model/world/idtable.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/commandmacro.hpp" + +#include "worldspacewidget.hpp" +#include "pathgrid.hpp" + +namespace CSVRender +{ + PathgridSelectionMode::PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget) + : SelectionMode(parent, worldspaceWidget, Mask_Pathgrid) + { + mRemoveSelectedNodes = new QAction("Remove selected nodes", this); + mRemoveSelectedEdges = new QAction("Remove edges between selected nodes", this); + + connect(mRemoveSelectedNodes, SIGNAL(triggered()), this, SLOT(removeSelectedNodes())); + connect(mRemoveSelectedEdges, SIGNAL(triggered()), this, SLOT(removeSelectedEdges())); + } + + bool PathgridSelectionMode::createContextMenu(QMenu* menu) + { + if (menu) + { + SelectionMode::createContextMenu(menu); + + menu->addAction(mRemoveSelectedNodes); + menu->addAction(mRemoveSelectedEdges); + } + + return true; + } + + void PathgridSelectionMode::removeSelectedNodes() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveNodes(macro); + } + } + } + + void PathgridSelectionMode::removeSelectedEdges() + { + std::vector > selection = getWorldspaceWidget().getSelection (Mask_Pathgrid); + + for (std::vector >::iterator it = selection.begin(); it != selection.end(); ++it) + { + if (PathgridTag* tag = dynamic_cast(it->get())) + { + QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack(); + QString description = "Remove edges between selected nodes"; + + CSMWorld::CommandMacro macro(undoStack, description); + tag->getPathgrid()->applyRemoveEdges(macro); + } + } + } +} diff --git a/apps/opencs/view/render/pathgridselectionmode.hpp b/apps/opencs/view/render/pathgridselectionmode.hpp new file mode 100644 index 000000000..e4cb1e044 --- /dev/null +++ b/apps/opencs/view/render/pathgridselectionmode.hpp @@ -0,0 +1,38 @@ +#ifndef CSV_RENDER_PATHGRID_SELECTION_MODE_H +#define CSV_RENDER_PATHGRID_SELECTION_MODE_H + +#include "selectionmode.hpp" + +namespace CSVRender +{ + class PathgridSelectionMode : public SelectionMode + { + Q_OBJECT + + public: + + PathgridSelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget); + + protected: + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + bool createContextMenu(QMenu* menu); + + private: + + QAction* mRemoveSelectedNodes; + QAction* mRemoveSelectedEdges; + + private slots: + + void removeSelectedNodes(); + void removeSelectedEdges(); + }; +} + +#endif diff --git a/apps/opencs/view/render/selectionmode.cpp b/apps/opencs/view/render/selectionmode.cpp new file mode 100644 index 000000000..82a3c49e4 --- /dev/null +++ b/apps/opencs/view/render/selectionmode.cpp @@ -0,0 +1,77 @@ +#include "selectionmode.hpp" + +#include +#include + +#include "worldspacewidget.hpp" + +namespace CSVRender +{ + SelectionMode::SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask) + : SceneToolMode(parent, "Selection mode") + , mWorldspaceWidget(worldspaceWidget) + , mInteractionMask(interactionMask) + { + addButton(":placeholder", "cube-centre", + "Centred cube" + "

  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection cube outwards
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton(":placeholder", "cube-corner", + "Cube corner to corner" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from one corner of the selection cube to the opposite corner
  • " + "
  • The selection cube is aligned to the word space axis
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + addButton(":placeholder", "sphere", + "Centred sphere" + "
  • Drag with primary (make instances the selection) or secondary (invert selection state) select button from the centre of the selection sphere outwards
  • " + "
  • If context selection mode is enabled, a drag with primary/secondary edit not starting on an instance will have the same effect
  • " + "
" + "Not implemented yet"); + + mSelectAll = new QAction("Select all", this); + mDeselectAll = new QAction("Clear selection", this); + mInvertSelection = new QAction("Invert selection", this); + + connect(mSelectAll, SIGNAL(triggered()), this, SLOT(selectAll())); + connect(mDeselectAll, SIGNAL(triggered()), this, SLOT(clearSelection())); + connect(mInvertSelection, SIGNAL(triggered()), this, SLOT(invertSelection())); + } + + WorldspaceWidget& SelectionMode::getWorldspaceWidget() + { + return mWorldspaceWidget; + } + + bool SelectionMode::createContextMenu (QMenu* menu) + { + if (menu) + { + menu->addAction(mSelectAll); + menu->addAction(mDeselectAll); + menu->addAction(mInvertSelection); + } + + return true; + } + + void SelectionMode::selectAll() + { + getWorldspaceWidget().selectAll(mInteractionMask); + } + + void SelectionMode::clearSelection() + { + getWorldspaceWidget().clearSelection(mInteractionMask); + } + + void SelectionMode::invertSelection() + { + getWorldspaceWidget().invertSelection(mInteractionMask); + } +} diff --git a/apps/opencs/view/render/selectionmode.hpp b/apps/opencs/view/render/selectionmode.hpp new file mode 100644 index 000000000..f28888bfd --- /dev/null +++ b/apps/opencs/view/render/selectionmode.hpp @@ -0,0 +1,51 @@ +#ifndef CSV_RENDER_SELECTION_MODE_H +#define CSV_RENDER_SELECTION_MODE_H + +#include "../widget/scenetoolmode.hpp" + +#include "mask.hpp" + +class QAction; + +namespace CSVRender +{ + class WorldspaceWidget; + + class SelectionMode : public CSVWidget::SceneToolMode + { + Q_OBJECT + + public: + + SelectionMode(CSVWidget::SceneToolbar* parent, WorldspaceWidget& worldspaceWidget, + unsigned int interactionMask); + + protected: + + WorldspaceWidget& getWorldspaceWidget(); + + /// Add context menu items to \a menu. + /// + /// \attention menu can be a 0-pointer + /// + /// \return Have there been any menu items to be added (if menu is 0 and there + /// items to be added, the function must return true anyway. + virtual bool createContextMenu (QMenu* menu); + + private: + + WorldspaceWidget& mWorldspaceWidget; + unsigned int mInteractionMask; + QAction* mSelectAll; + QAction* mDeselectAll; + QAction* mInvertSelection; + + protected slots: + + virtual void selectAll(); + virtual void clearSelection(); + virtual void invertSelection(); + }; +} + +#endif diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index 84b7a0ca8..d9a85fe35 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -104,6 +104,12 @@ void CSVRender::UnpagedWorldspaceWidget::clearSelection (int elementMask) flagAsModified(); } +void CSVRender::UnpagedWorldspaceWidget::invertSelection (int elementMask) +{ + mCell->setSelection (elementMask, Cell::Selection_Invert); + flagAsModified(); +} + void CSVRender::UnpagedWorldspaceWidget::selectAll (int elementMask) { mCell->setSelection (elementMask, Cell::Selection_All); @@ -121,6 +127,11 @@ std::string CSVRender::UnpagedWorldspaceWidget::getCellId (const osg::Vec3f& poi return mCellId; } +CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& point) const +{ + return mCell.get(); +} + std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { @@ -201,32 +212,28 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridDataChanged (const QModelIndex& { const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); + int rowStart = -1; + int rowEnd = -1; + if (topLeft.parent().isValid()) { - int row = topLeft.parent().row(); - - const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - if (mCellId == pathgrid.mId) - { - mCell->pathgridDataChanged(topLeft, bottomRight); - flagAsModified(); - } + rowStart = topLeft.parent().row(); + rowEnd = bottomRight.parent().row(); + } + else + { + rowStart = topLeft.row(); + rowEnd = bottomRight.row(); } -} - -void CSVRender::UnpagedWorldspaceWidget::pathgridRemoved (const QModelIndex& parent, int start, int end) -{ - const CSMWorld::SubCellCollection& pathgrids = mDocument.getData().getPathgrids(); - - if (parent.isValid()){ - // Pathgrid data was modified - int row = parent.row(); + for (int row = rowStart; row <= rowEnd; ++row) + { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); if (mCellId == pathgrid.mId) { - mCell->pathgridRowRemoved(parent, start, end); + mCell->pathgridModified(); flagAsModified(); + return; } } } @@ -245,6 +252,7 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAboutToBeRemoved (const QModelI { mCell->pathgridRemoved(); flagAsModified(); + return; } } } @@ -256,29 +264,17 @@ void CSVRender::UnpagedWorldspaceWidget::pathgridAdded (const QModelIndex& paren if (!parent.isValid()) { - // Pathgrid added theoretically, unable to test until it is possible to add pathgrids for (int row = start; row <= end; ++row) { const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); if (mCellId == pathgrid.mId) { - mCell->pathgridAdded(pathgrid); + mCell->pathgridModified(); flagAsModified(); + return; } } } - else - { - // Pathgrid data was modified - int row = parent.row(); - - const CSMWorld::Pathgrid& pathgrid = pathgrids.getRecord(row).get(); - if (mCellId == pathgrid.mId) - { - mCell->pathgridRowAdded(parent, start, end); - flagAsModified(); - } - } } void CSVRender::UnpagedWorldspaceWidget::addVisibilitySelectorButtons ( diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index f06302032..57e8d1a19 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -47,6 +47,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask); + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask); @@ -58,6 +61,8 @@ namespace CSVRender virtual std::string getCellId (const osg::Vec3f& point) const; + virtual Cell* getCell(const osg::Vec3d& point) const; + virtual std::vector > getSelection (unsigned int elementMask) const; @@ -86,8 +91,6 @@ namespace CSVRender virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight); - virtual void pathgridRemoved (const QModelIndex& parent, int start, int end); - virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end); virtual void pathgridAdded (const QModelIndex& parent, int start, int end); diff --git a/apps/opencs/view/render/worldspacewidget.cpp b/apps/opencs/view/render/worldspacewidget.cpp index 1bd3df981..f8c3af2bb 100644 --- a/apps/opencs/view/render/worldspacewidget.cpp +++ b/apps/opencs/view/render/worldspacewidget.cpp @@ -1,7 +1,6 @@ #include "worldspacewidget.hpp" #include -#include #include #include @@ -27,8 +26,8 @@ #include "object.hpp" #include "mask.hpp" -#include "editmode.hpp" #include "instancemode.hpp" +#include "pathgridmode.hpp" #include "cameracontroller.hpp" CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidget* parent) @@ -63,8 +62,6 @@ CSVRender::WorldspaceWidget::WorldspaceWidget (CSMDoc::Document& document, QWidg connect (pathgrids, SIGNAL (dataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (pathgridDataChanged (const QModelIndex&, const QModelIndex&))); - connect (pathgrids, SIGNAL (rowsRemoved (const QModelIndex&, int, int)), - this, SLOT (pathgridRemoved (const QModelIndex&, int, int))); connect (pathgrids, SIGNAL (rowsAboutToBeRemoved (const QModelIndex&, int, int)), this, SLOT (pathgridAboutToBeRemoved (const QModelIndex&, int, int))); connect (pathgrids, SIGNAL (rowsInserted (const QModelIndex&, int, int)), @@ -320,9 +317,7 @@ void CSVRender::WorldspaceWidget::addEditModeSelectorButtons (CSVWidget::SceneTo { /// \todo replace EditMode with suitable subclasses tool->addButton (new InstanceMode (this, tool), "object"); - tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Pathgrid, "Pathgrid editing"), - "pathgrid"); + tool->addButton (new PathgridMode (this, tool), "pathgrid"); } CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() @@ -330,52 +325,80 @@ CSMDoc::Document& CSVRender::WorldspaceWidget::getDocument() return mDocument; } -osg::Vec3f CSVRender::WorldspaceWidget::getIntersectionPoint (const QPoint& localPos, - unsigned int interactionMask, bool ignoreHidden) const +CSVRender::WorldspaceHitResult CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos, + unsigned int interactionMask) const { // (0,0) is considered the lower left corner of an OpenGL window int x = localPos.x(); int y = height() - localPos.y(); - osg::ref_ptr intersector ( - new osgUtil::LineSegmentIntersector (osgUtil::Intersector::WINDOW, x, y)); + // Convert from screen space to world space + osg::Matrixd wpvMat; + wpvMat.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); + wpvMat.preMult (mView->getCamera()->getProjectionMatrix()); + wpvMat.preMult (mView->getCamera()->getViewMatrix()); + wpvMat = osg::Matrixd::inverse (wpvMat); - intersector->setIntersectionLimit (osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor (intersector); + osg::Vec3d start = wpvMat.preMult (osg::Vec3d(x, y, 0)); + osg::Vec3d end = wpvMat.preMult (osg::Vec3d(x, y, 1)); + osg::Vec3d direction = end - start; - unsigned int mask = interactionMask; + // Get intersection + osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector( + osgUtil::Intersector::MODEL, start, end)); - if (ignoreHidden) - mask &= getVisibilityMask(); + intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); + osgUtil::IntersectionVisitor visitor(intersector); - visitor.setTraversalMask (mask); + visitor.setTraversalMask(interactionMask); - mView->getCamera()->accept (visitor); + mView->getCamera()->accept(visitor); - for (osgUtil::LineSegmentIntersector::Intersections::iterator iter = intersector->getIntersections().begin(); - iter!=intersector->getIntersections().end(); ++iter) + // Get relevant data + for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); + it != intersector->getIntersections().end(); ++it) { - // reject back-facing polygons - osg::Vec3f normal = osg::Matrix::transform3x3 ( - iter->getWorldIntersectNormal(), mView->getCamera()->getViewMatrix()); + osgUtil::LineSegmentIntersector::Intersection intersection = *it; - if (normal.z()>=0) - return iter->getWorldIntersectPoint(); - } + // reject back-facing polygons + if (direction * intersection.getWorldIntersectNormal() > 0) + { + continue; + } - osg::Matrixd matrix; - matrix.preMult (mView->getCamera()->getViewport()->computeWindowMatrix()); - matrix.preMult (mView->getCamera()->getProjectionMatrix()); - matrix.preMult (mView->getCamera()->getViewMatrix()); - matrix = osg::Matrixd::inverse (matrix); + for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) + { + osg::Node* node = *it; + if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) + { + WorldspaceHitResult hit = { true, tag, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; + } + } - osg::Vec3d start = matrix.preMult (intersector->getStart()); - osg::Vec3d end = matrix.preMult (intersector->getEnd()); + // Something untagged, probably terrain + WorldspaceHitResult hit = { true, 0, 0, 0, 0, intersection.getWorldIntersectPoint() }; + if (intersection.indexList.size() >= 3) + { + hit.index0 = intersection.indexList[0]; + hit.index1 = intersection.indexList[1]; + hit.index2 = intersection.indexList[2]; + } + return hit; + } - osg::Vec3d direction = end-start; + // Default placement direction.normalize(); + direction *= CSMPrefs::get()["Scene Drops"]["distance"].toInt(); - return start + direction * CSMPrefs::get()["Scene Drops"]["distance"].toInt(); + WorldspaceHitResult hit = { false, 0, 0, 0, 0, start + direction }; + return hit; } void CSVRender::WorldspaceWidget::abortDrag() @@ -469,48 +492,6 @@ bool CSVRender::WorldspaceWidget::storeMappingSetting (const CSMPrefs::Setting * return SceneWidget::storeMappingSetting(setting); } -osg::ref_ptr CSVRender::WorldspaceWidget::mousePick (const QPoint& localPos) -{ - // (0,0) is considered the lower left corner of an OpenGL window - int x = localPos.x(); - int y = height() - localPos.y(); - - osg::ref_ptr intersector (new osgUtil::LineSegmentIntersector(osgUtil::Intersector::WINDOW, x, y)); - - intersector->setIntersectionLimit(osgUtil::LineSegmentIntersector::NO_LIMIT); - osgUtil::IntersectionVisitor visitor(intersector); - - visitor.setTraversalMask(getInteractionMask()); - - mView->getCamera()->accept(visitor); - - for (osgUtil::LineSegmentIntersector::Intersections::iterator it = intersector->getIntersections().begin(); - it != intersector->getIntersections().end(); ++it) - { - osgUtil::LineSegmentIntersector::Intersection intersection = *it; - - // reject back-facing polygons - osg::Vec3f normal = intersection.getWorldIntersectNormal(); - normal = osg::Matrix::transform3x3(normal, mView->getCamera()->getViewMatrix()); - if (normal.z() < 0) - continue; - - for (std::vector::iterator it = intersection.nodePath.begin(); it != intersection.nodePath.end(); ++it) - { - osg::Node* node = *it; - if (osg::ref_ptr tag = dynamic_cast(node->getUserData())) - return tag; - } - -// ignoring terrain for now - // must be terrain, report coordinates -// std::cout << "Terrain hit at " << intersection.getWorldIntersectPoint().x() << " " << intersection.getWorldIntersectPoint().y() << std::endl; -// return; - } - - return osg::ref_ptr(); -} - void CSVRender::WorldspaceWidget::dropEvent (QDropEvent* event) { const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); @@ -595,10 +576,11 @@ void CSVRender::WorldspaceWidget::showToolTip() { QPoint pos = QCursor::pos(); - if (osg::ref_ptr tag = mousePick (mapFromGlobal (pos))) + WorldspaceHitResult hit = mousePick (mapFromGlobal (pos), getInteractionMask()); + if (hit.tag) { bool hideBasics = CSMPrefs::get()["Tooltips"]["scene-hide-basic"].isTrue(); - QToolTip::showText (pos, tag->getToolTip (hideBasics), this); + QToolTip::showText (pos, hit.tag->getToolTip (hideBasics), this); } } } @@ -631,22 +613,20 @@ void CSVRender::WorldspaceWidget::mouseMoveEvent (QMouseEvent *event) EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.drag (diffX, diffY, factor); + editMode.drag (event->pos(), diffX, diffY, factor); } else if (mDragMode=="p-edit" || mDragMode=="s-edit" || mDragMode=="p-select" || mDragMode=="s-select") { - osg::ref_ptr tag = mousePick (event->pos()); - EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (mDragMode=="p-edit") - mDragging = editMode.primaryEditStartDrag (tag); + mDragging = editMode.primaryEditStartDrag (event->pos()); else if (mDragMode=="s-edit") - mDragging = editMode.secondaryEditStartDrag (tag); + mDragging = editMode.secondaryEditStartDrag (event->pos()); else if (mDragMode=="p-select") - mDragging = editMode.primarySelectStartDrag (tag); + mDragging = editMode.primarySelectStartDrag (event->pos()); else if (mDragMode=="s-select") - mDragging = editMode.secondarySelectStartDrag (tag); + mDragging = editMode.secondarySelectStartDrag (event->pos()); if (mDragging) { @@ -699,14 +679,14 @@ void CSVRender::WorldspaceWidget::mouseReleaseEvent (QMouseEvent *event) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); - editMode.dragCompleted(); + editMode.dragCompleted(event->pos()); mDragging = false; } else { - osg::ref_ptr tag = mousePick (event->pos()); + WorldspaceHitResult hit = mousePick(event->pos(), getInteractionMask()); - handleMouseClick (tag, button, event->modifiers() & Qt::ShiftModifier); + handleMouseClick (hit, button, event->modifiers() & Qt::ShiftModifier); } } else @@ -740,18 +720,18 @@ void CSVRender::WorldspaceWidget::keyPressEvent (QKeyEvent *event) SceneWidget::keyPressEvent(event); } -void CSVRender::WorldspaceWidget::handleMouseClick (osg::ref_ptr tag, const std::string& button, bool shift) +void CSVRender::WorldspaceWidget::handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift) { EditMode& editMode = dynamic_cast (*mEditMode->getCurrent()); if (button=="p-edit") - editMode.primaryEditPressed (tag); + editMode.primaryEditPressed (hit); else if (button=="s-edit") - editMode.secondaryEditPressed (tag); + editMode.secondaryEditPressed (hit); else if (button=="p-select") - editMode.primarySelectPressed (tag); + editMode.primarySelectPressed (hit); else if (button=="s-select") - editMode.secondarySelectPressed (tag); + editMode.secondarySelectPressed (hit); } CSVRender::EditMode *CSVRender::WorldspaceWidget::getEditMode() diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index d70694d22..92d31eb9e 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -4,6 +4,7 @@ #include #include +#include #include "../../model/doc/document.hpp" #include "../../model/world/tablemimedata.hpp" @@ -32,9 +33,18 @@ namespace CSVWidget namespace CSVRender { class TagBase; + class Cell; class CellArrow; class EditMode; + struct WorldspaceHitResult + { + bool hit; + osg::ref_ptr tag; + unsigned int index0, index1, index2; // indices of mesh vertices + osg::Vec3d worldPos; + }; + class WorldspaceWidget : public SceneWidget { Q_OBJECT @@ -127,6 +137,9 @@ namespace CSVRender /// \param elementMask Elements to be affected by the clear operation virtual void clearSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation + virtual void invertSelection (int elementMask) = 0; + /// \param elementMask Elements to be affected by the select operation virtual void selectAll (int elementMask) = 0; @@ -136,19 +149,17 @@ namespace CSVRender /// \param elementMask Elements to be affected by the select operation virtual void selectAllWithSameParentId (int elementMask) = 0; - /// Return the next intersection point with scene elements matched by + /// Return the next intersection with scene elements matched by /// \a interactionMask based on \a localPos and the camera vector. - /// If there is no such point, instead a point "in front" of \a localPos will be + /// If there is no such intersection, instead a point "in front" of \a localPos will be /// returned. - /// - /// \param ignoreHidden ignore elements specified in interactionMask that are - /// flagged as not visible. - osg::Vec3f getIntersectionPoint (const QPoint& localPos, - unsigned int interactionMask = Mask_Reference | Mask_Terrain, - bool ignoreHidden = false) const; + WorldspaceHitResult mousePick (const QPoint& localPos, unsigned int interactionMask) const; virtual std::string getCellId (const osg::Vec3f& point) const = 0; + /// \note Returns the cell if it exists, otherwise a null pointer + virtual Cell* getCell(const osg::Vec3d& point) const = 0; + virtual std::vector > getSelection (unsigned int elementMask) const = 0; @@ -191,7 +202,7 @@ namespace CSVRender virtual void wheelEvent (QWheelEvent *event); virtual void keyPressEvent (QKeyEvent *event); - virtual void handleMouseClick (osg::ref_ptr tag, const std::string& button, + virtual void handleMouseClick (const WorldspaceHitResult& hit, const std::string& button, bool shift); /// \return Is \a key a button mapping setting? (ignored otherwise) @@ -209,8 +220,6 @@ namespace CSVRender void dragMoveEvent(QDragMoveEvent *event); - osg::ref_ptr mousePick (const QPoint& localPos); - virtual std::string getStartupInstruction() = 0; private slots: @@ -230,8 +239,6 @@ namespace CSVRender virtual void pathgridDataChanged (const QModelIndex& topLeft, const QModelIndex& bottomRight) = 0; - virtual void pathgridRemoved (const QModelIndex& parent, int start, int end) = 0; - virtual void pathgridAboutToBeRemoved (const QModelIndex& parent, int start, int end) = 0; virtual void pathgridAdded (const QModelIndex& parent, int start, int end) = 0; diff --git a/apps/opencs/view/world/nestedtable.cpp b/apps/opencs/view/world/nestedtable.cpp index 23d566439..0ccb88ce5 100644 --- a/apps/opencs/view/world/nestedtable.cpp +++ b/apps/opencs/view/world/nestedtable.cpp @@ -119,9 +119,14 @@ void CSVWorld::NestedTable::removeRowActionTriggered() void CSVWorld::NestedTable::addNewRowActionTriggered() { + int row = 0; + + if (!selectionModel()->selectedRows().empty()) + row = selectionModel()->selectedRows().back().row() + 1; + mDocument.getUndoStack().push(new CSMWorld::AddNestedCommand(*(mModel->model()), mModel->getParentId(), - selectionModel()->selectedRows().size(), + row, mModel->getParentColumn())); } diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 15accd95b..708685e72 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -127,6 +127,28 @@ namespace ESM void Pathgrid::save(ESMWriter &esm, bool isDeleted) const { + // Correct connection count and sort edges by point + // Can probably be optimized + PointList correctedPoints = mPoints; + std::vector sortedEdges; + + sortedEdges.reserve(mEdges.size()); + + for (size_t point = 0; point < correctedPoints.size(); ++point) + { + correctedPoints[point].mConnectionNum = 0; + + for (EdgeList::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + { + if (static_cast(it->mV0) == point) + { + sortedEdges.push_back(it->mV1); + ++correctedPoints[point].mConnectionNum; + } + } + } + + // Save esm.writeHNCString("NAME", mCell); esm.writeHNT("DATA", mData, 12); @@ -136,22 +158,22 @@ namespace ESM return; } - if (!mPoints.empty()) + if (!correctedPoints.empty()) { esm.startSubRecord("PGRP"); - for (PointList::const_iterator it = mPoints.begin(); it != mPoints.end(); ++it) + for (PointList::const_iterator it = correctedPoints.begin(); it != correctedPoints.end(); ++it) { esm.writeT(*it); } esm.endRecord("PGRP"); } - if (!mEdges.empty()) + if (!sortedEdges.empty()) { esm.startSubRecord("PGRC"); - for (std::vector::const_iterator it = mEdges.begin(); it != mEdges.end(); ++it) + for (std::vector::const_iterator it = sortedEdges.begin(); it != sortedEdges.end(); ++it) { - esm.writeT(it->mV1); + esm.writeT(*it); } esm.endRecord("PGRC"); } diff --git a/components/sceneutil/pathgridutil.cpp b/components/sceneutil/pathgridutil.cpp index 5d71efd1e..1497beb2b 100644 --- a/components/sceneutil/pathgridutil.cpp +++ b/components/sceneutil/pathgridutil.cpp @@ -9,13 +9,13 @@ namespace SceneUtil { const unsigned short DiamondVertexCount = 6; const unsigned short DiamondIndexCount = 24; + const unsigned short DiamondWireframeIndexCount = 24; const unsigned short DiamondConnectorVertexCount = 4; const unsigned short DiamondTotalVertexCount = DiamondVertexCount + DiamondConnectorVertexCount; - const float DiamondHalfHeight = 40.f; - const float DiamondHalfWidth = 16.f; + const float DiamondWireframeScalar = 1.1f; const osg::Vec3f DiamondPoints[DiamondVertexCount] = { @@ -39,6 +39,22 @@ namespace SceneUtil 5, 2, 4 }; + const unsigned short DiamondWireframeIndices[DiamondWireframeIndexCount] = + { + 0, 1, + 0, 2, + 0, 3, + 0, 4, + 1, 2, + 2, 4, + 4, 3, + 3, 1, + 5, 1, + 5, 2, + 5, 3, + 5, 4 + }; + const unsigned short DiamondConnectorVertices[DiamondConnectorVertexCount] = { 1, 2, 3, 4 @@ -55,6 +71,8 @@ namespace SceneUtil }; const osg::Vec4f DiamondEdgeColor = osg::Vec4f(0.5f, 1.f, 1.f, 1.f); + const osg::Vec4f DiamondWireColor = osg::Vec4f(0.72f, 0.f, 0.96f, 1.f); + const osg::Vec4f DiamondFocusWireColor = osg::Vec4f(0.91f, 0.66f, 1.f, 1.f); osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid) { @@ -155,4 +173,61 @@ namespace SceneUtil return gridGeometry; } + + osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, + const std::vector& selected) + { + const unsigned short PointCount = selected.size(); + + const unsigned short VertexCount = PointCount * DiamondVertexCount; + const unsigned short ColorCount = VertexCount; + const size_t IndexCount = PointCount * DiamondWireframeIndexCount; + + osg::ref_ptr wireframeGeometry = new osg::Geometry(); + + osg::ref_ptr vertices = new osg::Vec3Array(VertexCount); + osg::ref_ptr colors = new osg::Vec4Array(ColorCount); + osg::ref_ptr indices = + new osg::DrawElementsUShort(osg::PrimitiveSet::LINES, IndexCount); + + osg::Vec3f wireOffset = osg::Vec3f(0, 0, (1 - DiamondWireframeScalar) * DiamondHalfHeight); + + // Add each point/node + for (unsigned short it = 0; it < PointCount; ++it) + { + const ESM::Pathgrid::Point& point = pathgrid.mPoints[selected[it]]; + osg::Vec3f position = osg::Vec3f(point.mX, point.mY, point.mZ) + wireOffset; + + unsigned short vertexOffset = it * DiamondVertexCount; + unsigned short indexOffset = it * DiamondWireframeIndexCount; + + // Point + for (unsigned short i = 0; i < DiamondVertexCount; ++i) + { + (*vertices)[vertexOffset + i] = position + DiamondPoints[i] * DiamondWireframeScalar; + + if (it == PointCount - 1) + (*colors)[vertexOffset + i] = DiamondFocusWireColor; + else + (*colors)[vertexOffset + i] = DiamondWireColor; + } + + for (unsigned short i = 0; i < DiamondWireframeIndexCount; ++i) + { + indices->setElement(indexOffset + i, vertexOffset + DiamondWireframeIndices[i]); + } + } + + wireframeGeometry->setVertexArray(vertices); + wireframeGeometry->setColorArray(colors, osg::Array::BIND_PER_VERTEX); + wireframeGeometry->addPrimitiveSet(indices); + wireframeGeometry->getOrCreateStateSet()->setMode(GL_LIGHTING, osg::StateAttribute::OFF); + + return wireframeGeometry; + } + + unsigned short getPathgridNode(unsigned short vertexIndex) + { + return vertexIndex / (DiamondVertexCount + DiamondConnectorVertexCount); + } } diff --git a/components/sceneutil/pathgridutil.hpp b/components/sceneutil/pathgridutil.hpp index eb1ab1e3e..de0ff35ed 100644 --- a/components/sceneutil/pathgridutil.hpp +++ b/components/sceneutil/pathgridutil.hpp @@ -11,7 +11,15 @@ namespace ESM namespace SceneUtil { + const float DiamondHalfHeight = 40.f; + const float DiamondHalfWidth = 16.f; + osg::ref_ptr createPathgridGeometry(const ESM::Pathgrid& pathgrid); + + osg::ref_ptr createPathgridSelectedWireframe(const ESM::Pathgrid& pathgrid, + const std::vector& selected); + + unsigned short getPathgridNode(unsigned short vertexIndex); } #endif