From 57c426b3b670f2136394418cd71d689e355453d8 Mon Sep 17 00:00:00 2001 From: Manu Evans Date: Tue, 29 May 2012 21:29:38 +0300 Subject: [PATCH] Fixed the run away terrain generation when encountering unknown tiles, added support for tile randomness factor. --- examples/desert.tsx | 8 +++++++ src/libtiled/mapreader.cpp | 9 +++++-- src/libtiled/mapwriter.cpp | 6 ++++- src/libtiled/tile.h | 14 ++++++++++- src/tiled/terrainbrush.cpp | 48 +++++++++++++++++++++++++++++++------- 5 files changed, 73 insertions(+), 12 deletions(-) diff --git a/examples/desert.tsx b/examples/desert.tsx index e8db8e6f50..de12708ec7 100644 --- a/examples/desert.tsx +++ b/examples/desert.tsx @@ -37,14 +37,22 @@ + + + + + + + + diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index 6924949a31..ae4cf7c3b0 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -327,13 +327,13 @@ void MapReaderPrivate::readTilesetTile(Tileset *tileset) xml.raiseError(tr("Invalid tile ID: %1").arg(id)); return; } + Tile *tile = tileset->tileAt(id); // TODO: Add support for individual tiles (then it needs to be added here) // Read tile quadrant terrain ids QString terrain = atts.value(QLatin1String("terrain")).toString(); if (!terrain.isEmpty()) { - Tile *tile = tileset->tileAt(id); QStringList quadrants = terrain.split(QLatin1String(",")); if (quadrants.size() == 4) { for (int i = 0; i < 4; ++i) { @@ -343,9 +343,14 @@ void MapReaderPrivate::readTilesetTile(Tileset *tileset) } } + // Read tile probability + QString probability = atts.value(QLatin1String("probability")).toString(); + if (!probability.isEmpty()) { + tile->setTerrainProbability(probability.toFloat()); + } + while (xml.readNextStartElement()) { if (xml.name() == "properties") { - Tile *tile = tileset->tileAt(id); tile->mergeProperties(readProperties()); } else { readUnknownElement(); diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index d20f998a2c..f2b325776b 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -307,11 +307,15 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset, const Tile *tile = tileset->tileAt(i); const Properties properties = tile->properties(); unsigned int terrain = tile->terrain(); - if (!properties.isEmpty() || terrain != 0xFFFFFFFF) { + int probability = tile->terrainProbability(); + + if (!properties.isEmpty() || terrain != 0xFFFFFFFF || probability != -1) { w.writeStartElement(QLatin1String("tile")); w.writeAttribute(QLatin1String("id"), QString::number(i)); if (terrain != 0xFFFFFFFF) w.writeAttribute(QLatin1String("terrain"), makeTerrainAttribute(tile)); + if (probability != -1) + w.writeAttribute(QLatin1String("probability"), QString::number(probability)); if (!properties.isEmpty()) writeProperties(w, properties); w.writeEndElement(); diff --git a/src/libtiled/tile.h b/src/libtiled/tile.h index 23aadc34d3..cf343405db 100644 --- a/src/libtiled/tile.h +++ b/src/libtiled/tile.h @@ -44,7 +44,8 @@ class TILEDSHARED_EXPORT Tile : public Object mId(id), mTileset(tileset), mImage(image), - mTerrain(-1) + mTerrain(-1), + mTerrainProbability(-1.f) {} /** @@ -111,11 +112,22 @@ class TILEDSHARED_EXPORT Tile : public Object unsigned short rightEdge() const { return ((terrain() >> 8) & 0xFF00) | (terrain() & 0xFF); } unsigned int terrain() const { return this == NULL ? 0xFFFFFFFF : mTerrain; } // HACK: NULL Tile has 'none' terrain type. + /** + * Returns the probability of this terrain type appearing while painting (0-100%). + */ + float terrainProbability() const { return mTerrainProbability; } + + /** + * Set the probability of this terrain type appearing while painting (0-100%). + */ + void setTerrainProbability(float probability) { mTerrainProbability = probability; } + private: int mId; Tileset *mTileset; QPixmap mImage; unsigned int mTerrain; + float mTerrainProbability; }; } // namespace Tiled diff --git a/src/tiled/terrainbrush.cpp b/src/tiled/terrainbrush.cpp index c42de88351..280280713b 100644 --- a/src/tiled/terrainbrush.cpp +++ b/src/tiled/terrainbrush.cpp @@ -290,18 +290,25 @@ Tile *TerrainBrush::findBestTile(Tileset *tileset, unsigned int terrain, unsigne QList matches; int penalty = INT_MAX; + // TODO: this is a slow linear search, perhaps we could use a better find algorithm... int tileCount = tileset->tileCount(); for (int i = 0; i < tileCount; ++i) { Tile *t = tileset->tileAt(i); if ((t->terrain() & considerationMask) != (terrain & considerationMask)) continue; - // calculate the tile transition penalty - int transitionPenalty = tileset->terrainTransitionPenalty(t->terrain() >> 24, terrain >> 24); - transitionPenalty += tileset->terrainTransitionPenalty((t->terrain() >> 16) & 0xFF, (terrain >> 16) & 0xFF); - transitionPenalty += tileset->terrainTransitionPenalty((t->terrain() >> 8) & 0xFF, (terrain >> 8) & 0xFF); - transitionPenalty += tileset->terrainTransitionPenalty(t->terrain() & 0xFF, terrain & 0xFF); + // calculate the tile transition penalty based on shortest distance to target terrain type + int tr = tileset->terrainTransitionPenalty(t->terrain() >> 24, terrain >> 24); + int tl = tileset->terrainTransitionPenalty((t->terrain() >> 16) & 0xFF, (terrain >> 16) & 0xFF); + int br = tileset->terrainTransitionPenalty((t->terrain() >> 8) & 0xFF, (terrain >> 8) & 0xFF); + int bl = tileset->terrainTransitionPenalty(t->terrain() & 0xFF, terrain & 0xFF); + // if there is no path to the destination terrain, this isn't a useful transition + if (tr < 0 || tl < 0 || br < 0 || bl < 0) + continue; + + // add tile to the candidate list + int transitionPenalty = tr + tl + br + bl; if (transitionPenalty <= penalty) { if (transitionPenalty < penalty) matches.clear(); @@ -312,9 +319,34 @@ Tile *TerrainBrush::findBestTile(Tileset *tileset, unsigned int terrain, unsigne } // choose a candidate at random, with consideration for terrain probability - if (!matches.isEmpty()) - return matches[0]; + if (!matches.isEmpty()) { + float random = ((float)rand() / RAND_MAX) * 100.f; + float total = 0, unassigned = 0; + + // allow the tiles with assigned probability to take their share + for (int i = 0; i < matches.size(); ++i) { + float probability = matches[i]->terrainProbability(); + if (probability < 0.f) { + ++unassigned; + continue; + } + if (random < total + probability) + return matches[i]; + total += probability; + } + + // divide the remaining percentile by the numer of unassigned tiles + float remainingShare = (100.f - total) / (float)unassigned; + for (int i = 0; i < matches.size(); ++i) { + if (matches[i]->terrainProbability() >= 0.f) + continue; + if (random < total + remainingShare) + return matches[i]; + total += remainingShare; + } + } + // TODO: conveniently, the NULL tile doesn't currently work, but when it does, we need to signal a failure to find any matches some other way return NULL; } @@ -388,7 +420,7 @@ void TerrainBrush::updateBrush(const QPoint &cursorPos, const QVector *l --initialTiles; } else { - // following tiles each need consideration against their surroundings + // tiles each need consideration against their surroundings preferredTerrain = tile->terrain(); mask = 0;