From 16138fc896002dc3925cdd3775bf49e1996bcecc Mon Sep 17 00:00:00 2001 From: Nelsson Huotari Date: Wed, 11 Sep 2019 12:59:15 +0300 Subject: [PATCH] Transient land shape editing --- CHANGELOG.md | 2 + apps/opencs/CMakeLists.txt | 4 +- apps/opencs/model/prefs/state.cpp | 6 +- apps/opencs/view/render/cell.cpp | 31 +- apps/opencs/view/render/cell.hpp | 12 + .../view/render/pagedworldspacewidget.cpp | 47 +- .../view/render/pagedworldspacewidget.hpp | 10 + apps/opencs/view/render/terrainselection.cpp | 12 +- apps/opencs/view/render/terrainshapemode.cpp | 1156 +++++++++++++++++ apps/opencs/view/render/terrainshapemode.hpp | 157 +++ apps/opencs/view/render/terrainstorage.cpp | 213 +++ apps/opencs/view/render/terrainstorage.hpp | 14 + .../view/render/unpagedworldspacewidget.cpp | 5 + .../view/render/unpagedworldspacewidget.hpp | 3 + apps/opencs/view/render/worldspacewidget.hpp | 3 + .../view/widget/scenetoolshapebrush.cpp | 265 ++++ .../view/widget/scenetoolshapebrush.hpp | 130 ++ components/esmterrain/storage.cpp | 102 -- components/esmterrain/storage.hpp | 124 +- 19 files changed, 2168 insertions(+), 128 deletions(-) create mode 100644 apps/opencs/view/render/terrainshapemode.cpp create mode 100644 apps/opencs/view/render/terrainshapemode.hpp create mode 100644 apps/opencs/view/widget/scenetoolshapebrush.cpp create mode 100644 apps/opencs/view/widget/scenetoolshapebrush.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index d369f2d25..ad8c814ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -184,6 +184,8 @@ Feature #4784: Launcher: Duplicate Content Lists Feature #4812: Support NiSwitchNode Feature #4836: Daytime node switch + Feature #4840: Editor: Transient terrain change support + Feature #????: Editor: Land shape editing, land selection Feature #4859: Make water reflections more configurable Feature #4882: Support for NiPalette node Feature #4887: Add openmw command option to set initial random seed 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..35a8ef2df 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(); @@ -248,6 +248,8 @@ void CSMPrefs::State::declare() addValues (landeditOutsideVisibleCell); declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50). setMin (1); + declareInt ("shapebrush-maximumsize", "Maximum texture brush size", 100). + setMin (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/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index a0c408df0..3915ee4fb 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); @@ -169,6 +169,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 +349,33 @@ 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::getAlteredHeights() +{ + return mTerrainStorage->getAlteredHeights(); +} + +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..36ac7ff81 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,16 @@ 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* getAlteredHeights(); + + 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..21624740e 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,46 @@ 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 0; +} + +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::getCellAlteredHeights(const CSMWorld::CellCoordinates& coords) +{ + std::map::iterator searchResult = mCells.find(coords); + if (searchResult != mCells.end()) return searchResult->second->getAlteredHeights(); + return nullptr; +} + +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() +{ + std::map::iterator iter (mCells.begin()); + + while (iter!=mCells.end()) + { + iter->second->resetAlteredHeights(); + ++iter; + } +} + 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..db93e8435 100644 --- a/apps/opencs/view/render/pagedworldspacewidget.hpp +++ b/apps/opencs/view/render/pagedworldspacewidget.hpp @@ -124,6 +124,16 @@ 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* getCellAlteredHeights(const CSMWorld::CellCoordinates& coords); + + 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..aa9125696 --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.cpp @@ -0,0 +1,1156 @@ +#include "terrainshapemode.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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 "object.hpp" // Something small needed regarding pointers from here () +#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), + mBrushSize(0), + mBrushShape(0), + mShapeBrushScenetool(0), + mDragMode(InteractionType_None), + mParentNode(parentNode), + mIsEditing(false), + mTotalDiffY(0), + mShapeEditTool(0), + mShapeEditToolStrength(0), + mTargetHeight(0) +{ +} + +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(int)), this, SLOT(setBrushShape(int))); + 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); + delete mShapeBrushScenetool; + mShapeBrushScenetool = 0; + } + EditMode::deactivate(toolbar); +} + +void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here +{ +} + +void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) +{ + mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + + if (hit.hit && hit.tag == 0) + { + } + 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()); + + mCellId = getWorldspaceWidget().getCellId (hit.worldPos); + + mDragMode = InteractionType_PrimaryEdit; + + if (hit.hit && hit.tag == 0) + { + mEditingPos = hit.worldPos; + mIsEditing = true; + if (mShapeEditTool == 4) + { + 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 * landSize + inCellX]; + } + } + + 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()); + std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos); + mTotalDiffY += diffY; + if (mIsEditing == true && mShapeEditTool == 0) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); + if (mIsEditing == true && mShapeEditTool > 0) 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 == true) + { + mTotalDiffY = 0; + mIsEditing = false; + } + + std::sort(mAlteredCells.begin(), mAlteredCells.end()); + std::set removeDuplicates(mAlteredCells.begin(), mAlteredCells.end()); + mAlteredCells.assign(removeDuplicates.begin(), removeDuplicates.end()); + + 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); + + QUndoStack& undoStack = document.getUndoStack(); + + 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(); + CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); + for(int i = 0; i < landSize; ++i) + { + for(int j = 0; j < landSize; ++j) + { + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (paged->getCellAlteredHeight(cellCoordinates, i, j)) + landShapeNew[j * landSize + i] = landShapePointer[j * landSize + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); + else + landShapeNew[j * landSize + i] = 0; + } + } + } + if (allowLandShapeEditing(cellId) == true) pushEditToCommand(landShapeNew, 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); + + for(int i = 0; i < landSize; ++i) + { + for(int j = 0; j < landSize; ++j) + { + float v1[3]; + float v2[3]; + float normal[3]; + float hyp; + + v1[0] = 128; + v1[1] = 0; + if (i < landSize - 1) v1[2] = landShapePointer[j*landSize+i+1] - landShapePointer[j*landSize+i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY())) == -1; + if (!noLand && !noCell) + v1[2] = landRightShapePointer[j*landSize+1] - landShapePointer[j*landSize+i]; + else + v1[2] = 0; + } + + v2[0] = 0; + v2[1] = 128; + if (j < landSize - 1) v2[2] = landShapePointer[(j+1)*landSize+i] - landShapePointer[j*landSize+i]; + else + { + bool noCell = document.getData().getCells().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + bool noLand = document.getData().getLand().searchId (CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1)) == -1; + if (!noLand && !noCell) + v2[2] = landDownShapePointer[landSize+i] - landShapePointer[j*landSize+i]; + else + v2[2] = 0; + } + + normal[1] = v1[2]*v2[0] - v1[0]*v2[2]; + normal[0] = v1[1]*v2[2] - v1[2]*v2[1]; + normal[2] = v1[0]*v2[1] - v1[1]*v2[0]; + + hyp = sqrt(normal[0]*normal[0] + normal[1]*normal[1] + normal[2]*normal[2]) / 127.0f; + + normal[0] /= hyp; + normal[1] /= hyp; + normal[2] /= hyp; + + landNormalsNew[(j*landSize+i)*3+0] = normal[0]; + landNormalsNew[(j*landSize+i)*3+1] = normal[1]; + landNormalsNew[(j*landSize+i)*3+2] = normal[2]; + } + } + if (allowLandShapeEditing(cellId) == true) pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId); + } + undoStack.endMacro(); + mAlteredCells.clear(); + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + paged->resetAllAlteredHeights(); + mTotalDiffY = 0; + } + } +} + + +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::editTerrainShapeGrid(const std::pair& vertexCoords, bool dragOperation) +{ + int r = mBrushSize / 2; + + std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); + CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (mShapeEditTool == 0) paged->resetAllAlteredHeights(); + } + + if(allowLandShapeEditing(cellId)==true) + { + if (mBrushShape == 0) + { + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); + if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + + if (mBrushShape == 1) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + if (mShapeEditTool == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + if (mBrushShape == 2) + { + for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) + { + for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); + cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; + int distanceX = abs(i - vertexCoords.first); + int distanceY = abs(j - vertexCoords.second); + int distance = std::round(sqrt(pow(distanceX, 2)+pow(distanceY, 2))); + int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); + int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); + float distancePerRadius = 1.0f * distance / r; + float smoothedByDistance = 0.0f; + if (mShapeEditTool == 0) smoothedByDistance = mTotalDiffY - mTotalDiffY * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (mShapeEditTool == 1 || mShapeEditTool == 2) smoothedByDistance = (r + mShapeEditToolStrength) - (r + mShapeEditToolStrength) * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius); + if (distance < r) + { + if (mShapeEditTool >= 0 && mShapeEditTool < 3) alterHeight(cellCoords, x, y, smoothedByDistance); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + } + if (mBrushShape == 3) + { + if(!mCustomBrushShape.empty()) + { + for(auto const& value: mCustomBrushShape) + { + cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); + 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 == 0) alterHeight(cellCoords, x, y, mTotalDiffY); + if (mShapeEditTool == 1 || mShapeEditTool == 2) alterHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 3) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); + if (mShapeEditTool == 4) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); + } + } + } + + } +} + +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()); + 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(allowLandShapeEditing(cellId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords); + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (useTool) + { + if (mShapeEditTool == 0) + { + // 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 == 1) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + if (mShapeEditTool == 2) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; + if (mShapeEditTool == 3) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; + } + + paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); + + // Change values of cornering cells + if (inCellX == 0 && inCellY == 0) + { + if(allowLandShapeEditing(cellUpLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, -1)); + paged->setCellAlteredHeight(cellCoords.move(-1, -1), landSize - 1, landSize - 1, alteredHeight); + } + } + else if (inCellX == 0 && inCellY == landSize - 1) + { + if(allowLandShapeEditing(cellDownLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 1)); + paged->setCellAlteredHeight(cellCoords.move(-1, 1), landSize - 1, 0, alteredHeight); + } + } + else if (inCellX == landSize - 1 && inCellY == 0) + { + if(allowLandShapeEditing(cellUpRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, -1)); + paged->setCellAlteredHeight(cellCoords.move(1, -1), 0, landSize - 1, alteredHeight); + } + } + else if (inCellX == landSize - 1 && inCellY == landSize -1) + { + if(allowLandShapeEditing(cellDownRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 1)); + paged->setCellAlteredHeight(cellCoords.move(1, 1), 0, 0, alteredHeight); + } + } + + // Change values of edging cells + if (inCellX == 0) + { + if(allowLandShapeEditing(cellLeftId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(-1, 0)); + paged->setCellAlteredHeight(cellCoords.move(-1, 0), landSize - 1, inCellY, alteredHeight); + } + } + if (inCellY == 0) + { + if(allowLandShapeEditing(cellUpId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, -1)); + paged->setCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 1, alteredHeight); + } + } + + if (inCellX == landSize - 1) + { + if(allowLandShapeEditing(cellRightId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(1, 0)); + paged->setCellAlteredHeight(cellCoords.move(1, 0), 0, inCellY, alteredHeight); + } + } + if (inCellY == landSize - 1) + { + if(allowLandShapeEditing(cellUpId)==true) + { + if (useTool) mAlteredCells.emplace_back(cellCoords.move(0, 1)); + paged->setCellAlteredHeight(cellCoords.move(0, 1), 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 * landSize + 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)==true) + { + //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 * landSize + (landSize - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, landSize - 2)) + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 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[(landSize - 2) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2)) + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + } + if (inCellX > 0) + { + leftHeight = landShapePointer[inCellY * landSize + inCellX - 1]; + leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); + } + if (inCellY > 0) + { + upHeight = landShapePointer[(inCellY - 1) * landSize + inCellX]; + upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + } + if (inCellX == landSize - 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 * landSize + 1]; + if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) + { + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); + } + } + if (inCellY == landSize - 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 * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) + { + downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); + } + } + if (inCellX < landSize - 1) + { + rightHeight = landShapePointer[inCellY * landSize + inCellX + 1]; + if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) + rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); + } + if (inCellY < landSize - 1) + { + downHeight = landShapePointer[(inCellY + 1) * landSize + 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 > 8.0f) toolStrength = + abs(thisHeight + thisAlteredHeight - averageHeight); //Cut down excessive changes + 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()); + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell && !noLand) + { + 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 * landSize + 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, -toolStrength); + if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, +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) +{ + 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 cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + bool noLeftCell = document.getData().getCells().searchId (cellLeftId) == -1; + bool noLeftLand = document.getData().getLand().searchId (cellLeftId) == -1; + bool noUpCell = document.getData().getCells().searchId (cellUpId) == -1; + bool noUpLand = document.getData().getLand().searchId (cellUpId) == -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; + + if (CSVRender::PagedWorldspaceWidget *paged = + dynamic_cast (&getWorldspaceWidget())) + { + if (!noCell && !noLand) + { + 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 * landSize + inCellX] + *thisAlteredHeight; + + // In case of cell edge, and touching cell/land is not found, assume that left and up -heights would be the same + // This is to prevent unnecessary action at limitHeightChange() + *leftHeight = *thisHeight; + *upHeight = *thisHeight; + + if (inCellX == 0) + { + if(!noLeftCell && !noLeftLand) + { + const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = + landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value(); + *leftHeight = landLeftShapePointer[inCellY * landSize + (landSize - 2)]; + if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY)) + { + *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), landSize - 2, inCellY); + *leftHeight += *leftAlteredHeight; + } + } + } + if (inCellY == 0) + { + cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); + if(!noUpCell && !noUpLand) + { + const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = + landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value(); + *upHeight = landUpShapePointer[(landSize - 2) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, landSize - 2)) + { + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, landSize - 2); + *upHeight += *upAlteredHeight; + } + } + } + if (inCellX != 0) + { + *leftHeight = landShapePointer[inCellY * landSize + 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) * landSize + inCellX]; + if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) + *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); + *upHeight += *upAlteredHeight; + } + } + } +} + +void CSVRender::TerrainShapeMode::fixEdges(const 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); + if (allowLandShapeEditing(cellId) == true) + { + 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 < landSize; ++i) + { + if(document.getData().getCells().searchId (cellLeftId) != -1 && document.getData().getLand().searchId (cellLeftId) != -1 && landShapePointer[i * landSize] != landLeftShapePointer[i * landSize + landSize - 1]) landShapeNew[i * landSize] = landLeftShapePointer[i * landSize + landSize - 1]; + if(document.getData().getCells().searchId (cellRightId) != -1 && document.getData().getLand().searchId (cellRightId) != -1 && landShapePointer[i * landSize + landSize - 1] != landRightShapePointer[i * landSize]) landShapeNew[i * landSize + landSize - 1] = landRightShapePointer[i * landSize]; + if(document.getData().getCells().searchId (cellUpId) != -1 && document.getData().getLand().searchId (cellUpId) != -1 && landShapePointer[i] != landUpShapePointer[(landSize - 1) * landSize + i]) landShapeNew[i] = landUpShapePointer[(landSize - 1) * landSize + i]; + if(document.getData().getCells().searchId (cellDownId) != -1 && document.getData().getLand().searchId (cellDownId) != -1 && landShapePointer[(landSize - 1) * landSize + i] != landDownShapePointer[i]) landShapeNew[(landSize - 1) * landSize + i] = landDownShapePointer[i]; + } + pushEditToCommand(landShapeNew, document, landTable, cellId); + } +} + +void CSVRender::TerrainShapeMode::limitAlteredHeights(const 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()); + + int limitHeightChange = 1024.0f; // Limited by save format + bool noCell = document.getData().getCells().searchId (cellId) == -1; + bool noLand = document.getData().getLand().searchId (cellId) == -1; + bool dataAlteredAtThisCell = false; + int maxPasses = 5; //Multiple passes are needed if there are consecutive height differences over the limit + + if (!noCell && !noLand) + { + const CSMWorld::LandHeightsColumn::DataType landShapePointer = + landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value(); + + for (int passes = 0; passes < maxPasses; ++passes) + { + for(int inCellX = 0; inCellX < landSize; ++inCellX) + { + for(int inCellY = 0; inCellY < landSize; ++inCellY) + { + // ### Variable naming key ### + // Variables here aim to hold the final value, which is (real_value + altered_value) + // this = this Cell + // left = x - 1, up = y - 1 + // Altered = transient edit (in current edited) + // Binding = the last row or column, the one that binds cell land together + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight); + + bool doChange = false; + + // Check for height limits, prioritize left over up, except in left-right -cell edges + if (thisHeight - upHeight >= limitHeightChange) + { + thisAlteredHeight = upHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - upHeight <= -limitHeightChange) + { + thisAlteredHeight = upHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight + (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight - (limitHeightChange / 2) - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + + // Apply limits + if (doChange == true) + { + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); + dataAlteredAtThisCell = true; + } + } + } + + //Skip unneeded extra passes + if (dataAlteredAtThisCell == false) + { + passes = maxPasses; + continue; + } + + //Inverse check (implement properly after default check works) + for(int inCellX = landSize - 1; inCellX >= 0; --inCellX) + { + for(int inCellY = landSize - 1; inCellY >= 0; --inCellY) + { + // ### Variable naming key ### + // Variables here aim to hold the final value, which is (real_value + altered_value) + // this = this Cell + // left = x - 1, up = y - 1 + // Altered = transient edit (in current edited) + // Binding = the last row or column, the one that binds cell land together + + float thisHeight = 0.0f; + float thisAlteredHeight = 0.0f; + float leftHeight = 0.0f; + float leftAlteredHeight = 0.0f; + float upHeight = 0.0f; + float upAlteredHeight = 0.0f; + + updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, + &upHeight, &upAlteredHeight); + + bool doChange = false; + + // Check for height limits, prioritize left over up, except in left-right -cell edges + if (thisHeight - upHeight >= limitHeightChange) + { + thisAlteredHeight = upHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - upHeight <= -limitHeightChange) + { + thisAlteredHeight = upHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight >= limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight + limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + if (thisHeight - leftHeight <= -limitHeightChange && !(doChange == true && (inCellX == 0 || inCellX == landSize - 1))) + { + thisAlteredHeight = leftHeight - limitHeightChange - landShapePointer[inCellY * landSize + inCellX]; + doChange = true; + } + + // Apply limits + if (doChange == true) + { + alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight, false); + dataAlteredAtThisCell = true; + } + } + } + } + } +} + +void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair& vertexCoords, unsigned char selectMode, bool dragOperation) +{ + int r = mBrushSize / 2; + std::vector> selections; + + if (mBrushShape == 0) + { + selections.emplace_back(vertexCoords); + } + + if (mBrushShape == 1) + { + 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 == 2) + { + 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 == 3) + { + 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, 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, 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)); +} + +bool CSVRender::TerrainShapeMode::allowLandShapeEditing(std::string cellId) +{ + 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)); + + bool noCell = document.getData().getCells().searchId (cellId)==-1; + bool noLand = document.getData().getLand().searchId (cellId)==-1; + + if (noCell) + { + 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") + { + 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") + { + selection.add (CSMWorld::CellCoordinates::fromId (cellId).first); + paged->setCellSelection (selection); + } + } + } + + if (noLand) + { + 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") + { + document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId)); + fixEdges(CSMWorld::CellCoordinates::fromId (cellId).first); + } + } + + return true; +} + +void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event) +{ +} + +void CSVRender::TerrainShapeMode::setBrushSize(int brushSize) +{ + mBrushSize = brushSize; +} + +void CSVRender::TerrainShapeMode::setBrushShape(int brushShape) +{ + mBrushShape = brushShape; + + //Set custom brush shape + if (mBrushShape == 3 && !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..f910b9ecd --- /dev/null +++ b/apps/opencs/view/render/terrainshapemode.hpp @@ -0,0 +1,157 @@ +#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" +#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 + }; + + /// Editmode for terrain shape grid + TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr); + + void primaryOpenPressed (const WorldspaceHitResult& hit); + + /// Create single command for one-click shape editing + void primaryEditPressed (const WorldspaceHitResult& hit); + + /// Open brush settings window + void primarySelectPressed(const WorldspaceHitResult&); + + void secondarySelectPressed(const WorldspaceHitResult&); + + void activate(CSVWidget::SceneToolbar*); + void deactivate(CSVWidget::SceneToolbar*); + + /// Start shape editing command macro + virtual bool primaryEditStartDrag (const QPoint& pos); + + virtual bool secondaryEditStartDrag (const QPoint& pos); + virtual bool primarySelectStartDrag (const QPoint& pos); + virtual bool secondarySelectStartDrag (const QPoint& pos); + + /// Handle shape edit behavior during dragging + virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor); + + /// End shape editing command macro + virtual void dragCompleted(const QPoint& pos); + + /// Cancel shape editing, and reset all pending changes + virtual void dragAborted(); + + virtual void dragWheel (int diff, double speedFactor); + virtual void dragMoveEvent (QDragMoveEvent *event); + + /// Handle brush mechanics for shape editing + void editTerrainShapeGrid (const std::pair& vertexCoords, bool dragOperation); + + /// 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); + + /// Update the key values used in height calculations + void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, + float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight); + + /// Bind edge vertices to next cells + void fixEdges(const CSMWorld::CellCoordinates& cellCoords); + + /// Check that the edit doesn't break save format limits, fix if necessary + void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords); + + /// 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, std::string cellId); + + /// Push land normals edits to command macro + void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document, + CSMWorld::IdTable& landTable, std::string cellId); + + /// Create new cell and land if needed + bool allowLandShapeEditing(std::string textureFileName); + + private: + std::string mCellId; + std::string mBrushTexture; + int mBrushSize; + int mBrushShape; + std::vector> mCustomBrushShape; + CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool; + int mDragMode; + osg::Group* mParentNode; + bool mIsEditing; + std::unique_ptr mTerrainShapeSelection; + int mTotalDiffY; + std::vector mAlteredCells; + osg::Vec3d mEditingPos; + int mShapeEditTool; + int mShapeEditToolStrength; + int mTargetHeight; + + const int cellSize {ESM::Land::REAL_SIZE}; + const int landSize {ESM::Land::LAND_SIZE}; + const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE}; + + PagedWorldspaceWidget& getPagedWorldspaceWidget(); + + signals: + void passBrushTexture(std::string brushTexture); + + public slots: + //void handleDropEvent(QDropEvent *event); + void setBrushSize(int brushSize); + void setBrushShape(int 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..774c204ed 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,15 +1,25 @@ #include "terrainstorage.hpp" +#include +#include + #include "../../model/world/land.hpp" #include "../../model/world/landtexture.hpp" +#include +#include +#include +#include + namespace CSVRender { + const float defaultHeight = ESM::Land::DEFAULT_HEIGHT; TerrainStorage::TerrainStorage(const CSMWorld::Data &data) : ESMTerrain::Storage(data.getResourceSystem()->getVFS()) , mData(data) { + resetHeights(); } osg::ref_ptr TerrainStorage::getLand(int cellX, int cellY) @@ -33,6 +43,209 @@ 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() + { + for (int x = 0; x < ESM::Land::LAND_SIZE; ++x) + { + for (int y = 0; y < ESM::Land::LAND_SIZE; ++y) + { + mAlteredHeight[y*ESM::Land::LAND_SIZE + x] = 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) : 0; + if (data) height = getVertexHeight(data, inCellX, inCellY); + } + else return height; + return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height; + + } + + float* TerrainStorage::getAlteredHeights() + { + return mAlteredHeight; + } + + float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY) + { + return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX]; + } + + void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) + { + // LOD level n means every 2^n-th vertex is kept + size_t increment = static_cast(1) << lodLevel; + + osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f); + + int startCellX = static_cast(std::floor(origin.x())); + int startCellY = static_cast(std::floor(origin.y())); + + size_t numVerts = static_cast(size*(ESM::Land::LAND_SIZE - 1) / increment + 1); + + positions->resize(numVerts*numVerts); + normals->resize(numVerts*numVerts); + colours->resize(numVerts*numVerts); + + osg::Vec3f normal; + osg::Vec4ub color; + + float vertY = 0; + float vertX = 0; + + ESMTerrain::LandCache cache; + + float vertY_ = 0; // of current cell corner + for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) + { + float vertX_ = 0; // of current cell corner + for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) + { + const ESMTerrain::LandObject* land = ESMTerrain::Storage::getLand(cellX, cellY, cache); + const ESM::Land::LandData *heightData = 0; + const ESM::Land::LandData *normalData = 0; + const ESM::Land::LandData *colourData = 0; + if (land) + { + heightData = land->getData(ESM::Land::DATA_VHGT); + normalData = land->getData(ESM::Land::DATA_VNML); + colourData = land->getData(ESM::Land::DATA_VCLR); + } + + int rowStart = 0; + int colStart = 0; + // Skip the first row / column unless we're at a chunk edge, + // since this row / column is already contained in a previous cell + // This is only relevant if we're creating a chunk spanning multiple cells + if (vertY_ != 0) + colStart += increment; + if (vertX_ != 0) + rowStart += increment; + + // Only relevant for chunks smaller than (contained in) one cell + rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; + colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; + int rowEnd = std::min(static_cast(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); + int colEnd = std::min(static_cast(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast(ESM::Land::LAND_SIZE)); + + vertY = vertY_; + for (int col=colStart; col= 0 && row < ESM::Land::LAND_SIZE); + assert(col >= 0 && col < ESM::Land::LAND_SIZE); + + assert (vertX < numVerts); + assert (vertY < numVerts); + + float height = defaultHeight; + if (heightData) + height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row]; + + (*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 + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)]); + + if (normalData) + { + for (int i=0; i<3; ++i) + normal[i] = normalData->mNormals[srcArrayIndex+i]; + + normal.normalize(); + } + else + normal = osg::Vec3f(0,0,1); + + // Normals apparently don't connect seamlessly between cells + if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1) + fixNormal(normal, cellX, cellY, col, row, cache); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1)) + averageNormal(normal, cellX, cellY, col, row, cache); + + assert(normal.z() > 0); + + (*normals)[static_cast(vertX*numVerts + vertY)] = normal; + + if (colourData) + { + for (int i=0; i<3; ++i) + color[i] = colourData->mColours[srcArrayIndex+i]; + } + else + { + color.r() = 255; + color.g() = 255; + color.b() = 255; + } + + // Highlight broken height changes + if ( ((col > 0 && row > 0) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) || + ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && + ((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] + + mAlteredHeight[static_cast((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) || + abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast(col*ESM::Land::LAND_SIZE + row)] - + (heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] + + mAlteredHeight[static_cast((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 ))) + { + color.r() = 255; + color.g() = 0; + color.b() = 0; + } + + // 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); + + color.a() = 255; + + (*colours)[static_cast(vertX*numVerts + vertY)] = color; + + ++vertX; + } + ++vertY; + } + vertX_ = vertX; + } + vertY_ = vertY; + + assert(vertX_ == numVerts); // Ensure we covered whole area + } + assert(vertY_ == numVerts); // Ensure we covered whole area*/ + } + 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 diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 6c3151e8d..4740f13bb 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -7,6 +7,7 @@ namespace CSVRender { + class LandCache; /** * @brief A bridge between the terrain component and OpenCS's terrain data storage. @@ -15,12 +16,25 @@ namespace CSVRender { public: TerrainStorage(const CSMWorld::Data& data); + float mAlteredHeight[ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE]; + void setAlteredHeight(int inCellX, int inCellY, float heightMap); + void resetHeights(); + float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY); + float* getAlteredHeights(); + float* getAlteredHeight(int inCellX, int inCellY); + private: const CSMWorld::Data& mData; virtual osg::ref_ptr getLand (int cellX, int cellY) override; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override; + /// Draws temporarily altered land (transient change support) + void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, + osg::ref_ptr positions, + osg::ref_ptr normals, + osg::ref_ptr colours) override; + virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; }; 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/scenetoolshapebrush.cpp b/apps/opencs/view/widget/scenetoolshapebrush.cpp new file mode 100644 index 000000000..a9f494bf3 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.cpp @@ -0,0 +1,265 @@ +#include "scenetoolshapebrush.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = new QSlider(Qt::Horizontal); + mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides); + mBrushSizeSlider->setTickInterval(10); + mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSlider->setSingleStep(1); + + mBrushSizeSpinBox = new QSpinBox; + mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt()); + mBrushSizeSpinBox->setSingleStep(1); + + mLayoutSliderSize = new QHBoxLayout; + mLayoutSliderSize->addWidget(mBrushSizeSlider); + mLayoutSliderSize->addWidget(mBrushSizeSpinBox); + + connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int))); + connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int))); + + setLayout(mLayoutSliderSize); +} + +CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent) + : QFrame(parent, Qt::Popup), + mBrushShape(0), + mBrushSize(0), + 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); + + 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 = 0; + if(mButtonSquare->isChecked()) mBrushShape = 1; + if(mButtonCircle->isChecked()) mBrushShape = 2; + if(mButtonCustom->isChecked()) mBrushShape = 3; + 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(int)), this, SLOT(setButtonIcon(int))); + 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 (int brushShape) +{ + QString tooltip = "Change brush settings

Currently selected: "; + + switch (brushShape) + { + case 0: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-point"))); + tooltip += mShapeBrushWindow->toolTipPoint; + break; + + case 1: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-square"))); + tooltip += mShapeBrushWindow->toolTipSquare; + break; + + case 2: + + setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle"))); + tooltip += mShapeBrushWindow->toolTipCircle; + break; + + case 3: + + 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..9048fa536 --- /dev/null +++ b/apps/opencs/view/widget/scenetoolshapebrush.hpp @@ -0,0 +1,130 @@ +#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 "scenetool.hpp" + +#include "../../model/doc/document.hpp" +#endif + +class QTableWidget; + +namespace CSVRender +{ + class TerrainShapeMode; +} + +namespace CSVWidget +{ + class SceneToolShapeBrush; + + /// \brief Layout-box for some brush button settings + class ShapeBrushSizeControls : public QGroupBox + { + Q_OBJECT + + public: + ShapeBrushSizeControls(const QString &title, QWidget *parent); + + private: + QHBoxLayout *mLayoutSliderSize; + QSlider *mBrushSizeSlider; + QSpinBox *mBrushSizeSpinBox; + + friend class SceneToolShapeBrush; + friend class CSVRender::TerrainShapeMode; + }; + + class SceneToolShapeBrush; + + /// \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 custom selection (not implemented yet)"; + + private: + int mBrushShape; + int mBrushSize; + 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(int 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(int 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..151f0ff91 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -16,13 +16,6 @@ namespace ESMTerrain { - class LandCache - { - public: - typedef std::map, osg::ref_ptr > Map; - Map mMap; - }; - LandObject::LandObject() : mLand(nullptr) , mLoadFlags(0) @@ -98,82 +91,6 @@ namespace ESMTerrain return false; } - void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - while (col >= ESM::Land::LAND_SIZE-1) - { - ++cellY; - col -= ESM::Land::LAND_SIZE-1; - } - while (row >= ESM::Land::LAND_SIZE-1) - { - ++cellX; - row -= ESM::Land::LAND_SIZE-1; - } - while (col < 0) - { - --cellY; - col += ESM::Land::LAND_SIZE-1; - } - while (row < 0) - { - --cellX; - row += ESM::Land::LAND_SIZE-1; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; - if (data) - { - normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; - normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; - normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; - normal.normalize(); - } - else - normal = osg::Vec3f(0,0,1); - } - - void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache) - { - osg::Vec3f n1,n2,n3,n4; - fixNormal(n1, cellX, cellY, col+1, row, cache); - fixNormal(n2, cellX, cellY, col-1, row, cache); - fixNormal(n3, cellX, cellY, col, row+1, cache); - fixNormal(n4, cellX, cellY, col, row-1, cache); - normal = (n1+n2+n3+n4); - normal.normalize(); - } - - void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) - { - if (col == ESM::Land::LAND_SIZE-1) - { - ++cellY; - col = 0; - } - if (row == ESM::Land::LAND_SIZE-1) - { - ++cellX; - row = 0; - } - - const LandObject* land = getLand(cellX, cellY, cache); - const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; - if (data) - { - color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; - color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; - color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; - } - else - { - color.r() = 255; - color.g() = 255; - color.b() = 255; - } - } - void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center, osg::ref_ptr positions, osg::ref_ptr normals, @@ -521,25 +438,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)); - if (found != cache.mMap.end()) - return found->second; - else - { - found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; - return found->second; - } - } - 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..79c75471f 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,8 +1,13 @@ #ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H #define COMPONENTS_ESM_TERRAIN_STORAGE_H +#include + #include +#include +#include + #include #include @@ -13,10 +18,13 @@ namespace VFS class Manager; } -namespace ESMTerrain +namespace CSVRender { + class TerrainStorage; +} - class LandCache; +namespace ESMTerrain +{ /// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use class LandObject : public osg::Object @@ -48,6 +56,13 @@ namespace ESMTerrain ESM::Land::LandData mData; }; + class LandCache + { + public: + typedef std::map, osg::ref_ptr > Map; + Map mMap; + }; + /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// into the terrain component, converting it on the fly as needed. class Storage : public Terrain::Storage @@ -110,14 +125,6 @@ namespace ESMTerrain private: const VFS::Manager* mVFS; - inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); - 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); - // Since plugins can define new texture palettes, we need to know the plugin index too // in order to retrieve the correct texture name. // pair @@ -137,6 +144,103 @@ namespace ESMTerrain bool mAutoUseSpecularMaps; Terrain::LayerInfo getLayerInfo(const std::string& texture); + + protected: + + inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + while (col >= ESM::Land::LAND_SIZE-1) + { + ++cellY; + col -= ESM::Land::LAND_SIZE-1; + } + while (row >= ESM::Land::LAND_SIZE-1) + { + ++cellX; + row -= ESM::Land::LAND_SIZE-1; + } + while (col < 0) + { + --cellY; + col += ESM::Land::LAND_SIZE-1; + } + while (row < 0) + { + --cellX; + row += ESM::Land::LAND_SIZE-1; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0; + if (data) + { + normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3]; + normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1]; + normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2]; + normal.normalize(); + } + else + normal = osg::Vec3f(0,0,1); + }; + + inline void fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) + { + if (col == ESM::Land::LAND_SIZE-1) + { + ++cellY; + col = 0; + } + if (row == ESM::Land::LAND_SIZE-1) + { + ++cellX; + row = 0; + } + + const LandObject* land = getLand(cellX, cellY, cache); + const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0; + if (data) + { + color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3]; + color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1]; + color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2]; + } + else + { + color.r() = 255; + color.g() = 255; + color.b() = 255; + } + }; + + inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) + { + osg::Vec3f n1,n2,n3,n4; + fixNormal(n1, cellX, cellY, col+1, row, cache); + fixNormal(n2, cellX, cellY, col-1, row, cache); + fixNormal(n3, cellX, cellY, col, row+1, cache); + fixNormal(n4, cellX, cellY, col, row-1, cache); + normal = (n1+n2+n3+n4); + normal.normalize(); + }; + + inline 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]; + }; + + inline const LandObject* getLand(int cellX, int cellY, LandCache& cache) + { + LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); + if (found != cache.mMap.end()) + return found->second; + else + { + found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; + return found->second; + } + }; }; }