Skip to content

Commit

Permalink
Fixed the run away terrain generation when encountering unknown tiles…
Browse files Browse the repository at this point in the history
…, added support for tile randomness factor.
  • Loading branch information
TurkeyMan committed May 31, 2012
1 parent da6ece2 commit 57c426b
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 12 deletions.
8 changes: 8 additions & 0 deletions examples/desert.tsx
Expand Up @@ -37,14 +37,22 @@
<tile id="27" terrain="1,0,1,1"/>
<tile id="28" terrain="0,1,1,1"/>
<tile id="29" terrain="0,0,0,0"/>
<tile id="30" terrain="0,0,0,0" probability="0.5"/>
<tile id="31" terrain="0,0,0,0" probability="0.5"/>
<tile id="32" terrain="0,2,0,2"/>
<tile id="33" terrain="2,2,2,2"/>
<tile id="34" terrain="2,0,2,0"/>
<tile id="35" terrain="2,2,2,0"/>
<tile id="36" terrain="2,2,0,2"/>
<tile id="37" terrain="0,0,0,0" probability="0.5"/>
<tile id="38" terrain="0,0,0,0" probability="0.5"/>
<tile id="39" terrain="0,0,0,0" probability="0.5"/>
<tile id="40" terrain="0,2,0,0"/>
<tile id="41" terrain="2,2,0,0"/>
<tile id="42" terrain="2,0,0,0"/>
<tile id="43" terrain="2,0,2,2"/>
<tile id="44" terrain="0,2,2,2"/>
<tile id="45" terrain="0,0,0,0" probability="0"/>
<tile id="46" terrain="0,0,0,0" probability="0.5"/>
<tile id="47" terrain="0,0,0,0" probability="0.5"/>
</tileset>
9 changes: 7 additions & 2 deletions src/libtiled/mapreader.cpp
Expand Up @@ -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) {
Expand All @@ -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();
Expand Down
6 changes: 5 additions & 1 deletion src/libtiled/mapwriter.cpp
Expand Up @@ -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();
Expand Down
14 changes: 13 additions & 1 deletion src/libtiled/tile.h
Expand Up @@ -44,7 +44,8 @@ class TILEDSHARED_EXPORT Tile : public Object
mId(id),
mTileset(tileset),
mImage(image),
mTerrain(-1)
mTerrain(-1),
mTerrainProbability(-1.f)
{}

/**
Expand Down Expand Up @@ -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
Expand Down
48 changes: 40 additions & 8 deletions src/tiled/terrainbrush.cpp
Expand Up @@ -290,18 +290,25 @@ Tile *TerrainBrush::findBestTile(Tileset *tileset, unsigned int terrain, unsigne
QList<Tile*> 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();
Expand All @@ -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;
}

Expand Down Expand Up @@ -388,7 +420,7 @@ void TerrainBrush::updateBrush(const QPoint &cursorPos, const QVector<QPoint> *l

--initialTiles;
} else {
// following tiles each need consideration against their surroundings
// tiles each need consideration against their surroundings
preferredTerrain = tile->terrain();
mask = 0;

Expand Down

0 comments on commit 57c426b

Please sign in to comment.