diff --git a/CHANGELOG.md b/CHANGELOG.md index 92044e3e6..0fd166c72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -187,6 +187,7 @@ Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch + Feature #4840: Editor: Transient terrain change support Feature #4859: Make water reflections more configurable Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed @@ -213,6 +214,7 @@ Feature #5132: Unique animations for different weapon types Feature #5146: Safe Dispose corpse Feature #5147: Show spell magicka cost in spell buying window + Feature #5170: Editor: Land shape editing, land selection Feature #5193: Weapon sheathing Task #4686: Upgrade media decoder to a more current FFmpeg API Task #4695: Optimize Distant Terrain memory consumption diff --git a/CHANGELOG_PR.md b/CHANGELOG_PR.md index 8862a8448..71677af50 100644 --- a/CHANGELOG_PR.md +++ b/CHANGELOG_PR.md @@ -40,6 +40,8 @@ New Features: New Editor Features: - "Faction Ranks" table for "Faction" records (#4209) +- Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840) +- Land heightmap/shape editing and vertex selection (#5170) Bug Fixes: - Scripted Items cannot be stacked anymore to avoid multiple script execution (#2969) diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index 00855dad0..a62dcb42f 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -82,14 +82,14 @@ opencs_units_noqt (view/world opencs_units (view/widget scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton - scenetooltoggle2 scenetooltexturebrush completerpopup coloreditor colorpickerpopup droplineedit + scenetooltoggle2 scenetooltexturebrush scenetoolshapebrush completerpopup coloreditor colorpickerpopup droplineedit ) opencs_units (view/render scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget previewwidget editmode instancemode instanceselectionmode instancemovemode orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller - cellwater terraintexturemode actor terrainselection + cellwater terraintexturemode actor terrainselection terrainshapemode ) opencs_units_noqt (view/render diff --git a/apps/opencs/model/prefs/state.cpp b/apps/opencs/model/prefs/state.cpp index bfe907c19..1bf5752f0 100644 --- a/apps/opencs/model/prefs/state.cpp +++ b/apps/opencs/model/prefs/state.cpp @@ -170,7 +170,7 @@ void CSMPrefs::State::declare() "list go to the first/last item"); declareCategory ("3D Scene Input"); - + declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0); declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0); declareSeparator(); @@ -178,7 +178,7 @@ void CSMPrefs::State::declare() declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0); declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false); declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0); - declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); + declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28); declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0); declareSeparator(); @@ -242,12 +242,24 @@ void CSMPrefs::State::declare() addValues (insertOutsideCell); declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert). addValues (insertOutsideVisibleCell); - declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit). + declareEnum ("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit). + setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record."). addValues (landeditOutsideCell); - declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit). + declareEnum ("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit). + setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible."). addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); + declareInt ("shapebrush-maximumsize", "Maximum height edit brush size", 100). + setTooltip("Setting for the slider range of brush size in terrain height editing."). + setMin (1); + declareBool ("landedit-post-smoothpainting", "Smooth land after painting height", false). + setTooltip("Raise and lower tools will leave bumpy finish without this option"); + declareDouble ("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25). + setTooltip("If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. " + "Negative values may be used to roughen instead of smooth."). + setMin (-1). + setMax (1); declareBool ("open-list-view", "Open displays list view", false). setTooltip ("When opening a reference from the scene view, it will open the" " instance list view instead of the individual instance record view."); diff --git a/apps/opencs/model/world/columnimp.cpp b/apps/opencs/model/world/columnimp.cpp index b202a97d9..948174b30 100644 --- a/apps/opencs/model/world/columnimp.cpp +++ b/apps/opencs/model/world/columnimp.cpp @@ -89,7 +89,7 @@ namespace CSMWorld DataType values(Size, 0); - if (land.isDataLoaded(Land::DATA_WNAM)) + if (land.mDataTypes & Land::DATA_WNAM) { for (int i = 0; i < Size; ++i) values[i] = land.mWnam[i]; diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a0c408df0..056c50e45 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -134,7 +134,7 @@ void CSVRender::Cell::updateLand() else { mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode, - mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain)); + mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain)); } mTerrain->loadCell(esmLand.mX, esmLand.mY); @@ -149,7 +149,6 @@ void CSVRender::Cell::updateLand() } // No land data - mLandDeleted = true; unloadLand(); } @@ -169,6 +168,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st { std::pair result = CSMWorld::CellCoordinates::fromId (id); + mTerrainStorage = new TerrainStorage(mData); + if (result.second) mCoordinates = result.first; @@ -347,6 +348,28 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int return addObjects (start, end); } +void CSVRender::Cell::setAlteredHeight(int inCellX, int inCellY, float height) +{ + mTerrainStorage->setAlteredHeight(inCellX, inCellY, height); + mUpdateLand = true; +} + +float CSVRender::Cell::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) +{ + return mTerrainStorage->getSumOfAlteredAndTrueHeight(cellX, cellY, inCellX, inCellY); +} + +float* CSVRender::Cell::getAlteredHeight(int inCellX, int inCellY) +{ + return mTerrainStorage->getAlteredHeight(inCellX, inCellY); +} + +void CSVRender::Cell::resetAlteredHeights() +{ + mTerrainStorage->resetHeights(); + mUpdateLand = true; +} + void CSVRender::Cell::pathgridModified() { if (mPathgrid) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 444608688..281ac6735 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -9,6 +9,7 @@ #include #include "../../model/world/cellcoordinates.hpp" +#include "terrainstorage.hpp" class QModelIndex; @@ -58,6 +59,7 @@ namespace CSVRender int mSubMode; unsigned int mSubModeElementMask; bool mUpdateLand, mLandDeleted; + TerrainStorage *mTerrainStorage; /// Ignored if cell does not have an object with the given ID. /// @@ -118,6 +120,14 @@ namespace CSVRender /// this cell? bool referenceAdded (const QModelIndex& parent, int start, int end); + void setAlteredHeight(int inCellX, int inCellY, float height); + + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + + float* getAlteredHeight(int inCellX, int inCellY); + + void resetAlteredHeights(); + void pathgridModified(); void pathgridRemoved(); diff --git a/apps/opencs/view/render/pagedworldspacewidget.cpp b/apps/opencs/view/render/pagedworldspacewidget.cpp index 540a15dd1..b5d9234e4 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.cpp +++ b/apps/opencs/view/render/pagedworldspacewidget.cpp @@ -25,6 +25,7 @@ #include "cameracontroller.hpp" #include "cellarrow.hpp" #include "terraintexturemode.hpp" +#include "terrainshapemode.hpp" bool CSVRender::PagedWorldspaceWidget::adjustCells() { @@ -137,11 +138,9 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons ( /// \todo replace EditMode with suitable subclasses tool->addButton ( - new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"), - "terrain-shape"); + new TerrainShapeMode (this, mRootNode, tool), "terrain-shape"); tool->addButton ( - new TerrainTextureMode (this, mRootNode, tool), - "terrain-texture"); + new TerrainTextureMode (this, mRootNode, tool), "terrain-texture"); tool->addButton ( new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex"); @@ -791,6 +790,36 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& poi return 0; } +CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const +{ + std::map::const_iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second; + else + return nullptr; +} + +void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + searchResult->second->setAlteredHeight(inCellX, inCellY, height); +} + +float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) + return searchResult->second->getAlteredHeight(inCellX, inCellY); + return nullptr; +} + +void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights() +{ + for (const auto& cell : mCells) + cell.second->resetAlteredHeights(); +} + 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 6672c2268..fcc55fe7d 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -124,6 +124,14 @@ namespace CSVRender virtual Cell* getCell(const osg::Vec3d& point) const; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const; + + void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height); + + float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY); + + void resetAllAlteredHeights(); + virtual std::vector > getSelection (unsigned int elementMask) const; diff --git a/apps/opencs/view/render/terrainselection.cpp b/apps/opencs/view/render/terrainselection.cpp index 225cfc20b..092688da2 100644 --- a/apps/opencs/view/render/terrainselection.cpp +++ b/apps/opencs/view/render/terrainselection.cpp @@ -249,13 +249,11 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver 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); + CSMWorld::CellCoordinates coords (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(); + float landHeight = 0.f; + if (CSVRender::Cell* cell = dynamic_cast(mWorldspaceWidget->getCell(coords))) + landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY); - return mPointer[localY*ESM::Land::LAND_SIZE + localX]; + return landHeight; } diff --git a/apps/opencs/view/render/terrainshapemode.cpp b/apps/opencs/view/render/terrainshapemode.cpp new file mode 100644 index 000000000..5f5a60da2 --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -0,0 +1,1447 @@ +#include "terrainshapemode.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "../widget/brushshapes.hpp" +#include "../widget/modebutton.hpp" +#include "../widget/scenetoolbar.hpp" +#include "../widget/scenetoolshapebrush.hpp" + +#include "../../model/doc/document.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/world/columnbase.hpp" +#include "../../model/world/commandmacro.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/idtree.hpp" +#include "../../model/world/land.hpp" +#include "../../model/world/resourcetable.hpp" +#include "../../model/world/tablemimedata.hpp" +#include "../../model/world/universalid.hpp" + +#include "editmode.hpp" +#include "pagedworldspacewidget.hpp" +#include "mask.hpp" +#include "tagbase.hpp" +#include "terrainselection.hpp" +#include "worldspacewidget.hpp" + +CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent) +: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain | Mask_Reference, "Terrain land editing", parent), + mParentNode(parentNode) +{ +} + +void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) +{ + if (!mTerrainShapeSelection) + { + mTerrainShapeSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape)); + } + + if(!mShapeBrushScenetool) + { + mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument()); + connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate())); + connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int))); + connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int))); + } + + EditMode::activate(toolbar); + toolbar->addTool (mShapeBrushScenetool); +} + +void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) +{ + if(mShapeBrushScenetool) + { + toolbar->removeTool (mShapeBrushScenetool); + } + + if (mTerrainShapeSelection) + { + mTerrainShapeSelection.reset(); + } + + EditMode::deactivate(toolbar); +} + +void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here +{ +} + +void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) +{ + if (hit.hit && hit.tag == 0) + { + if (mShapeEditTool == ShapeEditTool_Flatten) + setFlattenToolTargetHeight(hit); + if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag) + { + editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + applyTerrainEditChanges(); + } + + if (mDragMode == InteractionType_PrimarySelect) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + } + + if (mDragMode == InteractionType_SecondarySelect) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + } + } + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } +} + +void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) +{ + if(hit.hit && hit.tag == 0) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false); + } +} + +void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) +{ + if(hit.hit && hit.tag == 0) + { + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false); + } +} + +bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos) +{ + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + + mDragMode = InteractionType_PrimaryEdit; + + if (hit.hit && hit.tag == 0) + { + mEditingPos = hit.worldPos; + mIsEditing = true; + if (mShapeEditTool == ShapeEditTool_Flatten) + setFlattenToolTargetHeight(hit); + } + + return true; +} + +bool CSVRender::TerrainShapeMode::secondaryEditStartDrag (const QPoint& pos) +{ + return false; +} + +bool CSVRender::TerrainShapeMode::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; + } + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + return false; +} + +bool CSVRender::TerrainShapeMode::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; + } + selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + return false; +} + +void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) +{ + if (mDragMode == InteractionType_PrimaryEdit) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + mTotalDiffY += diffY; + if (mIsEditing) + { + if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); + } + } + + if (mDragMode == InteractionType_PrimarySelect) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true); + } + + if (mDragMode == InteractionType_SecondarySelect) + { + WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask()); + if (hit.hit && hit.tag == 0) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true); + } +} + +void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) +{ + if (mDragMode == InteractionType_PrimaryEdit) + { + if (mIsEditing) + { + mTotalDiffY = 0; + mIsEditing = false; + } + + applyTerrainEditChanges(); + + if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget())) + paged->resetAllAlteredHeights(); + } +} + + +void CSVRender::TerrainShapeMode::dragAborted() +{ + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } +} + +void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor) +{ +} + +void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() +{ + bool passing = false; + int passes = 0; + + std::sort(mAlteredCells.begin(), mAlteredCells.end()); + mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); + + while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously + { + passing = true; + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + limitAlteredHeights(cellCoordinates); + } + std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + if (!limitAlteredHeights(cellCoordinates, true)) passing = false; + } + ++passes; + if (passes > 2) + { + Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded."; + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mAlteredCells.clear(); + return; + } + } + } +} + +void CSVRender::TerrainShapeMode::applyTerrainEditChanges() +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landMapLodColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandMapLodIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + QUndoStack& undoStack = document.getUndoStack(); + + sortAndLimitAlteredCells(); + + undoStack.beginMacro ("Edit shape and normal records"); + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + undoStack.push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandMapLodColumn::DataType landMapLodPointer = landTable.data(landTable.getModelIndex(cellId, landMapLodColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + CSMWorld::LandMapLodColumn::DataType mapLodShapeNew(landMapLodPointer); + CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); + + // Generate land height record + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j)) + landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + else + landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; + } + } + + // Generate WNAM record + int sqrtLandGlobalMapLodSize = sqrt(ESM::Land::LAND_GLOBAL_MAP_LOD_SIZE); + for(int i = 0; i < sqrtLandGlobalMapLodSize; ++i) + { + for(int j = 0; j < sqrtLandGlobalMapLodSize; ++j) + { + int col = (static_cast(j) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); + int row = (static_cast(i) / sqrtLandGlobalMapLodSize) * (ESM::Land::LAND_SIZE - 1); + signed char lodHeight = 0; + float floatLodHeight = 0; + if (landShapeNew[col * ESM::Land::LAND_SIZE + row] > 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 128; + if (landShapeNew[col * ESM::Land::LAND_SIZE + row] <= 0) floatLodHeight = landShapeNew[col * ESM::Land::LAND_SIZE + row] / 16; + if (floatLodHeight > std::numeric_limits::max()) lodHeight = std::numeric_limits::max(); + else if (floatLodHeight < std::numeric_limits::min()) lodHeight = std::numeric_limits::min(); + else lodHeight = static_cast(floatLodHeight); + mapLodShapeNew[j * sqrtLandGlobalMapLodSize + i] = lodHeight; + } + } + pushEditToCommand(landShapeNew, document, landTable, cellId); + pushLodToCommand(mapLodShapeNew, document, landTable, cellId); + } + + for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells) + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + + // Generate land normals record + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + osg::Vec3f v1(128, 0, 0); + osg::Vec3f v2(0, 128, 0); + + if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()); + if (isLandLoaded(cellId)) + v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + } + + if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + else + { + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1); + if (isLandLoaded(cellId)) + v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i]; + } + + osg::Vec3f normal = v1 ^ v2; + const float hyp = normal.length() / 127.0f; + + normal /= hyp; + + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal.x(); + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal.y(); + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal.z(); + } + } + pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); + } + undoStack.endMacro(); + mAlteredCells.clear(); +} + +float CSVRender::TerrainShapeMode::calculateBumpShape(float distance, int radius, float height) +{ + float distancePerRadius = distance / radius; + return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); +} + +void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) +{ + int r = mBrushSize / 2; + if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); + } + + if (mBrushShape == CSVWidget::BrushShape_Point) + { + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + + if (mBrushShape == CSVWidget::BrushShape_Square) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + if (mBrushShape == CSVWidget::BrushShape_Circle) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2)); + float smoothedByDistance = 0.0f; + if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); + if (distance <= r) + { + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, smoothedByDistance); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + } + if (mBrushShape == CSVWidget::BrushShape_Custom) + { + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); + if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) + { + alterHeight(cellCoords, x, y, mShapeEditToolStrength); + float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); + if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); + } + if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } +} + +void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHitResult& hit) +{ + std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + + CSMDoc::Document& document = getWorldspaceWidget().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 landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; +} + + +void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) +{ + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + + if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId))))) + return; + CSVRender::PagedWorldspaceWidget *paged = dynamic_cast (&getWorldspaceWidget()); + if (!paged) + return; + + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); + std::string cellUpRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() - 1); + std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); + std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); + + if (useTool) + { + mAlteredCells.emplace_back(cellCoords); + if (mShapeEditTool == ShapeEditTool_Drag) + { + // Get distance from modified land, alter land change based on zoom + osg::Vec3d eye, center, up; + paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); + osg::Vec3d distance = eye - mEditingPos; + alteredHeight = alteredHeight * (distance.length() / 500); + } + if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + } + + if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + + // Change values of cornering cells + if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) + { + if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; + } + else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) + { + if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); + } else return; + } + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) + { + if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); + } else return; + } + else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) + { + if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(cornerCellCoords); + paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); + } else return; + } + + // Change values of edging cells + if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) + { + if(allowLandShapeEditing(cellLeftId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); + } + } + if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) + { + if(allowLandShapeEditing(cellUpId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); + } + } + + if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) + { + if(allowLandShapeEditing(cellRightId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); + } + } + if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) + { + if(allowLandShapeEditing(cellDownId, useTool)) + { + CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); + if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) + mAlteredCells.emplace_back(edgeCellCoords); + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); + } + } + +} + +void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) +{ + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + // ### Variable naming key ### + // Variables here hold either the real value, or the altered value of current edit. + // this = this Cell + // left = x - 1, up = y - 1, right = x + 1, down = y + 1 + // Altered = transient edit (in current edited) + float thisAlteredHeight = 0.0f; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) + thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + float upHeight = 0.0f; + + if(allowLandShapeEditing(cellId)) + { + //Get key values for calculating average, handle cell edges, check for null pointers + if (inCellX == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); + } + if (inCellY == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); + } + if (inCellX > 0) + { + leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + } + if (inCellY > 0) + { + upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + } + if (inCellX == ESM::Land::LAND_SIZE - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) + { + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); + } + } + if (inCellY == ESM::Land::LAND_SIZE - 1) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) + { + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); + } + } + if (inCellX < ESM::Land::LAND_SIZE - 1) + { + rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; + if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); + } + if (inCellY < ESM::Land::LAND_SIZE - 1) + { + downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); + } + + float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + + upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; + if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); + if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); + if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, - toolStrength); + if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, + toolStrength); + } + } +} + +void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell(cellId) && !noLand(cellId)) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; + } + } + + if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = + abs(thisHeight - targetHeight); //Cut down excessive changes + if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); + if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); +} + +void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, + float* rightAlteredHeight, float* downHeight, float* downAlteredHeight) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + + *thisHeight = 0.0f; // real + altered height + *thisAlteredHeight = 0.0f; // only altered height + *leftHeight = 0.0f; + *leftAlteredHeight = 0.0f; + *upHeight = 0.0f; + *upAlteredHeight = 0.0f; + *rightHeight = 0.0f; + *rightAlteredHeight = 0.0f; + *downHeight = 0.0f; + *downAlteredHeight = 0.0f; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell(cellId) && !noLand(cellId)) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) + *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); + *thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight; + + // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found, + // which is to prevent unnecessary action at limitHeightChange(). + *leftHeight = *thisHeight; + *upHeight = *thisHeight; + *rightHeight = *thisHeight; + *downHeight = *thisHeight; + + //If at edge, get values from neighboring cell + if (inCellX == 0) + { + if(isLandLoaded(cellLeftId)) + { + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = + landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + *leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY)) + { + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); + *leftHeight += *leftAlteredHeight; + } + } + } + if (inCellY == 0) + { + if(isLandLoaded(cellUpId)) + { + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = + landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + *upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, ESM::Land::LAND_SIZE - 2)) + { + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); + *upHeight += *upAlteredHeight; + } + } + } + if (inCellX == ESM::Land::LAND_SIZE - 1) + { + if(isLandLoaded(cellRightId)) + { + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + *rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) + { + *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); + *rightHeight += *rightAlteredHeight; + } + } + } + if (inCellY == ESM::Land::LAND_SIZE - 1) + { + if(isLandLoaded(cellDownId)) + { + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + *downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) + { + *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); + *downHeight += *downAlteredHeight; + } + } + } + + //If not at edge, get values from the same cell + if (inCellX != 0) + { + *leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; + if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + *leftHeight += *leftAlteredHeight; + } + if (inCellY != 0) + { + *upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + *upHeight += *upAlteredHeight; + } + if (inCellX != ESM::Land::LAND_SIZE - 1) + { + *rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; + if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); + *rightHeight += *rightAlteredHeight; + } + if (inCellY != ESM::Land::LAND_SIZE - 1) + { + *downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) + *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); + *downHeight += *downAlteredHeight; + } + + } + } +} + +void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) +{ + if (limitedAlteredHeightXAxis) + { + if (limitedAlteredHeightYAxis) + { + if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + *steepnessIsWithinLimits = false; + } + else + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + *steepnessIsWithinLimits = false; + } + } + else + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); + *steepnessIsWithinLimits = false; + } + } + else if (limitedAlteredHeightYAxis) + { + alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); + *steepnessIsWithinLimits = false; + } +} + +bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast (*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + + int limitHeightChange = 1016.0f; // Limited by save format + bool steepnessIsWithinLimits = true; + + if (isLandLoaded(cellId)) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + float rightHeight = 0.0f; + float rightAlteredHeight = 0.0f; + float downHeight = 0.0f; + float downAlteredHeight = 0.0f; + + if (!reverseMode) + { + for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) + { + for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) + { + std::unique_ptr limitedAlteredHeightXAxis(nullptr); + std::unique_ptr limitedAlteredHeightYAxis(nullptr); + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); + + // Check for height limits on x-axis + if (leftHeight - thisHeight > limitHeightChange) + limitedAlteredHeightXAxis.reset(new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + else if (leftHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightXAxis.reset(new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + + // Check for height limits on y-axis + if (upHeight - thisHeight > limitHeightChange) + limitedAlteredHeightYAxis.reset(new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + else if (upHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightYAxis.reset(new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + + // Limit altered height value based on x or y, whichever is the smallest + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); + } + } + } + + if (reverseMode) + { + for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) + { + for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) + { + std::unique_ptr limitedAlteredHeightXAxis(nullptr); + std::unique_ptr limitedAlteredHeightYAxis(nullptr); + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); + + // Check for height limits on x-axis + if (rightHeight - thisHeight > limitHeightChange) + limitedAlteredHeightXAxis.reset(new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + else if (rightHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightXAxis.reset(new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + + // Check for height limits on y-axis + if (downHeight - thisHeight > limitHeightChange) + limitedAlteredHeightYAxis.reset(new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight))); + else if (downHeight - thisHeight < -limitHeightChange) + limitedAlteredHeightYAxis.reset(new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight))); + + // Limit altered height value based on x or y, whichever is the smallest + compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); + } + } + } + } + return steepnessIsWithinLimits; +} + +void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) +{ + int r = mBrushSize / 2; + std::vector> selections; + + if (mBrushShape == CSVWidget::BrushShape_Point) + { + selections.emplace_back(vertexCoords); + } + + if (mBrushShape == CSVWidget::BrushShape_Square) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + selections.emplace_back(std::make_pair(i, j)); + } + } + } + + if (mBrushShape == CSVWidget::BrushShape_Circle) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + if (distance <= r) selections.emplace_back(std::make_pair(i, j)); + } + } + } + + if (mBrushShape == CSVWidget::BrushShape_Custom) + { + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + selections.emplace_back(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + } + } + } + + if(selectMode == 0) mTerrainShapeSelection->onlySelect(selections); + if(selectMode == 1) mTerrainShapeSelection->toggleSelect(selections, dragOperation); + +} + +void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId) +{ + QVariant changedLand; + changedLand.setValue(newLandGrid); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId) +{ + QVariant changedLand; + changedLand.setValue(newLandGrid); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +void CSVRender::TerrainShapeMode::pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId) +{ + QVariant changedLod; + changedLod.setValue(newLandMapLod); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandMapLodIndex))); + + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLod)); +} + +bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& cellCollection = document.getData().getCells(); + return cellCollection.searchId (cellId) == -1; +} + +bool CSVRender::TerrainShapeMode::noLand(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& landCollection = document.getData().getLand(); + return landCollection.searchId (cellId) == -1; +} + +bool CSVRender::TerrainShapeMode::noLandLoaded(const std::string& cellId) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + const CSMWorld::IdCollection& landCollection = document.getData().getLand(); + return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML); +} + +bool CSVRender::TerrainShapeMode::isLandLoaded(const std::string& cellId) +{ + if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true; + return false; +} + +void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordinates& cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTable& ltexTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); + + float defaultHeight = 0.f; + int averageDivider = 0; + CSMWorld::CellCoordinates cellLeftCoords = cellCoords.move(-1, 0); + CSMWorld::CellCoordinates cellRightCoords = cellCoords.move(1, 0); + CSMWorld::CellCoordinates cellUpCoords = cellCoords.move(0, -1); + CSMWorld::CellCoordinates cellDownCoords = cellCoords.move(0, 1); + + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellLeftCoords.getX(), cellLeftCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellRightCoords.getX(), cellRightCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellUpCoords.getX(), cellUpCoords.getY()); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellDownCoords.getX(), cellDownCoords.getY()); + + float leftCellSampleHeight = 0.0f; + float rightCellSampleHeight = 0.0f; + float upCellSampleHeight = 0.0f; + float downCellSampleHeight = 0.0f; + + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (isLandLoaded(cellLeftId)) + { + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = + landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + + ++averageDivider; + leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) + leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); + } + if (isLandLoaded(cellRightId)) + { + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = + landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + + ++averageDivider; + rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE]; + if(paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) + rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2); + } + if (isLandLoaded(cellUpId)) + { + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = + landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + + ++averageDivider; + upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; + if(paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) + upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); + } + if (isLandLoaded(cellDownId)) + { + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = + landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + + ++averageDivider; + downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2]; + if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE / 2, 0)) + downCellSampleHeight += *paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0); + } + } + if (averageDivider > 0) defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) / averageDivider; + + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + for(int j = 0; j < ESM::Land::LAND_SIZE; ++j) + { + landShapeNew[j * ESM::Land::LAND_SIZE + i] = defaultHeight; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = 0; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = 0; + landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = 127; + } + } + QVariant changedShape; + changedShape.setValue(landShapeNew); + QVariant changedNormals; + changedNormals.setValue(landNormalsNew); + QModelIndex indexShape(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QModelIndex indexNormal(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex))); + document.getUndoStack().push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId)); + document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexShape, changedShape)); + document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals)); +} + +bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId, bool useTool) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + CSMWorld::IdTree& cellTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells)); + + if (noCell(cellId)) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + // target cell does not exist + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit" && useTool) + { + std::unique_ptr createCommand ( + new CSMWorld::CreateCommand (cellTable, cellId)); + int parentIndex = cellTable.findColumnIndex (CSMWorld::Columns::ColumnId_Cell); + int index = cellTable.findNestedColumnIndex (parentIndex, CSMWorld::Columns::ColumnId_Interior); + createCommand->addNestedValue (parentIndex, index, false); + document.getUndoStack().push (createCommand.release()); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + else if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + CSMWorld::CellSelection selection = paged->getCellSelection(); + if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first)) + { + // target cell exists, but is not shown + std::string mode = + CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); + + if (mode=="Discard") + return false; + + if (mode=="Show cell and edit" && useTool) + { + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + + if (noLand(cellId)) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + // target cell does not exist + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit" && useTool) + { + document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); + fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); + sortAndLimitAlteredCells(); + } + } + else if (noLandLoaded(cellId)) + { + std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); + + if (mode=="Discard") + return false; + + if (mode=="Create cell and land, then edit" && useTool) + { + createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); + fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); + sortAndLimitAlteredCells(); + } + } + + if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId))) + { + Log(Debug::Warning) << "Land creation failed at cell id: " << cellId; + return false; + } + return true; +} + +void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) +{ + CSMDoc::Document& document = getWorldspaceWidget().getDocument(); + CSMWorld::IdTable& landTable = dynamic_cast ( + *document.getData().getTableModel (CSMWorld::UniversalId::Type_Land)); + int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); + std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); + std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); + std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); + std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); + + const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value(); + + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < ESM::Land::LAND_SIZE; ++i) + { + if (isLandLoaded(cellLeftId) && + landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) + landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; + if (isLandLoaded(cellRightId) && + landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE]) + landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE]; + if (isLandLoaded(cellUpId) && + landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) + landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]; + if (isLandLoaded(cellDownId) && + landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) + landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i]; + } + + QVariant changedLand; + changedLand.setValue(landShapeNew); + + QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex))); + QUndoStack& undoStack = document.getUndoStack(); + undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand)); +} + +void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) +{ +} + +void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; +} + +void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape) +{ + mBrushShape = brushShape; + + //Set custom brush shape + if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty()) + { + auto terrainSelection = mTerrainShapeSelection->getTerrainSelection(); + int selectionCenterX = 0; + int selectionCenterY = 0; + int selectionAmount = 0; + + for(auto const& value: terrainSelection) + { + selectionCenterX = selectionCenterX + value.first; + selectionCenterY = selectionCenterY + value.second; + ++selectionAmount; + } + selectionCenterX = selectionCenterX / selectionAmount; + selectionCenterY = selectionCenterY / selectionAmount; + + mCustomBrushShape.clear(); + std::pair differentialPos {}; + for(auto const& value: terrainSelection) + { + differentialPos.first = value.first - selectionCenterX; + differentialPos.second = value.second - selectionCenterY; + mCustomBrushShape.push_back(differentialPos); + } + } +} + +void CSVRender::TerrainShapeMode::setShapeEditTool(int shapeEditTool) +{ + mShapeEditTool = shapeEditTool; +} + +void CSVRender::TerrainShapeMode::setShapeEditToolStrength(int shapeEditToolStrength) +{ + mShapeEditToolStrength = shapeEditToolStrength; +} + +CSVRender::PagedWorldspaceWidget& CSVRender::TerrainShapeMode::getPagedWorldspaceWidget() +{ + return dynamic_cast(getWorldspaceWidget()); +} diff --git a/apps/opencs/view/render/terrainshapemode.hpp b/apps/opencs/view/render/terrainshapemode.hpp new file mode 100644 index 000000000..8e2a616c9 --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -0,0 +1,190 @@ +#ifndef CSV_RENDER_TERRAINSHAPEMODE_H +#define CSV_RENDER_TERRAINSHAPEMODE_H + +#include "editmode.hpp" + +#include +#include + +#include +#include + +#ifndef Q_MOC_RUN +#include "../../model/world/data.hpp" +#include "../../model/world/land.hpp" +#include "../../model/doc/document.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/landtexture.hpp" +#include "../widget/brushshapes.hpp" +#endif + +#include "terrainselection.hpp" + +namespace CSVWidget +{ + class SceneToolShapeBrush; +} + +namespace CSVRender +{ + class PagedWorldspaceWidget; + + /// \brief EditMode for handling the terrain shape editing + class TerrainShapeMode : public EditMode + { + Q_OBJECT + + public: + + enum InteractionType + { + InteractionType_PrimaryEdit, + InteractionType_PrimarySelect, + InteractionType_SecondaryEdit, + InteractionType_SecondarySelect, + InteractionType_None + }; + + enum ShapeEditTool + { + ShapeEditTool_Drag = 0, + ShapeEditTool_PaintToRaise = 1, + ShapeEditTool_PaintToLower = 2, + ShapeEditTool_Smooth = 3, + ShapeEditTool_Flatten = 4 + }; + + /// Editmode for terrain shape grid + TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); + + void primaryOpenPressed (const WorldspaceHitResult& hit) final; + + /// Create single command for one-click shape editing + void primaryEditPressed (const WorldspaceHitResult& hit) final; + + /// Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&) final; + + void secondarySelectPressed(const WorldspaceHitResult&) final; + + void activate(CSVWidget::SceneToolbar*) final; + void deactivate(CSVWidget::SceneToolbar*) final; + + /// Start shape editing command macro + bool primaryEditStartDrag (const QPoint& pos) final; + + bool secondaryEditStartDrag (const QPoint& pos) final; + bool primarySelectStartDrag (const QPoint& pos) final; + bool secondarySelectStartDrag (const QPoint& pos) final; + + /// Handle shape edit behavior during dragging + void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) final; + + /// End shape editing command macro + void dragCompleted(const QPoint& pos) final; + + /// Cancel shape editing, and reset all pending changes + void dragAborted() final; + + void dragWheel (int diff, double speedFactor) final; + void dragMoveEvent (QDragMoveEvent *event) final; + + private: + + /// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse + void sortAndLimitAlteredCells(); + + /// Move pending alteredHeights changes to omwgame/omwaddon -data + void applyTerrainEditChanges(); + + /// Handle brush mechanics for shape editing + void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + + /// Calculate height, when aiming for bump-shaped terrain change + float calculateBumpShape(float distance, int radius, float height); + + /// set the target height for flatten tool + void setFlattenToolTargetHeight(const WorldspaceHitResult& hit); + + /// Do a single height alteration for transient shape edit map + void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true); + + /// Do a single smoothing height alteration for transient shape edit map + void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength); + + /// Do a single flattening height alteration for transient shape edit map + void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight); + + /// Get altered height values around one vertex + void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, + float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight); + + ///Limit steepness based on either X or Y and return false if steepness is limited + void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, + float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits); + + /// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is within limits + bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false); + + /// Handle brush mechanics for terrain shape selection + void selectTerrainShapes (const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation); + + /// Push terrain shape edits to command macro + void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId); + + /// Push land normals edits to command macro + void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId); + + /// Generate new land map LOD + void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, const std::string& cellId); + + bool noCell(const std::string& cellId); + + bool noLand(const std::string& cellId); + + bool noLandLoaded(const std::string& cellId); + + bool isLandLoaded(const std::string& cellId); + + /// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and set the average height based on that + void createNewLandData(const CSMWorld::CellCoordinates& cellCoords); + + /// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for automated land changes) + bool allowLandShapeEditing(const std::string& textureFileName, bool useTool = true); + + /// Bind the edging vertice to the values of the adjancent cells + void fixEdges(CSMWorld::CellCoordinates cellCoords); + + std::string mBrushTexture; + int mBrushSize = 1; + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + std::vector> mCustomBrushShape; + CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr; + int mDragMode = InteractionType_None; + osg::Group* mParentNode; + bool mIsEditing = false; + std::unique_ptr mTerrainShapeSelection; + int mTotalDiffY = 0; + std::vector mAlteredCells; + osg::Vec3d mEditingPos; + int mShapeEditTool = ShapeEditTool_Drag; + int mShapeEditToolStrength = 8; + int mTargetHeight = 0; + + PagedWorldspaceWidget& getPagedWorldspaceWidget(); + + public slots: + void setBrushSize(int brushSize); + void setBrushShape(CSVWidget::BrushShape brushShape); + void setShapeEditTool(int shapeEditTool); + void setShapeEditToolStrength(int shapeEditToolStrength); + }; +} + + +#endif diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index e0edae774..d9cc3015e 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -3,13 +3,15 @@ #include "../../model/world/land.hpp" #include "../../model/world/landtexture.hpp" +#include + namespace CSVRender { - TerrainStorage::TerrainStorage(const CSMWorld::Data &data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) { + resetHeights(); } osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) @@ -33,10 +35,118 @@ namespace CSVRender return &mData.getLandTextures().getRecord(row).get(); } + void TerrainStorage::setAlteredHeight(int inCellX, int inCellY, float height) + { + mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] = height - fmod(height, 8); //Limit to divisible by 8 to avoid cell seam breakage + } + + void TerrainStorage::resetHeights() + { + std::fill(std::begin(mAlteredHeight), std::end(mAlteredHeight), 0); + } + + float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY) + { + float height = 0.f; + osg::ref_ptr land = getLand (cellX, cellY); + if (land) + { + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; + if (data) height = getVertexHeight(data, inCellX, inCellY); + } + else return height; + return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; + + } + + float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) + { + return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; + } + void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY) { // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells throw std::runtime_error("getBounds not implemented"); } + int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)]; + } + + int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[col*ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row + 1)]; + } + + int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const + { + return heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)]; + } + + int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData)); + } + + int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const + { + return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); + } + + bool TerrainStorage::leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + { + return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit || + getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; + } + + bool TerrainStorage::rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const + { + return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit || + getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; + } + + void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const + { + // Highlight broken height changes + int heightWarningLimit = 1024; + if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) + { + color.r() = 255; + color.g() = 0; + color.b() = 0; + } + } + + float TerrainStorage::getAlteredHeight(int col, int row) const + { + return mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]; + } } diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 6c3151e8d..032261ad4 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -1,13 +1,14 @@ #ifndef OPENCS_RENDER_TERRAINSTORAGE_H #define OPENCS_RENDER_TERRAINSTORAGE_H +#include + #include #include "../../model/world/data.hpp" namespace CSVRender { - /** * @brief A bridge between the terrain component and OpenCS's terrain data storage. */ @@ -15,13 +16,34 @@ namespace CSVRender { public: TerrainStorage(const CSMWorld::Data& data); + void setAlteredHeight(int inCellX, int inCellY, float heightMap); + void resetHeights(); + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + float* getAlteredHeight(int inCellX, int inCellY); + private: const CSMWorld::Data& mData; + std::array mAlteredHeight; - virtual osg::ref_ptr getLand (int cellX, int cellY) override; - virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; + osg::ref_ptr getLand (int cellX, int cellY) final; + const ESM::LandTexture* getLandTexture(int index, short plugin) final; - virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; + void getBounds(float& minX, float& maxX, float& minY, float& maxY) final; + + int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const; + int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const; + bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const; + + void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const final; + float getAlteredHeight(int col, int row) const final; }; } diff --git a/apps/opencs/view/render/unpagedworldspacewidget.cpp b/apps/opencs/view/render/unpagedworldspacewidget.cpp index a3f0636be..b1088aa60 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.cpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.cpp @@ -150,6 +150,11 @@ CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& p return mCell.get(); } +CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const +{ + return mCell.get(); +} + std::vector > CSVRender::UnpagedWorldspaceWidget::getSelection ( unsigned int elementMask) const { diff --git a/apps/opencs/view/render/unpagedworldspacewidget.hpp b/apps/opencs/view/render/unpagedworldspacewidget.hpp index 527463990..d169220f9 100644 --- a/apps/opencs/view/render/unpagedworldspacewidget.hpp +++ b/apps/opencs/view/render/unpagedworldspacewidget.hpp @@ -17,6 +17,7 @@ namespace CSMDoc namespace CSMWorld { class IdTable; + class CellCoordinates; } namespace CSVRender @@ -63,6 +64,8 @@ namespace CSVRender virtual Cell* getCell(const osg::Vec3d& point) const; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const; + virtual std::vector > getSelection (unsigned int elementMask) const; diff --git a/apps/opencs/view/render/worldspacewidget.hpp b/apps/opencs/view/render/worldspacewidget.hpp index 06c182b0c..a80032b82 100644 --- a/apps/opencs/view/render/worldspacewidget.hpp +++ b/apps/opencs/view/render/worldspacewidget.hpp @@ -17,6 +17,7 @@ namespace CSMPrefs namespace CSMWorld { + class CellCoordinates; class UniversalId; } @@ -170,6 +171,8 @@ namespace CSVRender /// \note Returns the cell if it exists, otherwise a null pointer virtual Cell* getCell(const osg::Vec3d& point) const = 0; + virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0; + virtual std::vector > getSelection (unsigned int elementMask) const = 0; diff --git a/apps/opencs/view/widget/brushshapes.hpp b/apps/opencs/view/widget/brushshapes.hpp new file mode 100644 index 000000000..2e931157c --- /dev/null +++ b/apps/opencs/view/widget/brushshapes.hpp @@ -0,0 +1,14 @@ +#ifndef CSV_WIDGET_BRUSHSHAPES_H +#define CSV_WIDGET_BRUSHSHAPES_H + +namespace CSVWidget +{ + enum BrushShape + { + BrushShape_Point, + BrushShape_Square, + BrushShape_Circle, + BrushShape_Custom + }; +} +#endif diff --git a/apps/opencs/view/widget/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp new file mode 100644 index 000000000..e4647d600 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -0,0 +1,263 @@ +#include "scenetoolshapebrush.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "brushshapes.hpp" +#include "scenetool.hpp" + +#include "../../model/doc/document.hpp" +#include "../../model/prefs/state.hpp" +#include "../../model/world/commands.hpp" +#include "../../model/world/data.hpp" +#include "../../model/world/idcollection.hpp" +#include "../../model/world/idtable.hpp" +#include "../../model/world/landtexture.hpp" +#include "../../model/world/universalid.hpp" + + +CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(const QString &title, QWidget *parent) + : QGroupBox(title, parent) +{ + mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); + mBrushSizeSlider->setTickInterval(10); + mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSlider->setSingleStep(1); + + mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSpinBox->setSingleStep(1); + + QHBoxLayout *layoutSliderSize = new QHBoxLayout; + layoutSliderSize->addWidget(mBrushSizeSlider); + layoutSliderSize->addWidget(mBrushSizeSpinBox); + + connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); + connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); + + setLayout(layoutSliderSize); +} + +CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) + : QFrame(parent, Qt::Popup), + mDocument(document) +{ + mButtonPoint = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-point")), "", this); + mButtonSquare = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-square")), "", this); + mButtonCircle = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-circle")), "", this); + mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this); + + mSizeSliders = new ShapeBrushSizeControls("Brush size", this); + + QVBoxLayout *layoutMain = new QVBoxLayout; + layoutMain->setSpacing(0); + layoutMain->setContentsMargins(4,0,4,4); + + QHBoxLayout *layoutHorizontal = new QHBoxLayout; + layoutHorizontal->setSpacing(0); + layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0)); + + configureButtonInitialSettings(mButtonPoint); + configureButtonInitialSettings(mButtonSquare); + configureButtonInitialSettings(mButtonCircle); + configureButtonInitialSettings(mButtonCustom); + + mButtonPoint->setToolTip (toolTipPoint); + mButtonSquare->setToolTip (toolTipSquare); + mButtonCircle->setToolTip (toolTipCircle); + mButtonCustom->setToolTip (toolTipCustom); + + QButtonGroup* brushButtonGroup = new QButtonGroup(this); + brushButtonGroup->addButton(mButtonPoint); + brushButtonGroup->addButton(mButtonSquare); + brushButtonGroup->addButton(mButtonCircle); + brushButtonGroup->addButton(mButtonCustom); + + brushButtonGroup->setExclusive(true); + + layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop); + layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop); + + mHorizontalGroupBox = new QGroupBox(tr("")); + mHorizontalGroupBox->setLayout(layoutHorizontal); + + mToolSelector = new QComboBox(this); + mToolSelector->addItem(tr("Height (drag)")); + mToolSelector->addItem(tr("Height, raise (paint)")); + mToolSelector->addItem(tr("Height, lower (paint)")); + mToolSelector->addItem(tr("Smooth (paint)")); + mToolSelector->addItem(tr("Flatten (paint)")); + + QLabel *brushStrengthLabel = new QLabel(this); + brushStrengthLabel->setText("Brush strength:"); + + mToolStrengthSlider = new QSlider(Qt::Horizontal); + mToolStrengthSlider->setTickPosition(QSlider::TicksBothSides); + mToolStrengthSlider->setTickInterval(8); + mToolStrengthSlider->setRange(8, 128); + mToolStrengthSlider->setSingleStep(8); + mToolStrengthSlider->setValue(8); + + layoutMain->addWidget(mHorizontalGroupBox); + layoutMain->addWidget(mSizeSliders); + layoutMain->addWidget(mToolSelector); + layoutMain->addWidget(brushStrengthLabel); + layoutMain->addWidget(mToolStrengthSlider); + + setLayout(layoutMain); + + connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape())); + connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape())); +} + +void CSVWidget::ShapeBrushWindow::configureButtonInitialSettings(QPushButton *button) +{ + button->setSizePolicy (QSizePolicy (QSizePolicy::Fixed, QSizePolicy::Fixed)); + button->setContentsMargins (QMargins (0, 0, 0, 0)); + button->setIconSize (QSize (48-6, 48-6)); + button->setFixedSize (48, 48); + button->setCheckable(true); +} + +void CSVWidget::ShapeBrushWindow::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; + emit passBrushSize(mBrushSize); +} + +void CSVWidget::ShapeBrushWindow::setBrushShape() +{ + if(mButtonPoint->isChecked()) mBrushShape = BrushShape_Point; + if(mButtonSquare->isChecked()) mBrushShape = BrushShape_Square; + if(mButtonCircle->isChecked()) mBrushShape = BrushShape_Circle; + if(mButtonCustom->isChecked()) mBrushShape = BrushShape_Custom; + emit passBrushShape(mBrushShape); +} + +void CSVWidget::SceneToolShapeBrush::adjustToolTips() +{ +} + +CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document) +: SceneTool (parent, Type_TopAction), + mToolTip (toolTip), + mDocument (document), + mShapeBrushWindow(new ShapeBrushWindow(document, this)) +{ + setAcceptDrops(true); + connect(mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape))); + setButtonIcon(mShapeBrushWindow->mBrushShape); + + mPanel = new QFrame (this, Qt::Popup); + + QHBoxLayout *layout = new QHBoxLayout (mPanel); + + layout->setContentsMargins (QMargins (0, 0, 0, 0)); + + mTable = new QTableWidget (0, 2, this); + + mTable->setShowGrid (true); + mTable->verticalHeader()->hide(); + mTable->horizontalHeader()->hide(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch); +#else + mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch); + mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch); +#endif + mTable->setSelectionMode (QAbstractItemView::NoSelection); + + layout->addWidget (mTable); + + connect (mTable, SIGNAL (clicked (const QModelIndex&)), + this, SLOT (clicked (const QModelIndex&))); + +} + +void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushShape) +{ + QString tooltip = "Change brush settings

Currently selected: "; + + switch (brushShape) + { + case BrushShape_Point: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + tooltip += mShapeBrushWindow->toolTipPoint; + break; + + case BrushShape_Square: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + tooltip += mShapeBrushWindow->toolTipSquare; + break; + + case BrushShape_Circle: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + tooltip += mShapeBrushWindow->toolTipCircle; + break; + + case BrushShape_Custom: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom"))); + tooltip += mShapeBrushWindow->toolTipCustom; + break; + } + + setToolTip (tooltip); +} + +void CSVWidget::SceneToolShapeBrush::showPanel (const QPoint& position) +{ +} + +void CSVWidget::SceneToolShapeBrush::updatePanel () +{ +} + +void CSVWidget::SceneToolShapeBrush::clicked (const QModelIndex& index) +{ +} + +void CSVWidget::SceneToolShapeBrush::activate () +{ + QPoint position = QCursor::pos(); + mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mShapeBrushWindow->move (position); + mShapeBrushWindow->show(); +} + +void CSVWidget::SceneToolShapeBrush::dragEnterEvent (QDragEnterEvent *event) +{ + emit passEvent(event); + event->accept(); +} +void CSVWidget::SceneToolShapeBrush::dropEvent (QDropEvent *event) +{ + emit passEvent(event); + event->accept(); +} diff --git a/apps/opencs/view/widget/scenetoolshapebrush.hpp b/apps/opencs/view/widget/scenetoolshapebrush.hpp new file mode 100644 index 000000000..2c027baf0 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -0,0 +1,127 @@ +#ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H +#define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef Q_MOC_RUN +#include "brushshapes.hpp" +#include "scenetool.hpp" + +#include "../../model/doc/document.hpp" +#endif + +class QTableWidget; + +namespace CSVRender +{ + class TerrainShapeMode; +} + +namespace CSVWidget +{ + /// \brief Layout-box for some brush button settings + class ShapeBrushSizeControls : public QGroupBox + { + Q_OBJECT + + public: + ShapeBrushSizeControls(const QString &title, QWidget *parent); + + private: + QSlider *mBrushSizeSlider = new QSlider(Qt::Horizontal); + QSpinBox *mBrushSizeSpinBox = new QSpinBox; + + friend class SceneToolShapeBrush; + friend class CSVRender::TerrainShapeMode; + }; + + /// \brief Brush settings window + class ShapeBrushWindow : public QFrame + { + Q_OBJECT + + public: + + ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0); + void configureButtonInitialSettings(QPushButton *button); + + const QString toolTipPoint = "Paint single point"; + const QString toolTipSquare = "Paint with square brush"; + const QString toolTipCircle = "Paint with circle brush"; + const QString toolTipCustom = "Paint with custom brush, defined by terrain selection"; + + private: + CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point; + int mBrushSize = 1; + CSMDoc::Document& mDocument; + QGroupBox *mHorizontalGroupBox; + QComboBox *mToolSelector; + QSlider *mToolStrengthSlider; + QPushButton *mButtonPoint; + QPushButton *mButtonSquare; + QPushButton *mButtonCircle; + QPushButton *mButtonCustom; + ShapeBrushSizeControls* mSizeSliders; + + friend class SceneToolShapeBrush; + friend class CSVRender::TerrainShapeMode; + + public slots: + void setBrushShape(); + void setBrushSize(int brushSize); + + signals: + void passBrushSize (int brushSize); + void passBrushShape(CSVWidget::BrushShape brushShape); + }; + + class SceneToolShapeBrush : public SceneTool + { + Q_OBJECT + + QString mToolTip; + CSMDoc::Document& mDocument; + QFrame *mPanel; + QTableWidget *mTable; + ShapeBrushWindow *mShapeBrushWindow; + + private: + + void adjustToolTips(); + + public: + + SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document); + + virtual void showPanel (const QPoint& position); + void updatePanel (); + + void dropEvent (QDropEvent *event); + void dragEnterEvent (QDragEnterEvent *event); + + friend class CSVRender::TerrainShapeMode; + + public slots: + void setButtonIcon(CSVWidget::BrushShape brushShape); + void clicked (const QModelIndex& index); + virtual void activate(); + + signals: + void passEvent(QDropEvent *event); + void passEvent(QDragEnterEvent *event); + }; +} + +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index c8d31c41c..52af530f5 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -255,7 +255,7 @@ namespace ESMTerrain (*positions)[static_cast(vertX*numVerts + vertY)] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, (vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, - height); + height + getAlteredHeight(col, row)); if (normalData) { @@ -291,6 +291,8 @@ namespace ESMTerrain color.b() = 255; } + adjustColor(col, row, heightData, color); //Does nothing by default, override in OpenMW-CS + // Unlike normals, colors mostly connect seamlessly between cells, but not always... if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) fixColour(color, cellX, cellY, col, row, cache); @@ -521,13 +523,6 @@ namespace ESMTerrain } - float Storage::getVertexHeight(const ESM::Land::LandData* data, int x, int y) - { - assert(x < ESM::Land::LAND_SIZE); - assert(y < ESM::Land::LAND_SIZE); - return data->mHeights[y * ESM::Land::LAND_SIZE + x]; - } - const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) { LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); @@ -540,6 +535,15 @@ namespace ESMTerrain } } + void Storage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const + { + } + + float Storage::getAlteredHeight(int col, int row) const + { + return 0; + } + Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture) { OpenThreads::ScopedLock lock(mLayerInfoMutex); diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 613d2e218..65e531e5c 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,6 +1,8 @@ #ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H +#include + #include #include @@ -107,6 +109,13 @@ namespace ESMTerrain virtual int getBlendmapScale(float chunkSize); + float getVertexHeight (const ESM::Land::LandData* data, int x, int y) + { + assert(x < ESM::Land::LAND_SIZE); + assert(y < ESM::Land::LAND_SIZE); + return data->mHeights[y * ESM::Land::LAND_SIZE + x]; + } + private: const VFS::Manager* mVFS; @@ -114,10 +123,11 @@ namespace ESMTerrain inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y); - inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); + virtual void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const; + virtual float getAlteredHeight(int col, int row) const; + // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair