Transient land shape editing

pull/2520/head
Nelsson Huotari 5 years ago
parent 262d87846c
commit 16138fc896

@ -184,6 +184,8 @@
Feature #4784: Launcher: Duplicate Content Lists
Feature #4812: Support NiSwitchNode
Feature #4836: Daytime node switch
Feature #4840: Editor: Transient terrain change support
Feature #????: Editor: Land shape editing, land selection
Feature #4859: Make water reflections more configurable
Feature #4882: Support for NiPalette node
Feature #4887: Add openmw command option to set initial random seed

@ -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

@ -248,6 +248,8 @@ void CSMPrefs::State::declare()
addValues (landeditOutsideVisibleCell);
declareInt ("texturebrush-maximumsize", "Maximum texture brush size", 50).
setMin (1);
declareInt ("shapebrush-maximumsize", "Maximum texture brush size", 100).
setMin (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.");

@ -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);
@ -169,6 +169,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 +349,33 @@ 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::getAlteredHeights()
{
return mTerrainStorage->getAlteredHeights();
}
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)

@ -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,16 @@ 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* getAlteredHeights();
float* getAlteredHeight(int inCellX, int inCellY);
void resetAlteredHeights();
void pathgridModified();
void pathgridRemoved();

@ -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,46 @@ 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 0;
}
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::getCellAlteredHeights(const CSMWorld::CellCoordinates& coords)
{
std::map<CSMWorld::CellCoordinates, Cell*>::iterator searchResult = mCells.find(coords);
if (searchResult != mCells.end()) return searchResult->second->getAlteredHeights();
return nullptr;
}
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()
{
std::map<CSMWorld::CellCoordinates, Cell *>::iterator iter (mCells.begin());
while (iter!=mCells.end())
{
iter->second->resetAlteredHeights();
++iter;
}
}
std::vector<osg::ref_ptr<CSVRender::TagBase> > CSVRender::PagedWorldspaceWidget::getSelection (
unsigned int elementMask) const
{

@ -124,6 +124,16 @@ 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* getCellAlteredHeights(const CSMWorld::CellCoordinates& coords);
float* getCellAlteredHeight(const CSMWorld::CellCoordinates& coords, int inCellX, int inCellY);
void resetAllAlteredHeights();
virtual std::vector<osg::ref_ptr<TagBase> > getSelection (unsigned int elementMask)
const;

@ -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

@ -0,0 +1,157 @@
#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"
#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
};
/// Editmode for terrain shape grid
TerrainShapeMode(WorldspaceWidget*, osg::Group* parentNode, QWidget* parent = nullptr);
void primaryOpenPressed (const WorldspaceHitResult& hit);
/// Create single command for one-click shape editing
void primaryEditPressed (const WorldspaceHitResult& hit);
/// Open brush settings window
void primarySelectPressed(const WorldspaceHitResult&);
void secondarySelectPressed(const WorldspaceHitResult&);
void activate(CSVWidget::SceneToolbar*);
void deactivate(CSVWidget::SceneToolbar*);
/// Start shape editing command macro
virtual bool primaryEditStartDrag (const QPoint& pos);
virtual bool secondaryEditStartDrag (const QPoint& pos);
virtual bool primarySelectStartDrag (const QPoint& pos);
virtual bool secondarySelectStartDrag (const QPoint& pos);
/// Handle shape edit behavior during dragging
virtual void drag (const QPoint& pos, int diffX, int diffY, double speedFactor);
/// End shape editing command macro
virtual void dragCompleted(const QPoint& pos);
/// Cancel shape editing, and reset all pending changes
virtual void dragAborted();
virtual void dragWheel (int diff, double speedFactor);
virtual void dragMoveEvent (QDragMoveEvent *event);
/// Handle brush mechanics for shape editing
void editTerrainShapeGrid (const std::pair<int, int>& vertexCoords, bool dragOperation);
/// 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);
/// Update the key values used in height calculations
void updateKeyHeightValues(const CSMWorld::CellCoordinates& cellCoords, int inCellX, int inCellY, float* thisHeight,
float* thisAlteredHeight, float* leftHeight, float* leftAlteredHeight, float* upHeight, float* upAlteredHeight);
/// Bind edge vertices to next cells
void fixEdges(const CSMWorld::CellCoordinates& cellCoords);
/// Check that the edit doesn't break save format limits, fix if necessary
void limitAlteredHeights(const CSMWorld::CellCoordinates& cellCoords);
/// 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, std::string cellId);
/// Push land normals edits to command macro
void pushNormalsEditToCommand(const CSMWorld::LandNormalsColumn::DataType& newLandGrid, CSMDoc::Document& document,
CSMWorld::IdTable& landTable, std::string cellId);
/// Create new cell and land if needed
bool allowLandShapeEditing(std::string textureFileName);
private:
std::string mCellId;
std::string mBrushTexture;
int mBrushSize;
int mBrushShape;
std::vector<std::pair<int, int>> mCustomBrushShape;
CSVWidget::SceneToolShapeBrush *mShapeBrushScenetool;
int mDragMode;
osg::Group* mParentNode;
bool mIsEditing;
std::unique_ptr<TerrainSelection> mTerrainShapeSelection;
int mTotalDiffY;
std::vector<CSMWorld::CellCoordinates> mAlteredCells;
osg::Vec3d mEditingPos;
int mShapeEditTool;
int mShapeEditToolStrength;
int mTargetHeight;
const int cellSize {ESM::Land::REAL_SIZE};
const int landSize {ESM::Land::LAND_SIZE};
const int landTextureSize {ESM::Land::LAND_TEXTURE_SIZE};
PagedWorldspaceWidget& getPagedWorldspaceWidget();
signals:
void passBrushTexture(std::string brushTexture);
public slots:
//void handleDropEvent(QDropEvent *event);
void setBrushSize(int brushSize);
void setBrushShape(int brushShape);
void setShapeEditTool(int shapeEditTool);
void setShapeEditToolStrength(int shapeEditToolStrength);
};
}
#endif

@ -1,15 +1,25 @@
#include "terrainstorage.hpp"
#include <set>
#include <memory>
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
#include <components/esmterrain/storage.hpp>
#include <components/debug/debuglog.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/vfs/manager.hpp>
namespace CSVRender
{
const float defaultHeight = ESM::Land::DEFAULT_HEIGHT;
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,6 +43,209 @@ 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()
{
for (int x = 0; x < ESM::Land::LAND_SIZE; ++x)
{
for (int y = 0; y < ESM::Land::LAND_SIZE; ++y)
{
mAlteredHeight[y*ESM::Land::LAND_SIZE + x] = 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) : 0;
if (data) height = getVertexHeight(data, inCellX, inCellY);
}
else return height;
return mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX] + height;
}
float* TerrainStorage::getAlteredHeights()
{
return mAlteredHeight;
}
float* TerrainStorage::getAlteredHeight(int inCellX, int inCellY)
{
return &mAlteredHeight[inCellY*ESM::Land::LAND_SIZE + inCellX];
}
void TerrainStorage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4ubArray> colours)
{
// LOD level n means every 2^n-th vertex is kept
size_t increment = static_cast<size_t>(1) << lodLevel;
osg::Vec2f origin = center - osg::Vec2f(size/2.f, size/2.f);
int startCellX = static_cast<int>(std::floor(origin.x()));
int startCellY = static_cast<int>(std::floor(origin.y()));
size_t numVerts = static_cast<size_t>(size*(ESM::Land::LAND_SIZE - 1) / increment + 1);
positions->resize(numVerts*numVerts);
normals->resize(numVerts*numVerts);
colours->resize(numVerts*numVerts);
osg::Vec3f normal;
osg::Vec4ub color;
float vertY = 0;
float vertX = 0;
ESMTerrain::LandCache cache;
float vertY_ = 0; // of current cell corner
for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY)
{
float vertX_ = 0; // of current cell corner
for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX)
{
const ESMTerrain::LandObject* land = ESMTerrain::Storage::getLand(cellX, cellY, cache);
const ESM::Land::LandData *heightData = 0;
const ESM::Land::LandData *normalData = 0;
const ESM::Land::LandData *colourData = 0;
if (land)
{
heightData = land->getData(ESM::Land::DATA_VHGT);
normalData = land->getData(ESM::Land::DATA_VNML);
colourData = land->getData(ESM::Land::DATA_VCLR);
}
int rowStart = 0;
int colStart = 0;
// Skip the first row / column unless we're at a chunk edge,
// since this row / column is already contained in a previous cell
// This is only relevant if we're creating a chunk spanning multiple cells
if (vertY_ != 0)
colStart += increment;
if (vertX_ != 0)
rowStart += increment;
// Only relevant for chunks smaller than (contained in) one cell
rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE;
colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE;
int rowEnd = std::min(static_cast<int>(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast<int>(ESM::Land::LAND_SIZE));
int colEnd = std::min(static_cast<int>(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE-1) + 1), static_cast<int>(ESM::Land::LAND_SIZE));
vertY = vertY_;
for (int col=colStart; col<colEnd; col += increment)
{
vertX = vertX_;
for (int row=rowStart; row<rowEnd; row += increment)
{
int srcArrayIndex = col*ESM::Land::LAND_SIZE*3+row*3;
assert(row >= 0 && row < ESM::Land::LAND_SIZE);
assert(col >= 0 && col < ESM::Land::LAND_SIZE);
assert (vertX < numVerts);
assert (vertY < numVerts);
float height = defaultHeight;
if (heightData)
height = heightData->mHeights[col*ESM::Land::LAND_SIZE + row];
(*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 + mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)]);
if (normalData)
{
for (int i=0; i<3; ++i)
normal[i] = normalData->mNormals[srcArrayIndex+i];
normal.normalize();
}
else
normal = osg::Vec3f(0,0,1);
// Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE-1 || row == ESM::Land::LAND_SIZE-1)
fixNormal(normal, cellX, cellY, col, row, cache);
// some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE-1) && (col == 0 || col == ESM::Land::LAND_SIZE-1))
averageNormal(normal, cellX, cellY, col, row, cache);
assert(normal.z() > 0);
(*normals)[static_cast<unsigned int>(vertX*numVerts + vertY)] = normal;
if (colourData)
{
for (int i=0; i<3; ++i)
color[i] = colourData->mColours[srcArrayIndex+i];
}
else
{
color.r() = 255;
color.g() = 255;
color.b() = 255;
}
// Highlight broken height changes
if ( ((col > 0 && row > 0) &&
((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)] -
(heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] +
mAlteredHeight[static_cast<unsigned int>((col)*ESM::Land::LAND_SIZE + row - 1)])) >= 1024 ) ||
abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)] -
(heightData->mHeights[(col - 1)*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>((col - 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )) ||
((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) &&
((abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)] -
(heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row + 1] +
mAlteredHeight[static_cast<unsigned int>((col)*ESM::Land::LAND_SIZE + row + 1)])) >= 1024 ) ||
abs(heightData->mHeights[col*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>(col*ESM::Land::LAND_SIZE + row)] -
(heightData->mHeights[(col + 1)*ESM::Land::LAND_SIZE + row] +
mAlteredHeight[static_cast<unsigned int>((col + 1)*ESM::Land::LAND_SIZE + row)])) >= 1024 )))
{
color.r() = 255;
color.g() = 0;
color.b() = 0;
}
// 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);
color.a() = 255;
(*colours)[static_cast<unsigned int>(vertX*numVerts + vertY)] = color;
++vertX;
}
++vertY;
}
vertX_ = vertX;
}
vertY_ = vertY;
assert(vertX_ == numVerts); // Ensure we covered whole area
}
assert(vertY_ == numVerts); // Ensure we covered whole area*/
}
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

@ -7,6 +7,7 @@
namespace CSVRender
{
class LandCache;
/**
* @brief A bridge between the terrain component and OpenCS's terrain data storage.
@ -15,12 +16,25 @@ namespace CSVRender
{
public:
TerrainStorage(const CSMWorld::Data& data);
float mAlteredHeight[ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE + ESM::Land::LAND_SIZE];
void setAlteredHeight(int inCellX, int inCellY, float heightMap);
void resetHeights();
float getSumOfAlteredAndTrueHeight(int cellX, int cellY, int inCellX, int inCellY);
float* getAlteredHeights();
float* getAlteredHeight(int inCellX, int inCellY);
private:
const CSMWorld::Data& mData;
virtual osg::ref_ptr<const ESMTerrain::LandObject> getLand (int cellX, int cellY) override;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) override;
/// Draws temporarily altered land (transient change support)
void fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4ubArray> colours) override;
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) override;
};

@ -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
{

@ -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;

@ -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;

@ -0,0 +1,265 @@
#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 "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 = new QSlider(Qt::Horizontal);
mBrushSizeSlider->setTickPosition(QSlider::TicksBothSides);
mBrushSizeSlider->setTickInterval(10);
mBrushSizeSlider->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mBrushSizeSlider->setSingleStep(1);
mBrushSizeSpinBox = new QSpinBox;
mBrushSizeSpinBox->setRange(1, CSMPrefs::get()["3D Scene Editing"]["shapebrush-maximumsize"].toInt());
mBrushSizeSpinBox->setSingleStep(1);
mLayoutSliderSize = new QHBoxLayout;
mLayoutSliderSize->addWidget(mBrushSizeSlider);
mLayoutSliderSize->addWidget(mBrushSizeSpinBox);
connect(mBrushSizeSlider, SIGNAL(valueChanged(int)), mBrushSizeSpinBox, SLOT(setValue(int)));
connect(mBrushSizeSpinBox, SIGNAL(valueChanged(int)), mBrushSizeSlider, SLOT(setValue(int)));
setLayout(mLayoutSliderSize);
}
CSVWidget::ShapeBrushWindow::ShapeBrushWindow(CSMDoc::Document& document, QWidget *parent)
: QFrame(parent, Qt::Popup),
mBrushShape(0),
mBrushSize(0),
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);
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 = 0;
if(mButtonSquare->isChecked()) mBrushShape = 1;
if(mButtonCircle->isChecked()) mBrushShape = 2;
if(mButtonCustom->isChecked()) mBrushShape = 3;
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(int)), this, SLOT(setButtonIcon(int)));
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 (int brushShape)
{
QString tooltip = "Change brush settings <p>Currently selected: ";
switch (brushShape)
{
case 0:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-point")));
tooltip += mShapeBrushWindow->toolTipPoint;
break;
case 1:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-square")));
tooltip += mShapeBrushWindow->toolTipSquare;
break;
case 2:
setIcon (QIcon (QPixmap (":scenetoolbar/brush-circle")));
tooltip += mShapeBrushWindow->toolTipCircle;
break;
case 3:
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();
}

@ -0,0 +1,130 @@
#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 "scenetool.hpp"
#include "../../model/doc/document.hpp"
#endif
class QTableWidget;
namespace CSVRender
{
class TerrainShapeMode;
}
namespace CSVWidget
{
class SceneToolShapeBrush;
/// \brief Layout-box for some brush button settings
class ShapeBrushSizeControls : public QGroupBox
{
Q_OBJECT
public:
ShapeBrushSizeControls(const QString &title, QWidget *parent);
private:
QHBoxLayout *mLayoutSliderSize;
QSlider *mBrushSizeSlider;
QSpinBox *mBrushSizeSpinBox;
friend class SceneToolShapeBrush;
friend class CSVRender::TerrainShapeMode;
};
class SceneToolShapeBrush;
/// \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 custom selection (not implemented yet)";
private:
int mBrushShape;
int mBrushSize;
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(int 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(int brushShape);
void clicked (const QModelIndex& index);
virtual void activate();
signals:
void passEvent(QDropEvent *event);
void passEvent(QDragEnterEvent *event);
};
}
#endif

@ -16,13 +16,6 @@
namespace ESMTerrain
{
class LandCache
{
public:
typedef std::map<std::pair<int, int>, osg::ref_ptr<const LandObject> > Map;
Map mMap;
};
LandObject::LandObject()
: mLand(nullptr)
, mLoadFlags(0)
@ -98,82 +91,6 @@ namespace ESMTerrain
return false;
}
void Storage::fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0;
if (data)
{
normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalize();
}
else
normal = osg::Vec3f(0,0,1);
}
void Storage::averageNormal(osg::Vec3f &normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
osg::Vec3f n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row, cache);
fixNormal(n2, cellX, cellY, col-1, row, cache);
fixNormal(n3, cellX, cellY, col, row+1, cache);
fixNormal(n4, cellX, cellY, col, row-1, cache);
normal = (n1+n2+n3+n4);
normal.normalize();
}
void Storage::fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
if (data)
{
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3];
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1];
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2];
}
else
{
color.r() = 255;
color.g() = 255;
color.b() = 255;
}
}
void Storage::fillVertexBuffers (int lodLevel, float size, const osg::Vec2f& center,
osg::ref_ptr<osg::Vec3Array> positions,
osg::ref_ptr<osg::Vec3Array> normals,
@ -521,25 +438,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));
if (found != cache.mMap.end())
return found->second;
else
{
found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first;
return found->second;
}
}
Terrain::LayerInfo Storage::getLayerInfo(const std::string& texture)
{
OpenThreads::ScopedLock<OpenThreads::Mutex> lock(mLayerInfoMutex);

@ -1,8 +1,13 @@
#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H
#define COMPONENTS_ESM_TERRAIN_STORAGE_H
#include <cassert>
#include <OpenThreads/Mutex>
#include <osg/Image>
#include <osg/Plane>
#include <components/terrain/storage.hpp>
#include <components/esm/loadland.hpp>
@ -13,10 +18,13 @@ namespace VFS
class Manager;
}
namespace ESMTerrain
namespace CSVRender
{
class TerrainStorage;
}
class LandCache;
namespace ESMTerrain
{
/// @brief Wrapper around Land Data with reference counting. The wrapper needs to be held as long as the data is still in use
class LandObject : public osg::Object
@ -48,6 +56,13 @@ namespace ESMTerrain
ESM::Land::LandData mData;
};
class LandCache
{
public:
typedef std::map<std::pair<int, int>, osg::ref_ptr<const LandObject> > Map;
Map mMap;
};
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage
@ -110,14 +125,6 @@ namespace ESMTerrain
private:
const VFS::Manager* mVFS;
inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache);
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);
// 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>
@ -137,6 +144,103 @@ namespace ESMTerrain
bool mAutoUseSpecularMaps;
Terrain::LayerInfo getLayerInfo(const std::string& texture);
protected:
inline void fixNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
while (col >= ESM::Land::LAND_SIZE-1)
{
++cellY;
col -= ESM::Land::LAND_SIZE-1;
}
while (row >= ESM::Land::LAND_SIZE-1)
{
++cellX;
row -= ESM::Land::LAND_SIZE-1;
}
while (col < 0)
{
--cellY;
col += ESM::Land::LAND_SIZE-1;
}
while (row < 0)
{
--cellX;
row += ESM::Land::LAND_SIZE-1;
}
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : 0;
if (data)
{
normal.x() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3];
normal.y() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+1];
normal.z() = data->mNormals[col*ESM::Land::LAND_SIZE*3+row*3+2];
normal.normalize();
}
else
normal = osg::Vec3f(0,0,1);
};
inline void fixColour (osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache)
{
if (col == ESM::Land::LAND_SIZE-1)
{
++cellY;
col = 0;
}
if (row == ESM::Land::LAND_SIZE-1)
{
++cellX;
row = 0;
}
const LandObject* land = getLand(cellX, cellY, cache);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : 0;
if (data)
{
color.r() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3];
color.g() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+1];
color.b() = data->mColours[col*ESM::Land::LAND_SIZE*3+row*3+2];
}
else
{
color.r() = 255;
color.g() = 255;
color.b() = 255;
}
};
inline void averageNormal (osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache)
{
osg::Vec3f n1,n2,n3,n4;
fixNormal(n1, cellX, cellY, col+1, row, cache);
fixNormal(n2, cellX, cellY, col-1, row, cache);
fixNormal(n3, cellX, cellY, col, row+1, cache);
fixNormal(n4, cellX, cellY, col, row-1, cache);
normal = (n1+n2+n3+n4);
normal.normalize();
};
inline 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];
};
inline const LandObject* getLand(int cellX, int cellY, LandCache& cache)
{
LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY));
if (found != cache.mMap.end())
return found->second;
else
{
found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first;
return found->second;
}
};
};
}

Loading…
Cancel
Save