mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-11-04 06:26:39 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1689 lines
		
	
	
	
		
			78 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1689 lines
		
	
	
	
		
			78 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 <osg/ref_ptr>
 | 
						|
 | 
						|
#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 "../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, QIcon{ ":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)
 | 
						|
            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)
 | 
						|
            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 (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 = 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());
 | 
						|
}
 |