1
0
Fork 1
mirror of https://github.com/TES3MP/openmw-tes3mp.git synced 2025-01-16 19:19:56 +00:00

Merge pull request #2520 from unelsson/transientlandshapeedit

[Review phase] Editor: Transient land shape editing
This commit is contained in:
Bret Curtis 2019-10-25 00:26:43 +02:00 committed by GitHub
commit 9f039fac87
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 2317 additions and 35 deletions

View file

@ -187,6 +187,7 @@
Feature #4784: Launcher: Duplicate Content Lists
Feature #4812: Support NiSwitchNode
Feature #4836: Daytime node switch
Feature #4840: Editor: Transient terrain change support
Feature #4859: Make water reflections more configurable
Feature #4882: Support for NiPalette node
Feature #4887: Add openmw command option to set initial random seed
@ -213,6 +214,7 @@
Feature #5132: Unique animations for different weapon types
Feature #5146: Safe Dispose corpse
Feature #5147: Show spell magicka cost in spell buying window
Feature #5170: Editor: Land shape editing, land selection
Feature #5193: Weapon sheathing
Task #4686: Upgrade media decoder to a more current FFmpeg API
Task #4695: Optimize Distant Terrain memory consumption

View file

@ -40,6 +40,8 @@ New Features:
New Editor Features:
- "Faction Ranks" table for "Faction" records (#4209)
- Changes to height editing can be cancelled without changes to data (press esc to cancel) (#4840)
- Land heightmap/shape editing and vertex selection (#5170)
Bug Fixes:
- Scripted Items cannot be stacked anymore to avoid multiple script execution (#2969)

View file

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

View file

@ -170,7 +170,7 @@ void CSMPrefs::State::declare()
"list go to the first/last item");
declareCategory ("3D Scene Input");
declareDouble ("navi-wheel-factor", "Camera Zoom Sensitivity", 8).setRange(-100.0, 100.0);
declareDouble ("s-navi-sensitivity", "Secondary Camera Movement Sensitivity", 50.0).setRange(-1000.0, 1000.0);
declareSeparator();
@ -178,7 +178,7 @@ void CSMPrefs::State::declare()
declareDouble ("p-navi-free-sensitivity", "Free Camera Sensitivity", 1/650.).setPrecision(5).setRange(0.0, 1.0);
declareBool ("p-navi-free-invert", "Invert Free Camera Mouse Input", false);
declareDouble ("navi-free-lin-speed", "Free Camera Linear Speed", 1000.0).setRange(1.0, 10000.0);
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
declareDouble ("navi-free-rot-speed", "Free Camera Rotational Speed", 3.14 / 2).setRange(0.001, 6.28);
declareDouble ("navi-free-speed-mult", "Free Camera Speed Multiplier (from Modifier)", 8).setRange(0.001, 1000.0);
declareSeparator();
@ -242,12 +242,24 @@ void CSMPrefs::State::declare()
addValues (insertOutsideCell);
declareEnum ("outside-visible-drop", "Handling drops outside of visible cells", showAndInsert).
addValues (insertOutsideVisibleCell);
declareEnum ("outside-landedit", "Handling land edit outside of cells", createAndLandEdit).
declareEnum ("outside-landedit", "Handling terrain edit outside of cells", createAndLandEdit).
setTooltip("Behavior of terrain editing, if land editing brush reaches an area without cell record.").
addValues (landeditOutsideCell);
declareEnum ("outside-visible-landedit", "Handling land edit outside of visible cells", showAndLandEdit).
declareEnum ("outside-visible-landedit", "Handling terrain edit outside of visible cells", showAndLandEdit).
setTooltip("Behavior of terrain editing, if land editing brush reaches an area that is not currently visible.").
addValues (landeditOutsideVisibleCell);
declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50).
setMin (1);
declareInt ("shapebrush-maximumsize", "Maximum height edit brush size", 100).
setTooltip("Setting for the slider range of brush size in terrain height editing.").
setMin (1);
declareBool ("landedit-post-smoothpainting", "Smooth land after painting height", false).
setTooltip("Raise and lower tools will leave bumpy finish without this option");
declareDouble ("landedit-post-smoothstrength", "Smoothing strength (post-edit)", 0.25).
setTooltip("If smoothing land after painting height is used, this is the percentage of smooth applied afterwards. "
"Negative values may be used to roughen instead of smooth.").
setMin (-1).
setMax (1);
declareBool ("open-list-view", "Open displays list view", false).
setTooltip ("When opening a reference from the scene view, it will open the"
" instance list view instead of the individual instance record view.");

View file

@ -89,7 +89,7 @@ namespace CSMWorld
DataType values(Size, 0);
if (land.isDataLoaded(Land::DATA_WNAM))
if (land.mDataTypes & Land::DATA_WNAM)
{
for (int i = 0; i < Size; ++i)
values[i] = land.mWnam[i];

View file

@ -134,7 +134,7 @@ void CSVRender::Cell::updateLand()
else
{
mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode,
mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain));
mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain));
}
mTerrain->loadCell(esmLand.mX, esmLand.mY);
@ -149,7 +149,6 @@ void CSVRender::Cell::updateLand()
}
// No land data
mLandDeleted = true;
unloadLand();
}
@ -169,6 +168,8 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, osg::Group* rootNode, const std::st
{
std::pair<CSMWorld::CellCoordinates, bool> result = CSMWorld::CellCoordinates::fromId (id);
mTerrainStorage = new TerrainStorage(mData);
if (result.second)
mCoordinates = result.first;
@ -347,6 +348,28 @@ bool CSVRender::Cell::referenceAdded (const QModelIndex& parent, int start, int
return addObjects (start, end);
}
void CSVRender::Cell::setAlteredHeight(int inCellX, int inCellY, float height)
{
mTerrainStorage->setAlteredHeight(inCellX, inCellY, height);
mUpdateLand = true;
}
float CSVRender::Cell::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY)
{
return mTerrainStorage->getSumOfAlteredAndTrueHeight(cellX, cellY, inCellX, inCellY);
}
float* CSVRender::Cell::getAlteredHeight(int inCellX, int inCellY)
{
return mTerrainStorage->getAlteredHeight(inCellX, inCellY);
}
void CSVRender::Cell::resetAlteredHeights()
{
mTerrainStorage->resetHeights();
mUpdateLand = true;
}
void CSVRender::Cell::pathgridModified()
{
if (mPathgrid)

View file

@ -9,6 +9,7 @@
#include <osg/ref_ptr>
#include "../../model/world/cellcoordinates.hpp"
#include "terrainstorage.hpp"
class QModelIndex;
@ -58,6 +59,7 @@ namespace CSVRender
int mSubMode;
unsigned int mSubModeElementMask;
bool mUpdateLand, mLandDeleted;
TerrainStorage *mTerrainStorage;
/// Ignored if cell does not have an object with the given ID.
///
@ -118,6 +120,14 @@ namespace CSVRender
/// this cell?
bool referenceAdded (const QModelIndex& parent, int start, int end);
void setAlteredHeight(int inCellX, int inCellY, float height);
float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY);
float* getAlteredHeight(int inCellX, int inCellY);
void resetAlteredHeights();
void pathgridModified();
void pathgridRemoved();

View file

@ -25,6 +25,7 @@
#include "cameracontroller.hpp"
#include "cellarrow.hpp"
#include "terraintexturemode.hpp"
#include "terrainshapemode.hpp"
bool CSVRender::PagedWorldspaceWidget::adjustCells()
{
@ -137,11 +138,9 @@ void CSVRender::PagedWorldspaceWidget::addEditModeSelectorButtons (
/// \todo replace EditMode with suitable subclasses
tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain shape editing"),
"terrain-shape");
new TerrainShapeMode (this, mRootNode, tool), "terrain-shape");
tool->addButton (
new TerrainTextureMode (this, mRootNode, tool),
"terrain-texture");
new TerrainTextureMode (this, mRootNode, tool), "terrain-texture");
tool->addButton (
new EditMode (this, QIcon (":placeholder"), Mask_Reference, "Terrain vertex paint editing"),
"terrain-vertex");
@ -791,6 +790,36 @@ CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const osg::Vec3d& poi
return 0;
}
CSVRender::Cell* CSVRender::PagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const
{
std::map<CSMWorld::CellCoordinates, Cell*>::const_iterator searchResult = mCells.find(coords);
if (searchResult != mCells.end())
return searchResult->second;
else
return nullptr;
}
void CSVRender::PagedWorldspaceWidget::setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height)
{
std::map<CSMWorld::CellCoordinates, Cell*>::iterator searchResult = mCells.find(coords);
if (searchResult != mCells.end())
searchResult->second->setAlteredHeight(inCellX, inCellY, height);
}
float* CSVRender::PagedWorldspaceWidget::getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY)
{
std::map<CSMWorld::CellCoordinates, Cell*>::iterator searchResult = mCells.find(coords);
if (searchResult != mCells.end())
return searchResult->second->getAlteredHeight(inCellX, inCellY);
return nullptr;
}
void CSVRender::PagedWorldspaceWidget::resetAllAlteredHeights()
{
for (const auto& cell : mCells)
cell.second->resetAlteredHeights();
}
std::vector<osg::ref_ptr<CSVRender::TagBase> > CSVRender::PagedWorldspaceWidget::getSelection (
unsigned int elementMask) const
{

View file

@ -124,6 +124,14 @@ namespace CSVRender
virtual Cell* getCell(const osg::Vec3d& point) const;
virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const;
void setCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY, float height);
float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY);
void resetAllAlteredHeights();
virtual std::vector<osg::ref_ptr<TagBase> > getSelection (unsigned int elementMask)
const;

View file

@ -249,13 +249,11 @@ int CSVRender::TerrainSelection::calculateLandHeight(int x, int y) // global ver
int localX = x - cellX * (ESM::Land::LAND_SIZE - 1);
int localY = y - cellY * (ESM::Land::LAND_SIZE - 1);
std::string cellId = CSMWorld::CellCoordinates::generateId(cellX, cellY);
CSMWorld::CellCoordinates coords (cellX, cellY);
CSMDoc::Document& document = mWorldspaceWidget->getDocument();
CSMWorld::IdTable& landTable = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_Land));
int landshapeColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandHeightsIndex);
const CSMWorld::LandHeightsColumn::DataType mPointer = landTable.data(landTable.getModelIndex(cellId, landshapeColumn)).value<CSMWorld::LandHeightsColumn::DataType>();
float landHeight = 0.f;
if (CSVRender::Cell* cell = dynamic_cast<CSVRender::Cell*>(mWorldspaceWidget->getCell(coords)))
landHeight = cell->getSumOfAlteredAndTrueHeight(cellX, cellY, localX, localY);
return mPointer[localY*ESM::Land::LAND_SIZE + localX];
return landHeight;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,190 @@
#ifndef CSV_RENDER_TERRAINSHAPEMODE_H
#define CSV_RENDER_TERRAINSHAPEMODE_H
#include "editmode.hpp"
#include <string>
#include <memory>
#include <QWidget>
#include <QEvent>
#ifndef Q_MOC_RUN
#include "../../model/world/data.hpp"
#include "../../model/world/land.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
#include "../widget/brushshapes.hpp"
#endif
#include "terrainselection.hpp"
namespace CSVWidget
{
class SceneToolShapeBrush;
}
namespace CSVRender
{
class PagedWorldspaceWidget;
/// \brief EditMode for handling the terrain shape editing
class TerrainShapeMode : public EditMode
{
Q_OBJECT
public:
enum InteractionType
{
InteractionType_PrimaryEdit,
InteractionType_PrimarySelect,
InteractionType_SecondaryEdit,
InteractionType_SecondarySelect,
InteractionType_None
};
enum ShapeEditTool
{
ShapeEditTool_Drag = 0,
ShapeEditTool_PaintToRaise = 1,
ShapeEditTool_PaintToLower = 2,
ShapeEditTool_Smooth = 3,
ShapeEditTool_Flatten = 4
};
/// Editmode for terrain shape grid
TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr);
void primaryOpenPressed (const WorldspaceHitResult& hit) final;
/// Create single command for one-click shape editing
void primaryEditPressed (const WorldspaceHitResult& hit) final;
/// Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&) final;
void secondarySelectPressed(const WorldspaceHitResult&) final;
void activate(CSVWidget::SceneToolbar*) final;
void deactivate(CSVWidget::SceneToolbar*) final;
/// Start shape editing command macro
bool primaryEditStartDrag (const QPoint& pos) final;
bool secondaryEditStartDrag (const QPoint& pos) final;
bool primarySelectStartDrag (const QPoint& pos) final;
bool secondarySelectStartDrag (const QPoint& pos) final;
/// Handle shape edit behavior during dragging
void drag (const QPoint& pos, int diffX, int diffY, double speedFactor) final;
/// End shape editing command macro
void dragCompleted(const QPoint& pos) final;
/// Cancel shape editing, and reset all pending changes
void dragAborted() final;
void dragWheel (int diff, double speedFactor) final;
void dragMoveEvent (QDragMoveEvent *event) final;
private:
/// Remove duplicates and sort mAlteredCells, then limitAlteredHeights forward and reverse
void sortAndLimitAlteredCells();
/// Move pending alteredHeights changes to omwgame/omwaddon -data
void applyTerrainEditChanges();
/// Handle brush mechanics for shape editing
void editTerrainShapeGrid (const std::pair<int, int>& vertexCoords, bool dragOperation);
/// Calculate height, when aiming for bump-shaped terrain change
float calculateBumpShape(float distance, int radius, float height);
/// set the target height for flatten tool
void setFlattenToolTargetHeight(const WorldspaceHitResult& hit);
/// Do a single height alteration for transient shape edit map
void alterHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float alteredHeight, bool useTool = true);
/// Do a single smoothing height alteration for transient shape edit map
void smoothHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength);
/// Do a single flattening height alteration for transient shape edit map
void flattenHeight(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, int toolStrength, int targetHeight);
/// Get altered height values around one vertex
void 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);
///Limit steepness based on either X or Y and return false if steepness is limited
void compareAndLimit(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* limitedAlteredHeightXAxis,
float* limitedAlteredHeightYAxis, bool* steepnessIsWithinLimits);
/// Check that the edit doesn't break save format limits, fix if necessary, return true if slope steepness is within limits
bool limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords, bool reverseMode = false);
/// Handle brush mechanics for terrain shape selection
void selectTerrainShapes (const std::pair<int, int>& vertexCoords, unsigned char selectMode, bool dragOperation);
/// Push terrain shape edits to command macro
void pushEditToCommand (const CSMWorld::LandHeightsColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
/// Push land normals edits to command macro
void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
/// Generate new land map LOD
void pushLodToCommand(const CSMWorld::LandMapLodColumn::DataType& newLandMapLod, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, const std::string& cellId);
bool noCell(const std::string& cellId);
bool noLand(const std::string& cellId);
bool noLandLoaded(const std::string& cellId);
bool isLandLoaded(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 allowLandShapeEditing(const std::string& textureFileName, bool useTool = true);
/// Bind the edging vertice to the values of the adjancent cells
void fixEdges(CSMWorld::CellCoordinates cellCoords);
std::string mBrushTexture;
int mBrushSize = 1;
CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point;
std::vector<std::pair<int, int>> mCustomBrushShape;
CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool = nullptr;
int mDragMode = InteractionType_None;
osg::Group* mParentNode;
bool mIsEditing = false;
std::unique_ptr<TerrainSelection> mTerrainShapeSelection;
int mTotalDiffY = 0;
std::vector<CSMWorld::CellCoordinates> mAlteredCells;
osg::Vec3d mEditingPos;
int mShapeEditTool = ShapeEditTool_Drag;
int mShapeEditToolStrength = 8;
int mTargetHeight = 0;
PagedWorldspaceWidget& getPagedWorldspaceWidget();
public slots:
void setBrushSize(int brushSize);
void setBrushShape(CSVWidget::BrushShape brushShape);
void setShapeEditTool(int shapeEditTool);
void setShapeEditToolStrength(int shapeEditToolStrength);
};
}
#endif

View file

@ -3,13 +3,15 @@
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
#include <components/esmterrain/storage.hpp>
namespace CSVRender
{
TerrainStorage::TerrainStorage(const CSMWorld::Data &data)
: ESMTerrain::Storage(data.getResourceSystem()->getVFS())
, mData(data)
{
resetHeights();
}
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY)
@ -33,10 +35,118 @@ namespace CSVRender
return &mData.getLandTextures().getRecord(row).get();
}
void TerrainStorage::setAlteredHeight(int inCellX, int inCellY, float height)
{
mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] = height - fmod(height, 8); //Limit to divisible by 8 to avoid cell seam breakage
}
void TerrainStorage::resetHeights()
{
std::fill(std::begin(mAlteredHeight), std::end(mAlteredHeight), 0);
}
float TerrainStorage::getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY)
{
float height = 0.f;
osg::ref_ptr<const ESMTerrain::LandObject> land = getLand (cellX, cellY);
if (land)
{
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
if (data) height = getVertexHeight(data, inCellX, inCellY);
}
else return height;
return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height;
}
float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY)
{
return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX];
}
void TerrainStorage::getBounds(float &minX, float &maxX, float &minY, float &maxY)
{
// not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells
throw std::runtime_error("getBounds not implemented");
}
int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const
{
return heightData->mHeights[col*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)];
}
int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const
{
return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] +
mAlteredHeight[static_cast<unsigned int>((col)*ESM::Land::LAND_SIZE + row - 1)];
}
int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const
{
return heightData->mHeights[col*ESM::Land::LAND_SIZE + row + 1] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row + 1)];
}
int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const
{
return heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>((col - 1)*ESM::Land::LAND_SIZE + row)];
}
int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const
{
return heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>((col + 1)*ESM::Land::LAND_SIZE + row)];
}
int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const
{
return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData));
}
int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const
{
return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData));
}
int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const
{
return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData));
}
int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const
{
return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData));
}
bool TerrainStorage::leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const
{
return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit ||
getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit;
}
bool TerrainStorage::rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const
{
return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit ||
getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit;
}
void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const
{
// Highlight broken height changes
int heightWarningLimit = 1024;
if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) ||
((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData)))
{
color.r() = 255;
color.g() = 0;
color.b() = 0;
}
}
float TerrainStorage::getAlteredHeight(int col, int row) const
{
return mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)];
}
}

View file

@ -1,13 +1,14 @@
#ifndef OPENCS_RENDER_TERRAINSTORAGE_H
#define OPENCS_RENDER_TERRAINSTORAGE_H
#include <array>
#include <components/esmterrain/storage.hpp>
#include "../../model/world/data.hpp"
namespace CSVRender
{
/**
* @brief A bridge between the terrain component and OpenCS's terrain data storage.
*/
@ -15,13 +16,34 @@ namespace CSVRender
{
public:
TerrainStorage(const CSMWorld::Data& data);
void setAlteredHeight(int inCellX, int inCellY, float heightMap);
void resetHeights();
float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY);
float* getAlteredHeight(int inCellX, int inCellY);
private:
const CSMWorld::Data& mData;
std::array<float, ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE> mAlteredHeight;
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) final;
const ESM::LandTexture* getLandTexture(int index, short plugin) final;
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
void getBounds(float& minX, float& maxX, float& minY, float& maxY) final;
int getThisHeight(int col, int row, const ESM::Land::LandData *heightData) const;
int getLeftHeight(int col, int row, const ESM::Land::LandData *heightData) const;
int getRightHeight(int col, int row, const ESM::Land::LandData *heightData) const;
int getUpHeight(int col, int row, const ESM::Land::LandData *heightData) const;
int getDownHeight(int col, int row, const ESM::Land::LandData *heightData) const;
int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData *heightData) const;
int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData *heightData) const;
int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData *heightData) const;
int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData *heightData) const;
bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const;
bool rightOrDownIsOverTheLimit(int col, int row, int heightWarningLimit, const ESM::Land::LandData *heightData) const;
void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const final;
float getAlteredHeight(int col, int row) const final;
};
}

View file

@ -150,6 +150,11 @@ CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const osg::Vec3d& p
return mCell.get();
}
CSVRender::Cell* CSVRender::UnpagedWorldspaceWidget::getCell(const CSMWorld::CellCoordinates& coords) const
{
return mCell.get();
}
std::vector<osg::ref_ptr<CSVRender::TagBase> > CSVRender::UnpagedWorldspaceWidget::getSelection (
unsigned int elementMask) const
{

View file

@ -17,6 +17,7 @@ namespace CSMDoc
namespace CSMWorld
{
class IdTable;
class CellCoordinates;
}
namespace CSVRender
@ -63,6 +64,8 @@ namespace CSVRender
virtual Cell* getCell(const osg::Vec3d& point) const;
virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const;
virtual std::vector<osg::ref_ptr<TagBase> > getSelection (unsigned int elementMask)
const;

View file

@ -17,6 +17,7 @@ namespace CSMPrefs
namespace CSMWorld
{
class CellCoordinates;
class UniversalId;
}
@ -170,6 +171,8 @@ namespace CSVRender
/// \note Returns the cell if it exists, otherwise a null pointer
virtual Cell* getCell(const osg::Vec3d& point) const = 0;
virtual Cell* getCell(const CSMWorld::CellCoordinates& coords) const = 0;
virtual std::vector<osg::ref_ptr<TagBase> > getSelection (unsigned int elementMask)
const = 0;

View file

@ -0,0 +1,14 @@
#ifndef CSV_WIDGET_BRUSHSHAPES_H
#define CSV_WIDGET_BRUSHSHAPES_H
namespace CSVWidget
{
enum BrushShape
{
BrushShape_Point,
BrushShape_Square,
BrushShape_Circle,
BrushShape_Custom
};
}
#endif

View file

@ -0,0 +1,263 @@
#include "scenetoolshapebrush.hpp"
#include <QFrame>
#include <QIcon>
#include <QTableWidget>
#include <QHBoxLayout>
#include <QWidget>
#include <QComboBox>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QDropEvent>
#include <QButtonGroup>
#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QDrag>
#include <QTableWidget>
#include <QHeaderView>
#include <QApplication>
#include <QSizePolicy>
#include "brushshapes.hpp"
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
#include "../../model/prefs/state.hpp"
#include "../../model/world/commands.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/idcollection.hpp"
#include "../../model/world/idtable.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/universalid.hpp"
CSVWidget::ShapeBrushSizeControls::ShapeBrushSizeControls(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, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int)));
connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int)));
setLayout(layoutSliderSize);
}
CSVWidget::ShapeBrushWindow::ShapeBrushWindow(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);
mButtonCustom = new QPushButton(QIcon (QPixmap (":scenetoolbar/brush-custom")), "", this);
mSizeSliders = new ShapeBrushSizeControls("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);
configureButtonInitialSettings(mButtonCustom);
mButtonPoint->setToolTip (toolTipPoint);
mButtonSquare->setToolTip (toolTipSquare);
mButtonCircle->setToolTip (toolTipCircle);
mButtonCustom->setToolTip (toolTipCustom);
QButtonGroup* brushButtonGroup = new QButtonGroup(this);
brushButtonGroup->addButton(mButtonPoint);
brushButtonGroup->addButton(mButtonSquare);
brushButtonGroup->addButton(mButtonCircle);
brushButtonGroup->addButton(mButtonCustom);
brushButtonGroup->setExclusive(true);
layoutHorizontal->addWidget(mButtonPoint, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonSquare, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCircle, 0, Qt::AlignTop);
layoutHorizontal->addWidget(mButtonCustom, 0, Qt::AlignTop);
mHorizontalGroupBox = new QGroupBox(tr(""));
mHorizontalGroupBox->setLayout(layoutHorizontal);
mToolSelector = new QComboBox(this);
mToolSelector->addItem(tr("Height (drag)"));
mToolSelector->addItem(tr("Height, raise (paint)"));
mToolSelector->addItem(tr("Height, lower (paint)"));
mToolSelector->addItem(tr("Smooth (paint)"));
mToolSelector->addItem(tr("Flatten (paint)"));
QLabel *brushStrengthLabel = new QLabel(this);
brushStrengthLabel->setText("Brush strength:");
mToolStrengthSlider = new QSlider(Qt::Horizontal);
mToolStrengthSlider->setTickPosition(QSlider::TicksBothSides);
mToolStrengthSlider->setTickInterval(8);
mToolStrengthSlider->setRange(8, 128);
mToolStrengthSlider->setSingleStep(8);
mToolStrengthSlider->setValue(8);
layoutMain->addWidget(mHorizontalGroupBox);
layoutMain->addWidget(mSizeSliders);
layoutMain->addWidget(mToolSelector);
layoutMain->addWidget(brushStrengthLabel);
layoutMain->addWidget(mToolStrengthSlider);
setLayout(layoutMain);
connect(mButtonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape()));
connect(mButtonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape()));
}
void CSVWidget::ShapeBrushWindow::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::ShapeBrushWindow::setBrushSize(int brushSize)
{
mBrushSize = brushSize;
emit passBrushSize(mBrushSize);
}
void CSVWidget::ShapeBrushWindow::setBrushShape()
{
if(mButtonPoint->isChecked()) mBrushShape = BrushShape_Point;
if(mButtonSquare->isChecked()) mBrushShape = BrushShape_Square;
if(mButtonCircle->isChecked()) mBrushShape = BrushShape_Circle;
if(mButtonCustom->isChecked()) mBrushShape = BrushShape_Custom;
emit passBrushShape(mBrushShape);
}
void CSVWidget::SceneToolShapeBrush::adjustToolTips()
{
}
CSVWidget::SceneToolShapeBrush::SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document)
: SceneTool (parent, Type_TopAction),
mToolTip (toolTip),
mDocument (document),
mShapeBrushWindow(new ShapeBrushWindow(document, this))
{
setAcceptDrops(true);
connect(mShapeBrushWindow, SIGNAL(passBrushShape(CSVWidget::BrushShape)), this, SLOT(setButtonIcon(CSVWidget::BrushShape)));
setButtonIcon(mShapeBrushWindow->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();
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
mTable->horizontalHeader()->setSectionResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setSectionResizeMode (1, QHeaderView::Stretch);
#else
mTable->horizontalHeader()->setResizeMode (0, QHeaderView::Stretch);
mTable->horizontalHeader()->setResizeMode (1, QHeaderView::Stretch);
#endif
mTable->setSelectionMode (QAbstractItemView::NoSelection);
layout->addWidget (mTable);
connect (mTable, SIGNAL (clicked (const QModelIndex&)),
this, SLOT (clicked (const QModelIndex&)));
}
void CSVWidget::SceneToolShapeBrush::setButtonIcon (CSVWidget::BrushShape brushShape)
{
QString tooltip = "Change brush settings <p>Currently selected: ";
switch (brushShape)
{
case BrushShape_Point:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-point")));
tooltip += mShapeBrushWindow->toolTipPoint;
break;
case BrushShape_Square:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-square")));
tooltip += mShapeBrushWindow->toolTipSquare;
break;
case BrushShape_Circle:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle")));
tooltip += mShapeBrushWindow->toolTipCircle;
break;
case BrushShape_Custom:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-custom")));
tooltip += mShapeBrushWindow->toolTipCustom;
break;
}
setToolTip (tooltip);
}
void CSVWidget::SceneToolShapeBrush::showPanel (const QPoint& position)
{
}
void CSVWidget::SceneToolShapeBrush::updatePanel ()
{
}
void CSVWidget::SceneToolShapeBrush::clicked (const QModelIndex& index)
{
}
void CSVWidget::SceneToolShapeBrush::activate ()
{
QPoint position = QCursor::pos();
mShapeBrushWindow->mSizeSliders->mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mShapeBrushWindow->mSizeSliders->mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mShapeBrushWindow->move (position);
mShapeBrushWindow->show();
}
void CSVWidget::SceneToolShapeBrush::dragEnterEvent (QDragEnterEvent *event)
{
emit passEvent(event);
event->accept();
}
void CSVWidget::SceneToolShapeBrush::dropEvent (QDropEvent *event)
{
emit passEvent(event);
event->accept();
}

View file

@ -0,0 +1,127 @@
#ifndef CSV_WIDGET_SCENETOOLSHAPEBRUSH_H
#define CSV_WIDGET_SCENETOOLSHAPEBRUSH_H
#include <QIcon>
#include <QFrame>
#include <QModelIndex>
#include <QWidget>
#include <QLabel>
#include <QComboBox>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QHBoxLayout>
#include <QPushButton>
#ifndef Q_MOC_RUN
#include "brushshapes.hpp"
#include "scenetool.hpp"
#include "../../model/doc/document.hpp"
#endif
class QTableWidget;
namespace CSVRender
{
class TerrainShapeMode;
}
namespace CSVWidget
{
/// \brief Layout-box for some brush button settings
class ShapeBrushSizeControls : public QGroupBox
{
Q_OBJECT
public:
ShapeBrushSizeControls(const QString &title, QWidget *parent);
private:
QSlider *mBrushSizeSlider = new QSlider(Qt::Horizontal);
QSpinBox *mBrushSizeSpinBox = new QSpinBox;
friend class SceneToolShapeBrush;
friend class CSVRender::TerrainShapeMode;
};
/// \brief Brush settings window
class ShapeBrushWindow : public QFrame
{
Q_OBJECT
public:
ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent = 0);
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, defined by terrain selection";
private:
CSVWidget::BrushShape mBrushShape = CSVWidget::BrushShape_Point;
int mBrushSize = 1;
CSMDoc::Document& mDocument;
QGroupBox *mHorizontalGroupBox;
QComboBox *mToolSelector;
QSlider *mToolStrengthSlider;
QPushButton *mButtonPoint;
QPushButton *mButtonSquare;
QPushButton *mButtonCircle;
QPushButton *mButtonCustom;
ShapeBrushSizeControls* mSizeSliders;
friend class SceneToolShapeBrush;
friend class CSVRender::TerrainShapeMode;
public slots:
void setBrushShape();
void setBrushSize(int brushSize);
signals:
void passBrushSize (int brushSize);
void passBrushShape(CSVWidget::BrushShape brushShape);
};
class SceneToolShapeBrush : public SceneTool
{
Q_OBJECT
QString mToolTip;
CSMDoc::Document& mDocument;
QFrame *mPanel;
QTableWidget *mTable;
ShapeBrushWindow *mShapeBrushWindow;
private:
void adjustToolTips();
public:
SceneToolShapeBrush (SceneToolbar *parent, const QString& toolTip, CSMDoc::Document& document);
virtual void showPanel (const QPoint& position);
void updatePanel ();
void dropEvent (QDropEvent *event);
void dragEnterEvent (QDragEnterEvent *event);
friend class CSVRender::TerrainShapeMode;
public slots:
void setButtonIcon(CSVWidget::BrushShape brushShape);
void clicked (const QModelIndex& index);
virtual void activate();
signals:
void passEvent(QDropEvent *event);
void passEvent(QDragEnterEvent *event);
};
}
#endif

View file

@ -255,7 +255,7 @@ namespace ESMTerrain
(*positions)[static_cast<unsigned int>(vertX*numVerts + vertY)]
= osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits,
(vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits,
height);
height + getAlteredHeight(col, row));
if (normalData)
{
@ -291,6 +291,8 @@ namespace ESMTerrain
color.b() = 255;
}
adjustColor(col, row, heightData, color); //Does nothing by default, override in OpenMW-CS
// Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixColour(color, cellX, cellY, col, row, cache);
@ -521,13 +523,6 @@ namespace ESMTerrain
}
float Storage::getVertexHeight(const ESM::Land::LandData* data, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return data->mHeights[y * ESM::Land::LAND_SIZE + x];
}
const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache)
{
LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY));
@ -540,6 +535,15 @@ namespace ESMTerrain
}
}
void Storage::adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const
{
}
float Storage::getAlteredHeight(int col, int row) const
{
return 0;
}
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mLayerInfoMutex);

View file

@ -1,6 +1,8 @@
#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H
#define COMPONENTS_ESM_TERRAIN_STORAGE_H
#include <cassert>
#include <OpenThreads/Mutex>
#include <components/terrain/storage.hpp>
@ -107,6 +109,13 @@ namespace ESMTerrain
virtual int getBlendmapScale(float chunkSize);
float getVertexHeight (const ESM::Land::LandData* data, int x, int y)
{
assert(x < ESM::Land::LAND_SIZE);
assert(y < ESM::Land::LAND_SIZE);
return data->mHeights[y * ESM::Land::LAND_SIZE + x];
}
private:
const VFS::Manager* mVFS;
@ -114,10 +123,11 @@ namespace ESMTerrain
inline void fixColour (osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache);
inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
inline float getVertexHeight (const ESM::Land::LandData* data, int x, int y);
inline const LandObject* getLand(int cellX, int cellY, LandCache& cache);
virtual void adjustColor(int col, int row, const ESM::Land::LandData *heightData, osg::Vec4ub& color) const;
virtual float getAlteredHeight(int col, int row) const;
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>