Skip to content

Commit

Permalink
Terrain tools now ignore irrelevant labels
Browse files Browse the repository at this point in the history
Changing the type of a Terrain Set does not clear no longer relevant
bits from each tile, in order to allow changing the type back and forth
without destroying this information.

However, this caused the terrain overlays to be confusing and terrain
tools to behave erratically. Now, the irrelevant bits of each tile's
terrain info is masked out where appropriate.

Closes #3204
Closes #3260
  • Loading branch information
bjorn committed Mar 6, 2024
1 parent 12dbd59 commit f46b444
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 45 deletions.
1 change: 1 addition & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
* tmxviewer: Added support for viewing JSON maps (#3866)
* AutoMapping: Ignore empty outputs per-rule (#3523)
* Windows: Fixed the support for WebP images (updated to Qt 6.6.1, #3661)
* Fixed terrain tool behavior and terrain overlays after changing terrain set type (#3204, #3260)
* Fixed mouse handling issue when zooming while painting (#3863)
* Fixed possible crash after a scripted tool disappears while active
* Fixed updating of used tilesets after resizing map (#3884)
Expand Down
76 changes: 60 additions & 16 deletions src/libtiled/wangset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ bool WangId::hasEdgeWildCards() const
/**
* Returns a mask that is 0 for any indexes that have no color defined.
*/
quint64 WangId::mask() const
WangId WangId::mask() const
{
quint64 mask = 0;
for (int i = 0; i < NumIndexes; ++i) {
Expand All @@ -188,7 +188,7 @@ quint64 WangId::mask() const
/**
* Returns a mask that is 0 for any indexes that don't match the given color.
*/
quint64 WangId::mask(int value) const
WangId WangId::mask(int value) const
{
quint64 mask = 0;
for (int i = 0; i < NumIndexes; ++i) {
Expand Down Expand Up @@ -413,9 +413,36 @@ WangSet::WangSet(Tileset *tileset,
: Object(Object::WangSetType)
, mTileset(tileset)
, mName(name)
, mType(type)
, mImageTileId(imageTileId)
{
setType(type);
}

/**
* Changes the type of this Wang set.
*
* Does not modify any WangIds to make sure they adhere to the type! Instead,
* a type mask is applied where relevant.
*/
void WangSet::setType(Type type)
{
mType = type;

switch (type) {
case Corner:
mTypeMask = WangId::MaskCorners;
break;
case Edge:
mTypeMask = WangId::MaskEdges;
break;
default:
case Mixed:
mTypeMask = WangId::FULL_MASK;
break;
}

mColorDistancesDirty = true;
mCellsDirty = true;
}

/**
Expand Down Expand Up @@ -516,7 +543,7 @@ void WangSet::setWangId(int tileId, WangId wangId)
removeTileId(tileId);
}

if (wangId == 0)
if (wangId.isEmpty())
return;

mTileIdToWangId.insert(tileId, wangId);
Expand All @@ -531,6 +558,12 @@ void WangSet::removeTileId(int tileId)
mCellsDirty = true;
}

/**
* Returns the list of WangIds and their corresponding cells, as defined by
* this Wang set.
*
* The WangIds are masked based on the type of the Wang set.
*/
const QVector<WangSet::WangIdAndCell> &WangSet::wangIdsAndCells() const
{
if (cellsDirty())
Expand All @@ -544,15 +577,19 @@ void WangSet::recalculateCells()
mCellsDirty = false;
mUniqueFullWangIdCount = 0;

const auto mask = typeMask();
QSet<WangId> addedWangIds;

// First insert all available tiles
QHashIterator<int, WangId> it(mTileIdToWangId);
while (it.hasNext()) {
it.next();
mUniqueFullWangIdCount += !it.value().hasWildCards() && !addedWangIds.contains(it.value());
addedWangIds.insert(it.value());
mWangIdAndCells.append({it.value(), Cell(mTileset, it.key())});

const auto wangId = WangId(it.value() & mask);

mUniqueFullWangIdCount += !wangId.hasWildCards() && !addedWangIds.contains(wangId);
addedWangIds.insert(wangId);
mWangIdAndCells.append({wangId, Cell(mTileset, it.key())});
}

const auto transformationFlags = tileset()->transformationFlags();
Expand All @@ -566,10 +603,12 @@ void WangSet::recalculateCells()
while (it.hasNext()) {
it.next();

const auto wangId = WangId(it.value() & mask);

Cell cells[8] = { Cell(mTileset, it.key()) };
WangId wangIds[8] = { it.value() };
WangId wangIds[8] = { wangId };
int count = 1;
const bool hasWildCards = it.value().hasWildCards();
const bool hasWildCards = wangId.hasWildCards();

// Add 90, 180 and 270 degree rotations if enabled
if (transformationFlags.testFlag(Tileset::AllowRotate)) {
Expand Down Expand Up @@ -629,7 +668,8 @@ void WangSet::recalculateColorDistances()
QVector<int> distance(colorCount() + 1, -1);

// Check all tiles for transitions to other Wang colors
for (const WangId wangId : std::as_const(mTileIdToWangId)) {
for (WangId wangId : std::as_const(mTileIdToWangId)) {
wangId &= typeMask();

// Don't consider edges and corners to be connected. This helps
// avoid seeing transitions to "no color" for edge or corner
Expand Down Expand Up @@ -734,6 +774,8 @@ WangId WangSet::wangIdOfTile(const Tile *tile) const
*
* If the cell refers to a different tileset than the one to which this WangSet
* belongs, an empty WangId is returned.
*
* The result is masked based on the type of the Wang set.
*/
WangId WangSet::wangIdOfCell(const Cell &cell) const
{
Expand All @@ -752,7 +794,7 @@ WangId WangSet::wangIdOfCell(const Cell &cell) const
wangId.flipVertically();
}

return wangId;
return wangId & typeMask();
}

/**
Expand Down Expand Up @@ -793,10 +835,13 @@ bool WangSet::wangIdIsValid(WangId wangId, int colorCount)
*
* When \a mask is given, returns whether there is a WangId assigned to a
* WangTile matching the part of the \a wangId indicated by the mask.
*
* The \a wangId is automatically masked based on the type of the Wang set.
*/
bool WangSet::wangIdIsUsed(WangId wangId, WangId mask) const
{
const quint64 maskedWangId = wangId & mask;
mask &= typeMask();
const WangId maskedWangId = wangId & mask;

for (const auto &wangIdAndCell : wangIdsAndCells())
if ((wangIdAndCell.wangId & mask) == maskedWangId)
Expand Down Expand Up @@ -844,7 +889,7 @@ bool WangSet::isComplete() const
*/
quint64 WangSet::completeSetSize() const
{
quint64 c = static_cast<quint64>(colorCount());
auto c = static_cast<quint64>(colorCount());

switch (mType) {
case Corner:
Expand Down Expand Up @@ -877,10 +922,9 @@ WangSet::Type WangSet::effectiveTypeForColor(int color) const

if (usedAsEdge == usedAsCorner)
return Mixed;
else if (usedAsEdge)
if (usedAsEdge)
return Edge;
else
return Corner;
return Corner;
}

return type();
Expand Down
28 changes: 17 additions & 11 deletions src/libtiled/wangset.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class TILEDSHARED_EXPORT WangId
constexpr operator quint64() const { return mId; }
inline void setId(quint64 id) { mId = id; }

bool isEmpty() const { return mId == 0; }

int edgeColor(int index) const;
int cornerColor(int index) const;

Expand All @@ -100,13 +102,13 @@ class TILEDSHARED_EXPORT WangId
void setIndexColor(int index, unsigned value);

void updateToAdjacent(WangId adjacent, int position);
void mergeWith(WangId wangId, quint64 mask);
void mergeWith(WangId wangId, WangId mask);

bool hasWildCards() const;
bool hasCornerWildCards() const;
bool hasEdgeWildCards() const;
quint64 mask() const;
quint64 mask(int value) const;
WangId mask() const;
WangId mask(int value) const;

bool hasCornerWithColor(int value) const;
bool hasEdgeWithColor(int value) const;
Expand All @@ -118,6 +120,11 @@ class TILEDSHARED_EXPORT WangId
WangId flippedHorizontally() const;
WangId flippedVertically() const;

bool operator==(WangId other) const { return mId == other.mId; }
bool operator!=(WangId other) const { return mId != other.mId; }
WangId operator& (quint64 mask) const { return mId & mask; }
WangId operator&=(quint64 mask) { return mId &= mask; }

static Index indexByGrid(int x, int y);
static Index oppositeIndex(int index);
static Index nextIndex(int index);
Expand All @@ -134,9 +141,9 @@ class TILEDSHARED_EXPORT WangId
quint64 mId;
};

inline void WangId::mergeWith(WangId wangId, quint64 mask)
inline void WangId::mergeWith(WangId wangId, WangId mask)
{
mId = (mId & ~mask) | (wangId & mask);
*this = (*this & ~mask) | (wangId & mask);
}

inline WangId::Index WangId::oppositeIndex(int index)
Expand Down Expand Up @@ -261,6 +268,8 @@ class TILEDSHARED_EXPORT WangSet : public Object
Type type() const;
void setType(Type type);

WangId typeMask() const;

int imageTileId() const;
void setImageTileId(int imageTileId);
Tile *imageTile() const;
Expand Down Expand Up @@ -323,6 +332,7 @@ class TILEDSHARED_EXPORT WangSet : public Object
Tileset *mTileset;
QString mName;
Type mType;
WangId mTypeMask;
int mImageTileId;

// How many unique, full WangIds are active in this set.
Expand Down Expand Up @@ -366,13 +376,9 @@ inline WangSet::Type WangSet::type() const
return mType;
}

/**
* Changes the type of this Wang set. Does not modify any WangIds to make sure
* they adhere to the type!
*/
inline void WangSet::setType(WangSet::Type type)
inline WangId WangSet::typeMask() const
{
mType = type;
return mTypeMask;
}

inline int WangSet::imageTileId() const
Expand Down
2 changes: 1 addition & 1 deletion src/tiled/changetilewangid.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ bool ChangeTileWangId::mergeWith(const QUndoCommand *other)
if (!mMergeable)
return false;

const ChangeTileWangId *o = static_cast<const ChangeTileWangId*>(other);
auto o = static_cast<const ChangeTileWangId*>(other);
if (o->mTilesetDocument && !(mTilesetDocument == o->mTilesetDocument &&
mWangSet == o->mWangSet))
return false;
Expand Down
3 changes: 2 additions & 1 deletion src/tiled/tilesetview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ void TileDelegate::drawWangOverlay(QPainter *painter,
setupTilesetGridTransform(*tile->tileset(), transform, targetRect);
painter->setTransform(transform, true);

paintWangOverlay(painter, wangSet->wangIdOfTile(tile),
paintWangOverlay(painter,
wangSet->wangIdOfTile(tile) & wangSet->typeMask(),
*wangSet,
targetRect);

Expand Down
2 changes: 1 addition & 1 deletion src/tiled/wangdock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ WangDock::WangDock(QWidget *parent)
mEraseWangIdsButton->setIcon(QIcon(QLatin1String(":images/22/stock-tool-eraser.png")));
mEraseWangIdsButton->setCheckable(true);
mEraseWangIdsButton->setAutoExclusive(true);
mEraseWangIdsButton->setChecked(mCurrentWangId == 0);
mEraseWangIdsButton->setChecked(mCurrentWangId.isEmpty());

connect(mEraseWangIdsButton, &QPushButton::clicked,
this, &WangDock::activateErase);
Expand Down
30 changes: 15 additions & 15 deletions src/tiled/wangfiller.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,18 @@ static void getSurroundingPoints(QPoint point,
* The position of the adjacent info is given by \a adjacentPosition. Also sets
* the mask for the given corner / side.
*/
static void updateAdjacent(WangFiller::CellInfo &adjacentInfo, WangId wangId, int adjacentPosition)
static void updateAdjacent(WangFiller::CellInfo &adjacentInfo, WangId wangId, int adjacentIndex)
{
const int position = WangId::oppositeIndex(adjacentPosition);
const int index = WangId::oppositeIndex(adjacentIndex);

adjacentInfo.desired.setIndexColor(position, wangId.indexColor(adjacentPosition));
adjacentInfo.mask.setIndexColor(position, WangId::INDEX_MASK);
adjacentInfo.desired.setIndexColor(index, wangId.indexColor(adjacentIndex));
adjacentInfo.mask.setIndexColor(index, WangId::INDEX_MASK);

if (!WangId::isCorner(position)) {
const int cornerA = WangId::nextIndex(position);
const int cornerB = WangId::previousIndex(position);
const int adjacentCornerA = WangId::previousIndex(adjacentPosition);
const int adjacentCornerB = WangId::nextIndex(adjacentPosition);
if (!WangId::isCorner(index)) {
const int cornerA = WangId::nextIndex(index);
const int cornerB = WangId::previousIndex(index);
const int adjacentCornerA = WangId::previousIndex(adjacentIndex);
const int adjacentCornerB = WangId::nextIndex(adjacentIndex);

adjacentInfo.desired.setIndexColor(cornerA, wangId.indexColor(adjacentCornerA));
adjacentInfo.mask.setIndexColor(cornerA, WangId::INDEX_MASK);
Expand Down Expand Up @@ -211,7 +211,7 @@ void WangFiller::apply(TileLayer &target)
if (!mCorrectionsEnabled) {
// Set the Wang IDs at the border of the region to prefer the tiles in
// the filled region to connect with those outside of it.
auto setDesiredWangId = [&] (int x, int y, quint64 mask) {
auto setDesiredWangId = [&] (int x, int y, WangId mask) {
const WangId surroundings = wangIdFromSurroundings(QPoint(x, y));
CellInfo &info = grid.add(x, y);

Expand Down Expand Up @@ -378,7 +378,7 @@ bool WangFiller::findBestMatch(const TileLayer &target,
Cell &result) const
{
const CellInfo info = grid.get(position);
const quint64 maskedWangId = info.desired & info.mask;
const WangId maskedWangId = info.desired & info.mask;

RandomPicker<Cell> matches;
int lowestPenalty = INT_MAX;
Expand Down Expand Up @@ -407,9 +407,9 @@ bool WangFiller::findBestMatch(const TileLayer &target,
// this candidate at all because it's impossible to
// transition to the desired color.
return;
} else {
penalty = mWangSet.maximumColorDistance() + 1;
}

penalty = mWangSet.maximumColorDistance() + 1;
}

totalPenalty += penalty;
Expand All @@ -431,8 +431,8 @@ bool WangFiller::findBestMatch(const TileLayer &target,
};

const auto &wangIdsAndCells = mWangSet.wangIdsAndCells();
for (int i = 0, i_end = wangIdsAndCells.size(); i < i_end; ++i)
processCandidate(wangIdsAndCells[i].wangId, wangIdsAndCells[i].cell);
for (const auto &wangIdAndCell : wangIdsAndCells)
processCandidate(wangIdAndCell.wangId, wangIdAndCell.cell);

if (mErasingEnabled)
processCandidate(WangId(), Cell());
Expand Down

0 comments on commit f46b444

Please sign in to comment.