Terrain Editing

Involved development of the OpenMW construction set.
Post Reply
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

Triple post woo-hoo!

There are all these modes: Primary edit, secondary edit, primary select, secondary select. What are they supposed to do at terrain texture editing?

There is an incomplete PR about "Terrain Texture Selection" Should I (or some other developer) be familiar what was done there?
User avatar
Zini
Posts: 5538
Joined: 06 Aug 2011, 15:16

Re: Terrain Editing

Post by Zini »

Primary edit and secondary edit are described at the top of the thread. But I'll sum it up once more anyway:

* Primary: If clicking on unselected terrain the current brush should be applied to the terrain
* Secondary: The texture of the current brush should be set to the terrain texture that was clicked on.

We discussed the terrain texture selection PR before. I believe you found it too complicated to deal with. This is basically about setting (and rendering) the terrain selection. This is needed for the primary edit function, since clicking on a selected piece of terrain will apply the brush texture to the whole selection (we may add a user setting to enable/disable this particular function, since not all users may want to use it).
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

The brush feature is more or less done, although it's still missing some features (dropdown menu of recently used textures, texture overlay to brush buttons) and if those features aren't yet developed, code needs to be cleaned. I'm gonna give it some time to get some feedback so I can continue development. It's been a good learning process of C++ and Qt, and therefore it's now easier to read other code.

I'm doing research on vfs for texture overlay, and terrain data structures / command system for texture editing. Those tasks are more complicated, and I'm not sure if my skills are yet up to the task. I'll try anyway, and if I make good progress, I'll post a PR. This is after all a superb opportunity for me to learn intermediate to advanced coding skills. Currently in my local branch, I've just got the terraingrid loaded from data, created some framework for new command for modifying land data (although I wonder if some of the existing ones would have been enough).

I would rather not reserve terrain texture editing task for myself, as it's a greatly anticipated feature, and I'm sure there are people out there who could code it better and quicker than me. On the other hand, I have plenty of time to dabble with this, and I'm motivated to learn.
User avatar
Zini
Posts: 5538
Joined: 06 Aug 2011, 15:16

Re: Terrain Editing

Post by Zini »

I would rather not reserve terrain texture editing task for myself, as it's a greatly anticipated feature, and I'm sure there are people out there who could code it better and quicker than me.
Maybe. But no one stepped forward recently who also had the necessary free time. Feel free to take any unclaimed task as long as you keep working on it.

I'll give your PR are thorough review and a test run later this week.
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

Well, I'll see what I can do.
https://bugs.openmw.org/issues/3872

Data structures and command system are trouble though. I don't really have good questions, but all help is great. :roll:

CSMWorld::IdCollection<CSMWorld::Land> should hold columns: StringIdColumn<Land>, RecordStateColumn<Land>, FixedRecordTypeColumn<Land> (UniversalId::Type_Land), LandPluginIndexColumn, LandMapLodColumn, LandNormalsColumn, LandHeightsColumn, LandColoursColumn and LandTexturesColumn.

In data.cpp command addColumn is adding those columns to mLand. I've backtraced that IdCollection holds std::vector<Column<ESXRecordT> *>mColumns, and addColumn uses push_back to append data to that variable. Not quite sure how that vector variable, apparently holding records, work.

Skipping that part, there's some code in view/render/cell.cpp that apparently gets something out of the columns:

Code: Select all

  if (landIndex != -1 && !land.getRecord(mId).isDeleted())
    {
        const ESM::Land& esmLand = land.getRecord(mId).get();

        if (esmLand.getLandData (ESM::Land::DATA_VHGT))
In the above code mId is probably cell id in some form (#X Y) maybe? And instead of DATA_VHGT, texture editing should target DATA_VTEX. As defined in components/esm/loadland.hpp, somewhere down there should be variables uint16_t mTextures[LAND_NUM_TEXTURES], where static const int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE, when static const int LAND_TEXTURE_SIZE = 16.

That mTextures data probably needs to be copied into temporary array, then somehow through command system it needs to go back to records. CSMWorld::TouchLandCommand at model/world/commands.cpp is probably a good start for that.



edit: I'm scrambling together something like this to terraintextureedit.cpp... Feeling a bit lost here.

Code: Select all

void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) //This is just for one click-edit, eventually command macro with drag&drop is needed
{
  std::cout << "PRIMARYPRESSED" << std::endl;
  std::cout << hit.index0 << std::endl;
  std::cout << hit.index1 << std::endl;
  std::cout << hit.index2 << std::endl;
  std::cout << hit.hit << std::endl;
  std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos);
  std::cout << cellId << std::endl;

  // Not sure if this slicing is needed for anything, as mCoordinates aren't used for anything
  std::string hash = "#";
  std::string space = " ";
  std::size_t hashlocation = cellId.find(hash);
  std::size_t spacelocation = cellId.find(space);
  std::string slicedX = cellId.substr (hashlocation+1, spacelocation-hashlocation);
  std::string slicedY = cellId.substr (spacelocation+1);
  std::cout << slicedX << std::endl;
  std::cout << slicedY << std::endl;
  CSMWorld::CellCoordinates mCoordinates(stoi(slicedX), stoi(slicedY));

  CSMDoc::Document& document = getWorldspaceWidget().getDocument();
  CSMWorld::Data& mData = document.getData();
  CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
  int landIndex = land.searchId(cellId);
  if (landIndex != -1 && !land.getRecord(cellId).isDeleted())
  {
      const ESM::Land& esmLand = land.getRecord(cellId).get();

      if (esmLand.getLandData (ESM::Land::DATA_VTEX))
      {
          std::cout << "Some texture data is loaded." << std::endl;
          ESM::Land::LandData * mutableLandData = new ESM::Land::LandData();
          for (int i=1;i<10;i++) // Just for testing, this should be brush size according to brush X and Y
          { 
            mutableLandData->mTextures[i] = 6; // Just for testing, this should of course be mBrushTexture
          }
          // Here should be the command for updating the Land I assume
          delete mutableLandData;
      }
  }
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

Keep shooting blindly long enough and you might hit something. I think I'm changing the correct variables, but without finesse or commands system yet.

Code: Select all

void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here
{
  std::cout << "PRIMARYPRESSED" << std::endl;
  std::cout << hit.index0 << std::endl;
  std::cout << hit.index1 << std::endl;
  std::cout << hit.index2 << std::endl;
  std::cout << hit.hit << std::endl;
  std::string cellId = getWorldspaceWidget().getCellId (hit.worldPos);
  std::cout << cellId << std::endl;
  std::string hash = "#";
  std::string space = " ";
  std::size_t hashlocation = cellId.find(hash);
  std::size_t spacelocation = cellId.find(space);
  std::string slicedX = cellId.substr (hashlocation+1, spacelocation-hashlocation);
  std::string slicedY = cellId.substr (spacelocation+1);
  std::cout << slicedX << std::endl;
  std::cout << slicedY << std::endl;
  CSMWorld::CellCoordinates mCoordinates(stoi(slicedX), stoi(slicedY));

  CSMDoc::Document& document = getWorldspaceWidget().getDocument();
  CSMWorld::Data& mData = document.getData();
  CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
  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 textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex);
  CSMWorld::LandTexturesColumn::DataType mNew = landTable.data(landTable.getModelIndex(cellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
  std::cout << "textureColumn" << textureColumn << std::endl;

  for (int i=1;i<100;i++){
    mNew[i] = 86; // Just for testing, 86 = lavacrust (bright red texture)
  }

  //setData (const QModelIndex &index, const QVariant &value, int role)
  QVariant variant;
  variant.setValue(mNew);
  landTable.setData(landTable.getModelIndex(cellId, textureColumn), variant);
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

There is something weird wit worldspacehitresult hit (WorldIntersectPoint), it doesn't correlate to map at all. Does it correlate to x-y coordinates of texels/texture maps in object that is hit? In the latter case totally different hit handling is needed. I wonder if PlutonicOverkill got it working here https://github.com/OpenMW/openmw/pull/1414 ... I don't get the coordinate code over there, at least not yet.

edit: The conversion of coordiantes was found from PlutonicOverkills code, using variables hit.worldPos.x() and hit.worldPos.y(). I can now edit textures in single point mode.
unelsson
Posts: 227
Joined: 17 Mar 2018, 14:57

Re: Terrain Editing

Post by unelsson »

I probably should make an early PR soon, but I don't know what happens with brush button PR if I make this now, so maybe not yet.

Anyway, here's code for terraintexturemode.cpp, implemented now CRUDE version of terrain texture mode primary edit. There's point edit, square edit, circle edit (simple algorithm), texture brush shape buttons and size slider, texture brush overlay (osg 3.6 and openmw-osg only). Not the cleanest code I think, but it's a start.

Code: Select all

#include "terraintexturemode.hpp"
#include "editmode.hpp"

#include <iostream>
#include <string>
#include <cmath>

//Some includes are not needed (have to clean this up later)
#include <QWidget>
#include <QIcon>
#include <QPainter>
#include <QSpinBox>
#include <QGroupBox>
#include <QSlider>
#include <QEvent>
#include <QDropEvent>
#include <QColor>
#include <QButtonGroup>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QDragEnterEvent>
#include <QDrag>
#include <QAbstractItemModel>
#include <QModelIndex>

#include "../../model/world/commands.hpp"

#include "../widget/modebutton.hpp"
#include "../widget/scenetoolmode.hpp"
#include "../widget/scenetoolbar.hpp"

#include "../../model/doc/document.hpp"
#include "../../model/world/land.hpp"
#include "../../model/world/landtexture.hpp"
#include "../../model/world/universalid.hpp"
#include "../../model/world/tablemimedata.hpp"
#include "../../model/world/idtable.hpp"

#include "../../model/world/columnbase.hpp"
#include "../../model/world/resourcetable.hpp"
#include "../../model/world/commandmacro.hpp"
#include "../../model/world/data.hpp"
#include "../../model/world/commands.hpp"

#include <osg/ref_ptr>
#include <osg/Image>

#include <components/esm/loadland.hpp>
#include <components/resource/imagemanager.hpp>
#include <extern/osgQt/GraphicsWindowQt>
#include <components/fallback/fallback.hpp>
#include <components/misc/stringops.hpp>

#include "terrainstorage.hpp"

#include "pagedworldspacewidget.hpp"


CSVRender::BrushSizeControls::BrushSizeControls(const QString &title, QWidget *parent)
    : QGroupBox(title, parent)
{
    brushSizeSlider = new QSlider(Qt::Horizontal);
    brushSizeSlider->setTickPosition(QSlider::TicksBothSides);
    brushSizeSlider->setTickInterval(10);
    brushSizeSlider->setSingleStep(1);

    brushSizeSpinBox = new QSpinBox;
    brushSizeSpinBox->setRange(1, 100);
    brushSizeSpinBox->setSingleStep(1);

    layoutSliderSize = new QHBoxLayout;
    layoutSliderSize->addWidget(brushSizeSlider);
    layoutSliderSize->addWidget(brushSizeSpinBox);

    connect(brushSizeSlider, SIGNAL(valueChanged(int)), brushSizeSpinBox, SLOT(setValue(int)));
    connect(brushSizeSpinBox, SIGNAL(valueChanged(int)), brushSizeSlider, SLOT(setValue(int)));

    setLayout(layoutSliderSize);
}

CSVRender::TextureBrushButton::TextureBrushButton (const QIcon & icon, const QString & text, QWidget * parent)
    : QPushButton(icon, text, parent)
{
}

void CSVRender::TextureBrushButton::dragEnterEvent (QDragEnterEvent *event)
{
  event->accept();
}

void CSVRender::TextureBrushButton::dropEvent (QDropEvent *event)
{
  const CSMWorld::TableMimeData* mime = dynamic_cast<const CSMWorld::TableMimeData*> (event->mimeData());

  if (!mime) // May happen when non-records (e.g. plain text) are dragged and dropped
      return;

  if (mime->holdsType (CSMWorld::UniversalId::Type_LandTexture))
  {
      const std::vector<CSMWorld::UniversalId> ids = mime->getData();

      for (const CSMWorld::UniversalId& uid : ids)
      {
          std::string mBrushTexture(uid.getId());
          emit passBrushTexture(mBrushTexture);
      }
  }
}

CSVRender::TextureBrushWindow::TextureBrushWindow(WorldspaceWidget *worldspaceWidget, QWidget *parent)
    : QWidget(parent), mWorldspaceWidget (worldspaceWidget)
{
    mBrushTextureLabel = "Brush: " + mBrushTexture;
    selectedBrush = new QLabel(QString::fromUtf8(mBrushTextureLabel.c_str()), this);

    pointIcon = drawIconTexture(QPixmap (iconPointImage.c_str()));
    squareIcon = drawIconTexture(QPixmap (iconSquareImage.c_str()));
    circleIcon = drawIconTexture(QPixmap (iconCircleImage.c_str()));
    customIcon = drawIconTexture(QPixmap (iconCustomImage.c_str()));

    buttonPoint->setIcon(drawIconTexture(QPixmap (iconPointImage.c_str())));
    buttonSquare->setIcon(drawIconTexture(QPixmap (iconSquareImage.c_str())));
    buttonCircle->setIcon(drawIconTexture(QPixmap (iconCircleImage.c_str())));
    buttonCustom->setIcon(drawIconTexture(QPixmap (iconCustomImage.c_str())));

    QVBoxLayout *layoutMain = new QVBoxLayout;
    layoutMain->setSpacing(0);

    QHBoxLayout *layoutHorizontal = new QHBoxLayout;
    layoutHorizontal->setContentsMargins (QMargins (0, 0, 0, 0));
    layoutHorizontal->setSpacing(0);

    configureButtonInitialSettings(buttonPoint);
    configureButtonInitialSettings(buttonSquare);
    configureButtonInitialSettings(buttonCircle);
    configureButtonInitialSettings(buttonCustom);

    QButtonGroup* brushButtonGroup = new QButtonGroup(this);
    brushButtonGroup->addButton(buttonPoint);
    brushButtonGroup->addButton(buttonSquare);
    brushButtonGroup->addButton(buttonCircle);
    brushButtonGroup->addButton(buttonCustom);

    brushButtonGroup->setExclusive(true);

    layoutHorizontal->addWidget(buttonPoint);
    layoutHorizontal->addWidget(buttonSquare);
    layoutHorizontal->addWidget(buttonCircle);
    layoutHorizontal->addWidget(buttonCustom);

    horizontalGroupBox = new QGroupBox(tr(""));
    horizontalGroupBox->setLayout(layoutHorizontal);

    BrushSizeControls* sizeSliders = new BrushSizeControls(tr(""), this);

    layoutMain->addWidget(horizontalGroupBox);
    layoutMain->addWidget(sizeSliders);
    layoutMain->addWidget(selectedBrush);

    setLayout(layoutMain);

    connect(buttonPoint, SIGNAL(passBrushTexture(std::string)), this, SLOT(getBrushTexture(std::string)));
    connect(buttonSquare, SIGNAL(passBrushTexture(std::string)), this, SLOT(getBrushTexture(std::string)));
    connect(buttonCircle, SIGNAL(passBrushTexture(std::string)), this, SLOT(getBrushTexture(std::string)));
    connect(buttonCustom, SIGNAL(passBrushTexture(std::string)), this, SLOT(getBrushTexture(std::string)));

    connect(buttonPoint, SIGNAL(clicked()), this, SLOT(setBrushShape()));
    connect(buttonSquare, SIGNAL(clicked()), this, SLOT(setBrushShape()));
    connect(buttonCircle, SIGNAL(clicked()), this, SLOT(setBrushShape()));
    connect(buttonCustom, SIGNAL(clicked()), this, SLOT(setBrushShape()));

    connect(sizeSliders->brushSizeSlider, SIGNAL(valueChanged(int)), parent, SLOT(setBrushSize(int)));

    setWindowFlags(Qt::Window);
}

void CSVRender::TextureBrushWindow::configureButtonInitialSettings(TextureBrushButton *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->setAcceptDrops(true);
  button->setCheckable(true);
}

void CSVRender::TextureBrushWindow::getBrushTexture(std::string brushTexture)
{
    mBrushTexture = brushTexture;
    mBrushTextureLabel = "Brush:" + mBrushTexture;
    selectedBrush->setText(QString::fromUtf8(mBrushTextureLabel.c_str()));
    buttonPoint->setIcon(drawIconTexture(QPixmap (iconPointImage.c_str())));
    buttonSquare->setIcon(drawIconTexture(QPixmap (iconSquareImage.c_str())));
    buttonCircle->setIcon(drawIconTexture(QPixmap (iconCircleImage.c_str())));
    buttonCustom->setIcon(drawIconTexture(QPixmap (iconCustomImage.c_str())));

    emit passBrushTextureToTextureEditMode(mBrushTexture);
}

void CSVRender::TextureBrushWindow::setBrushSize(int brushSize)
{
    mBrushSize = brushSize;
    emit passBrushSizeToTextureEditMode(mBrushSize);
}

void CSVRender::TextureBrushWindow::setBrushShape()
{
    if(buttonPoint->isChecked()) mBrushShape = 0;
    if(buttonSquare->isChecked()) mBrushShape = 1;
    if(buttonCircle->isChecked()) mBrushShape = 2;
    if(buttonCustom->isChecked()) mBrushShape = 3;    
    emit passBrushShapeToTextureEditMode(mBrushShape);
}

// This function should eventually load brush texture as bitmap and set it as overlay
// Currently only holds very basic code for icon drawing and landtexture loading
QIcon CSVRender::TextureBrushWindow::drawIconTexture(QPixmap pixmapBrush)
{
    //Get Image Texture
    CSMDoc::Document& document = mWorldspaceWidget->getDocument();
    CSMWorld::Data& mData = document.getData();
    CSMWorld::IdTable& ltexs = dynamic_cast<CSMWorld::IdTable&> (
*document.getData().getTableModel (CSMWorld::UniversalId::Type_LandTextures));

    std::string hash = "#";
    std::size_t hashlocation = mBrushTexture.find(hash);
    std::string idRow = mBrushTexture.substr (hashlocation+1);    

    QModelIndex index = ltexs.index(stoi(idRow), 6);
    QString text = ltexs.data(index).toString();
    std::string textureFilenameTga = text.toUtf8().constData();    

    std::string textureName = "textures/" + textureFilenameTga.substr(0, textureFilenameTga.size()-3)+"dds";    
    for(std::size_t i=0; i<textureName.size();++i)
        textureName[i] = std::tolower(textureName[i]);      
    Resource::ImageManager* imageManager = mData.getResourceSystem()->getImageManager();
    osg::ref_ptr<osg::Image> brushTextureOsg = new osg::Image();
    brushTextureOsg = imageManager->getNewImage(textureName);

    //Decompress to pixel format GL_RGB8
    if (brushTextureOsg->getPixelFormat() == 33776)
    {

        osg::ref_ptr<osg::Image> newImage = new osg::Image;
        newImage->setFileName(brushTextureOsg->getFileName());
        newImage->allocateImage(brushTextureOsg->s(), brushTextureOsg->t(), brushTextureOsg->r(), GL_RGB, GL_UNSIGNED_BYTE);
        for (int s=0; s<brushTextureOsg->s(); ++s)
            for (int t=0; t<brushTextureOsg->t(); ++t)
                for (int r=0; r<brushTextureOsg->r(); ++r)
                    newImage->setColor(brushTextureOsg->getColor(s,t,r), s,t,r);
        brushTextureOsg = newImage;
        brushTextureOsg->setInternalTextureFormat(GL_RGB8);
    }

    const uchar *qImageBuffer = (const uchar*)brushTextureOsg->data();

    QImage img(qImageBuffer, brushTextureOsg->s(), brushTextureOsg->t(), QImage::Format_RGB888);
    QPixmap pixmapObject(QPixmap::fromImage(img));

    QPainter painter(&pixmapBrush);
    painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
    QBrush brush;
    brush.setTexture(pixmapObject);
    painter.setBrush(brush); //QBrush::setTexture(const QPixmap &pixmap)
    painter.drawRect(pixmapBrush.rect());

    QIcon brushIcon(pixmapBrush);
    return brushIcon;
}

CSVRender::TerrainTextureMode::TerrainTextureMode (WorldspaceWidget *worldspaceWidget, QWidget *parent)
: EditMode (worldspaceWidget, QIcon {":scenetoolbar/editing-terrain-texture"}, Mask_Terrain | Mask_Reference, "Terrain texture editing", parent)
, textureBrushWindow(new TextureBrushWindow(worldspaceWidget, this))
{
    connect(textureBrushWindow, SIGNAL(passBrushTextureToTextureEditMode(std::string)), this, SLOT(getBrushTexture(std::string)));
    connect(textureBrushWindow, SIGNAL(passBrushSizeToTextureEditMode(int)), this, SLOT(setBrushSize(int)));
    connect(textureBrushWindow, SIGNAL(passBrushShapeToTextureEditMode(int)), this, SLOT(setBrushShape(int)));
}

void CSVRender::TerrainTextureMode::getBrushTexture(std::string brushTexture)
{
    mBrushTexture = brushTexture;
}

void CSVRender::TerrainTextureMode::setBrushSize(int brushSize)
{
    mBrushSize = brushSize;
}

void CSVRender::TerrainTextureMode::setBrushShape(int brushShape)
{
    mBrushShape = brushShape;
}

void CSVRender::TerrainTextureMode::activate(CSVWidget::SceneToolbar* toolbar)
{
    EditMode::activate(toolbar);

}

void CSVRender::TerrainTextureMode::deactivate(CSVWidget::SceneToolbar* toolbar)
{
    EditMode::deactivate(toolbar);
}

void CSVRender::TerrainTextureMode::primaryEditPressed(const WorldspaceHitResult& hit) // Apply changes here
{  
  cellId = getWorldspaceWidget().getCellId (hit.worldPos);
  std::string hash = "#";
  std::string space = " ";
  std::size_t hashlocation = cellId.find(hash);
  std::size_t spacelocation = cellId.find(space);
  std::string slicedX = cellId.substr (hashlocation+1, spacelocation-hashlocation);
  std::string slicedY = cellId.substr (spacelocation+1);  
  CSMWorld::CellCoordinates mCoordinates(stoi(slicedX), stoi(slicedY));

  CSMDoc::Document& document = getWorldspaceWidget().getDocument();
  CSMWorld::Data& mData = document.getData();
  CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
  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 textureColumn = landTable.findColumnIndex(CSMWorld::Columns::ColumnId_LandTexturesIndex);
  CSMWorld::LandTexturesColumn::DataType mNew = landTable.data(landTable.getModelIndex(cellId, textureColumn)).value<CSMWorld::LandTexturesColumn::DataType>();
  
  int xInCell {(hit.worldPos.x() - (stoi(slicedX)* cellSize)) * landTextureSize / cellSize};
  int yInCell {(hit.worldPos.y() - (stoi(slicedY)* cellSize)) * landTextureSize / cellSize};

  hashlocation = mBrushTexture.find(hash);
  std::string mBrushTextureInt = mBrushTexture.substr (hashlocation+1);  
  int brushInt = stoi(mBrushTexture.substr (hashlocation+1));
  
  if (mBrushShape == 0) mNew[yInCell*landTextureSize+xInCell] = brushInt;
  if (mBrushShape == 1)
  {
      for(int i=-mBrushSize/2;i<mBrushSize/2;i++)
      {
          for(int j=-mBrushSize/2;j<mBrushSize/2;j++)
          {
              if (xInCell+i >= 0 && yInCell+j >= 0 && xInCell+i <= 15 && yInCell+j <= 15)
                  mNew[(yInCell+j)*landTextureSize+(xInCell+i)] = brushInt;
          }
      }
  }
  double distance = 0;
  if (mBrushShape == 2)
  {
    for(int i=-mBrushSize/2;i<mBrushSize/2;i++)
    {
        for(int j=-mBrushSize/2;j<mBrushSize/2;j++)
        {
            distance = sqrt(pow((xInCell+i)-xInCell, 2) + pow((yInCell+j)-yInCell, 2));
            if (xInCell+i >= 0 && yInCell+j >= 0 && xInCell+i <= 15 && yInCell+j <= 15 && distance < mBrushSize/2)
                mNew[(yInCell+j)*landTextureSize+(xInCell+i)] = brushInt;
        }
    }
  }
  if (mBrushShape == 3)
  {
    // Not implemented
  }

  // Modify data, this should be done via command system!
  QVariant variant;
  variant.setValue(mNew);
  landTable.setData(landTable.getModelIndex(cellId, textureColumn), variant);

  // Reference
  /*CSMWorld::ModifyLandTexturesCommand::ModifyLandTexturesCommand(IdTable& landTable,
      IdTable& ltexTable, QUndoCommand* parent)*/

  // Reference
  /*CSMWorld::DeleteCommand* command = new CSMWorld::DeleteCommand(referencesTable,
      static_cast<ObjectTag*>(iter->get())->mObject->getReferenceId());

  getWorldspaceWidget().getDocument().getUndoStack().push(command);*/
}

void CSVRender::TerrainTextureMode::primarySelectPressed(const WorldspaceHitResult& hit)
{
      textureBrushWindow->setWindowTitle("Texture brush settings");
      textureBrushWindow->show();
}

void CSVRender::TerrainTextureMode::secondarySelectPressed(const WorldspaceHitResult& hit)
{
}

bool CSVRender::TerrainTextureMode::primaryEditStartDrag (const QPoint& pos) //Collect one command to macro here
{
}

bool CSVRender::TerrainTextureMode::secondaryEditStartDrag (const QPoint& pos)
{
    return false;
}

bool CSVRender::TerrainTextureMode::primarySelectStartDrag (const QPoint& pos)
{
    return false;
}

bool CSVRender::TerrainTextureMode::secondarySelectStartDrag (const QPoint& pos)
{
    return false;
}

void CSVRender::TerrainTextureMode::drag (const QPoint& pos, int diffX, int diffY, double speedFactor) {
}

void CSVRender::TerrainTextureMode::dragCompleted(const QPoint& pos) {
}

void CSVRender::TerrainTextureMode::dragAborted() {
}

void CSVRender::TerrainTextureMode::dragWheel (int diff, double speedFactor) {}

void CSVRender::TerrainTextureMode::dragEnterEvent (QDragEnterEvent *event) {
}

void CSVRender::TerrainTextureMode::dropEvent (QDropEvent *event) {
}

void CSVRender::TerrainTextureMode::dragMoveEvent (QDragMoveEvent *event) {
}

void CSVRender::TerrainTextureMode::modifyLandMacro (CSMWorld::CommandMacro& commands)
{
  //Reference (from cell.cpp)
  /*CSMDoc::Document& document = getWorldspaceWidget().getDocument();
  CSMWorld::Data& mData = document.getData();

  // Setup land if available
  const CSMWorld::IdCollection<CSMWorld::Land>& land = mData.getLand();
  int landIndex = land.searchId(mId);
  if (landIndex != -1 && !land.getRecord(mId).isDeleted())
  {
      const ESM::Land& esmLand = land.getRecord(mId).get();

      if (esmLand.getLandData (ESM::Land::DATA_VHGT))
      {
          if (mTerrain)
          {
              mTerrain->unloadCell(mCoordinates.getX(), mCoordinates.getY());
              mTerrain->clearAssociatedCaches();
          }
              else
          {
              mTerrain.reset(new Terrain::TerrainGrid(mCellNode, mCellNode,
                  mData.getResourceSystem().get(), new TerrainStorage(mData), Mask_Terrain));
          }

          mTerrain->loadCell(esmLand.mX, esmLand.mY);

          return;
      }
  }*/
}
Here's screencap video what it does.
https://youtu.be/J8Dv702gpOY

edit (to-do): make a deep copy of land data when modifying (mNew), do changes via commands system, enhance circle algorithm, use mouse release to build command macro's instead of just single click editing.
User avatar
Zini
Posts: 5538
Joined: 06 Aug 2011, 15:16

Re: Terrain Editing

Post by Zini »

Uhm, lots of stuff. Too much to go into every detail. But I'll try to pick out the important parts.
In data.cpp command addColumn is adding those columns to mLand. I've backtraced that IdCollection holds std::vector<Column<ESXRecordT> *>mColumns, and addColumn uses push_back to append data to that variable. Not quite sure how that vector variable, apparently holding records, work.
It does not. Again, the columns are just a binding layer between the underlying data structure and the Qt tables. They don't hold data. The data is in the mRecords.
That mTextures data probably needs to be copied into temporary array, then somehow through command system it needs to go back to records
Correct.
CSMWorld::TouchLandCommand at model/world/commands.cpp is probably a good start for that.
Its more complicated. Usually you change fields in a table with the ModifyCommand. TouchLand Command modifies the LandTexture records, which (again) do not hold the texture grid. Since the land record and the land texture records must have the same modification state it is necessary to touch the land texture records (and pulling them out of base state) when modifying a Land record that is still in base state.

Code: Select all

ESM::Land::LandData * mutableLandData = new ESM::Land::LandData();
Please don't ever do that! You are assigning a dynamic allocated object to a local variable. That is basically just a resources leak waiting to happen. Its different with Qt, because Qt has this fancy (and annoying) QObject resources management system. But in generally in C++ this construct is a very bad idea.
User avatar
Zini
Posts: 5538
Joined: 06 Aug 2011, 15:16

Re: Terrain Editing

Post by Zini »

Thanks for the video. Very helpful. I was about to test your code myself, but I can already see the remaining issues now.

We don't allow permanently open UI windows like that. The window is supposed to be transient. And the texture dropping is meant to aim for the toolbar button, not the buttons in the window. Also, there are a few transient windows like this already (don't remember exactly which off the top of my head). Maybe you could look at them and see if you can match the style/layout?

After that we should go ahead with the merge (without the texture overlay).

I think there may be an issue with your editing code, but I don't really want to review it on the forum. That's what github is for. I suggest you move your editing code to a separate branch (based on the terraintexturemodebrushes branch) and start a separate pull request.
Post Reply