diff --git a/CHANGELOG.md b/CHANGELOG.md index 372f9c761..4adb32534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -139,11 +139,13 @@ Bug #5137: Textures with Clamp Mode set to Clamp instead of Wrap are too dark outside the boundaries Bug #5149: Failing lock pick attempts isn't always a crime Bug #5188: Objects without a name don't fallback to their ID + Bug #5161: Creature companions can't be activated when they are knocked down Feature #1774: Handle AvoidNode Feature #2229: Improve pathfinding AI Feature #3025: Analogue gamepad movement controls Feature #3442: Default values for fallbacks from ini file Feature #3610: Option to invert X axis + Feature #3871: Editor: Terrain Selection Feature #3893: Implicit target for "set" function in console Feature #3980: In-game option to disable controller Feature #3999: Shift + Double Click should maximize/restore menu size diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index b0bd95eb9..00855dad0 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -89,7 +89,7 @@ opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode actor + cellwater terraintexturemode actor terrainselection ) opencs_units_noqt (view/render diff --git a/apps/opencs/model/world/cellcoordinates.cpp b/apps/opencs/model/world/cellcoordinates.cpp index 1e545e38d..9f98c7b4c 100644 --- a/apps/opencs/model/world/cellcoordinates.cpp +++ b/apps/opencs/model/world/cellcoordinates.cpp @@ -8,13 +8,6 @@ #include #include -namespace -{ - const int cellSize {ESM::Land::REAL_SIZE}; - const int landSize {ESM::Land::LAND_SIZE}; - const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; -} - CSMWorld::CellCoordinates::CellCoordinates() : mX (0), mY (0) {} CSMWorld::CellCoordinates::CellCoordinates (int x, int y) : mX (x), mY (y) {} @@ -76,10 +69,10 @@ std::pair CSMWorld::CellCoordinates::coordinatesToCellIndex (float x, return std::make_pair (std::floor (x / Constants::CellSizeInUnits), std::floor (y / Constants::CellSizeInUnits)); } -std::pair CSMWorld::CellCoordinates::toTextureCoords(osg::Vec3d worldPos) +std::pair CSMWorld::CellCoordinates::toTextureCoords(const osg::Vec3d& worldPos) { - const auto xd = static_cast(worldPos.x() * landTextureSize / cellSize - 0.25f); - const auto yd = static_cast(worldPos.y() * landTextureSize / cellSize + 0.25f); + const auto xd = static_cast(worldPos.x() * ESM::Land::LAND_TEXTURE_SIZE / ESM::Land::REAL_SIZE - 0.25f); + const auto yd = static_cast(worldPos.y() * ESM::Land::LAND_TEXTURE_SIZE / ESM::Land::REAL_SIZE + 0.25f); const auto x = static_cast(std::floor(xd)); const auto y = static_cast(std::floor(yd)); @@ -87,10 +80,10 @@ std::pair CSMWorld::CellCoordinates::toTextureCoords(osg::Vec3d worldP return std::make_pair(x, y); } -std::pair CSMWorld::CellCoordinates::toVertexCoords(osg::Vec3d worldPos) +std::pair CSMWorld::CellCoordinates::toVertexCoords(const osg::Vec3d& worldPos) { - const auto xd = static_cast(worldPos.x() * (landSize - 1) / cellSize + 0.5f); - const auto yd = static_cast(worldPos.y() * (landSize - 1) / cellSize + 0.5f); + const auto xd = static_cast(worldPos.x() * (ESM::Land::LAND_SIZE - 1) / ESM::Land::REAL_SIZE + 0.5f); + const auto yd = static_cast(worldPos.y() * (ESM::Land::LAND_SIZE - 1) / ESM::Land::REAL_SIZE + 0.5f); const auto x = static_cast(std::floor(xd)); const auto y = static_cast(std::floor(yd)); @@ -98,25 +91,32 @@ std::pair CSMWorld::CellCoordinates::toVertexCoords(osg::Vec3d worldPo return std::make_pair(x, y); } -float CSMWorld::CellCoordinates::textureSelectionToWorldCoords(int pos) +float CSMWorld::CellCoordinates::textureGlobalToWorldCoords(int textureGlobal) { - return cellSize * static_cast(pos) / landTextureSize; + return ESM::Land::REAL_SIZE * static_cast(textureGlobal) / ESM::Land::LAND_TEXTURE_SIZE; } -float CSMWorld::CellCoordinates::vertexSelectionToWorldCoords(int pos) +float CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(int vertexGlobal) { - return cellSize * static_cast(pos) / (landSize - 1); + return ESM::Land::REAL_SIZE * static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1); } -int CSMWorld::CellCoordinates::vertexSelectionToInCellCoords(int pos) +int CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(int vertexGlobal) { - return static_cast(pos - std::floor(static_cast(pos) / (landSize - 1)) * (landSize - 1)); + return static_cast(vertexGlobal - std::floor(static_cast(vertexGlobal) / (ESM::Land::LAND_SIZE - 1)) * (ESM::Land::LAND_SIZE - 1)); +} + +std::string CSMWorld::CellCoordinates::textureGlobalToCellId(const std::pair& textureGlobal) +{ + int x = std::floor(static_cast(textureGlobal.first) / ESM::Land::LAND_TEXTURE_SIZE); + int y = std::floor(static_cast(textureGlobal.second) / ESM::Land::LAND_TEXTURE_SIZE); + return generateId(x, y); } -std::string CSMWorld::CellCoordinates::vertexGlobalToCellId(std::pair vertexGlobal) +std::string CSMWorld::CellCoordinates::vertexGlobalToCellId(const std::pair& vertexGlobal) { - int x = std::floor(static_cast(vertexGlobal.first) / (landSize - 1)); - int y = std::floor(static_cast(vertexGlobal.second) / (landSize - 1)); + int x = std::floor(static_cast(vertexGlobal.first) / (ESM::Land::LAND_SIZE - 1)); + int y = std::floor(static_cast(vertexGlobal.second) / (ESM::Land::LAND_SIZE - 1)); return generateId(x, y); } diff --git a/apps/opencs/model/world/cellcoordinates.hpp b/apps/opencs/model/world/cellcoordinates.hpp index 3b2119517..77d76f6ef 100644 --- a/apps/opencs/model/world/cellcoordinates.hpp +++ b/apps/opencs/model/world/cellcoordinates.hpp @@ -49,22 +49,25 @@ namespace CSMWorld static std::pair coordinatesToCellIndex (float x, float y); ///Converts worldspace coordinates to global texture selection, taking in account the texture offset. - static std::pair toTextureCoords(osg::Vec3d worldPos); + static std::pair toTextureCoords(const osg::Vec3d& worldPos); ///Converts worldspace coordinates to global vertex selection. - static std::pair toVertexCoords(osg::Vec3d worldPos); + static std::pair toVertexCoords(const osg::Vec3d& worldPos); ///Converts global texture coordinate to worldspace coordinate that is at the upper left corner of the selected texture. - static float textureSelectionToWorldCoords(int); + static float textureGlobalToWorldCoords(int textureGlobal); ///Converts global vertex coordinate to worldspace coordinate - static float vertexSelectionToWorldCoords(int); + static float vertexGlobalToWorldCoords(int vertexGlobal); - ///Converts local cell's heightmap coordinates from the global vertex coordinate - static int vertexSelectionToInCellCoords(int); + ///Converts global vertex coordinate to local cell's heightmap coordinates + static int vertexGlobalToInCellCoords(int vertexGlobal); + + ///Converts global texture coordinates to cell id + static std::string textureGlobalToCellId(const std::pair& textureGlobal); ///Converts global vertex coordinates to cell id - static std::string vertexGlobalToCellId(std::pair); + static std::string vertexGlobalToCellId(const std::pair& vertexGlobal); }; bool operator== (const CellCoordinates& left, const CellCoordinates& right); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 7f31373ee..540a15dd1 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -140,7 +140,7 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), "terrain-shape"); tool->addButton ( - new TerrainTextureMode (this, tool), + new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp new file mode 100644 index 000000000..225cfc20b --- /dev/null +++ b/apps/opencs/view/render/terrainselection.cpp @@ -0,0 +1,261 @@ +#include "terrainselection.hpp" + +#include + +#include +#include +#include + +#include + +#include "../../model/world/cellcoordinates.hpp" +#include "../../model/world/columnimp.hpp" +#include "../../model/world/idtable.hpp" + +#include "cell.hpp" +#include "worldspacewidget.hpp" + +CSVRender::TerrainSelection::TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type): +mParentNode(parentNode), mWorldspaceWidget (worldspaceWidget), mDraggedOperationFlag(false), mSelectionType(type) +{ + mGeometry = new osg::Geometry(); + + mSelectionNode = new osg::Group(); + mSelectionNode->addChild(mGeometry); + + activate(); +} + +CSVRender::TerrainSelection::~TerrainSelection() +{ + deactivate(); +} + +std::vector> CSVRender::TerrainSelection::getTerrainSelection() const +{ + return mSelection; +} + +void CSVRender::TerrainSelection::onlySelect(const std::vector> &localPositions) +{ + mSelection = localPositions; + update(); +} + +void CSVRender::TerrainSelection::addSelect(const std::pair &localPos) +{ + if (std::find(mSelection.begin(), mSelection.end(), localPos) == mSelection.end()) + { + mSelection.emplace_back(localPos); + update(); + } +} + +void CSVRender::TerrainSelection::toggleSelect(const std::vector> &localPositions, bool toggleInProgress) +{ + if (toggleInProgress) + { + for(auto const& localPos: localPositions) + { + auto iterTemp = std::find(mTemporarySelection.begin(), mTemporarySelection.end(), localPos); + mDraggedOperationFlag = true; + + if (iterTemp == mTemporarySelection.end()) + { + auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); + if (iter != mSelection.end()) + { + mSelection.erase(iter); + } + else + { + mSelection.emplace_back(localPos); + } + } + + mTemporarySelection.push_back(localPos); + } + } + else if (mDraggedOperationFlag == false) + { + for(auto const& localPos: localPositions) + { + const auto iter = std::find(mSelection.begin(), mSelection.end(), localPos); + if (iter != mSelection.end()) + { + mSelection.erase(iter); + } + else + { + mSelection.emplace_back(localPos); + } + } + } + else + { + mDraggedOperationFlag = false; + mTemporarySelection.clear(); + } + update(); +} + +void CSVRender::TerrainSelection::activate() +{ + if (!mParentNode->containsNode(mSelectionNode)) mParentNode->addChild(mSelectionNode); +} + +void CSVRender::TerrainSelection::deactivate() +{ + mParentNode->removeChild(mSelectionNode); +} + +void CSVRender::TerrainSelection::update() +{ + mSelectionNode->removeChild(mGeometry); + mGeometry = new osg::Geometry(); + + const osg::ref_ptr vertices (new osg::Vec3Array); + + switch (mSelectionType) + { + case TerrainSelectionType::Texture : drawTextureSelection(vertices); + break; + case TerrainSelectionType::Shape : drawShapeSelection(vertices); + break; + } + + mGeometry->setVertexArray(vertices); + osg::ref_ptr drawArrays = new osg::DrawArrays(osg::PrimitiveSet::LINES); + drawArrays->setCount(vertices->size()); + if (vertices->size() != 0) mGeometry->addPrimitiveSet(drawArrays); + mSelectionNode->addChild(mGeometry); +} + +void CSVRender::TerrainSelection::drawShapeSelection(const osg::ref_ptr vertices) +{ + if (!mSelection.empty()) + { + for (std::pair &localPos : mSelection) + { + int x (localPos.first); + int y (localPos.second); + + float xWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x)); + float yWorldCoord(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y)); + + osg::Vec3f pointXY(xWorldCoord, yWorldCoord, calculateLandHeight(x, y) + 2); + + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y - 1), calculateLandHeight(x, y - 1) + 2)); + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x - 1), yWorldCoord, calculateLandHeight(x - 1, y) + 2)); + + const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); + if (north == mSelection.end()) + { + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(xWorldCoord, CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(y + 1), calculateLandHeight(x, y + 1) + 2)); + } + + const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); + if (east == mSelection.end()) + { + vertices->push_back(pointXY); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::vertexGlobalToWorldCoords(x + 1), yWorldCoord, calculateLandHeight(x + 1, y) + 2)); + } + } + } +} + +void CSVRender::TerrainSelection::drawTextureSelection(const osg::ref_ptr vertices) +{ + if (!mSelection.empty()) + { + // Nudge selection by 1/4th of a texture size, similar how blendmaps are nudged + const float nudgePercentage = 0.25f; + const int nudgeOffset = (ESM::Land::REAL_SIZE / ESM::Land::LAND_TEXTURE_SIZE) * nudgePercentage; + const int landHeightsNudge = (ESM::Land::REAL_SIZE / ESM::Land::LAND_SIZE) / (ESM::Land::LAND_SIZE - 1); // Does this work with all land size configurations? + + const int textureSizeToLandSizeModifier = (ESM::Land::LAND_SIZE - 1) / ESM::Land::LAND_TEXTURE_SIZE; + + for (std::pair &localPos : mSelection) + { + int x (localPos.first); + int y (localPos.second); + + // convert texture selection to global vertex coordinates at selection box corners + int x1 = x * textureSizeToLandSizeModifier + landHeightsNudge; + int x2 = x * textureSizeToLandSizeModifier + textureSizeToLandSizeModifier + landHeightsNudge; + int y1 = y * textureSizeToLandSizeModifier - landHeightsNudge; + int y2 = y * textureSizeToLandSizeModifier + textureSizeToLandSizeModifier - landHeightsNudge; + + // Draw edges (check all sides, draw lines between vertices, +1 height to keep lines above ground) + // Check adjancent selections, draw lines only to edges of the selection + const auto north = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y + 1)); + if (north == mSelection.end()) + { + for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + { + float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(drawPreviousX + nudgeOffset, CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y + 1) - nudgeOffset, calculateLandHeight(x1+(i-1), y2)+2)); + vertices->push_back(osg::Vec3f(drawCurrentX + nudgeOffset, CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y + 1) - nudgeOffset, calculateLandHeight(x1+i, y2)+2)); + } + } + + const auto south = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x, y - 1)); + if (south == mSelection.end()) + { + for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + { + float drawPreviousX = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + (i - 1) *(ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentX = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(drawPreviousX + nudgeOffset, CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) - nudgeOffset, calculateLandHeight(x1+(i-1), y1)+2)); + vertices->push_back(osg::Vec3f(drawCurrentX + nudgeOffset, CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) - nudgeOffset, calculateLandHeight(x1+i, y1)+2)); + } + } + + const auto east = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x + 1, y)); + if (east == mSelection.end()) + { + for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + { + float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x + 1) + nudgeOffset, drawPreviousY - nudgeOffset, calculateLandHeight(x2, y1+(i-1))+2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x + 1) + nudgeOffset, drawCurrentY - nudgeOffset, calculateLandHeight(x2, y1+i)+2)); + } + } + + const auto west = std::find(mSelection.begin(), mSelection.end(), std::make_pair(x - 1, y)); + if (west == mSelection.end()) + { + for(int i = 1; i < (textureSizeToLandSizeModifier + 1); i++) + { + float drawPreviousY = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) + (i - 1) * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + float drawCurrentY = CSMWorld::CellCoordinates::textureGlobalToWorldCoords(y) + i * (ESM::Land::REAL_SIZE / (ESM::Land::LAND_SIZE - 1)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + nudgeOffset, drawPreviousY - nudgeOffset, calculateLandHeight(x1, y1+(i-1))+2)); + vertices->push_back(osg::Vec3f(CSMWorld::CellCoordinates::textureGlobalToWorldCoords(x) + nudgeOffset, drawCurrentY - nudgeOffset, calculateLandHeight(x1, y1+i)+2)); + } + } + } + } +} + +int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global vertex coordinates +{ + int cellX = std::floor((1.0f*x / (ESM::Land::LAND_SIZE - 1))); + int cellY = std::floor((1.0f*y / (ESM::Land::LAND_SIZE - 1))); + int localX = x - cellX * (ESM::Land::LAND_SIZE - 1); + int localY = y - cellY * (ESM::Land::LAND_SIZE - 1); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); + + CSMDoc::Document& document = mWorldspaceWidget->getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + return mPointer[localY*ESM::Land::LAND_SIZE + localX]; +} diff --git a/apps/opencs/view/render/terrainselection.hpp b/apps/opencs/view/render/terrainselection.hpp new file mode 100644 index 000000000..ba80aeb07 --- /dev/null +++ b/apps/opencs/view/render/terrainselection.hpp @@ -0,0 +1,70 @@ +#ifndef CSV_RENDER_TERRAINSELECTION_H +#define CSV_RENDER_TERRAINSELECTION_H + +#include +#include + +#include +#include +#include + +#include +#include "../../model/world/cellcoordinates.hpp" + +namespace osg +{ + class Group; +} + +namespace CSVRender +{ + struct WorldspaceHitResult; + class WorldspaceWidget; + + enum class TerrainSelectionType + { + Texture, + Shape + }; + + /// \brief Class handling the terrain selection data and rendering + class TerrainSelection + { + public: + + TerrainSelection(osg::Group* parentNode, WorldspaceWidget *worldspaceWidget, TerrainSelectionType type); + ~TerrainSelection(); + + void onlySelect(const std::vector> &localPositions); + void addSelect(const std::pair &localPos); + void toggleSelect(const std::vector> &localPositions, bool toggleInProgress); + + void activate(); + void deactivate(); + + std::vector> getTerrainSelection() const; + + protected: + + void update(); + + void drawShapeSelection(const osg::ref_ptr vertices); + void drawTextureSelection(const osg::ref_ptr vertices); + + int calculateLandHeight(int x, int y); + + private: + + osg::Group* mParentNode; + WorldspaceWidget *mWorldspaceWidget; + osg::ref_ptr mBaseNode; + osg::ref_ptr mGeometry; + osg::ref_ptr mSelectionNode; + std::vector> mSelection; // Global terrain selection coordinate in either vertex or texture units + std::vector> mTemporarySelection; // Used during toggle to compare the most recent drag operation + bool mDraggedOperationFlag; //true during drag operation, false when click-operation + TerrainSelectionType mSelectionType; + }; +} + +#endif diff --git a/apps/opencs/view/render/terraintexturemode.cpp b/apps/opencs/view/render/terraintexturemode.cpp index 4205188e4..b8181eed5 100644 --- a/apps/opencs/view/render/terraintexturemode.cpp +++ b/apps/opencs/view/render/terraintexturemode.cpp @@ -10,6 +10,8 @@ #include #include +#include + #include #include "../widget/modebutton.hpp" @@ -36,12 +38,15 @@ #include "object.hpp" // Something small needed regarding pointers from here () #include "worldspacewidget.hpp" -CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, QWidget *parent) +CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) : EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent), mBrushTexture("L0#0"), mBrushSize(0), mBrushShape(0), - mTextureBrushScenetool(0) + mTextureBrushScenetool(0), + mDragMode(InteractionType_None), + mParentNode(parentNode), + mIsEditing(false) { } @@ -62,6 +67,11 @@ void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar) connect(this, SIGNAL(passBrushTexture(std::string)), mTextureBrushScenetool, SLOT(updateBrushHistory(std::string))); } + if (!mTerrainTextureSelection) + { + mTerrainTextureSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Texture)); + } + EditMode::activate(toolbar); toolbar->addTool (mTextureBrushScenetool); } @@ -74,6 +84,12 @@ void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar) delete mTextureBrushScenetool; mTextureBrushScenetool = 0; } + + if (mTerrainTextureSelection) + { + mTerrainTextureSelection.reset(); + } + EditMode::deactivate(toolbar); } @@ -95,10 +111,10 @@ void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true) + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) { undoStack.beginMacro ("Edit texture records"); - if(allowLandTextureEditing(mCellId)==true) + if(allowLandTextureEditing(mCellId)) { undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); @@ -109,10 +125,18 @@ void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit) { + if(hit.hit && hit.tag == 0) + { + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, false); + } } void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit) { + if(hit.hit && hit.tag == 0) + { + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, false); + } } bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) @@ -129,13 +153,16 @@ bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) QUndoStack& undoStack = document.getUndoStack(); + mDragMode = InteractionType_PrimaryEdit; + CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) { undoStack.beginMacro ("Edit texture records"); - if(allowLandTextureEditing(mCellId)==true && hit.hit == true) + mIsEditing = true; + if(allowLandTextureEditing(mCellId)) { undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, mCellId)); editTerrainTextureGrid(hit); @@ -152,47 +179,88 @@ bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos) bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos) { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mDragMode = InteractionType_PrimarySelect; + if (!hit.hit || hit.tag != 0) + { + mDragMode = InteractionType_None; + return false; + } + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); return false; } bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos) { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mDragMode = InteractionType_SecondarySelect; + if (!hit.hit || hit.tag != 0) + { + mDragMode = InteractionType_None; + return false; + } + selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); return false; } void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) { - WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + if (mDragMode == InteractionType_PrimaryEdit) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); + int index = landtexturesCollection.searchId(mBrushTexture); + + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit && hit.tag == 0) + { + editTerrainTextureGrid(hit); + } + } - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted() && hit.hit == true) + if (mDragMode == InteractionType_PrimarySelect) { - editTerrainTextureGrid(hit); + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 0, true); + } + + if (mDragMode == InteractionType_SecondarySelect) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainTextures(CSMWorld::CellCoordinates::toTextureCoords(hit.worldPos), 1, true); } } -void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) { - CSMDoc::Document& document = getWorldspaceWidget().getDocument(); - QUndoStack& undoStack = document.getUndoStack(); +void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) +{ + if (mDragMode == InteractionType_PrimaryEdit && mIsEditing) + { + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + QUndoStack& undoStack = document.getUndoStack(); - CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); - int index = landtexturesCollection.searchId(mBrushTexture); + CSMWorld::IdCollection& landtexturesCollection = document.getData().getLandTextures(); + int index = landtexturesCollection.searchId(mBrushTexture); - if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) - { - undoStack.endMacro(); + if (index != -1 && !landtexturesCollection.getRecord(index).isDeleted()) + { + undoStack.endMacro(); + mIsEditing = false; + } } } -void CSVRender::TerrainTextureMode::dragAborted() { +void CSVRender::TerrainTextureMode::dragAborted() +{ } -void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) {} +void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) +{ +} -void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) { +void CSVRender::TerrainTextureMode::handleDropEvent (QDropEvent *event) +{ const CSMWorld::TableMimeData* mime = dynamic_cast (event->mimeData()); if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped @@ -228,7 +296,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); mCellId = getWorldspaceWidget().getCellId (hit.worldPos); - if(allowLandTextureEditing(mCellId)==true) {} + if(allowLandTextureEditing(mCellId)) {} std::pair cellCoordinates_pair = CSMWorld::CellCoordinates::fromId (mCellId); @@ -236,8 +304,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe int cellY = cellCoordinates_pair.first.getY(); // The coordinates of hit in mCellId - int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.5)); - int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.5)); + int xHitInCell (float(((hit.worldPos.x() - (cellX* cellSize)) * landTextureSize / cellSize) - 0.25)); + int yHitInCell (float(((hit.worldPos.y() - (cellY* cellSize)) * landTextureSize / cellSize) + 0.25)); if (xHitInCell < 0) { xHitInCell = xHitInCell + landTextureSize; @@ -249,8 +317,8 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe cellY = cellY + 1; } - mCellId = "#" + std::to_string(cellX) + " " + std::to_string(cellY); - if(allowLandTextureEditing(mCellId)==true) {} + mCellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); + if(allowLandTextureEditing(mCellId)) {} std::string iteratedCellId; @@ -266,13 +334,13 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (mBrushShape == 0) { - CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); - CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); - if(allowLandTextureEditing(mCellId)==true) + if(allowLandTextureEditing(mCellId)) { - mNew[yHitInCell*landTextureSize+xHitInCell] = brushInt; - pushEditToCommand(mNew, document, landTable, mCellId); + newTerrain[yHitInCell*landTextureSize+xHitInCell] = brushInt; + pushEditToCommand(newTerrain, document, landTable, mCellId); } } @@ -292,19 +360,19 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { - iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell); - if(allowLandTextureEditing(iteratedCellId)==true) + iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); + if(allowLandTextureEditing(iteratedCellId)) { - CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); - CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); for(int i = 0; i < landTextureSize; i++) { for(int j = 0; j < landTextureSize; j++) { - if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) + if (i_cell == cellX && j_cell == cellY && abs(i-xHitInCell) < r && abs(j-yHitInCell) < r) { - mNew[j*landTextureSize+i] = brushInt; + newTerrain[j*landTextureSize+i] = brushInt; } else { @@ -316,11 +384,11 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (j_cell > cellY) distanceY = -yHitInCell + landTextureSize * abs(j_cell-cellY) + j; if (i_cell == cellX) distanceX = abs(i-xHitInCell); if (j_cell == cellY) distanceY = abs(j-yHitInCell); - if (distanceX < r && distanceY < r) mNew[j*landTextureSize+i] = brushInt; + if (distanceX < r && distanceY < r) newTerrain[j*landTextureSize+i] = brushInt; } } } - pushEditToCommand(mNew, document, landTable, iteratedCellId); + pushEditToCommand(newTerrain, document, landTable, iteratedCellId); } } } @@ -342,11 +410,11 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe { for(int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++) { - iteratedCellId = "#" + std::to_string(i_cell) + " " + std::to_string(j_cell); - if(allowLandTextureEditing(iteratedCellId)==true) + iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell); + if(allowLandTextureEditing(iteratedCellId)) { - CSMWorld::LandTexturesColumn::DataType mPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); - CSMWorld::LandTexturesColumn::DataType mNew(mPointer); + CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(iteratedCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); for(int i = 0; i < landTextureSize; i++) { for(int j = 0; j < landTextureSize; j++) @@ -363,7 +431,7 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (i_cell == cellX) distanceX = abs(i-xHitInCell); if (j_cell == cellY) distanceY = abs(j-yHitInCell); distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); - if (distance < rf) mNew[j*landTextureSize+i] = brushInt; + if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; } else { @@ -376,11 +444,104 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (i_cell == cellX) distanceX = abs(i-xHitInCell); if (j_cell == cellY) distanceY = abs(j-yHitInCell); distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); - if (distance < rf) mNew[j*landTextureSize+i] = brushInt; + if (distance < rf) newTerrain[j*landTextureSize+i] = brushInt; } } } - pushEditToCommand(mNew, document, landTable, iteratedCellId); + pushEditToCommand(newTerrain, document, landTable, iteratedCellId); + } + } + } + } + + if (mBrushShape == 3) + { + CSMWorld::LandTexturesColumn::DataType newTerrainPointer = landTable.data(landTable.getModelIndex(mCellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrain(newTerrainPointer); + + if(allowLandTextureEditing(mCellId) && !mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + if(yHitInCell + value.second >= 0 && yHitInCell + value.second <= 15 && xHitInCell + value.first >= 0 && xHitInCell + value.first <= 15) + { + newTerrain[(yHitInCell+value.second)*landTextureSize+xHitInCell+value.first] = brushInt; + } + else + { + int cellXDifference = std::floor(1.0f*(xHitInCell + value.first)/landTextureSize); + int cellYDifference = std::floor(1.0f*(yHitInCell + value.second)/landTextureSize); + int xInOtherCell = xHitInCell + value.first - cellXDifference * landTextureSize; + int yInOtherCell = yHitInCell + value.second - cellYDifference * landTextureSize; + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellX+cellXDifference, cellY+cellYDifference); + if (allowLandTextureEditing(cellId)) + { + CSMWorld::LandTexturesColumn::DataType newTerrainPointerOtherCell = landTable.data(landTable.getModelIndex(cellId, textureColumn)).value(); + CSMWorld::LandTexturesColumn::DataType newTerrainOtherCell(newTerrainPointerOtherCell); + newTerrainOtherCell[yInOtherCell*landTextureSize+xInOtherCell] = brushInt; + pushEditToCommand(newTerrainOtherCell, document, landTable, cellId); + } + } + } + pushEditToCommand(newTerrain, document, landTable, mCellId); + } + } +} + +bool CSVRender::TerrainTextureMode::isInCellSelection(int globalSelectionX, int globalSelectionY) +{ + if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) + { + std::pair textureCoords = std::make_pair(globalSelectionX, globalSelectionY); + std::string cellId = CSMWorld::CellCoordinates::textureGlobalToCellId(textureCoords); + return paged->getCellSelection().has(CSMWorld::CellCoordinates::fromId(cellId).first); + } + return false; +} + + +void CSVRender::TerrainTextureMode::selectTerrainTextures(const std::pair& texCoords, unsigned char selectMode, bool dragOperation) +{ + int r = mBrushSize / 2; + std::vector> selections; + + if (mBrushShape == 0) + { + if (isInCellSelection(texCoords.first, texCoords.second)) selections.emplace_back(texCoords); + } + + if (mBrushShape == 1) + { + for (int i = -r; i <= r; i++) + { + for (int j = -r; j <= r; j++) + { + int x = i + texCoords.first; + int y = j + texCoords.second; + if (isInCellSelection(x, y)) + { + selections.emplace_back(x, y); + } + } + } + } + + if (mBrushShape == 2) + { + for (int i = -r; i <= r; i++) + { + for (int j = -r; j <= r; j++) + { + osg::Vec2f coords(i,j); + if (std::round(coords.length()) < r) + { + int x = i + texCoords.first; + int y = j + texCoords.second; + if (isInCellSelection(x, y)) + { + selections.emplace_back(x, y); + } } } } @@ -388,9 +549,22 @@ void CSVRender::TerrainTextureMode::editTerrainTextureGrid(const WorldspaceHitRe if (mBrushShape == 3) { - // Not implemented + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + int x = texCoords.first + value.first; + int y = texCoords.second + value.second; + if (isInCellSelection(x, y)) + { + selections.emplace_back(x, y); + } + } + } } + if(selectMode == 0) mTerrainTextureSelection->onlySelect(selections); + if(selectMode == 1) mTerrainTextureSelection->toggleSelect(selections, dragOperation); } void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, @@ -405,8 +579,8 @@ void CSVRender::TerrainTextureMode::pushEditToCommand(CSMWorld::LandTexturesColu QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandTexturesIndex))); QUndoStack& undoStack = document.getUndoStack(); - undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); + undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); } void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) @@ -422,13 +596,15 @@ void CSVRender::TerrainTextureMode::createTexture(std::string textureFileName) int counter=0; bool freeIndexFound = false; - do { + do + { const size_t maxCounter = std::numeric_limits::max() - 1; try { newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); if (ltexTable.getRecord(newId).isDeleted() == 0) counter = (counter + 1) % maxCounter; - } catch (const std::exception& e) + } + catch (const std::exception& e) { newId = CSMWorld::LandTexture::createUniqueRecordId(0, counter); freeIndexFound = true; @@ -527,7 +703,8 @@ bool CSVRender::TerrainTextureMode::allowLandTextureEditing(std::string cellId) return true; } -void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) { +void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) +{ } void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) @@ -538,6 +715,28 @@ void CSVRender::TerrainTextureMode::setBrushSize(int brushSize) void CSVRender::TerrainTextureMode::setBrushShape(int brushShape) { mBrushShape = brushShape; + + //Set custom brush shape + if (mBrushShape == 3 && !mTerrainTextureSelection->getTerrainSelection().empty()) + { + auto terrainSelection = mTerrainTextureSelection->getTerrainSelection(); + int selectionCenterX = 0; + int selectionCenterY = 0; + int selectionAmount = 0; + + for(auto const& value: terrainSelection) + { + selectionCenterX += value.first; + selectionCenterY += value.second; + ++selectionAmount; + } + selectionCenterX /= selectionAmount; + selectionCenterY /= selectionAmount; + + mCustomBrushShape.clear(); + for (auto const& value: terrainSelection) + mCustomBrushShape.emplace_back(value.first - selectionCenterX, value.second - selectionCenterY); + } } void CSVRender::TerrainTextureMode::setBrushTexture(std::string brushTexture) diff --git a/apps/opencs/view/render/terraintexturemode.hpp b/apps/opencs/view/render/terraintexturemode.hpp index 10ea842c9..0d670d725 100644 --- a/apps/opencs/view/render/terraintexturemode.hpp +++ b/apps/opencs/view/render/terraintexturemode.hpp @@ -4,6 +4,7 @@ #include "editmode.hpp" #include +#include #include #include @@ -18,6 +19,13 @@ #include "../../model/world/landtexture.hpp" #endif +#include "terrainselection.hpp" + +namespace osg +{ + class Group; +} + namespace CSVWidget { class SceneToolTextureBrush; @@ -25,15 +33,23 @@ namespace CSVWidget namespace CSVRender { - class TerrainTextureMode : public EditMode { Q_OBJECT public: + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; + /// \brief Editmode for terrain texture grid - TerrainTextureMode(WorldspaceWidget*, QWidget* parent = nullptr); + TerrainTextureMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); void primaryOpenPressed (const WorldspaceHitResult& hit); @@ -68,6 +84,12 @@ namespace CSVRender /// \brief Handle brush mechanics, maths regarding worldspace hit etc. void editTerrainTextureGrid (const WorldspaceHitResult& hit); + /// \brief Check if global selection coordinate belongs to cell in view + bool isInCellSelection(int globalSelectionX, int globalSelectionY); + + /// \brief Handle brush mechanics for texture selection + void selectTerrainTextures (const std::pair& texCoords, unsigned char selectMode, bool dragOperation); + /// \brief Push texture edits to command macro void pushEditToCommand (CSMWorld::LandTexturesColumn::DataType& newLandGrid, CSMDoc::Document& document, CSMWorld::IdTable& landTable, std::string cellId); @@ -83,7 +105,12 @@ namespace CSVRender std::string mBrushTexture; int mBrushSize; int mBrushShape; + std::vector> mCustomBrushShape; CSVWidget::SceneToolTextureBrush *mTextureBrushScenetool; + int mDragMode; + osg::Group* mParentNode; + bool mIsEditing; + std::unique_ptr mTerrainTextureSelection; const int cellSize {ESM::Land::REAL_SIZE}; const int landSize {ESM::Land::LAND_SIZE}; diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index aed0882ed..b53b7d040 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -455,18 +455,15 @@ namespace MWClass // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); - - // death animation is not finished, do nothing - return std::shared_ptr (new MWWorld::FailedAction("")); } + else if (!stats.getAiSequence().isInCombat() && !stats.getKnockedDown()) + return std::shared_ptr(new MWWorld::ActionTalk(ptr)); - if(stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::FailedAction("")); - - if(stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::FailedAction("")); + // Tribunal and some mod companions oddly enough must use open action as fallback + if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::shared_ptr(new MWWorld::FailedAction("")); } MWWorld::ContainerStore& Creature::getContainerStore (const MWWorld::Ptr& ptr) const diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 83c989bd4..5c8557dfd 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -886,22 +886,22 @@ namespace MWClass // otherwise wait until death animation if(stats.isDeathAnimationFinished()) return std::shared_ptr(new MWWorld::ActionOpen(ptr)); - - // death animation is not finished, do nothing - return std::shared_ptr (new MWWorld::FailedAction("")); } + else if (!stats.getAiSequence().isInCombat()) + { + if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown()) + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing - if(stats.getAiSequence().isInCombat()) - return std::shared_ptr(new MWWorld::FailedAction("")); - - if(getCreatureStats(actor).getStance(MWMechanics::CreatureStats::Stance_Sneak) || stats.getKnockedDown()) - return std::shared_ptr(new MWWorld::ActionOpen(ptr)); // stealing + // Can't talk to werewolves + if (!getNpcStats(ptr).isWerewolf()) + return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + } - // Can't talk to werewolfs - if(getNpcStats(ptr).isWerewolf()) - return std::shared_ptr (new MWWorld::FailedAction("")); + // Tribunal and some mod companions oddly enough must use open action as fallback + if (!getScript(ptr).empty() && ptr.getRefData().getLocals().getIntVar(getScript(ptr), "companion")) + return std::shared_ptr(new MWWorld::ActionOpen(ptr)); - return std::shared_ptr(new MWWorld::ActionTalk(ptr)); + return std::shared_ptr (new MWWorld::FailedAction("")); } MWWorld::ContainerStore& Npc::getContainerStore (const MWWorld::Ptr& ptr) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index c94907e68..b02b8b9ef 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -208,18 +208,13 @@ namespace void init(Nif::Extra& value) { - value.extra = Nif::ExtraPtr(nullptr); - } - - void init(Nif::Controlled& value) - { - init(static_cast(value)); - value.controller = Nif::ControllerPtr(nullptr); + value.next = Nif::ExtraPtr(nullptr); } void init(Nif::Named& value) { - init(static_cast(value)); + value.extra = Nif::ExtraPtr(nullptr); + value.controller = Nif::ControllerPtr(nullptr); } void init(Nif::Node& value) @@ -254,7 +249,7 @@ namespace value.phase = 0; value.timeStart = 0; value.timeStop = 0; - value.target = Nif::ControlledPtr(nullptr); + value.target = Nif::NamedPtr(nullptr); } void copy(const btTransform& src, Nif::Transformation& dst) @@ -884,7 +879,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) { - mNiStringExtraData.extra = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 4b2e40dec..f67de0221 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -10,17 +10,19 @@ namespace Nif { -/** A record that can have extra data. The extra data objects - themselves descend from the Extra class, and all the extra data - connected to an object form a linked list -*/ +// An extra data record. All the extra data connected to an object form a linked list. class Extra : public Record { public: - ExtraPtr extra; + ExtraPtr next; // Next extra data record in the list + + void read(NIFStream *nif) + { + next.read(nif); + nif->getUInt(); // Size of the record + } - void read(NIFStream *nif) { extra.read(nif); } - void post(NIFFile *nif) { extra.post(nif); } + void post(NIFFile *nif) { next.post(nif); } }; class Controller : public Record @@ -30,43 +32,33 @@ public: int flags; float frequency, phase; float timeStart, timeStop; - ControlledPtr target; + NamedPtr target; void read(NIFStream *nif); void post(NIFFile *nif); }; -/// Anything that has a controller -class Controlled : public Extra +/// Has name, extra-data and controller +class Named : public Record { public: + std::string name; + ExtraPtr extra; ControllerPtr controller; void read(NIFStream *nif) { - Extra::read(nif); + name = nif->getString(); + extra.read(nif); controller.read(nif); } void post(NIFFile *nif) { - Extra::post(nif); + extra.post(nif); controller.post(nif); } }; - -/// Has name, extra-data and controller -class Named : public Controlled -{ -public: - std::string name; - - void read(NIFStream *nif) - { - name = nif->getString(); - Controlled::read(nif); - } -}; typedef Named NiSequenceStreamHelper; } // Namespace diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp index 52e7a7302..51ccf8541 100644 --- a/components/nif/controlled.cpp +++ b/components/nif/controlled.cpp @@ -31,28 +31,40 @@ namespace Nif data.post(nif); } + void NiParticleModifier::read(NIFStream *nif) + { + next.read(nif); + controller.read(nif); + } + + void NiParticleModifier::post(NIFFile *nif) + { + next.post(nif); + controller.post(nif); + } + void NiParticleGrowFade::read(NIFStream *nif) { - Controlled::read(nif); + NiParticleModifier::read(nif); growTime = nif->getFloat(); fadeTime = nif->getFloat(); } void NiParticleColorModifier::read(NIFStream *nif) { - Controlled::read(nif); + NiParticleModifier::read(nif); data.read(nif); } void NiParticleColorModifier::post(NIFFile *nif) { - Controlled::post(nif); + NiParticleModifier::post(nif); data.post(nif); } void NiGravity::read(NIFStream *nif) { - Controlled::read(nif); + NiParticleModifier::read(nif); mDecay = nif->getFloat(); mForce = nif->getFloat(); @@ -61,11 +73,17 @@ namespace Nif mDirection = nif->getVector3(); } - void NiPlanarCollider::read(NIFStream *nif) + void NiParticleCollider::read(NIFStream *nif) { - Controlled::read(nif); + NiParticleModifier::read(nif); mBounceFactor = nif->getFloat(); + } + + void NiPlanarCollider::read(NIFStream *nif) + { + NiParticleCollider::read(nif); + /*unknown*/nif->getFloat(); for (int i=0;i<10;++i) @@ -77,7 +95,7 @@ namespace Nif void NiParticleRotation::read(NIFStream *nif) { - Controlled::read(nif); + NiParticleModifier::read(nif); /* byte (0 or 1) @@ -89,9 +107,8 @@ namespace Nif void NiSphericalCollider::read(NIFStream* nif) { - Controlled::read(nif); + NiParticleCollider::read(nif); - mBounceFactor = nif->getFloat(); mRadius = nif->getFloat(); mCenter = nif->getVector3(); } diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp index be48e912e..00ff45eda 100644 --- a/components/nif/controlled.hpp +++ b/components/nif/controlled.hpp @@ -66,7 +66,16 @@ public: void post(NIFFile *nif); }; -class NiParticleGrowFade : public Controlled +struct NiParticleModifier : public Record +{ + NiParticleModifierPtr next; + ControllerPtr controller; + + void read(NIFStream *nif); + void post(NIFFile *nif); +}; + +class NiParticleGrowFade : public NiParticleModifier { public: float growTime; @@ -75,7 +84,7 @@ public: void read(NIFStream *nif); }; -class NiParticleColorModifier : public Controlled +class NiParticleColorModifier : public NiParticleModifier { public: NiColorDataPtr data; @@ -84,7 +93,7 @@ public: void post(NIFFile *nif); }; -class NiGravity : public Controlled +class NiGravity : public NiParticleModifier { public: float mForce; @@ -99,29 +108,32 @@ public: void read(NIFStream *nif); }; +struct NiParticleCollider : public NiParticleModifier +{ + float mBounceFactor; + void read(NIFStream *nif); +}; + // NiPinaColada -class NiPlanarCollider : public Controlled +class NiPlanarCollider : public NiParticleCollider { public: void read(NIFStream *nif); - float mBounceFactor; - osg::Vec3f mPlaneNormal; float mPlaneDistance; }; -class NiSphericalCollider : public Controlled +class NiSphericalCollider : public NiParticleCollider { public: - float mBounceFactor; float mRadius; osg::Vec3f mCenter; void read(NIFStream *nif); }; -class NiParticleRotation : public Controlled +class NiParticleRotation : public NiParticleModifier { public: void read(NIFStream *nif); diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 113a7becd..52ab6f1f6 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -72,8 +72,8 @@ public: int activeCount; std::vector particles; - ExtraPtr affectors; - ExtraPtr colliders; + NiParticleModifierPtr affectors; + NiParticleModifierPtr colliders; void read(NIFStream *nif); void post(NIFFile *nif); diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index b7e221668..cb654d5a0 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -6,8 +6,6 @@ namespace Nif void NiStringExtraData::read(NIFStream *nif) { Extra::read(nif); - - nif->getInt(); // size of string + 4. Really useful... string = nif->getString(); } @@ -15,8 +13,6 @@ void NiTextKeyExtraData::read(NIFStream *nif) { Extra::read(nif); - nif->getInt(); // 0 - int keynum = nif->getInt(); list.resize(keynum); for(int i=0; igetInt(); - int s = nif->getUShort(); - - nif->skip(s * sizeof(float)); // vertex weights I guess + nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess } diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index e23beb786..fbd148a04 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -127,7 +127,7 @@ class NiUVData; class NiPosData; class NiVisData; class Controller; -class Controlled; +class Named; class NiSkinData; class NiFloatData; struct NiMorphData; @@ -141,6 +141,7 @@ class NiSourceTexture; class NiRotatingParticlesData; class NiAutoNormalParticlesData; class NiPalette; +struct NiParticleModifier; typedef RecordPtrT NodePtr; typedef RecordPtrT ExtraPtr; @@ -148,7 +149,7 @@ typedef RecordPtrT NiUVDataPtr; typedef RecordPtrT NiPosDataPtr; typedef RecordPtrT NiVisDataPtr; typedef RecordPtrT ControllerPtr; -typedef RecordPtrT ControlledPtr; +typedef RecordPtrT NamedPtr; typedef RecordPtrT NiSkinDataPtr; typedef RecordPtrT NiMorphDataPtr; typedef RecordPtrT NiPixelDataPtr; @@ -162,6 +163,7 @@ typedef RecordPtrT NiSourceTexturePtr; typedef RecordPtrT NiRotatingParticlesDataPtr; typedef RecordPtrT NiAutoNormalParticlesDataPtr; typedef RecordPtrT NiPalettePtr; +typedef RecordPtrT NiParticleModifierPtr; typedef RecordListT NodeList; typedef RecordListT PropertyList; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 2f24a4067..5993d04fd 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -260,18 +260,13 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName << ". Treating it as a common NiTriShape."; // Check for extra data - Nif::Extra const *e = node; - while (!e->extra.empty()) + for (Nif::ExtraPtr e = node->extra; !e.empty(); e = e->next) { - // Get the next extra data in the list - e = e->extra.getPtr(); - assert(e != nullptr); - if (e->recType == Nif::RC_NiStringExtraData) { // String markers may contain important information // affecting the entire subtree of this node - Nif::NiStringExtraData *sd = (Nif::NiStringExtraData*)e; + Nif::NiStringExtraData *sd = (Nif::NiStringExtraData*)e.getPtr(); if (Misc::StringUtils::ciCompareLen(sd->string, "NC", 2) == 0) { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 568286a4d..7699271d3 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -222,9 +222,9 @@ namespace NifOsg extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); - extra = extra->extra; + extra = extra->next; Nif::ControllerPtr ctrl = seq->controller; - for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next)) + for(;!extra.empty() && !ctrl.empty();(extra=extra->next),(ctrl=ctrl->next)) { if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) { @@ -524,7 +524,7 @@ namespace NifOsg node->getOrCreateUserDataContainer()->addUserObject( new NodeUserData(nifNode->recIndex, nifNode->trafo.scale, nifNode->trafo.rotation)); - for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->extra) + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) { if(e->recType == Nif::RC_NiTextKeyExtraData && textKeys) { @@ -802,13 +802,13 @@ namespace NifOsg } } - void handleParticlePrograms(Nif::ExtraPtr affectors, Nif::ExtraPtr colliders, osg::Group *attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) + void handleParticlePrograms(Nif::NiParticleModifierPtr affectors, Nif::NiParticleModifierPtr colliders, osg::Group *attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) { osgParticle::ModularProgram* program = new osgParticle::ModularProgram; attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); - for (; !affectors.empty(); affectors = affectors->extra) + for (; !affectors.empty(); affectors = affectors->next) { if (affectors->recType == Nif::RC_NiParticleGrowFade) { @@ -833,7 +833,7 @@ namespace NifOsg else Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; } - for (; !colliders.empty(); colliders = colliders->extra) + for (; !colliders.empty(); colliders = colliders->next) { if (colliders->recType == Nif::RC_NiPlanarCollider) {