#include "terrainshapemode.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../widget/scenetoolbar.hpp" #include "../widget/scenetoolshapebrush.hpp" #include "../../model/prefs/state.hpp" #include "../../model/world/commands.hpp" #include "../../model/world/idtree.hpp" #include "brushdraw.hpp" #include "commands.hpp" #include "editmode.hpp" #include "mask.hpp" #include "pagedworldspacewidget.hpp" #include "terrainselection.hpp" #include "worldspacewidget.hpp" class QPoint; class QWidget; namespace CSMWorld { struct Cell; } namespace osg { class Group; } CSVRender::TerrainShapeMode::TerrainShapeMode( WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) : EditMode(worldspaceWidget, Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-shape"), Mask_Terrain, "Terrain land editing", parent) , mParentNode(parentNode) { } void CSVRender::TerrainShapeMode::activate(CSVWidget::SceneToolbar* toolbar) { if (!mTerrainShapeSelection) { mTerrainShapeSelection = std::make_shared(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(&QComboBox::currentIndexChanged), this, &TerrainShapeMode::setShapeEditTool); connect(mShapeBrushScenetool->mShapeBrushWindow->mToolStrengthSlider, &QSlider::valueChanged, this, &TerrainShapeMode::setShapeEditToolStrength); } if (!mBrushDraw) mBrushDraw = std::make_unique(mParentNode); EditMode::activate(toolbar); toolbar->addTool(mShapeBrushScenetool); } void CSVRender::TerrainShapeMode::deactivate(CSVWidget::SceneToolbar* toolbar) { if (mShapeBrushScenetool) { toolbar->removeTool(mShapeBrushScenetool); } if (mTerrainShapeSelection) { mTerrainShapeSelection.reset(); } if (mBrushDraw) mBrushDraw.reset(); EditMode::deactivate(toolbar); } void CSVRender::TerrainShapeMode::primaryOpenPressed(const WorldspaceHitResult& hit) // Apply changes here { } void CSVRender::TerrainShapeMode::primaryEditPressed(const WorldspaceHitResult& hit) { if (hit.hit && hit.tag == nullptr) { if (mShapeEditTool == ShapeEditTool_Flatten || mShapeEditTool == ShapeEditTool_Equalize) setFlattenToolTargetHeight(hit); if (mDragMode == InteractionType_PrimaryEdit && mShapeEditTool != ShapeEditTool_Drag) { editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); applyTerrainEditChanges(); } } clearTransientEdits(); } void CSVRender::TerrainShapeMode::primarySelectPressed(const WorldspaceHitResult& hit) { if (hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); mTerrainShapeSelection->clearTemporarySelection(); } } void CSVRender::TerrainShapeMode::secondarySelectPressed(const WorldspaceHitResult& hit) { if (hit.hit && hit.tag == nullptr) { selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); mTerrainShapeSelection->clearTemporarySelection(); } } bool CSVRender::TerrainShapeMode::primaryEditStartDrag(const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimaryEdit; if (hit.hit && hit.tag == nullptr) { mEditingPos = hit.worldPos; mIsEditing = true; if (mShapeEditTool == ShapeEditTool_Flatten || mShapeEditTool == ShapeEditTool_Equalize) setFlattenToolTargetHeight(hit); } return true; } bool CSVRender::TerrainShapeMode::secondaryEditStartDrag(const QPoint& pos) { return false; } bool CSVRender::TerrainShapeMode::primarySelectStartDrag(const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_PrimarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); return true; } bool CSVRender::TerrainShapeMode::secondarySelectStartDrag(const QPoint& pos) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mDragMode = InteractionType_SecondarySelect; if (!hit.hit || hit.tag != nullptr) { mDragMode = InteractionType_None; return false; } selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); return true; } void CSVRender::TerrainShapeMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor) { if (mDragMode == InteractionType_PrimaryEdit) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); mTotalDiffY += diffY; if (mIsEditing) { if (mShapeEditTool == ShapeEditTool_Drag) editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(mEditingPos), true); else editTerrainShapeGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); } } if (mDragMode == InteractionType_PrimarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0); } if (mDragMode == InteractionType_SecondarySelect) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1); } } void CSVRender::TerrainShapeMode::dragCompleted(const QPoint& pos) { if (mDragMode == InteractionType_PrimaryEdit) { applyTerrainEditChanges(); clearTransientEdits(); } if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect) { mTerrainShapeSelection->clearTemporarySelection(); } } void CSVRender::TerrainShapeMode::dragAborted() { clearTransientEdits(); mDragMode = InteractionType_None; } void CSVRender::TerrainShapeMode::dragWheel(int diff, double speedFactor) {} void CSVRender::TerrainShapeMode::sortAndLimitAlteredCells() { bool passing = false; int passes = 0; std::sort(mAlteredCells.begin(), mAlteredCells.end()); mAlteredCells.erase(std::unique(mAlteredCells.begin(), mAlteredCells.end()), mAlteredCells.end()); while (!passing) // Multiple passes are needed when steepness problems arise for both x and y axis simultaneously { passing = true; for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { limitAlteredHeights(cellCoordinates); } std::reverse(mAlteredCells.begin(), mAlteredCells .end()); // Instead of alphabetical order, this should be fixed to sort cells by cell coordinates for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { if (!limitAlteredHeights(cellCoordinates, true)) passing = false; } ++passes; if (passes > 2) { Log(Debug::Warning) << "Warning: User edit exceeds accepted slope steepness. Automatic limiting has " "failed, edit has been discarded."; clearTransientEdits(); return; } } } void CSVRender::TerrainShapeMode::clearTransientEdits() { mTotalDiffY = 0; mIsEditing = false; mAlteredCells.clear(); if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) paged->resetAllAlteredHeights(); mTerrainShapeSelection->update(); } void CSVRender::TerrainShapeMode::applyTerrainEditChanges() { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int 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 landShapeNew(landShapePointer); CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget()); // Generate land height record for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { for (int j = 0; j < ESM::Land::LAND_SIZE; ++j) { if (paged && paged->getCellAlteredHeight(cellCoordinates, i, j)) landShapeNew[j * ESM::Land::LAND_SIZE + i] = landShapePointer[j * ESM::Land::LAND_SIZE + i] + *paged->getCellAlteredHeight(cellCoordinates, i, j); else landShapeNew[j * ESM::Land::LAND_SIZE + i] = 0; } } pushEditToCommand(landShapeNew, document, landTable, cellId); } for (CSMWorld::CellCoordinates cellCoordinates : mAlteredCells) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY()); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable .data(landTable.getModelIndex( CSMWorld::CellCoordinates::generateId(cellCoordinates.getX() + 1, cellCoordinates.getY()), landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable .data(landTable.getModelIndex( CSMWorld::CellCoordinates::generateId(cellCoordinates.getX(), cellCoordinates.getY() + 1), landshapeColumn)) .value(); const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)) .value(); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); // 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& vertexCoords, bool dragOperation) { int r = mBrushSize / 2; if (r == 0) r = 1; // Prevent division by zero later, which might happen when mBrushSize == 1 if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { if (mShapeEditTool == ShapeEditTool_Drag) paged->resetAllAlteredHeights(); } if (mBrushShape == CSVWidget::BrushShape_Point) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast(CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); if (mShapeEditTool == ShapeEditTool_Equalize) equalizeHeight(cellCoords, x, y, mTargetHeight); } if (mBrushShape == CSVWidget::BrushShape_Square) { for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast( CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); if (mShapeEditTool == ShapeEditTool_Equalize) equalizeHeight(cellCoords, x, y, mTargetHeight); } } } if (mBrushShape == CSVWidget::BrushShape_Circle) { for (int i = vertexCoords.first - r; i <= vertexCoords.first + r; ++i) { for (int j = vertexCoords.second - r; j <= vertexCoords.second + r; ++j) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(std::make_pair(i, j)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(i); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(j); int distanceX = abs(i - vertexCoords.first); int distanceY = abs(j - vertexCoords.second); float distance = sqrt(pow(distanceX, 2) + pow(distanceY, 2)); float smoothedByDistance = 0.0f; if (mShapeEditTool == ShapeEditTool_Drag) smoothedByDistance = calculateBumpShape(distance, r, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) smoothedByDistance = calculateBumpShape(distance, r, r + mShapeEditToolStrength); // Using floating-point radius here to prevent selecting too few vertices. if (distance <= mBrushSize / 2.0f) { if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, smoothedByDistance); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, smoothedByDistance); float smoothMultiplier = static_cast( CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); if (mShapeEditTool == ShapeEditTool_Equalize) equalizeHeight(cellCoords, x, y, mTargetHeight); } } } } if (mBrushShape == CSVWidget::BrushShape_Custom) { if (!mCustomBrushShape.empty()) { for (auto const& value : mCustomBrushShape) { std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId( std::make_pair(vertexCoords.first + value.first, vertexCoords.second + value.second)); CSMWorld::CellCoordinates cellCoords = CSMWorld::CellCoordinates::fromId(cellId).first; int x = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first + value.first); int y = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second + value.second); if (mShapeEditTool == ShapeEditTool_Drag) alterHeight(cellCoords, x, y, mTotalDiffY); if (mShapeEditTool == ShapeEditTool_PaintToRaise || mShapeEditTool == ShapeEditTool_PaintToLower) { alterHeight(cellCoords, x, y, mShapeEditToolStrength); float smoothMultiplier = static_cast( CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothstrength"].toDouble()); if (CSMPrefs::get()["3D Scene Editing"]["landedit-post-smoothpainting"].isTrue()) smoothHeight(cellCoords, x, y, mShapeEditToolStrength * smoothMultiplier); } if (mShapeEditTool == ShapeEditTool_Smooth) smoothHeight(cellCoords, x, y, mShapeEditToolStrength); if (mShapeEditTool == ShapeEditTool_Flatten) flattenHeight(cellCoords, x, y, mShapeEditToolStrength, mTargetHeight); if (mShapeEditTool == ShapeEditTool_Equalize) equalizeHeight(cellCoords, x, y, mTargetHeight); } } } mTerrainShapeSelection->update(); } void CSVRender::TerrainShapeMode::setFlattenToolTargetHeight(const WorldspaceHitResult& hit) { std::pair vertexCoords = CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos); std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords); int inCellX = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int inCellY = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); mTargetHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } void CSVRender::TerrainShapeMode::alterHeight( const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool) { std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (!(allowLandShapeEditing(cellId, useTool) && (useTool || (isLandLoaded(cellId))))) return; CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget()); if (!paged) return; std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); std::string cellUpLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() - 1); std::string cellUpRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() - 1); std::string cellDownLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY() + 1); std::string cellDownRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY() + 1); if (useTool) { mAlteredCells.emplace_back(cellCoords); if (mShapeEditTool == ShapeEditTool_Drag) { // Get distance from modified land, alter land change based on zoom osg::Vec3d eye, center, up; paged->getCamera()->getViewMatrixAsLookAt(eye, center, up); osg::Vec3d distance = eye - mEditingPos; alteredHeight = alteredHeight * (distance.length() / 500); } if (mShapeEditTool == ShapeEditTool_PaintToRaise) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; if (mShapeEditTool == ShapeEditTool_PaintToLower) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) - alteredHeight; if (mShapeEditTool == ShapeEditTool_Smooth) alteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) + alteredHeight; } if (inCellX != 0 && inCellY != 0 && inCellX != ESM::Land::LAND_SIZE - 1 && inCellY != ESM::Land::LAND_SIZE - 1) paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); // Change values of cornering cells if ((inCellX == 0 && inCellY == 0) && (useTool || isLandLoaded(cellUpLeftId))) { if (allowLandShapeEditing(cellUpLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight( cornerCellCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } else if ((inCellX == 0 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownLeftId))) { if (allowLandShapeEditing(cellDownLeftId, useTool) && allowLandShapeEditing(cellLeftId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(-1, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, ESM::Land::LAND_SIZE - 1, 0, alteredHeight); } else return; } else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == 0) && (useTool || isLandLoaded(cellUpRightId))) { if (allowLandShapeEditing(cellUpRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, ESM::Land::LAND_SIZE - 1, alteredHeight); } else return; } else if ((inCellX == ESM::Land::LAND_SIZE - 1 && inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownRightId))) { if (allowLandShapeEditing(cellDownRightId, useTool) && allowLandShapeEditing(cellRightId, useTool) && allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates cornerCellCoords = cellCoords.move(1, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), cornerCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(cornerCellCoords); paged->setCellAlteredHeight(cornerCellCoords, 0, 0, alteredHeight); } else return; } // Change values of edging cells if ((inCellX == 0) && (useTool || isLandLoaded(cellLeftId))) { if (allowLandShapeEditing(cellLeftId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(-1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, ESM::Land::LAND_SIZE - 1, inCellY, alteredHeight); } } if ((inCellY == 0) && (useTool || isLandLoaded(cellUpId))) { if (allowLandShapeEditing(cellUpId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, -1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, ESM::Land::LAND_SIZE - 1, alteredHeight); } } if ((inCellX == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellRightId))) { if (allowLandShapeEditing(cellRightId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(1, 0); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, 0, inCellY, alteredHeight); } } if ((inCellY == ESM::Land::LAND_SIZE - 1) && (useTool || isLandLoaded(cellDownId))) { if (allowLandShapeEditing(cellDownId, useTool)) { CSMWorld::CellCoordinates edgeCellCoords = cellCoords.move(0, 1); if (useTool && std::find(mAlteredCells.begin(), mAlteredCells.end(), edgeCellCoords) == mAlteredCells.end()) mAlteredCells.emplace_back(edgeCellCoords); paged->setCellAlteredHeight(cellCoords, inCellX, inCellY, alteredHeight); paged->setCellAlteredHeight(edgeCellCoords, inCellX, 0, alteredHeight); } } } void CSVRender::TerrainShapeMode::smoothHeight( const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength) { if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); // ### Variable naming key ### // Variables here hold either the real value, or the altered value of current edit. // this = this Cell // left = x - 1, up = y - 1, right = x + 1, down = y + 1 // Altered = transient edit (in current edited) float thisAlteredHeight = 0.0f; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY) != nullptr) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); float thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upAlteredHeight = 0.0f; float rightHeight = 0.0f; float rightAlteredHeight = 0.0f; float downHeight = 0.0f; float downAlteredHeight = 0.0f; float upHeight = 0.0f; if (allowLandShapeEditing(cellId)) { // Get key values for calculating average, handle cell edges, check for null pointers if (inCellX == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), inCellX, ESM::Land::LAND_SIZE - 2)) leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); } if (inCellY == 0) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); } if (inCellX > 0) { leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); } if (inCellY > 0) { upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); } if (inCellX == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); } } if (inCellY == ESM::Land::LAND_SIZE - 1) { cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); downHeight = landDownShapePointer[1 * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); } } if (inCellX < ESM::Land::LAND_SIZE - 1) { rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); } if (inCellY < ESM::Land::LAND_SIZE - 1) { downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); } float averageHeight = (upHeight + downHeight + rightHeight + leftHeight + upAlteredHeight + downAlteredHeight + rightAlteredHeight + leftAlteredHeight) / 4; if ((thisHeight + thisAlteredHeight) != averageHeight) mAlteredCells.emplace_back(cellCoords); if (toolStrength > abs(thisHeight + thisAlteredHeight - averageHeight)) toolStrength = abs(thisHeight + thisAlteredHeight - averageHeight); if (thisHeight + thisAlteredHeight > averageHeight) alterHeight(cellCoords, inCellX, inCellY, -toolStrength); if (thisHeight + thisAlteredHeight < averageHeight) alterHeight(cellCoords, inCellX, inCellY, +toolStrength); } } } void CSVRender::TerrainShapeMode::flattenHeight( const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } } if (toolStrength > abs(thisHeight - targetHeight) && toolStrength > 8.0f) toolStrength = abs(thisHeight - targetHeight); // Cut down excessive changes if (thisHeight + thisAlteredHeight > targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight - toolStrength); if (thisHeight + thisAlteredHeight < targetHeight) alterHeight(cellCoords, inCellX, inCellY, thisAlteredHeight + toolStrength); } void CSVRender::TerrainShapeMode::equalizeHeight( const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int targetHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); float thisHeight = 0.0f; const std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX]; } alterHeight(cellCoords, inCellX, inCellY, targetHeight - thisHeight); } void CSVRender::TerrainShapeMode::updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight, float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight, float* rightHeight, float* rightAlteredHeight, float* downHeight, float* downAlteredHeight) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); *thisHeight = 0.0f; // real + altered height *thisAlteredHeight = 0.0f; // only altered height *leftHeight = 0.0f; *leftAlteredHeight = 0.0f; *upHeight = 0.0f; *upAlteredHeight = 0.0f; *rightHeight = 0.0f; *rightAlteredHeight = 0.0f; *downHeight = 0.0f; *downAlteredHeight = 0.0f; if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { if (!noCell(cellId) && !noLand(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY)) *thisAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY); *thisHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX] + *thisAlteredHeight; // Default to the same value as thisHeight, which happens in the case of cell edge where next cell/land is // not found, which is to prevent unnecessary action at limitHeightChange(). *leftHeight = *thisHeight; *upHeight = *thisHeight; *rightHeight = *thisHeight; *downHeight = *thisHeight; // If at edge, get values from neighboring cell if (inCellX == 0) { if (isLandLoaded(cellLeftId)) { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) .value(); *leftHeight = landLeftShapePointer[inCellY * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE - 2)]; if (paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY)) { *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(-1, 0), ESM::Land::LAND_SIZE - 2, inCellY); *leftHeight += *leftAlteredHeight; } } } if (inCellY == 0) { if (isLandLoaded(cellUpId)) { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) .value(); *upHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 2) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2)) { *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, -1), inCellX, ESM::Land::LAND_SIZE - 2); *upHeight += *upAlteredHeight; } } } if (inCellX == ESM::Land::LAND_SIZE - 1) { if (isLandLoaded(cellRightId)) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) .value(); *rightHeight = landRightShapePointer[inCellY * ESM::Land::LAND_SIZE + 1]; if (paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY)) { *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(1, 0), 1, inCellY); *rightHeight += *rightAlteredHeight; } } } if (inCellY == ESM::Land::LAND_SIZE - 1) { if (isLandLoaded(cellDownId)) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) .value(); *downHeight = landDownShapePointer[ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1)) { *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords.move(0, 1), inCellX, 1); *downHeight += *downAlteredHeight; } } } // If not at edge, get values from the same cell if (inCellX != 0) { *leftHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX - 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY)) *leftAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX - 1, inCellY); *leftHeight += *leftAlteredHeight; } if (inCellY != 0) { *upHeight = landShapePointer[(inCellY - 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1)) *upAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY - 1); *upHeight += *upAlteredHeight; } if (inCellX != ESM::Land::LAND_SIZE - 1) { *rightHeight = landShapePointer[inCellY * ESM::Land::LAND_SIZE + inCellX + 1]; if (paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY)) *rightAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX + 1, inCellY); *rightHeight += *rightAlteredHeight; } if (inCellY != ESM::Land::LAND_SIZE - 1) { *downHeight = landShapePointer[(inCellY + 1) * ESM::Land::LAND_SIZE + inCellX]; if (paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1)) *downAlteredHeight = *paged->getCellAlteredHeight(cellCoords, inCellX, inCellY + 1); *downHeight += *downAlteredHeight; } } } } void CSVRender::TerrainShapeMode::compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis, float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits) { if (limitedAlteredHeightXAxis) { if (limitedAlteredHeightYAxis) { if (std::abs(*limitedAlteredHeightXAxis) >= std::abs(*limitedAlteredHeightYAxis)) { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); *steepnessIsWithinLimits = false; } else { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); *steepnessIsWithinLimits = false; } } else { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightXAxis, false); *steepnessIsWithinLimits = false; } } else if (limitedAlteredHeightYAxis) { alterHeight(cellCoords, inCellX, inCellY, *limitedAlteredHeightYAxis, false); *steepnessIsWithinLimits = false; } } bool CSVRender::TerrainShapeMode::limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); int limitHeightChange = 1016.0f; // Limited by save format bool steepnessIsWithinLimits = true; if (isLandLoaded(cellId)) { const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); float thisHeight = 0.0f; float thisAlteredHeight = 0.0f; float leftHeight = 0.0f; float leftAlteredHeight = 0.0f; float upHeight = 0.0f; float upAlteredHeight = 0.0f; float rightHeight = 0.0f; float rightAlteredHeight = 0.0f; float downHeight = 0.0f; float downAlteredHeight = 0.0f; if (!reverseMode) { for (int inCellY = 0; inCellY < ESM::Land::LAND_SIZE; ++inCellY) { for (int inCellX = 0; inCellX < ESM::Land::LAND_SIZE; ++inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (leftHeight - thisHeight > limitHeightChange) limitedAlteredHeightXAxis = std::make_unique( leftHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (leftHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis = std::make_unique( leftHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (upHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(upHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (upHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis = std::make_unique(upHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Limit altered height value based on x or y, whichever is the smallest compareAndLimit(cellCoords, inCellX, inCellY, limitedAlteredHeightXAxis.get(), limitedAlteredHeightYAxis.get(), &steepnessIsWithinLimits); } } } if (reverseMode) { for (int inCellY = ESM::Land::LAND_SIZE - 1; inCellY >= 0; --inCellY) { for (int inCellX = ESM::Land::LAND_SIZE - 1; inCellX >= 0; --inCellX) { std::unique_ptr limitedAlteredHeightXAxis(nullptr); std::unique_ptr limitedAlteredHeightYAxis(nullptr); updateKeyHeightValues(cellCoords, inCellX, inCellY, &thisHeight, &thisAlteredHeight, &leftHeight, &leftAlteredHeight, &upHeight, &upAlteredHeight, &rightHeight, &rightAlteredHeight, &downHeight, &downAlteredHeight); // Check for height limits on x-axis if (rightHeight - thisHeight > limitHeightChange) limitedAlteredHeightXAxis = std::make_unique( rightHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (rightHeight - thisHeight < -limitHeightChange) limitedAlteredHeightXAxis = std::make_unique( rightHeight + limitHeightChange - (thisHeight - thisAlteredHeight)); // Check for height limits on y-axis if (downHeight - thisHeight > limitHeightChange) limitedAlteredHeightYAxis = std::make_unique( downHeight - limitHeightChange - (thisHeight - thisAlteredHeight)); else if (downHeight - thisHeight < -limitHeightChange) limitedAlteredHeightYAxis = std::make_unique( 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(&getWorldspaceWidget())) { std::pair 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>* 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& vertexCoords, unsigned char selectMode) { int r = mBrushSize / 2; std::vector> 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 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& 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& 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& 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(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTable& ltexTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_LandTextures)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); int landnormalsColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandNormalsIndex); float defaultHeight = 0.f; int averageDivider = 0; CSMWorld::CellCoordinates cellLeftCoords = cellCoords.move(-1, 0); CSMWorld::CellCoordinates cellRightCoords = cellCoords.move(1, 0); CSMWorld::CellCoordinates cellUpCoords = cellCoords.move(0, -1); CSMWorld::CellCoordinates cellDownCoords = cellCoords.move(0, 1); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellLeftCoords.getX(), cellLeftCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellRightCoords.getX(), cellRightCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellUpCoords.getX(), cellUpCoords.getY()); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellDownCoords.getX(), cellDownCoords.getY()); float leftCellSampleHeight = 0.0f; float rightCellSampleHeight = 0.0f; float upCellSampleHeight = 0.0f; float downCellSampleHeight = 0.0f; const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); const CSMWorld::LandNormalsColumn::DataType landNormalsPointer = landTable.data(landTable.getModelIndex(cellId, landnormalsColumn)) .value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); CSMWorld::LandNormalsColumn::DataType landNormalsNew(landNormalsPointer); if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { if (isLandLoaded(cellLeftId)) { const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) .value(); ++averageDivider; leftCellSampleHeight = landLeftShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; if (paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2)) leftCellSampleHeight += *paged->getCellAlteredHeight(cellLeftCoords, ESM::Land::LAND_SIZE - 1, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellRightId)) { const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) .value(); ++averageDivider; rightCellSampleHeight = landRightShapePointer[(ESM::Land::LAND_SIZE / 2) * ESM::Land::LAND_SIZE]; if (paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2)) rightCellSampleHeight += *paged->getCellAlteredHeight(cellRightCoords, 0, ESM::Land::LAND_SIZE / 2); } if (isLandLoaded(cellUpId)) { const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) .value(); ++averageDivider; upCellSampleHeight = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + (ESM::Land::LAND_SIZE / 2)]; if (paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1)) upCellSampleHeight += *paged->getCellAlteredHeight(cellUpCoords, ESM::Land::LAND_SIZE / 2, ESM::Land::LAND_SIZE - 1); } if (isLandLoaded(cellDownId)) { const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) .value(); ++averageDivider; downCellSampleHeight = landDownShapePointer[ESM::Land::LAND_SIZE / 2]; if (paged->getCellAlteredHeight(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(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); CSMWorld::IdTree& cellTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Cells)); if (noCell(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode == "Discard") return false; if (mode == "Create cell and land, then edit" && useTool) { auto createCommand = std::make_unique(cellTable, cellId); int parentIndex = cellTable.findColumnIndex(CSMWorld::Columns::ColumnId_Cell); int index = cellTable.findNestedColumnIndex(parentIndex, CSMWorld::Columns::ColumnId_Interior); createCommand->addNestedValue(parentIndex, index, false); document.getUndoStack().push(createCommand.release()); if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); paged->setCellSelection(selection); } } } else if (CSVRender::PagedWorldspaceWidget* paged = dynamic_cast(&getWorldspaceWidget())) { CSMWorld::CellSelection selection = paged->getCellSelection(); if (!selection.has(CSMWorld::CellCoordinates::fromId(cellId).first)) { // target cell exists, but is not shown std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-visible-landedit"].toString(); if (mode == "Discard") return false; if (mode == "Show cell and edit" && useTool) { selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); paged->setCellSelection(selection); } } } if (noLand(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); // target cell does not exist if (mode == "Discard") return false; if (mode == "Create cell and land, then edit" && useTool) { document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, cellId)); createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); sortAndLimitAlteredCells(); } } else if (noLandLoaded(cellId)) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString(); if (mode == "Discard") return false; if (mode == "Create cell and land, then edit" && useTool) { createNewLandData(CSMWorld::CellCoordinates::fromId(cellId).first); fixEdges(CSMWorld::CellCoordinates::fromId(cellId).first); sortAndLimitAlteredCells(); } } if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId))) { Log(Debug::Warning) << "Land creation failed at cell id: " << cellId; return false; } return true; } void CSVRender::TerrainShapeMode::fixEdges(CSMWorld::CellCoordinates cellCoords) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land)); int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex); std::string cellId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY()); std::string cellLeftId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() - 1, cellCoords.getY()); std::string cellRightId = CSMWorld::CellCoordinates::generateId(cellCoords.getX() + 1, cellCoords.getY()); std::string cellUpId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() - 1); std::string cellDownId = CSMWorld::CellCoordinates::generateId(cellCoords.getX(), cellCoords.getY() + 1); const CSMWorld::LandHeightsColumn::DataType landShapePointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landLeftShapePointer = landTable.data(landTable.getModelIndex(cellLeftId, landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landRightShapePointer = landTable.data(landTable.getModelIndex(cellRightId, landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landUpShapePointer = landTable.data(landTable.getModelIndex(cellUpId, landshapeColumn)) .value(); const CSMWorld::LandHeightsColumn::DataType landDownShapePointer = landTable.data(landTable.getModelIndex(cellDownId, landshapeColumn)) .value(); CSMWorld::LandHeightsColumn::DataType landShapeNew(landShapePointer); for (int i = 0; i < ESM::Land::LAND_SIZE; ++i) { if (isLandLoaded(cellLeftId) && landShapePointer[i * ESM::Land::LAND_SIZE] != landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]) landShapeNew[i * ESM::Land::LAND_SIZE] = landLeftShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1]; if (isLandLoaded(cellRightId) && landShapePointer[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] != landRightShapePointer[i * ESM::Land::LAND_SIZE]) landShapeNew[i * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE - 1] = landRightShapePointer[i * ESM::Land::LAND_SIZE]; if (isLandLoaded(cellUpId) && landShapePointer[i] != landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]) landShapeNew[i] = landUpShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i]; if (isLandLoaded(cellDownId) && landShapePointer[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] != landDownShapePointer[i]) landShapeNew[(ESM::Land::LAND_SIZE - 1) * ESM::Land::LAND_SIZE + i] = landDownShapePointer[i]; } QVariant changedLand; changedLand.setValue(landShapeNew); QModelIndex index( landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex))); QUndoStack& undoStack = document.getUndoStack(); undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand)); } void CSVRender::TerrainShapeMode::dragMoveEvent(QDragMoveEvent* event) {} void CSVRender::TerrainShapeMode::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::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 differentialPos{}; for (auto const& value : terrainSelection) { differentialPos.first = value.first - selectionCenterX; differentialPos.second = value.second - selectionCenterY; mCustomBrushShape.push_back(differentialPos); } } } void CSVRender::TerrainShapeMode::setShapeEditTool(int shapeEditTool) { mShapeEditTool = shapeEditTool; } void CSVRender::TerrainShapeMode::setShapeEditToolStrength(int shapeEditToolStrength) { mShapeEditToolStrength = shapeEditToolStrength; } CSVRender::PagedWorldspaceWidget& CSVRender::TerrainShapeMode::getPagedWorldspaceWidget() { return dynamic_cast(getWorldspaceWidget()); }