mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-20 16:53:55 +00:00
716eebe616
Fix terrain selection crash See merge request OpenMW/openmw!1165 (cherry picked from commitec4e3b04a7
)b84e41bd
Avoid storing ref, dynamic cast worldspacewidget for safetyd62ddc00
Use QPointer to detect object existence, less verbose debug messagescb42b528
Remove friend, make getEditMode public to allow editmode testing.4b148180
Restucture code08f7c73e
Fix text
1506 lines
74 KiB
C++
1506 lines
74 KiB
C++
#include "terrainshapemode.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <sstream>
|
|
#include <memory>
|
|
|
|
#include <QWidget>
|
|
#include <QIcon>
|
|
#include <QEvent>
|
|
#include <QDropEvent>
|
|
#include <QDragEnterEvent>
|
|
#include <QDrag>
|
|
|
|
#include <osg/Group>
|
|
#include <osg/Vec3f>
|
|
|
|
#include <components/esm/loadland.hpp>
|
|
#include <components/debug/debuglog.hpp>
|
|
|
|
#include "../widget/brushshapes.hpp"
|
|
#include "../widget/modebutton.hpp"
|
|
#include "../widget/scenetoolbar.hpp"
|
|
#include "../widget/scenetoolshapebrush.hpp"
|
|
|
|
#include "../../model/doc/document.hpp"
|
|
#include "../../model/prefs/state.hpp"
|
|
#include "../../model/world/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/tablemimedata.hpp"
|
|
#include "../../model/world/universalid.hpp"
|
|
|
|
#include "brushdraw.hpp"
|
|
#include "commands.hpp"
|
|
#include "editmode.hpp"
|
|
#include "pagedworldspacewidget.hpp"
|
|
#include "mask.hpp"
|
|
#include "tagbase.hpp"
|
|
#include "terrainselection.hpp"
|
|
#include "worldspacewidget.hpp"
|
|
|
|
CSVRender::TerrainShapeMode::TerrainShapeMode (WorldspaceWidget *worldspaceWidget, osg::Group* parentNode, QWidget *parent)
|
|
: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-shape"}, Mask_Terrain, "Terrain land editing", parent),
|
|
mParentNode(parentNode)
|
|
{
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar)
|
|
{
|
|
if (!mTerrainShapeSelection)
|
|
{
|
|
mTerrainShapeSelection.reset(new TerrainSelection(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape));
|
|
}
|
|
|
|
if(!mShapeBrushScenetool)
|
|
{
|
|
mShapeBrushScenetool = new CSVWidget::SceneToolShapeBrush (toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument());
|
|
connect(mShapeBrushScenetool, SIGNAL (clicked()), mShapeBrushScenetool, SLOT (activate()));
|
|
connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushSize(int)), this, SLOT(setBrushSize(int)));
|
|
connect(mShapeBrushScenetool->mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setBrushShape(CSVWidget::BrushShape)));
|
|
connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(setBrushSize(int)));
|
|
connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(setShapeEditTool(int)));
|
|
connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, SIGNAL(valueChanged(int)), this, SLOT(setShapeEditToolStrength(int)));
|
|
}
|
|
|
|
if (!mBrushDraw)
|
|
mBrushDraw.reset(new BrushDraw(mParentNode));
|
|
|
|
EditMode::activate(toolbar);
|
|
toolbar->addTool (mShapeBrushScenetool);
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar)
|
|
{
|
|
if(mShapeBrushScenetool)
|
|
{
|
|
toolbar->removeTool (mShapeBrushScenetool);
|
|
}
|
|
|
|
if (mTerrainShapeSelection)
|
|
{
|
|
mTerrainShapeSelection.reset();
|
|
}
|
|
|
|
if (mBrushDraw)
|
|
mBrushDraw.reset();
|
|
|
|
EditMode::deactivate(toolbar);
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::primaryOpenPressed (const WorldspaceHitResult& hit) // Apply changes here
|
|
{
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit)
|
|
{
|
|
if (hit.hit && hit.tag == nullptr)
|
|
{
|
|
if (mShapeEditTool == ShapeEditTool_Flatten)
|
|
setFlattenToolTargetHeight(hit);
|
|
if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag)
|
|
{
|
|
editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true);
|
|
applyTerrainEditChanges();
|
|
}
|
|
|
|
if (mDragMode == InteractionType_PrimarySelect)
|
|
{
|
|
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true);
|
|
}
|
|
|
|
if (mDragMode == InteractionType_SecondarySelect)
|
|
{
|
|
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true);
|
|
}
|
|
}
|
|
clearTransientEdits();
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit)
|
|
{
|
|
if(hit.hit && hit.tag == nullptr)
|
|
{
|
|
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, false);
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit)
|
|
{
|
|
if(hit.hit && hit.tag == nullptr)
|
|
{
|
|
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, false);
|
|
}
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::primaryEditStartDrag (const QPoint& pos)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
|
|
|
mDragMode = InteractionType_PrimaryEdit;
|
|
|
|
if (hit.hit && hit.tag == nullptr)
|
|
{
|
|
mEditingPos = hit.worldPos;
|
|
mIsEditing = true;
|
|
if (mShapeEditTool == ShapeEditTool_Flatten)
|
|
setFlattenToolTargetHeight(hit);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::secondaryEditStartDrag (const QPoint& pos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::primarySelectStartDrag (const QPoint& pos)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
|
mDragMode = InteractionType_PrimarySelect;
|
|
if (!hit.hit || hit.tag != nullptr)
|
|
{
|
|
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 != nullptr)
|
|
{
|
|
mDragMode = InteractionType_None;
|
|
return false;
|
|
}
|
|
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true);
|
|
return false;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor)
|
|
{
|
|
if (mDragMode == InteractionType_PrimaryEdit)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
|
mTotalDiffY += diffY;
|
|
if (mIsEditing)
|
|
{
|
|
if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true);
|
|
else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true);
|
|
}
|
|
}
|
|
|
|
if (mDragMode == InteractionType_PrimarySelect)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
|
if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0, true);
|
|
}
|
|
|
|
if (mDragMode == InteractionType_SecondarySelect)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick (pos, getWorldspaceWidget().getInteractionMask());
|
|
if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1, true);
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos)
|
|
{
|
|
if (mDragMode == InteractionType_PrimaryEdit)
|
|
{
|
|
applyTerrainEditChanges();
|
|
clearTransientEdits();
|
|
}
|
|
}
|
|
|
|
|
|
void CSVRender::TerrainShapeMode::dragAborted()
|
|
{
|
|
clearTransientEdits();
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::dragWheel (int diff, double speedFactor)
|
|
{
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells()
|
|
{
|
|
bool passing = false;
|
|
int passes = 0;
|
|
|
|
std::sort(mAlteredCells.begin(), mAlteredCells.end());
|
|
mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end());
|
|
|
|
while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously
|
|
{
|
|
passing = true;
|
|
for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells)
|
|
{
|
|
limitAlteredHeights(cellCoordinates);
|
|
}
|
|
std::reverse(mAlteredCells.begin(), mAlteredCells.end()); //Instead of alphabetical order, this should be fixed to sort cells by cell coordinates
|
|
for(CSMWorld::CellCoordinates cellCoordinates: mAlteredCells)
|
|
{
|
|
if (!limitAlteredHeights(cellCoordinates, true)) passing = false;
|
|
}
|
|
++passes;
|
|
if (passes > 2)
|
|
{
|
|
Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has failed, edit has been discarded.";
|
|
clearTransientEdits();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::clearTransientEdits()
|
|
{
|
|
mTotalDiffY = 0;
|
|
mIsEditing = false;
|
|
mAlteredCells.clear();
|
|
if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
paged->resetAllAlteredHeights();
|
|
mTerrainShapeSelection->update();
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::applyTerrainEditChanges()
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*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();
|
|
|
|
sortAndLimitAlteredCells();
|
|
|
|
undoStack.beginMacro ("Edit shape and normal records");
|
|
|
|
// One command at the beginning of the macro for redrawing the terrain-selection grid when undoing the changes.
|
|
undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
|
|
|
|
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>();
|
|
CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer);
|
|
CSVRender::PagedWorldspaceWidget *paged = dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget());
|
|
|
|
// Generate land height record
|
|
for(int i = 0; i < ESM::Land::LAND_SIZE; ++i)
|
|
{
|
|
for(int j = 0; j < ESM::Land::LAND_SIZE; ++j)
|
|
{
|
|
if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j))
|
|
landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j);
|
|
else
|
|
landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0;
|
|
}
|
|
}
|
|
|
|
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<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value<CSMWorld::LandNormalsColumn::DataType>();
|
|
CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer);
|
|
|
|
// Generate land normals record
|
|
for(int i = 0; i < ESM::Land::LAND_SIZE; ++i)
|
|
{
|
|
for(int j = 0; j < ESM::Land::LAND_SIZE; ++j)
|
|
{
|
|
osg::Vec3f v1(128, 0, 0);
|
|
osg::Vec3f v2(0, 128, 0);
|
|
|
|
if (i < ESM::Land::LAND_SIZE - 1) v1.z() = landShapePointer[j * ESM::Land::LAND_SIZE + i + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i];
|
|
else
|
|
{
|
|
std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY());
|
|
if (isLandLoaded(shiftedCellId))
|
|
v1.z() = landRightShapePointer[j * ESM::Land::LAND_SIZE + 1] - landShapePointer[j * ESM::Land::LAND_SIZE + i];
|
|
}
|
|
|
|
if (j < ESM::Land::LAND_SIZE - 1) v2.z() = landShapePointer[(j + 1) * ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i];
|
|
else
|
|
{
|
|
std::string shiftedCellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1);
|
|
if (isLandLoaded(shiftedCellId))
|
|
v2.z() = landDownShapePointer[ESM::Land::LAND_SIZE + i] - landShapePointer[j * ESM::Land::LAND_SIZE + i];
|
|
}
|
|
|
|
osg::Vec3f normal = v1 ^ v2;
|
|
const float hyp = normal.length() / 127.0f;
|
|
|
|
normal /= hyp;
|
|
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = normal.x();
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = normal.y();
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = normal.z();
|
|
}
|
|
}
|
|
pushNormalsEditToCommand(landNormalsNew, document, landTable, cellId);
|
|
}
|
|
// One command at the end of the macro for redrawing the terrain-selection grid when redoing the changes.
|
|
undoStack.push(new DrawTerrainSelectionCommand(&getWorldspaceWidget()));
|
|
|
|
undoStack.endMacro();
|
|
clearTransientEdits();
|
|
}
|
|
|
|
float CSVRender::TerrainShapeMode::calculateBumpShape(float distance, int radius, float height)
|
|
{
|
|
float distancePerRadius = distance / radius;
|
|
return height - height * (3 * distancePerRadius * distancePerRadius - 2 * distancePerRadius * distancePerRadius * distancePerRadius);
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::editTerrainShapeGrid(const std::pair<int, int>& vertexCoords, bool dragOperation)
|
|
{
|
|
int r = mBrushSize / 2;
|
|
if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1
|
|
|
|
if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights();
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Point)
|
|
{
|
|
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords);
|
|
CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first;
|
|
int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first);
|
|
int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second);
|
|
if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY);
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower)
|
|
{
|
|
alterHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
float smoothMultiplier = static_cast<float>(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble());
|
|
if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier);
|
|
}
|
|
if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight);
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Square)
|
|
{
|
|
for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i)
|
|
{
|
|
for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j)
|
|
{
|
|
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j));
|
|
CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first;
|
|
int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i);
|
|
int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j);
|
|
if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY);
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower)
|
|
{
|
|
alterHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
float smoothMultiplier = static_cast<float>(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble());
|
|
if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier);
|
|
}
|
|
if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Circle)
|
|
{
|
|
for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i)
|
|
{
|
|
for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j)
|
|
{
|
|
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j));
|
|
CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first;
|
|
int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i);
|
|
int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j);
|
|
int distanceX = abs(i - vertexCoords.first);
|
|
int distanceY = abs(j - vertexCoords.second);
|
|
float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2));
|
|
float smoothedByDistance = 0.0f;
|
|
if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY);
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength);
|
|
|
|
// Using floating-point radius here to prevent selecting too few vertices.
|
|
if (distance <= mBrushSize / 2.0f)
|
|
{
|
|
if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance);
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower)
|
|
{
|
|
alterHeight(cellCoords, x, y, smoothedByDistance);
|
|
float smoothMultiplier = static_cast<float>(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble());
|
|
if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier);
|
|
}
|
|
if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (mBrushShape == CSVWidget::BrushShape_Custom)
|
|
{
|
|
if(!mCustomBrushShape.empty())
|
|
{
|
|
for(auto const& value: mCustomBrushShape)
|
|
{
|
|
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second));
|
|
CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first;
|
|
int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first);
|
|
int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second);
|
|
if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY);
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower)
|
|
{
|
|
alterHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
float smoothMultiplier = static_cast<float>(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble());
|
|
if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier);
|
|
}
|
|
if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength);
|
|
if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight);
|
|
}
|
|
}
|
|
}
|
|
mTerrainShapeSelection->update();
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHitResult& hit)
|
|
{
|
|
std::pair<int, int> 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<CSMWorld::IdTable&> (
|
|
*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<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
|
|
}
|
|
|
|
|
|
void CSVRender::TerrainShapeMode::alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool)
|
|
{
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
|
|
if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId)))))
|
|
return;
|
|
CSVRender::PagedWorldspaceWidget *paged = dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget());
|
|
if (!paged)
|
|
return;
|
|
|
|
std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
|
|
std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
|
|
std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
|
|
std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
|
|
std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1);
|
|
std::string cellUpRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() - 1);
|
|
std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1);
|
|
std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1);
|
|
|
|
if (useTool)
|
|
{
|
|
mAlteredCells.emplace_back(cellCoords);
|
|
if (mShapeEditTool == ShapeEditTool_Drag)
|
|
{
|
|
// Get distance from modified land, alter land change based on zoom
|
|
osg::Vec3d eye, center, up;
|
|
paged->getCamera()->getViewMatrixAsLookAt(eye, center, up);
|
|
osg::Vec3d distance = eye - mEditingPos;
|
|
alteredHeight = alteredHeight * (distance.length() / 500);
|
|
}
|
|
if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight;
|
|
if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight;
|
|
if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight;
|
|
}
|
|
|
|
if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1)
|
|
paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight);
|
|
|
|
// Change values of cornering cells
|
|
if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId)))
|
|
{
|
|
if(allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(cornerCellCoords);
|
|
paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight);
|
|
} else return;
|
|
}
|
|
else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId)))
|
|
{
|
|
if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(cornerCellCoords);
|
|
paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight);
|
|
} else return;
|
|
}
|
|
else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId)))
|
|
{
|
|
if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(cornerCellCoords);
|
|
paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight);
|
|
} else return;
|
|
}
|
|
else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId)))
|
|
{
|
|
if(allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(cornerCellCoords);
|
|
paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight);
|
|
} else return;
|
|
}
|
|
|
|
// Change values of edging cells
|
|
if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId)))
|
|
{
|
|
if(allowLandShapeEditing(cellLeftId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(edgeCellCoords);
|
|
paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight);
|
|
paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight);
|
|
}
|
|
}
|
|
if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId)))
|
|
{
|
|
if(allowLandShapeEditing(cellUpId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(edgeCellCoords);
|
|
paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight);
|
|
paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight);
|
|
}
|
|
}
|
|
|
|
if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId)))
|
|
{
|
|
if(allowLandShapeEditing(cellRightId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(edgeCellCoords);
|
|
paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight);
|
|
paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight);
|
|
}
|
|
}
|
|
if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId)))
|
|
{
|
|
if(allowLandShapeEditing(cellDownId, useTool))
|
|
{
|
|
CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1);
|
|
if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end())
|
|
mAlteredCells.emplace_back(edgeCellCoords);
|
|
paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight);
|
|
paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength)
|
|
{
|
|
if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*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<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
// ### Variable naming key ###
|
|
// Variables here hold either the real value, or the altered value of current edit.
|
|
// this = this Cell
|
|
// left = x - 1, up = y - 1, right = x + 1, down = y + 1
|
|
// Altered = transient edit (in current edited)
|
|
float thisAlteredHeight = 0.0f;
|
|
if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr)
|
|
thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY);
|
|
float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
|
|
float leftHeight = 0.0f;
|
|
float leftAlteredHeight = 0.0f;
|
|
float upAlteredHeight = 0.0f;
|
|
float rightHeight = 0.0f;
|
|
float rightAlteredHeight = 0.0f;
|
|
float downHeight = 0.0f;
|
|
float downAlteredHeight = 0.0f;
|
|
float upHeight = 0.0f;
|
|
|
|
if(allowLandShapeEditing(cellId))
|
|
{
|
|
//Get key values for calculating average, handle cell edges, check for null pointers
|
|
if (inCellX == 0)
|
|
{
|
|
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
|
|
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2))
|
|
leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY);
|
|
}
|
|
if (inCellY == 0)
|
|
{
|
|
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
|
|
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2))
|
|
upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2);
|
|
}
|
|
if (inCellX > 0)
|
|
{
|
|
leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1];
|
|
leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY);
|
|
}
|
|
if (inCellY > 0)
|
|
{
|
|
upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX];
|
|
upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1);
|
|
}
|
|
if (inCellX == ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
|
|
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY))
|
|
{
|
|
rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY);
|
|
}
|
|
}
|
|
if (inCellY == ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
|
|
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1))
|
|
{
|
|
downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1);
|
|
}
|
|
}
|
|
if (inCellX < ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1];
|
|
if(paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY))
|
|
rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY);
|
|
}
|
|
if (inCellY < ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX];
|
|
if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1))
|
|
downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1);
|
|
}
|
|
|
|
float averageHeight = (upHeight + downHeight + rightHeight + leftHeight +
|
|
upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4;
|
|
if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords);
|
|
if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight);
|
|
if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, - toolStrength);
|
|
if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, + toolStrength);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
|
|
|
|
float thisHeight = 0.0f;
|
|
float thisAlteredHeight = 0.0f;
|
|
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
|
|
if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
if (!noCell(cellId) && !noLand(cellId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY))
|
|
thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY);
|
|
thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
|
|
}
|
|
}
|
|
|
|
if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength =
|
|
abs(thisHeight - targetHeight); //Cut down excessive changes
|
|
if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength);
|
|
if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength);
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight,
|
|
float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight,
|
|
float* rightAlteredHeight, float* downHeight, float* downAlteredHeight)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
|
|
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
|
|
std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
|
|
std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
|
|
std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
|
|
|
|
*thisHeight = 0.0f; // real + altered height
|
|
*thisAlteredHeight = 0.0f; // only altered height
|
|
*leftHeight = 0.0f;
|
|
*leftAlteredHeight = 0.0f;
|
|
*upHeight = 0.0f;
|
|
*upAlteredHeight = 0.0f;
|
|
*rightHeight = 0.0f;
|
|
*rightAlteredHeight = 0.0f;
|
|
*downHeight = 0.0f;
|
|
*downAlteredHeight = 0.0f;
|
|
|
|
if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
if (!noCell(cellId) && !noLand(cellId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
if(paged->getCellAlteredHeight(cellCoords, inCellX, inCellY))
|
|
*thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY);
|
|
*thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight;
|
|
|
|
// Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is not found,
|
|
// which is to prevent unnecessary action at limitHeightChange().
|
|
*leftHeight = *thisHeight;
|
|
*upHeight = *thisHeight;
|
|
*rightHeight = *thisHeight;
|
|
*downHeight = *thisHeight;
|
|
|
|
//If at edge, get values from neighboring cell
|
|
if (inCellX == 0)
|
|
{
|
|
if(isLandLoaded(cellLeftId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
*leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY))
|
|
{
|
|
*leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY);
|
|
*leftHeight += *leftAlteredHeight;
|
|
}
|
|
}
|
|
}
|
|
if (inCellY == 0)
|
|
{
|
|
if(isLandLoaded(cellUpId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
*upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(0,-1), inCellX, ESM::Land::LAND_SIZE - 2))
|
|
{
|
|
*upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2);
|
|
*upHeight += *upAlteredHeight;
|
|
}
|
|
}
|
|
}
|
|
if (inCellX == ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
if(isLandLoaded(cellRightId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
*rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY))
|
|
{
|
|
*rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY);
|
|
*rightHeight += *rightAlteredHeight;
|
|
}
|
|
}
|
|
}
|
|
if (inCellY == ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
if(isLandLoaded(cellDownId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
*downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1))
|
|
{
|
|
*downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1);
|
|
*downHeight += *downAlteredHeight;
|
|
}
|
|
}
|
|
}
|
|
|
|
//If not at edge, get values from the same cell
|
|
if (inCellX != 0)
|
|
{
|
|
*leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1];
|
|
if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY))
|
|
*leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY);
|
|
*leftHeight += *leftAlteredHeight;
|
|
}
|
|
if (inCellY != 0)
|
|
{
|
|
*upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1))
|
|
*upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1);
|
|
*upHeight += *upAlteredHeight;
|
|
}
|
|
if (inCellX != ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
*rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1];
|
|
if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY))
|
|
*rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY);
|
|
*rightHeight += *rightAlteredHeight;
|
|
}
|
|
if (inCellY != ESM::Land::LAND_SIZE - 1)
|
|
{
|
|
*downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX];
|
|
if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1))
|
|
*downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1);
|
|
*downHeight += *downAlteredHeight;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits)
|
|
{
|
|
if (limitedAlteredHeightXAxis)
|
|
{
|
|
if (limitedAlteredHeightYAxis)
|
|
{
|
|
if(std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis))
|
|
{
|
|
alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false);
|
|
*steepnessIsWithinLimits = false;
|
|
}
|
|
else
|
|
{
|
|
alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false);
|
|
*steepnessIsWithinLimits = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false);
|
|
*steepnessIsWithinLimits = false;
|
|
}
|
|
}
|
|
else if (limitedAlteredHeightYAxis)
|
|
{
|
|
alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false);
|
|
*steepnessIsWithinLimits = false;
|
|
}
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land));
|
|
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
|
|
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
|
|
int limitHeightChange = 1016.0f; // Limited by save format
|
|
bool steepnessIsWithinLimits = true;
|
|
|
|
if (isLandLoaded(cellId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
float thisHeight = 0.0f;
|
|
float thisAlteredHeight = 0.0f;
|
|
float leftHeight = 0.0f;
|
|
float leftAlteredHeight = 0.0f;
|
|
float upHeight = 0.0f;
|
|
float upAlteredHeight = 0.0f;
|
|
float rightHeight = 0.0f;
|
|
float rightAlteredHeight = 0.0f;
|
|
float downHeight = 0.0f;
|
|
float downAlteredHeight = 0.0f;
|
|
|
|
if (!reverseMode)
|
|
{
|
|
for(int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY)
|
|
{
|
|
for(int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX)
|
|
{
|
|
std::unique_ptr<float> limitedAlteredHeightXAxis(nullptr);
|
|
std::unique_ptr<float> limitedAlteredHeightYAxis(nullptr);
|
|
updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight,
|
|
&upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight);
|
|
|
|
// Check for height limits on x-axis
|
|
if (leftHeight - thisHeight > limitHeightChange)
|
|
limitedAlteredHeightXAxis.reset(new float(leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
else if (leftHeight - thisHeight < -limitHeightChange)
|
|
limitedAlteredHeightXAxis.reset(new float(leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
|
|
// Check for height limits on y-axis
|
|
if (upHeight - thisHeight > limitHeightChange)
|
|
limitedAlteredHeightYAxis.reset(new float(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
else if (upHeight - thisHeight < -limitHeightChange)
|
|
limitedAlteredHeightYAxis.reset(new float(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
|
|
// Limit altered height value based on x or y, whichever is the smallest
|
|
compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (reverseMode)
|
|
{
|
|
for(int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY)
|
|
{
|
|
for(int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX)
|
|
{
|
|
std::unique_ptr<float> limitedAlteredHeightXAxis(nullptr);
|
|
std::unique_ptr<float> limitedAlteredHeightYAxis(nullptr);
|
|
updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight,
|
|
&upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight);
|
|
|
|
// Check for height limits on x-axis
|
|
if (rightHeight - thisHeight > limitHeightChange)
|
|
limitedAlteredHeightXAxis.reset(new float(rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
else if (rightHeight - thisHeight < -limitHeightChange)
|
|
limitedAlteredHeightXAxis.reset(new float(rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
|
|
// Check for height limits on y-axis
|
|
if (downHeight - thisHeight > limitHeightChange)
|
|
limitedAlteredHeightYAxis.reset(new float(downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
else if (downHeight - thisHeight < -limitHeightChange)
|
|
limitedAlteredHeightYAxis.reset(new float(downHeight + limitHeightChange - (thisHeight - thisAlteredHeight)));
|
|
|
|
// Limit altered height value based on x or y, whichever is the smallest
|
|
compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return steepnessIsWithinLimits;
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::isInCellSelection(int globalSelectionX, int globalSelectionY)
|
|
{
|
|
if (CSVRender::PagedWorldspaceWidget *paged = dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
std::pair<int, int> vertexCoords = std::make_pair(globalSelectionX, globalSelectionY);
|
|
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords);
|
|
return paged->getCellSelection().has(CSMWorld::CellCoordinates::fromId(cellId).first) && isLandLoaded(cellId);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::handleSelection(int globalSelectionX, int globalSelectionY, std::vector<std::pair<int, int>>* selections)
|
|
{
|
|
if (isInCellSelection(globalSelectionX, globalSelectionY)) selections->emplace_back(globalSelectionX, globalSelectionY);
|
|
else
|
|
{
|
|
int moduloX = globalSelectionX % (ESM::Land::LAND_SIZE - 1);
|
|
int moduloY = globalSelectionY % (ESM::Land::LAND_SIZE - 1);
|
|
bool xIsAtCellBorder = moduloX == 0;
|
|
bool yIsAtCellBorder = moduloY == 0;
|
|
if (!xIsAtCellBorder && !yIsAtCellBorder)
|
|
return;
|
|
int selectionX = globalSelectionX;
|
|
int selectionY = globalSelectionY;
|
|
|
|
/*
|
|
The northern and eastern edges don't belong to the current cell.
|
|
If the corresponding adjacent cell is not loaded, some special handling is necessary to select border vertices.
|
|
*/
|
|
if (xIsAtCellBorder && yIsAtCellBorder)
|
|
{
|
|
/*
|
|
Handle the NW, NE, and SE corner vertices.
|
|
NW corner: (+1, -1) offset to reach current cell.
|
|
NE corner: (-1, -1) offset to reach current cell.
|
|
SE corner: (-1, +1) offset to reach current cell.
|
|
*/
|
|
if (isInCellSelection(globalSelectionX - 1, globalSelectionY - 1)
|
|
|| isInCellSelection(globalSelectionX + 1, globalSelectionY - 1)
|
|
|| isInCellSelection(globalSelectionX - 1, globalSelectionY + 1))
|
|
{
|
|
selections->emplace_back(globalSelectionX, globalSelectionY);
|
|
}
|
|
}
|
|
else if (xIsAtCellBorder)
|
|
{
|
|
selectionX--;
|
|
}
|
|
else if (yIsAtCellBorder)
|
|
{
|
|
selectionY--;
|
|
}
|
|
|
|
if (isInCellSelection(selectionX, selectionY))
|
|
selections->emplace_back(globalSelectionX, globalSelectionY);
|
|
}
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::selectTerrainShapes(const std::pair<int, int>& vertexCoords, unsigned char selectMode, bool dragOperation)
|
|
{
|
|
int r = mBrushSize / 2;
|
|
std::vector<std::pair<int, int>> selections;
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Point)
|
|
{
|
|
handleSelection(vertexCoords.first, vertexCoords.second, &selections);
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Square)
|
|
{
|
|
for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i)
|
|
{
|
|
for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j)
|
|
{
|
|
handleSelection(i, j, &selections);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Circle)
|
|
{
|
|
for(int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i)
|
|
{
|
|
for(int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j)
|
|
{
|
|
int distanceX = abs(i - vertexCoords.first);
|
|
int distanceY = abs(j - vertexCoords.second);
|
|
float distance = sqrt(pow(distanceX, 2)+pow(distanceY, 2));
|
|
|
|
// Using floating-point radius here to prevent selecting too few vertices.
|
|
if (distance <= mBrushSize / 2.0f)
|
|
handleSelection(i, j, &selections);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mBrushShape == CSVWidget::BrushShape_Custom)
|
|
{
|
|
if(!mCustomBrushShape.empty())
|
|
{
|
|
for(auto const& value: mCustomBrushShape)
|
|
{
|
|
std::pair<int, int> localVertexCoords (vertexCoords.first + value.first, vertexCoords.second + value.second);
|
|
handleSelection(localVertexCoords.first, localVertexCoords.second, &selections);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::string selectAction;
|
|
|
|
if (selectMode == 0)
|
|
selectAction = CSMPrefs::get()["3D Scene Editing"]["primary-select-action"].toString();
|
|
else
|
|
selectAction = CSMPrefs::get()["3D Scene Editing"]["secondary-select-action"].toString();
|
|
|
|
if (selectAction == "Select only")
|
|
mTerrainShapeSelection->onlySelect(selections);
|
|
else if (selectAction == "Add to selection")
|
|
mTerrainShapeSelection->addSelect(selections, dragOperation);
|
|
else if (selectAction == "Remove from selection")
|
|
mTerrainShapeSelection->removeSelect(selections, dragOperation);
|
|
else if (selectAction == "Invert selection")
|
|
mTerrainShapeSelection->toggleSelect(selections, dragOperation);
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::pushEditToCommand(const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document,
|
|
CSMWorld::IdTable& landTable, const std::string& cellId)
|
|
{
|
|
QVariant changedLand;
|
|
changedLand.setValue(newLandGrid);
|
|
|
|
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex)));
|
|
|
|
QUndoStack& undoStack = document.getUndoStack();
|
|
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document,
|
|
CSMWorld::IdTable& landTable, const std::string& cellId)
|
|
{
|
|
QVariant changedLand;
|
|
changedLand.setValue(newLandGrid);
|
|
|
|
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex)));
|
|
|
|
QUndoStack& undoStack = document.getUndoStack();
|
|
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::noCell(const std::string& cellId)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
const CSMWorld::IdCollection<CSMWorld::Cell>& cellCollection = document.getData().getCells();
|
|
return cellCollection.searchId (cellId) == -1;
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::noLand(const std::string& cellId)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
const CSMWorld::IdCollection<CSMWorld::Land>& landCollection = document.getData().getLand();
|
|
return landCollection.searchId (cellId) == -1;
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::noLandLoaded(const std::string& cellId)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
const CSMWorld::IdCollection<CSMWorld::Land>& landCollection = document.getData().getLand();
|
|
return !landCollection.getRecord(cellId).get().isDataLoaded(ESM::Land::DATA_VNML);
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::isLandLoaded(const std::string& cellId)
|
|
{
|
|
if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId)) return true;
|
|
return false;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::createNewLandData(const CSMWorld::CellCoordinates& cellCoords)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
CSMWorld::IdTable& ltexTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));
|
|
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
|
|
int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex);
|
|
|
|
float defaultHeight = 0.f;
|
|
int averageDivider = 0;
|
|
CSMWorld::CellCoordinates cellLeftCoords = cellCoords.move(-1, 0);
|
|
CSMWorld::CellCoordinates cellRightCoords = cellCoords.move(1, 0);
|
|
CSMWorld::CellCoordinates cellUpCoords = cellCoords.move(0, -1);
|
|
CSMWorld::CellCoordinates cellDownCoords = cellCoords.move(0, 1);
|
|
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellLeftCoords.getX(), cellLeftCoords.getY());
|
|
std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellRightCoords.getX(), cellRightCoords.getY());
|
|
std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellUpCoords.getX(), cellUpCoords.getY());
|
|
std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellDownCoords.getX(), cellDownCoords.getY());
|
|
|
|
float leftCellSampleHeight = 0.0f;
|
|
float rightCellSampleHeight = 0.0f;
|
|
float upCellSampleHeight = 0.0f;
|
|
float downCellSampleHeight = 0.0f;
|
|
|
|
const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)).value<CSMWorld::LandNormalsColumn::DataType>();
|
|
CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer);
|
|
CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer);
|
|
|
|
if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
if (isLandLoaded(cellLeftId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
++averageDivider;
|
|
leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1];
|
|
if(paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2))
|
|
leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2);
|
|
}
|
|
if (isLandLoaded(cellRightId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
++averageDivider;
|
|
rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE];
|
|
if(paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2))
|
|
rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2);
|
|
}
|
|
if (isLandLoaded(cellUpId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
++averageDivider;
|
|
upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)];
|
|
if(paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1))
|
|
upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1);
|
|
}
|
|
if (isLandLoaded(cellDownId))
|
|
{
|
|
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer =
|
|
landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
++averageDivider;
|
|
downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2];
|
|
if(paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0))
|
|
downCellSampleHeight += *paged->getCellAlteredHeight(cellDownCoords, ESM::Land::LAND_SIZE / 2, 0);
|
|
}
|
|
}
|
|
if (averageDivider > 0) defaultHeight = (leftCellSampleHeight + rightCellSampleHeight + upCellSampleHeight + downCellSampleHeight) / averageDivider;
|
|
|
|
for(int i = 0; i < ESM::Land::LAND_SIZE; ++i)
|
|
{
|
|
for(int j = 0; j < ESM::Land::LAND_SIZE; ++j)
|
|
{
|
|
landShapeNew[j * ESM::Land::LAND_SIZE + i] = defaultHeight;
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 0] = 0;
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 1] = 0;
|
|
landNormalsNew[(j * ESM::Land::LAND_SIZE + i) * 3 + 2] = 127;
|
|
}
|
|
}
|
|
QVariant changedShape;
|
|
changedShape.setValue(landShapeNew);
|
|
QVariant changedNormals;
|
|
changedNormals.setValue(landNormalsNew);
|
|
QModelIndex indexShape(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex)));
|
|
QModelIndex indexNormal(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandNormalsIndex)));
|
|
document.getUndoStack().push (new CSMWorld::TouchLandCommand(landTable, ltexTable, cellId));
|
|
document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexShape, changedShape));
|
|
document.getUndoStack().push (new CSMWorld::ModifyCommand(landTable, indexNormal, changedNormals));
|
|
}
|
|
|
|
bool CSVRender::TerrainShapeMode::allowLandShapeEditing(const std::string& cellId, bool useTool)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
CSMWorld::IdTree& cellTable = dynamic_cast<CSMWorld::IdTree&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Cells));
|
|
|
|
if (noCell(cellId))
|
|
{
|
|
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
|
|
|
|
// target cell does not exist
|
|
if (mode=="Discard")
|
|
return false;
|
|
|
|
if (mode=="Create cell and land, then edit" && useTool)
|
|
{
|
|
std::unique_ptr<CSMWorld::CreateCommand> 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<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
CSMWorld::CellSelection selection = paged->getCellSelection();
|
|
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
|
|
paged->setCellSelection (selection);
|
|
}
|
|
}
|
|
}
|
|
else if (CSVRender::PagedWorldspaceWidget *paged =
|
|
dynamic_cast<CSVRender::PagedWorldspaceWidget *> (&getWorldspaceWidget()))
|
|
{
|
|
CSMWorld::CellSelection selection = paged->getCellSelection();
|
|
if (!selection.has (CSMWorld::CellCoordinates::fromId (cellId).first))
|
|
{
|
|
// target cell exists, but is not shown
|
|
std::string mode =
|
|
CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString();
|
|
|
|
if (mode=="Discard")
|
|
return false;
|
|
|
|
if (mode=="Show cell and edit" && useTool)
|
|
{
|
|
selection.add (CSMWorld::CellCoordinates::fromId (cellId).first);
|
|
paged->setCellSelection (selection);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (noLand(cellId))
|
|
{
|
|
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
|
|
|
|
// target cell does not exist
|
|
if (mode=="Discard")
|
|
return false;
|
|
|
|
if (mode=="Create cell and land, then edit" && useTool)
|
|
{
|
|
document.getUndoStack().push (new CSMWorld::CreateCommand (landTable, cellId));
|
|
createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first);
|
|
fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first);
|
|
sortAndLimitAlteredCells();
|
|
}
|
|
}
|
|
else if (noLandLoaded(cellId))
|
|
{
|
|
std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
|
|
|
|
if (mode=="Discard")
|
|
return false;
|
|
|
|
if (mode=="Create cell and land, then edit" && useTool)
|
|
{
|
|
createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first);
|
|
fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first);
|
|
sortAndLimitAlteredCells();
|
|
}
|
|
}
|
|
|
|
if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId)))
|
|
{
|
|
Log(Debug::Warning) << "Land creation failed at cell id: " << cellId;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords)
|
|
{
|
|
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
|
|
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
|
|
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
|
|
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
|
|
std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
|
|
std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY());
|
|
std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY());
|
|
std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1);
|
|
std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1);
|
|
|
|
const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
|
|
|
|
CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer);
|
|
for(int i = 0; i < ESM::Land::LAND_SIZE; ++i)
|
|
{
|
|
if (isLandLoaded(cellLeftId) &&
|
|
landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1])
|
|
landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1];
|
|
if (isLandLoaded(cellRightId) &&
|
|
landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE])
|
|
landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE];
|
|
if (isLandLoaded(cellUpId) &&
|
|
landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i])
|
|
landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i];
|
|
if (isLandLoaded(cellDownId) &&
|
|
landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i])
|
|
landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i];
|
|
}
|
|
|
|
QVariant changedLand;
|
|
changedLand.setValue(landShapeNew);
|
|
|
|
QModelIndex index(landTable.getModelIndex (cellId, landTable.findColumnIndex (CSMWorld::Columns::ColumnId_LandHeightsIndex)));
|
|
QUndoStack& undoStack = document.getUndoStack();
|
|
undoStack.push (new CSMWorld::ModifyCommand(landTable, index, changedLand));
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::dragMoveEvent (QDragMoveEvent *event)
|
|
{
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::mouseMoveEvent (QMouseEvent *event)
|
|
{
|
|
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask());
|
|
if (hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
|
mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape);
|
|
if (!hit.hit && mBrushDraw && !(mShapeEditTool == ShapeEditTool_Drag && mIsEditing))
|
|
mBrushDraw->hide();
|
|
}
|
|
|
|
std::shared_ptr<CSVRender::TerrainSelection> CSVRender::TerrainShapeMode::getTerrainSelection()
|
|
{
|
|
return mTerrainShapeSelection;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::setBrushSize(int brushSize)
|
|
{
|
|
mBrushSize = brushSize;
|
|
}
|
|
|
|
void CSVRender::TerrainShapeMode::setBrushShape(CSVWidget::BrushShape brushShape)
|
|
{
|
|
mBrushShape = brushShape;
|
|
|
|
//Set custom brush shape
|
|
if (mBrushShape == CSVWidget::BrushShape_Custom && !mTerrainShapeSelection->getTerrainSelection().empty())
|
|
{
|
|
auto terrainSelection = mTerrainShapeSelection->getTerrainSelection();
|
|
int selectionCenterX = 0;
|
|
int selectionCenterY = 0;
|
|
int selectionAmount = 0;
|
|
|
|
for(auto const& value: terrainSelection)
|
|
{
|
|
selectionCenterX = selectionCenterX + value.first;
|
|
selectionCenterY = selectionCenterY + value.second;
|
|
++selectionAmount;
|
|
}
|
|
|
|
if (selectionAmount != 0)
|
|
{
|
|
selectionCenterX /= selectionAmount;
|
|
selectionCenterY /= selectionAmount;
|
|
}
|
|
|
|
mCustomBrushShape.clear();
|
|
std::pair<int, int> 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<PagedWorldspaceWidget&>(getWorldspaceWidget());
|
|
}
|