Merge branch 'feat-openmw-cs-colour-paint' into 'master'
Some checks failed
Build and test / Ubuntu (push) Has been cancelled
Build and test / MacOS (push) Has been cancelled
Build and test / Read .env file and expose it as output (push) Has been cancelled
Build and test / Windows (2019) (push) Has been cancelled
Build and test / Windows (2022) (push) Has been cancelled

OpenMW-CS: Land color painting

See merge request OpenMW/openmw!4031
This commit is contained in:
cykoder 2025-04-25 06:57:11 +00:00
commit e77b3c0e92
7 changed files with 1457 additions and 4 deletions

View file

@ -237,6 +237,7 @@
Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking
Feature #3537: Shader-based water ripples Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty Feature #5173: Support for NiFogProperty
Feature #5197: Editor: terrain vertex paint editmode
Feature #5492: Let rain and snow collide with statics Feature #5492: Let rain and snow collide with statics
Feature #5926: Refraction based on water depth Feature #5926: Refraction based on water depth
Feature #5944: Option to use camera as sound listener Feature #5944: Option to use camera as sound listener

View file

@ -80,7 +80,7 @@ opencs_units (view/world
) )
opencs_units (view/widget opencs_units (view/widget
scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton scenetoolbar scenetool scenetoolmode pushbutton scenetooltoggle scenetoolrun modebutton scenetoolvertexpaintbrush
scenetooltoggle2 scenetooltexturebrush scenetoolshapebrush completerpopup coloreditor colorpickerpopup droplineedit scenetooltoggle2 scenetooltexturebrush scenetoolshapebrush completerpopup coloreditor colorpickerpopup droplineedit
) )
@ -88,7 +88,7 @@ opencs_units (view/render
scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget scenewidget worldspacewidget pagedworldspacewidget unpagedworldspacewidget
previewwidget editmode instancemode instanceselectionmode instancemovemode previewwidget editmode instancemode instanceselectionmode instancemovemode
orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller orbitcameramode pathgridmode selectionmode pathgridselectionmode cameracontroller
cellwater terraintexturemode actor terrainselection terrainshapemode brushdraw commands cellwater terraintexturemode terrainvertexpaintmode actor terrainselection terrainshapemode brushdraw commands
) )
opencs_units (view/render opencs_units (view/render

View file

@ -42,6 +42,7 @@
#include "mask.hpp" #include "mask.hpp"
#include "terrainshapemode.hpp" #include "terrainshapemode.hpp"
#include "terraintexturemode.hpp" #include "terraintexturemode.hpp"
#include "terrainvertexpaintmode.hpp"
class QWidget; class QWidget;
@ -170,9 +171,8 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons(CSVWidget::Sce
/// \todo replace EditMode with suitable subclasses /// \todo replace EditMode with suitable subclasses
tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape"); tool->addButton(new TerrainShapeMode(this, mRootNode, tool), "terrain-shape");
tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture"); tool->addButton(new TerrainTextureMode(this, mRootNode, tool), "terrain-texture");
const QIcon vertexIcon = Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-vertex-paint"); tool->addButton(new TerrainVertexPaintMode(this, mRootNode, tool), "terrain-vertex");
const QIcon movementIcon = Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-movement"); const QIcon movementIcon = Misc::ScalableIcon::load(":scenetoolbar/editing-terrain-movement");
tool->addButton(new EditMode(this, vertexIcon, Mask_Reference, "Terrain vertex paint editing"), "terrain-vertex");
tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move"); tool->addButton(new EditMode(this, movementIcon, Mask_Reference, "Terrain movement"), "terrain-move");
} }

View file

@ -0,0 +1,867 @@
#include "terrainvertexpaintmode.hpp"
#include <algorithm>
#include <cmath>
#include <memory>
#include <string>
#include <QComboBox>
#include <QDropEvent>
#include <QEvent>
#include <QIcon>
#include <QWidget>
#include <osg/Camera>
#include <osg/Vec3f>
#include <apps/opencs/model/doc/document.hpp>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/model/world/cellselection.hpp>
#include <apps/opencs/model/world/columnimp.hpp>
#include <apps/opencs/model/world/columns.hpp>
#include <apps/opencs/model/world/data.hpp>
#include <apps/opencs/model/world/idcollection.hpp>
#include <apps/opencs/model/world/idtable.hpp>
#include <apps/opencs/model/world/land.hpp>
#include <apps/opencs/model/world/record.hpp>
#include <apps/opencs/model/world/universalid.hpp>
#include <apps/opencs/view/widget/brushshapes.hpp>
#include <apps/opencs/view/widget/scenetool.hpp>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadland.hpp>
#include "../widget/scenetoolbar.hpp"
#include "../widget/scenetoolvertexpaintbrush.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::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 (!mTerrainSelection)
{
mTerrainSelection
= std::make_shared<TerrainSelection>(mParentNode, &getWorldspaceWidget(), TerrainSelectionType::Shape);
}
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 (mTerrainSelection)
{
mTerrainSelection.reset();
}
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)
{
if (mDragMode == InteractionType_PrimaryEdit)
{
editVertexColourGrid(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), true);
}
}
endVertexPaintEditing();
}
void CSVRender::TerrainVertexPaintMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.hit && hit.tag == nullptr)
{
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 0);
mTerrainSelection->clearTemporarySelection();
}
}
void CSVRender::TerrainVertexPaintMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
if (hit.hit && hit.tag == nullptr)
{
selectTerrainShapes(CSMWorld::CellCoordinates::toVertexCoords(hit.worldPos), 1);
mTerrainSelection->clearTemporarySelection();
}
}
bool CSVRender::TerrainVertexPaintMode::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;
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
QUndoStack& undoStack = document.getUndoStack();
undoStack.beginMacro("Set land vertex colours");
}
return true;
}
bool CSVRender::TerrainVertexPaintMode::secondaryEditStartDrag(const QPoint& pos)
{
return false;
}
bool CSVRender::TerrainVertexPaintMode::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::TerrainVertexPaintMode::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::TerrainVertexPaintMode::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)
editVertexColourGrid(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::TerrainVertexPaintMode::dragCompleted(const QPoint& pos)
{
if (mDragMode == InteractionType_PrimaryEdit)
{
if (mIsEditing)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
QUndoStack& undoStack = document.getUndoStack();
undoStack.endMacro();
}
endVertexPaintEditing();
}
if (mDragMode == InteractionType_PrimarySelect || mDragMode == InteractionType_SecondarySelect)
{
mTerrainSelection->clearTemporarySelection();
}
}
void CSVRender::TerrainVertexPaintMode::dragAborted()
{
if (mIsEditing)
{
CSMDoc::Document& document = getWorldspaceWidget().getDocument();
QUndoStack& undoStack = document.getUndoStack();
undoStack.endMacro();
}
endVertexPaintEditing();
mDragMode = InteractionType_None;
}
void CSVRender::TerrainVertexPaintMode::dragWheel(int diff, double speedFactor) {}
void CSVRender::TerrainVertexPaintMode::endVertexPaintEditing()
{
mIsEditing = false;
mTerrainSelection->update();
}
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 mCellId = CSMWorld::CellCoordinates::vertexGlobalToCellId(vertexCoords);
if (allowLandColourEditing(mCellId))
{
}
std::pair<CSMWorld::CellCoordinates, bool> cellCoordinates_pair = CSMWorld::CellCoordinates::fromId(mCellId);
int cellX = cellCoordinates_pair.first.getX();
int cellY = cellCoordinates_pair.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 > 64)
{
yHitInCell = yHitInCell - ESM::Land::LAND_SIZE;
cellY = cellY + 1;
}
mCellId = CSMWorld::CellCoordinates::generateId(cellX, cellY);
if (allowLandColourEditing(mCellId))
{
}
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(mCellId, colourColumn))
.value<CSMWorld::LandColoursColumn::DataType>();
if (allowLandColourEditing(mCellId))
{
alterColour(newTerrain, xHitInCell, yHitInCell, 0.0f);
pushEditToCommand(newTerrain, document, landTable, mCellId);
}
}
if (mBrushShape == CSVWidget::BrushShape_Square)
{
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 i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for (int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell);
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++)
{
if (i_cell == cellX && j_cell == cellY && abs(i - xHitInCell) < r
&& abs(j - yHitInCell) < r)
{
alterColour(newTerrain, i, j, 0.0f);
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX)
distanceX = xHitInCell + ESM::Land::LAND_SIZE * abs(i_cell - cellX) - i;
if (j_cell < cellY)
distanceY = yHitInCell + ESM::Land::LAND_SIZE * abs(j_cell - cellY) - j;
if (i_cell > cellX)
distanceX = -xHitInCell + ESM::Land::LAND_SIZE * abs(i_cell - cellX) + i;
if (j_cell > cellY)
distanceY = -yHitInCell + ESM::Land::LAND_SIZE * abs(j_cell - cellY) + j;
if (i_cell == cellX)
distanceX = abs(i - xHitInCell);
if (j_cell == cellY)
distanceY = abs(j - yHitInCell);
if (distanceX < r && distanceY < r)
alterColour(newTerrain, i, j, 0.0f);
}
}
}
pushEditToCommand(newTerrain, document, landTable, iteratedCellId);
}
}
}
}
if (mBrushShape == CSVWidget::BrushShape_Circle)
{
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 i_cell = upperLeftCellX; i_cell <= lowerrightCellX; i_cell++)
{
for (int j_cell = upperLeftCellY; j_cell <= lowerrightCellY; j_cell++)
{
iteratedCellId = CSMWorld::CellCoordinates::generateId(i_cell, j_cell);
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++)
{
if (i_cell == cellX && j_cell == cellY && abs(i - xHitInCell) < r
&& abs(j - yHitInCell) < r)
{
int distanceX = abs(i - xHitInCell);
int distanceY = abs(j - yHitInCell);
float distance = std::round(sqrt(pow(distanceX, 2) + pow(distanceY, 2)));
if (distance < r)
alterColour(newTerrain, i, j, 0.0f);
}
else
{
int distanceX(0);
int distanceY(0);
if (i_cell < cellX)
distanceX = xHitInCell + ESM::Land::LAND_SIZE * abs(i_cell - cellX) - i;
if (j_cell < cellY)
distanceY = yHitInCell + ESM::Land::LAND_SIZE * abs(j_cell - cellY) - j;
if (i_cell > cellX)
distanceX = -xHitInCell + ESM::Land::LAND_SIZE * abs(i_cell - cellX) + i;
if (j_cell > cellY)
distanceY = -yHitInCell + ESM::Land::LAND_SIZE * abs(j_cell - cellY) + j;
if (i_cell == cellX)
distanceX = abs(i - xHitInCell);
if (j_cell == cellY)
distanceY = abs(j - yHitInCell);
float distance = std::round(sqrt(pow(distanceX, 2) + pow(distanceY, 2)));
if (distance < r)
alterColour(newTerrain, i, j, 0.0f);
}
}
}
pushEditToCommand(newTerrain, document, landTable, iteratedCellId);
}
}
}
}
}
void CSVRender::TerrainVertexPaintMode::alterColour(
CSMWorld::LandColoursColumn::DataType& landColorsNew, int inCellX, int inCellY, float alteredHeight, bool useTool)
{
const int red = mVertexPaintEditToolColor.red();
const int green = mVertexPaintEditToolColor.green();
const int blue = 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::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::TerrainVertexPaintMode::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::TerrainVertexPaintMode::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);
}
}
}
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")
mTerrainSelection->onlySelect(selections);
else if (selectAction == "Add to selection")
mTerrainSelection->addSelect(selections);
else if (selectAction == "Remove from selection")
mTerrainSelection->removeSelect(selections);
else if (selectAction == "Invert selection")
mTerrainSelection->toggleSelect(selections);
}
bool CSVRender::TerrainVertexPaintMode::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::TerrainVertexPaintMode::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::TerrainVertexPaintMode::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::TerrainVertexPaintMode::isLandLoaded(const std::string& cellId)
{
if (!noCell(cellId) && !noLand(cellId) && !noLandLoaded(cellId))
return true;
return false;
}
void CSVRender::TerrainVertexPaintMode::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::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));
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);
}
}
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);
}
}
if (useTool && (noCell(cellId) || noLand(cellId) || noLandLoaded(cellId)))
{
Log(Debug::Warning) << "Land creation failed at cell id: " << cellId;
return false;
}
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();
}
std::shared_ptr<CSVRender::TerrainSelection> CSVRender::TerrainVertexPaintMode::getTerrainSelection()
{
return mTerrainSelection;
}
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;
}
CSVRender::PagedWorldspaceWidget& CSVRender::TerrainVertexPaintMode::getPagedWorldspaceWidget()
{
return dynamic_cast<PagedWorldspaceWidget&>(getWorldspaceWidget());
}

View file

@ -0,0 +1,180 @@
#ifndef CSV_RENDER_TERRAINVERTEXPAINTMODE_H
#define CSV_RENDER_TERRAINVERTEXPAINTMODE_H
#include "editmode.hpp"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <apps/opencs/model/world/cellcoordinates.hpp>
#include <osg/Vec3d>
#ifndef Q_MOC_RUN
#include "../../model/world/columnimp.hpp"
#include "../widget/brushshapes.hpp"
#endif
#include "brushdraw.hpp"
class QDragMoveEvent;
class QMouseEvent;
class QObject;
class QPoint;
class QWidget;
namespace osg
{
class Group;
}
namespace CSMDoc
{
class Document;
}
namespace CSVWidget
{
class SceneToolVertexPaintBrush;
}
namespace CSMWorld
{
class IdTable;
}
namespace CSVRender
{
class PagedWorldspaceWidget;
class TerrainSelection;
class WorldspaceWidget;
struct WorldspaceHitResult;
class SceneToolbar;
/// \brief EditMode for handling the terrain shape editing
class TerrainVertexPaintMode : public EditMode
{
Q_OBJECT
public:
enum InteractionType
{
InteractionType_PrimaryEdit,
InteractionType_PrimarySelect,
InteractionType_SecondaryEdit,
InteractionType_SecondarySelect,
InteractionType_None
};
enum VertexPaintEditTool
{
VertexPaintEditTool_Replace = 0
};
/// Editmode for terrain vertex colour grid
TerrainVertexPaintMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr);
void primaryOpenPressed(const WorldspaceHitResult& hit) override;
/// Create single command for one-click vertex paint editing
void primaryEditPressed(const WorldspaceHitResult& hit) override;
/// Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&) override;
void secondarySelectPressed(const WorldspaceHitResult&) override;
void activate(CSVWidget::SceneToolbar*) override;
void deactivate(CSVWidget::SceneToolbar*) override;
/// Start vertex paint editing command macro
bool primaryEditStartDrag(const QPoint& pos) override;
bool secondaryEditStartDrag(const QPoint& pos) override;
bool primarySelectStartDrag(const QPoint& pos) override;
bool secondarySelectStartDrag(const QPoint& pos) override;
/// Handle vertex paint edit behavior during dragging
void drag(const QPoint& pos, int diffX, int diffY, double speedFactor) override;
/// End vertex paint editing command macro
void dragCompleted(const QPoint& pos) override;
/// Cancel vertex paint editing, and reset all pending changes
void dragAborted() override;
void dragWheel(int diff, double speedFactor) override;
void dragMoveEvent(QDragMoveEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
std::shared_ptr<TerrainSelection> getTerrainSelection();
private:
/// Reset everything in the current edit
void endVertexPaintEditing();
/// Handle brush mechanics for colour editing
void editVertexColourGrid(const std::pair<int, int>& vertexCoords, bool dragOperation);
/// Alter one pixel's colour
void alterColour(CSMWorld::LandColoursColumn::DataType& landColorsNew, int inCellX, int inCellY,
float alteredHeight, bool useTool = true);
/// Check if global selection coordinate belongs to cell in view
bool isInCellSelection(int globalSelectionX, int globalSelectionY);
/// Select vertex at global selection coordinate
void handleSelection(int globalSelectionX, int globalSelectionY, std::vector<std::pair<int, int>>* selections);
/// Handle brush mechanics for terrain selection
void selectTerrainShapes(const std::pair<int, int>& vertexCoords, unsigned char selectMode);
bool noCell(const std::string& cellId);
bool noLand(const std::string& cellId);
bool noLandLoaded(const std::string& cellId);
bool isLandLoaded(const std::string& cellId);
/// Push terrain vertex coloir edits to command macro
void pushEditToCommand(const CSMWorld::LandColoursColumn::DataType& newLandColours, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
/// Create new blank height record and new normals, if there are valid adjancent cell, take sample points and
/// set the average height based on that
void createNewLandData(const CSMWorld::CellCoordinates& cellCoords);
/// Create new cell and land if needed, only user tools may ask for opening new cells (useTool == false is for
/// automated land changes)
bool allowLandColourEditing(const std::string& textureFileName, bool useTool = true);
std::string mBrushTexture;
int mBrushSize = 1;
CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point;
std::unique_ptr<BrushDraw> mBrushDraw;
CSVWidget::SceneToolVertexPaintBrush* mVertexPaintBrushScenetool = nullptr;
int mDragMode = InteractionType_None;
osg::Group* mParentNode;
bool mIsEditing = false;
std::shared_ptr<TerrainSelection> mTerrainSelection;
int mTotalDiffY = 0;
std::vector<CSMWorld::CellCoordinates> mAlteredCells;
osg::Vec3d mEditingPos;
int mVertexPaintEditTool = VertexPaintEditTool_Replace;
QColor mVertexPaintEditToolColor;
int mTargetHeight = 0;
PagedWorldspaceWidget& getPagedWorldspaceWidget();
public slots:
void setBrushSize(int brushSize);
void setBrushShape(CSVWidget::BrushShape brushShape);
void setVertexPaintEditTool(int shapeEditTool);
void setVertexPaintColor(const QColor& color);
};
}
#endif

View file

@ -0,0 +1,240 @@
#include "scenetoolvertexpaintbrush.hpp"
#include <QButtonGroup>
#include <QComboBox>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QFrame>
#include <QGroupBox>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QIcon>
#include <QLabel>
#include <QSizePolicy>
#include <QSlider>
#include <QTableWidget>
#include <QVBoxLayout>
#include <QWidget>
#include <apps/opencs/model/prefs/category.hpp>
#include <apps/opencs/model/prefs/setting.hpp>
#include <apps/opencs/view/widget/pushbutton.hpp>
#include "brushshapes.hpp"
#include "scenetool.hpp"
#include "../../model/prefs/state.hpp"
namespace CSVWidget
{
class SceneToolbar;
}
namespace CSMDoc
{
class Document;
}
CSVWidget::VertexPaintBrushSizeControls::VertexPaintBrushSizeControls(const QString& title, QWidget* parent)
: QGroupBox(title, parent)
{
mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides);
mBrushSizeSlider->setTickInterval(10);
mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mBrushSizeSlider->setSingleStep(1);
mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mBrushSizeSpinBox->setSingleStep(1);
QHBoxLayout* layoutSliderSize = new QHBoxLayout;
layoutSliderSize->addWidget(mBrushSizeSlider);
layoutSliderSize->addWidget(mBrushSizeSpinBox);
connect(mBrushSizeSlider, &QSlider::valueChanged, mBrushSizeSpinBox, &QSpinBox::setValue);
connect(mBrushSizeSpinBox, qOverload<int>(&QSpinBox::valueChanged), mBrushSizeSlider, &QSlider::setValue);
setLayout(layoutSliderSize);
}
CSVWidget::VertexPaintBrushWindow::VertexPaintBrushWindow(CSMDoc::Document& document, QWidget* parent)
: QFrame(parent, Qt::Popup)
, mDocument(document)
{
mButtonPoint = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-point")), "", this);
mButtonSquare = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-square")), "", this);
mButtonCircle = new QPushButton(QIcon(QPixmap(":scenetoolbar/brush-circle")), "", this);
mSizeSliders = new VertexPaintBrushSizeControls("Brush size", this);
QVBoxLayout* layoutMain = new QVBoxLayout;
layoutMain->setSpacing(0);
layoutMain->setContentsMargins(4, 0, 4, 4);
QHBoxLayout* layoutHorizontal = new QHBoxLayout;
layoutHorizontal->setSpacing(0);
layoutHorizontal->setContentsMargins(QMargins(0, 0, 0, 0));
configureButtonInitialSettings(mButtonPoint);
configureButtonInitialSettings(mButtonSquare);
configureButtonInitialSettings(mButtonCircle);
mButtonPoint->setToolTip(toolTipPoint);
mButtonSquare->setToolTip(toolTipSquare);
mButtonCircle->setToolTip(toolTipCircle);
QButtonGroup* brushButtonGroup = new QButtonGroup(this);
brushButtonGroup->addButton(mButtonPoint);
brushButtonGroup->addButton(mButtonSquare);
brushButtonGroup->addButton(mButtonCircle);
brushButtonGroup->setExclusive(true);
layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop);
mHorizontalGroupBox = new QGroupBox(tr(""));
mHorizontalGroupBox->setLayout(layoutHorizontal);
mToolSelector = new QComboBox(this);
mToolSelector->addItem(tr("Replace"));
// TOOD: in the future could add types like smooth blend, multiply etc
QLabel* colorLabel = new QLabel(this);
colorLabel->setText("Color:");
mColorButtonWidget = new ColorButtonWidget();
layoutMain->addWidget(mHorizontalGroupBox);
layoutMain->addWidget(mSizeSliders);
layoutMain->addWidget(mToolSelector);
layoutMain->addWidget(colorLabel);
layoutMain->addWidget(mColorButtonWidget);
setLayout(layoutMain);
connect(mButtonPoint, &QPushButton::clicked, this, &VertexPaintBrushWindow::setBrushShape);
connect(mButtonSquare, &QPushButton::clicked, this, &VertexPaintBrushWindow::setBrushShape);
connect(mButtonCircle, &QPushButton::clicked, this, &VertexPaintBrushWindow::setBrushShape);
}
void CSVWidget::VertexPaintBrushWindow::configureButtonInitialSettings(QPushButton* button)
{
button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
button->setContentsMargins(QMargins(0, 0, 0, 0));
button->setIconSize(QSize(48 - 6, 48 - 6));
button->setFixedSize(48, 48);
button->setCheckable(true);
}
void CSVWidget::VertexPaintBrushWindow::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
emit passBrushSize(mBrushSize);
}
void CSVWidget::VertexPaintBrushWindow::setBrushShape()
{
if (mButtonPoint->isChecked())
mBrushShape = BrushShape_Point;
if (mButtonSquare->isChecked())
mBrushShape = BrushShape_Square;
if (mButtonCircle->isChecked())
mBrushShape = BrushShape_Circle;
emit passBrushShape(mBrushShape);
}
void CSVWidget::SceneToolVertexPaintBrush::adjustToolTips() {}
CSVWidget::SceneToolVertexPaintBrush::SceneToolVertexPaintBrush(
SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document)
: SceneTool(parent, Type_TopAction)
, mToolTip(toolTip)
, mDocument(document)
, mVertexPaintBrushWindow(new VertexPaintBrushWindow(document, this))
{
setAcceptDrops(true);
connect(mVertexPaintBrushWindow, &VertexPaintBrushWindow::passBrushShape, this,
&SceneToolVertexPaintBrush::setButtonIcon);
setButtonIcon(mVertexPaintBrushWindow->mBrushShape);
mPanel = new QFrame(this, Qt::Popup);
QHBoxLayout* layout = new QHBoxLayout(mPanel);
layout->setContentsMargins(QMargins(0, 0, 0, 0));
mTable = new QTableWidget(0, 2, this);
mTable->setShowGrid(true);
mTable->verticalHeader()->hide();
mTable->horizontalHeader()->hide();
mTable->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch);
mTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
mTable->setSelectionMode(QAbstractItemView::NoSelection);
layout->addWidget(mTable);
connect(mTable, &QTableWidget::clicked, this, &SceneToolVertexPaintBrush::clicked);
}
void CSVWidget::SceneToolVertexPaintBrush::setButtonIcon(CSVWidget::BrushShape brushShape)
{
QString tooltip = "Change brush settings <p>Currently selected: ";
switch (brushShape)
{
case BrushShape_Point:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-point")));
tooltip += mVertexPaintBrushWindow->toolTipPoint;
break;
case BrushShape_Square:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-square")));
tooltip += mVertexPaintBrushWindow->toolTipSquare;
break;
case BrushShape_Circle:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-circle")));
tooltip += mVertexPaintBrushWindow->toolTipCircle;
break;
case BrushShape_Custom:
setIcon(QIcon(QPixmap(":scenetoolbar/brush-custom")));
tooltip += mVertexPaintBrushWindow->toolTipCustom;
break;
}
setToolTip(tooltip);
}
void CSVWidget::SceneToolVertexPaintBrush::showPanel(const QPoint& position) {}
void CSVWidget::SceneToolVertexPaintBrush::updatePanel() {}
void CSVWidget::SceneToolVertexPaintBrush::clicked(const QModelIndex& index) {}
void CSVWidget::SceneToolVertexPaintBrush::activate()
{
QPoint position = QCursor::pos();
mVertexPaintBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(
1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mVertexPaintBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(
1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mVertexPaintBrushWindow->move(position);
mVertexPaintBrushWindow->show();
}
void CSVWidget::SceneToolVertexPaintBrush::dragEnterEvent(QDragEnterEvent* event)
{
emit passEvent(event);
event->accept();
}
void CSVWidget::SceneToolVertexPaintBrush::dropEvent(QDropEvent* event)
{
emit passEvent(event);
event->accept();
}

View file

@ -0,0 +1,165 @@
#ifndef CSV_WIDGET_SCENETOOLVERTEXPAINTBRUSH_H
#define CSV_WIDGET_SCENETOOLVERTEXPAINTBRUSH_H
#include <QColorDialog>
#include <QFrame>
#include <QGroupBox>
#include <QSlider>
#include <QSpinBox>
#ifndef Q_MOC_RUN
#include "brushshapes.hpp"
#include "scenetool.hpp"
#endif
class QComboBox;
class QDragEnterEvent;
class QDropEvent;
class QModelIndex;
class QObject;
class QPoint;
class QPushButton;
class QWidget;
namespace CSMDoc
{
class Document;
}
class QTableWidget;
namespace CSVRender
{
class TerrainVertexPaintMode;
}
namespace CSVWidget
{
class SceneToolbar;
class ColorButtonWidget : public QPushButton
{
Q_OBJECT
public:
ColorButtonWidget(QWidget* parent = nullptr)
: QPushButton(parent)
{
this->setFixedSize(50, 25);
this->setObjectName("colorSwatchButton");
this->setStyleSheet("QPushButton#colorSwatchButton { border: 1px solid #ccc; }");
connect(this, &QPushButton::clicked, this, &ColorButtonWidget::openColorDialog);
}
private:
QColor mColor = Qt::white;
signals:
void colorChanged(const QColor& newColor);
private slots:
void openColorDialog()
{
QColor color = QColorDialog::getColor(mColor, this, "Select Color");
if (color.isValid())
{
mColor = color;
QString css = QString("QPushButton#colorSwatchButton { background-color: %1; border: 1px solid #ccc; }")
.arg(color.name());
this->setStyleSheet(css);
emit colorChanged(color);
}
}
};
/// \brief Layout-box for some brush button settings
class VertexPaintBrushSizeControls : public QGroupBox
{
Q_OBJECT
public:
VertexPaintBrushSizeControls(const QString& title, QWidget* parent);
private:
QSlider* mBrushSizeSlider = new QSlider(Qt::Horizontal);
QSpinBox* mBrushSizeSpinBox = new QSpinBox;
friend class SceneToolVertexPaintBrush;
friend class CSVRender::TerrainVertexPaintMode;
};
/// \brief Brush settings window
class VertexPaintBrushWindow : public QFrame
{
Q_OBJECT
public:
VertexPaintBrushWindow(CSMDoc::Document& document, QWidget* parent = nullptr);
void configureButtonInitialSettings(QPushButton* button);
const QString toolTipPoint = "Paint single point";
const QString toolTipSquare = "Paint with square brush";
const QString toolTipCircle = "Paint with circle brush";
const QString toolTipCustom = "Paint with custom brush";
private:
CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point;
int mBrushSize = 1;
CSMDoc::Document& mDocument;
QGroupBox* mHorizontalGroupBox;
QComboBox* mToolSelector;
QPushButton* mButtonPoint;
QPushButton* mButtonSquare;
QPushButton* mButtonCircle;
VertexPaintBrushSizeControls* mSizeSliders;
ColorButtonWidget* mColorButtonWidget;
friend class SceneToolVertexPaintBrush;
friend class CSVRender::TerrainVertexPaintMode;
public slots:
void setBrushShape();
void setBrushSize(int brushSize);
signals:
void passBrushSize(int brushSize);
void passBrushShape(CSVWidget::BrushShape brushShape);
};
class SceneToolVertexPaintBrush : public SceneTool
{
Q_OBJECT
QString mToolTip;
CSMDoc::Document& mDocument;
QFrame* mPanel;
QTableWidget* mTable;
VertexPaintBrushWindow* mVertexPaintBrushWindow;
private:
void adjustToolTips();
public:
SceneToolVertexPaintBrush(SceneToolbar* parent, const QString& toolTip, CSMDoc::Document& document);
void showPanel(const QPoint& position) override;
void updatePanel();
void dropEvent(QDropEvent* event) override;
void dragEnterEvent(QDragEnterEvent* event) override;
friend class CSVRender::TerrainVertexPaintMode;
public slots:
void setButtonIcon(CSVWidget::BrushShape brushShape);
void clicked(const QModelIndex& index);
void activate() override;
signals:
void passEvent(QDropEvent* event);
void passEvent(QDragEnterEvent* event);
};
}
#endif