Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457#include "terrainvertexpaintmode.hpp"
#include <algorithm>#include <cmath>#include <memory>#include <string>
#include <QComboBox>#include <QDropEvent>#include <QEvent>#include <QIcon>
#include <apps/opencs/model/doc/document.hpp>#include <apps/opencs/model/prefs/state.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/idtable.hpp>#include <apps/opencs/model/world/land.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/scenetoolvertexpaintbrush.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 "worldspacewidget.hpp"
class QPoint;class QWidget;
namespace CSMWorld{ struct Cell;}
namespace osg{ class Group;}
CSVRender::TerrainVertexPaintMode::TerrainVertexPaintMode( WorldspaceWidget* worldspaceWidget, osg::Group* parentNode, QWidget* parent) : EditMode(worldspaceWidget, QIcon{ ":scenetoolbar/editing-terrain-vertex-paint" }, Mask_Terrain, "Terrain vertex paint editing", parent) , mParentNode(parentNode) , mVertexPaintEditToolColor(Qt::white){}
void CSVRender::TerrainVertexPaintMode::activate(CSVWidget::SceneToolbar* toolbar){ if (!mVertexPaintBrushScenetool) { mVertexPaintBrushScenetool = new CSVWidget::SceneToolVertexPaintBrush( toolbar, "scenetoolvertexpaintbrush", getWorldspaceWidget().getDocument()); connect(mVertexPaintBrushScenetool->mVertexPaintBrushWindow, &CSVWidget::VertexPaintBrushWindow::passBrushSize, this, &TerrainVertexPaintMode::setBrushSize); connect(mVertexPaintBrushScenetool->mVertexPaintBrushWindow, &CSVWidget::VertexPaintBrushWindow::passBrushShape, this, &TerrainVertexPaintMode::setBrushShape); connect(mVertexPaintBrushScenetool->mVertexPaintBrushWindow->mSizeSliders->mBrushSizeSlider, &QSlider::valueChanged, this, &TerrainVertexPaintMode::setBrushSize); connect(mVertexPaintBrushScenetool->mVertexPaintBrushWindow->mToolSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &TerrainVertexPaintMode::setVertexPaintEditTool); connect(mVertexPaintBrushScenetool->mVertexPaintBrushWindow->mColorButtonWidget, &CSVWidget::ColorButtonWidget::colorChanged, this, &TerrainVertexPaintMode::setVertexPaintColor); }
if (!mBrushDraw) mBrushDraw = std::make_unique<BrushDraw>(mParentNode);
EditMode::activate(toolbar); toolbar->addTool(mVertexPaintBrushScenetool);}
void CSVRender::TerrainVertexPaintMode::deactivate(CSVWidget::SceneToolbar* toolbar){ if (mVertexPaintBrushScenetool) { toolbar->removeTool(mVertexPaintBrushScenetool); }
if (mBrushDraw) mBrushDraw.reset();
EditMode::deactivate(toolbar);}
void CSVRender::TerrainVertexPaintMode::primaryOpenPressed(const WorldspaceHitResult& hit) // Apply changes here{}
void CSVRender::TerrainVertexPaintMode::primaryEditPressed(const WorldspaceHitResult& hit){ if (hit.hit && hit.tag == nullptr) { mRestoreMode = true; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.beginMacro("Restore land vertex colour"); editVertexColourGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); undoStack.endMacro(); } mRestoreMode = false;}
void CSVRender::TerrainVertexPaintMode::primarySelectPressed(const WorldspaceHitResult& hit){ if (hit.hit && hit.tag == nullptr) { mRestoreMode = false; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.beginMacro("Paint land vertex colour"); editVertexColourGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); undoStack.endMacro(); }}
bool CSVRender::TerrainVertexPaintMode::primaryEditStartDrag(const QPoint& pos){ WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) { mDragMode = InteractionType_PrimaryEdit; mIsEditing = true; mRestoreMode = true; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.beginMacro("Restore land vertex colours"); return true; }
return false;}
bool CSVRender::TerrainVertexPaintMode::secondaryEditStartDrag(const QPoint& pos){ return false;}
bool CSVRender::TerrainVertexPaintMode::primarySelectStartDrag(const QPoint& pos){ WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (hit.hit && hit.tag == nullptr) { mDragMode = InteractionType_PrimarySelect; mIsEditing = true; mRestoreMode = false; CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.beginMacro("Paint land vertex colours"); return true; }
return false;}
void CSVRender::TerrainVertexPaintMode::drag(const QPoint& pos, int diffX, int diffY, double speedFactor){ if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_PrimaryEdit) { WorldspaceHitResult hit = getWorldspaceWidget().mousePick(pos, getWorldspaceWidget().getInteractionMask()); if (mIsEditing && hit.hit && hit.tag == nullptr) editVertexColourGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true); }}
void CSVRender::TerrainVertexPaintMode::dragCompleted(const QPoint& pos){ if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_PrimaryEdit) { if (mIsEditing) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.endMacro(); } endVertexPaintEditing(); mRestoreMode = false; }}
void CSVRender::TerrainVertexPaintMode::dragAborted(){ if (mIsEditing) { CSMDoc::Document& document = getWorldspaceWidget().getDocument(); QUndoStack& undoStack = document.getUndoStack(); undoStack.endMacro(); } endVertexPaintEditing(); mDragMode = InteractionType_None; mRestoreMode = false;}
void CSVRender::TerrainVertexPaintMode::dragWheel(int diff, double speedFactor) {}
void CSVRender::TerrainVertexPaintMode::endVertexPaintEditing(){ mIsEditing = false;}
void CSVRender::TerrainVertexPaintMode::editVertexColourGrid( const std::pair<int, int>& vertexCoords, bool dragOperation){ CSMDoc::Document& document = getWorldspaceWidget().getDocument(); CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&>(*document.getData().getTableModel(CSMWorld::UniversalId::Type_Land));
std::string cellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords);
if (!allowLandColourEditing(cellId)) return;
std::pair<CSMWorld::CellCoordinates, bool> cellCoordinatesPair = CSMWorld::CellCoordinates::fromId(cellId);
int cellX = cellCoordinatesPair.first.getX(); int cellY = cellCoordinatesPair.first.getY();
int xHitInCell = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.first); int yHitInCell = CSMWorld::CellCoordinates::vertexGlobalToInCellCoords(vertexCoords.second); if (xHitInCell < 0) { xHitInCell = xHitInCell + ESM::Land::LAND_SIZE; cellX = cellX - 1; } if (yHitInCell > ESM::Land::LAND_SIZE - 1) { yHitInCell = yHitInCell - ESM::Land::LAND_SIZE; cellY = cellY + 1; }
cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY); if (!allowLandColourEditing(cellId)) return;
std::string iteratedCellId;
int colourColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandColoursIndex);
int r = std::max(static_cast<float>(mBrushSize) / 2.0f, 1.0f);
if (mBrushShape == CSVWidget::BrushShape_Point) { CSMWorld::LandColoursColumn::DataType newTerrain = landTable.data(landTable.getModelIndex(cellId, colourColumn)) .value<CSMWorld::LandColoursColumn::DataType>();
if (allowLandColourEditing(cellId)) { alterColour(newTerrain, xHitInCell, yHitInCell); pushEditToCommand(newTerrain, document, landTable, cellId); } }
const bool isSquareBrush = mBrushShape == CSVWidget::BrushShape_Square; const bool isCircleBrush = mBrushShape == CSVWidget::BrushShape_Circle; if (isSquareBrush || isCircleBrush) { int upperLeftCellX = cellX - std::floor(r / ESM::Land::LAND_SIZE); int upperLeftCellY = cellY - std::floor(r / ESM::Land::LAND_SIZE); if (xHitInCell - (r % ESM::Land::LAND_SIZE) < 0) upperLeftCellX--; if (yHitInCell - (r % ESM::Land::LAND_SIZE) < 0) upperLeftCellY--;
int lowerrightCellX = cellX + std::floor(r / ESM::Land::LAND_SIZE); int lowerrightCellY = cellY + std::floor(r / ESM::Land::LAND_SIZE); if (xHitInCell + (r % ESM::Land::LAND_SIZE) > ESM::Land::LAND_SIZE - 1) lowerrightCellX++; if (yHitInCell + (r % ESM::Land::LAND_SIZE) > ESM::Land::LAND_SIZE - 1) lowerrightCellY++;
for (int iCell = upperLeftCellX; iCell <= lowerrightCellX; iCell++) { for (int jCell = upperLeftCellY; jCell <= lowerrightCellY; jCell++) { iteratedCellId = CSMWorld::CellCoordinates::generateId(iCell, jCell); if (allowLandColourEditing(iteratedCellId)) { CSMWorld::LandColoursColumn::DataType newTerrain = landTable.data(landTable.getModelIndex(iteratedCellId, colourColumn)) .value<CSMWorld::LandColoursColumn::DataType>(); for (int i = 0; i < ESM::Land::LAND_SIZE; i++) { for (int j = 0; j < ESM::Land::LAND_SIZE; j++) { osg::Vec2f relativeCoords(0.0, 0.0); if (iCell < cellX) relativeCoords.x() = xHitInCell + ESM::Land::LAND_SIZE * abs(iCell - cellX) - i; else if (iCell > cellX) relativeCoords.x() = -xHitInCell + ESM::Land::LAND_SIZE * abs(iCell - cellX) + i; else relativeCoords.x() = abs(i - xHitInCell); if (jCell < cellY) relativeCoords.y() = yHitInCell + ESM::Land::LAND_SIZE * abs(jCell - cellY) - j; else if (jCell > cellY) relativeCoords.y() = -yHitInCell + ESM::Land::LAND_SIZE * abs(jCell - cellY) + j; else relativeCoords.y() = abs(j - yHitInCell); if (isSquareBrush && (relativeCoords.x() >= r || relativeCoords.y() >= r)) continue; if (isCircleBrush && relativeCoords.length() >= r) continue; alterColour(newTerrain, i, j); } } pushEditToCommand(newTerrain, document, landTable, iteratedCellId); } } } }}
void CSVRender::TerrainVertexPaintMode::alterColour( CSMWorld::LandColoursColumn::DataType& landColorsNew, int inCellX, int inCellY){ const int red = mRestoreMode ? 255 : mVertexPaintEditToolColor.red(); const int green = mRestoreMode ? 255 : mVertexPaintEditToolColor.green(); const int blue = mRestoreMode ? 255 : mVertexPaintEditToolColor.blue();
// TODO: handle different smoothing/blend types with different tools, right now this expects Replace landColorsNew[(inCellY * ESM::Land::LAND_SIZE + inCellX) * 3 + 0] = red; landColorsNew[(inCellY * ESM::Land::LAND_SIZE + inCellX) * 3 + 1] = green; landColorsNew[(inCellY * ESM::Land::LAND_SIZE + inCellX) * 3 + 2] = blue;}
void CSVRender::TerrainVertexPaintMode::pushEditToCommand(const CSMWorld::LandColoursColumn::DataType& newLandColours, CSMDoc::Document& document, CSMWorld::IdTable& landTable, const std::string& cellId){ QVariant changedLand; changedLand.setValue(newLandColours);
QModelIndex index( landTable.getModelIndex(cellId, landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandColoursIndex)));
QUndoStack& undoStack = document.getUndoStack(); undoStack.push(new CSMWorld::ModifyCommand(landTable, index, changedLand));}
bool CSVRender::TerrainVertexPaintMode::allowLandColourEditing(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));
const ESM::RefId cellRefId = ESM::RefId::stringRefId(cellId); const bool noCell = document.getData().getCells().searchId(cellRefId) == -1; const bool noLand = document.getData().getLand().searchId(cellRefId) == -1;
if (noCell) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist if (mode == "Discard") return false;
if (useTool && mode == "Create cell and land, then edit") { 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 (useTool && mode == "Show cell and edit") { selection.add(CSMWorld::CellCoordinates::fromId(cellId).first); paged->setCellSelection(selection); } } }
if (noLand) { std::string mode = CSMPrefs::get()["3D Scene Editing"]["outside-landedit"].toString();
// target cell does not exist if (mode == "Discard") return false;
if (useTool && mode == "Create cell and land, then edit") { document.getUndoStack().push(new CSMWorld::CreateCommand(landTable, cellId)); } }
return true;}
void CSVRender::TerrainVertexPaintMode::dragMoveEvent(QDragMoveEvent* event) {}
void CSVRender::TerrainVertexPaintMode::mouseMoveEvent(QMouseEvent* event){ WorldspaceHitResult hit = getWorldspaceWidget().mousePick(event->pos(), getInteractionMask()); if (hit.hit && mBrushDraw) mBrushDraw->update(hit.worldPos, mBrushSize, mBrushShape); if (!hit.hit && mBrushDraw) mBrushDraw->hide();}
void CSVRender::TerrainVertexPaintMode::setBrushSize(int brushSize){ mBrushSize = brushSize;}
void CSVRender::TerrainVertexPaintMode::setBrushShape(CSVWidget::BrushShape brushShape){ mBrushShape = brushShape;}
void CSVRender::TerrainVertexPaintMode::setVertexPaintEditTool(int shapeEditTool){ mVertexPaintEditTool = shapeEditTool;}
void CSVRender::TerrainVertexPaintMode::setVertexPaintColor(const QColor& color){ mVertexPaintEditToolColor = color;}