You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
openmw/apps/opencs/view/render/terrainshapemode.cpp

1722 lines
79 KiB
C++

#include "terrainshapemode.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <QComboBox>
#include <QDropEvent>
#include <QEvent>
#include <QIcon>
#include <QWidget>
#include <osg/Camera>
#include <osg/Vec3f>
#include <apps/opencs/model/doc/document.hpp>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/model/world/cellselection.hpp>
#include <apps/opencs/model/world/columnimp.hpp>
#include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/data.hpp>
#include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/idtable.hpp>
#include <apps/opencs/model/world/land.hpp>
#include <apps/opencs/model/world/record.hpp>
#include <apps/opencs/model/world/universalid.hpp>
#include <apps/opencs/view/widget/brushshapes.hpp>
#include <apps/opencs/view/widget/scenetool.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadland.hpp>
#include <components/misc/scalableicon.hpp>
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetoolshapebrush.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/idtree.hpp"
#include "brushdraw.hpp"
#include "commands.hpp"
#include "editmode.hpp"
#include "mask.hpp"
#include "pagedworldspacewidget.hpp"
#include "terrainselection.hpp"
#include "worldspacewidget.hpp"
class QPoint;
class QWidget;
namespace CSMWorld
{
struct Cell;
}
namespace osg
{
class Group;
}
CSVRender::TerrainShapeMode::TerrainShapeMode(
WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent)
: EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-shape"), Mask_Terrain,
"Terrain land editing", parent)
, mParentNode(parentNode)
{
}
void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar)
{
if (!mTerrainShapeSelection)
{
mTerrainShapeSelection
= std::make_shared<TerrainSelection>(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape);
}
if (!mShapeBrushScenetool)
{
mShapeBrushScenetool
= new CSVWidget::SceneToolShapeBrush(toolbar, "scenetoolshapebrush", getWorldspaceWidget().getDocument());
connect(mShapeBrushScenetool, &CSVWidget::SceneTool::clicked, mShapeBrushScenetool,
&CSVWidget::SceneToolShapeBrush::activate);
connect(mShapeBrushScenetool->mShapeBrushWindow, &CSVWidget::ShapeBrushWindow::passBrushSize, this,
&TerrainShapeMode::setBrushSize);
connect(mShapeBrushScenetool->mShapeBrushWindow, &CSVWidget::ShapeBrushWindow::passBrushShape, this,
&TerrainShapeMode::setBrushShape);
connect(mShapeBrushScenetool->mShapeBrushWindow->mSizeSliders->mBrushSizeSlider, &QSlider::valueChanged, this,
&TerrainShapeMode::setBrushSize);
connect(mShapeBrushScenetool->mShapeBrushWindow->mToolSelector, qOverload<int>(&QComboBox::currentIndexChanged),
this, &TerrainShapeMode::setShapeEditTool);
connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, &QSlider::valueChanged, this,
&TerrainShapeMode::setShapeEditToolStrength);
}
if (!mBrushDraw)
mBrushDraw = std::make_unique<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 || mShapeEditTool == ShapeEditTool_Equalize)
setFlattenToolTargetHeight(hit);
if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag)
{
editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true);
applyTerrainEditChanges();
}
}
clearTransientEdits();
}
void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.hit && hit.tag == nullptr)
{
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0);
mTerrainShapeSelection->clearTemporarySelection();
}
}
void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.hit && hit.tag == nullptr)
{
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1);
mTerrainShapeSelection->clearTemporarySelection();
}
}
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 || mShapeEditTool == ShapeEditTool_Equalize)
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);
return true;
}
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);
return true;
}
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);
}
if (mDragMode == InteractionType_SecondarySelect)
{
WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask());
if (hit.hit && hit.tag == nullptr)
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1);
}
}
void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos)
{
if (mDragMode == InteractionType_PrimaryEdit)
{
applyTerrainEditChanges();
clearTransientEdits();
}
if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect)
{
mTerrainShapeSelection->clearTemporarySelection();
}
}
void CSVRender::TerrainShapeMode::dragAborted()
{
clearTransientEdits();
mDragMode = InteractionType_None;
}
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 (mShapeEditTool == ShapeEditTool_Equalize)
equalizeHeight(cellCoords, x, y, 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 (mShapeEditTool == ShapeEditTool_Equalize)
equalizeHeight(cellCoords, x, y, 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 (mShapeEditTool == ShapeEditTool_Equalize)
equalizeHeight(cellCoords, x, y, 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);
if (mShapeEditTool == ShapeEditTool_Equalize)
equalizeHeight(cellCoords, x, y, 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::equalizeHeight(
const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, 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;
const std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY());
if (!noCell(cellId) && !noLand(cellId))
{
const CSMWorld::LandHeightsColumn::DataType landShapePointer
= landTable.data(landTable.getModelIndex(cellId, landshapeColumn))
.value<CSMWorld::LandHeightsColumn::DataType>();
thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX];
}
alterHeight(cellCoords, inCellX, inCellY, targetHeight - thisHeight);
}
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 = std::make_unique<float>(
leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight));
else if (leftHeight - thisHeight < -limitHeightChange)
limitedAlteredHeightXAxis = std::make_unique<float>(
leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight));
// Check for height limits on y-axis
if (upHeight - thisHeight > limitHeightChange)
limitedAlteredHeightYAxis
= std::make_unique<float>(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight));
else if (upHeight - thisHeight < -limitHeightChange)
limitedAlteredHeightYAxis
= std::make_unique<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 = std::make_unique<float>(
rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight));
else if (rightHeight - thisHeight < -limitHeightChange)
limitedAlteredHeightXAxis = std::make_unique<float>(
rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight));
// Check for height limits on y-axis
if (downHeight - thisHeight > limitHeightChange)
limitedAlteredHeightYAxis = std::make_unique<float>(
downHeight - limitHeightChange - (thisHeight - thisAlteredHeight));
else if (downHeight - thisHeight < -limitHeightChange)
limitedAlteredHeightYAxis = std::make_unique<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)
{
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);
else if (selectAction == "Remove from selection")
mTerrainShapeSelection->removeSelect(selections);
else if (selectAction == "Invert selection")
mTerrainShapeSelection->toggleSelect(selections);
}
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(ESM::RefId::stringRefId(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(ESM::RefId::stringRefId(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(ESM::RefId::stringRefId(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)
{
auto createCommand = std::make_unique<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());
}