Skip to content

Commit

Permalink
Distant terrain transitions working now.
Browse files Browse the repository at this point in the history
Also:
- Updated desert example with terrain painting data.
  • Loading branch information
TurkeyMan committed May 31, 2012
1 parent 4e35398 commit 9dc152d
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 34 deletions.
4 changes: 1 addition & 3 deletions examples/desert.tmx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="40" height="40" tilewidth="32" tileheight="32">
<tileset firstgid="1" name="Desert" tilewidth="32" tileheight="32" spacing="1" margin="1">
<image source="tmw_desert_spacing.png" width="265" height="199"/>
</tileset>
<tileset firstgid="1" source="desert.tsx"/>
<layer name="Ground" width="40" height="40">
<data encoding="base64" compression="zlib">
eJztmNkKwjAQRaN9cAPrAq5Yq3Xf6v9/nSM2VIbQJjEZR+nDwQZScrwztoORECLySBcIgZ7nc2y4KfyWDLx+Jb9nViNgDEwY+KioAXUgQN4+zpoCMwPmQAtoAx2CLFbA2oDEo9+hwG8DnIDtF/2K8ks086Tw2zH0uyMv7HcRr/6/EvvhnsPrsrxwX7rwU/0ODig/eV3mh3N1ld8eraWPaX6+64s9McesfrqcHfg1MpoifxcVEWjukyw+9AtFPl/I71pER3Of6j4bv7HI54s+MChhqLlPdZ/P3qMmFuo5h5NnTOhjM5tReN2yT51n5/v7J3F0vi46fk+ne7aX0i9l6If7mpufTX3f5wsqv9TAD2fJLT9VrTn7UeZnM5tR+v0LMQOHXwFnxe2/warGFRWf8QDjOLfP
Expand Down
50 changes: 50 additions & 0 deletions examples/desert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<tileset name="Desert" tilewidth="32" tileheight="32" spacing="1" margin="1">
<image source="tmw_desert_spacing.png" width="265" height="199"/>
<terraintypes>
<terrain name="Desert" tile="29" distances=",0,1,1,1"/>
<terrain name="Brick" tile="9" distances=",1,0,2,2"/>
<terrain name="Cobblestone" tile="33" distances=",1,2,0,2"/>
<terrain name="Dirt" tile="14" distances=",1,2,2,0"/>
</terraintypes>
<tile id="0" terrain="0,0,0,1"/>
<tile id="1" terrain="0,0,1,1"/>
<tile id="2" terrain="0,0,1,0"/>
<tile id="3" terrain="3,3,3,0"/>
<tile id="4" terrain="3,3,0,3"/>
<tile id="5" terrain="0,0,0,3"/>
<tile id="6" terrain="0,0,3,3"/>
<tile id="7" terrain="0,0,3,0"/>
<tile id="8" terrain="0,1,0,1"/>
<tile id="9" terrain="1,1,1,1"/>
<tile id="10" terrain="1,0,1,0"/>
<tile id="11" terrain="3,0,3,3"/>
<tile id="12" terrain="0,3,3,3"/>
<tile id="13" terrain="0,3,0,3"/>
<tile id="14" terrain="3,3,3,3"/>
<tile id="15" terrain="3,0,3,0"/>
<tile id="16" terrain="0,1,0,0"/>
<tile id="17" terrain="1,1,0,0"/>
<tile id="18" terrain="1,0,0,0"/>
<tile id="19" terrain="1,1,1,0"/>
<tile id="20" terrain="1,1,0,1"/>
<tile id="21" terrain="0,3,0,0"/>
<tile id="22" terrain="3,3,0,0"/>
<tile id="23" terrain="3,0,0,0"/>
<tile id="24" terrain="0,0,0,2"/>
<tile id="25" terrain="0,0,2,2"/>
<tile id="26" terrain="0,0,2,0"/>
<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="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="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"/>
</tileset>
8 changes: 7 additions & 1 deletion src/libtiled/mapreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QVector>
#include <QXmlStreamReader>

using namespace Tiled;
Expand Down Expand Up @@ -308,6 +309,9 @@ Tileset *MapReaderPrivate::readTileset()
if (tileset && !mReadingExternalTileset)
mGidMapper.insert(firstGid, tileset);

if (tileset)
tileset->calculateTerrainDistances();

return tileset;
}

Expand Down Expand Up @@ -384,8 +388,10 @@ void MapReaderPrivate::readTilesetTerrainTypes(Tileset *tileset)
const QXmlStreamAttributes atts = xml.attributes();
QString name = atts.value(QLatin1String("name")).toString();
int tile = atts.value(QLatin1String("tile")).toString().toInt();
// int tile = atts.value(QLatin1String("color")).toString().toInt();
QString distances = atts.value(QLatin1String("distances")).toString();

tileset->addTerrainType(name, tile);
tileset->addTerrainType(name, tile, distances);

xml.skipCurrentElement();
}
Expand Down
17 changes: 16 additions & 1 deletion src/libtiled/mapwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,19 @@ static QString makeTerrainAttribute(const Tile *tile)
return terrain;
}

static QString makeTransitionDistanceAttribute(const TerrainType *t, int numTerains)
{
QString distance;
for (int i = -1; i < numTerains; ++i ) {
if (i > -1)
distance += QLatin1String(",");
int d = t->transitionDistance(i);
if (d > -1)
distance += QString::number(d);
}
return distance;
}

void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
uint firstGid)
{
Expand Down Expand Up @@ -279,8 +292,10 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
TerrainType* tt = tileset->terrainType(i);
w.writeStartElement(QLatin1String("terrain"));
w.writeAttribute(QLatin1String("name"), tt->name());
// w.writeAttribute(QLatin1String("color"), tt->color());
// w.writeAttribute(QLatin1String("color"), tt->color());
w.writeAttribute(QLatin1String("tile"), QString::number(tt->paletteImageTile()));
if (tt->hasTransitionDistances())
w.writeAttribute(QLatin1String("distances"), makeTransitionDistanceAttribute(tt, tileset->terrainTypeCount()));
w.writeEndElement();
}
w.writeEndElement();
Expand Down
118 changes: 118 additions & 0 deletions src/libtiled/tileset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,121 @@ int Tileset::columnCountForWidth(int width) const
Q_ASSERT(mTileWidth > 0);
return (width - mMargin + mTileSpacing) / (mTileWidth + mTileSpacing);
}

void Tileset::addTerrainType(QString name, int imageTile, QString distances)
{
TerrainType *tt = new TerrainType(mTerrainTypes.size(), this, name, imageTile);

if (!distances.isEmpty()) {
QStringList distStrings = distances.split(QLatin1Char(','));
QVector<int> dist(distStrings.size(), -1);
for (int i = 0; i < distStrings.size(); ++i) {
if (!distStrings[i].isEmpty())
dist[i] = distStrings[i].toInt();
}
tt->setTransitionDistances(dist);
}

mTerrainTypes.push_back(tt);
}

int Tileset::terrainTransitionPenalty(int terrainType0, int terrainType1)
{
terrainType0 = terrainType0 == 255 ? -1 : terrainType0;
terrainType1 = terrainType1 == 255 ? -1 : terrainType1;

// Do some magic, since we don't have a transition array for no-terrain
if (terrainType0 == -1 && terrainType1 == -1)
return 0;
if (terrainType0 == -1)
return mTerrainTypes[terrainType1]->transitionDistance(terrainType0);
return mTerrainTypes[terrainType0]->transitionDistance(terrainType1);
}

void Tileset::calculateTerrainDistances()
{
// some fancy macros which can search for a value in each byte of a word simultaneously
#define hasZeroByte(dword) (((dword) - 0x01010101UL) & ~(dword) & 0x80808080UL)
#define hasByteEqualTo(dword, value) (hasZeroByte((dword) ^ (~0UL/255 * (value))))

// Calculate terrain distances if they are not already present...
// Terrain distances are the number of transitions required before one terrain may meet another
// Terrains that have no transition path have a distance of -1

for (int i = 0; i < terrainTypeCount(); ++i) {
TerrainType *type = terrainType(i);
if (type->hasTransitionDistances())
continue;

QVector<int> distance(terrainTypeCount() + 1, -1);

// Check all tiles for transitions to other terrain types
for (int j = 0; j < tileCount(); ++j) {
Tile *t = tileAt(j);

if (!hasByteEqualTo(t->terrain(), i))
continue;

// This tile has transitions, add the transitions as neightbours (distance 1)
int tl = t->cornerTerrainType(0);
int tr = t->cornerTerrainType(1);
int bl = t->cornerTerrainType(2);
int br = t->cornerTerrainType(3);

// Terrain on diagonally opposite corners are not actually a neighbour
if (tl == i || br == i) {
distance[tr + 1] = 1;
distance[bl + 1] = 1;
}
if (tr == i || bl == i) {
distance[tl + 1] = 1;
distance[br + 1] = 1;
}

// terrain has at least one tile of its own type
distance[i + 1] = 0;
}

type->setTransitionDistances(distance);
}

// Calculate indirect transition distances
bool bNewConnections;
do {
bNewConnections = false;

// For each combination of terrain types
for (int i = 0; i < terrainTypeCount(); ++i) {
TerrainType *t0 = terrainType(i);
for (int j = 0; j < terrainTypeCount(); ++j) {
if (i == j)
continue;
TerrainType *t1 = terrainType(j);

// Scan through each terrain type, and see if we have any in common
for (int t = -1; t < terrainTypeCount(); ++t) {
int d0 = t0->transitionDistance(t);
int d1 = t1->transitionDistance(t);
if (d0 == -1 || d1 == -1)
continue;

// We have cound a common connection
int d = t0->transitionDistance(j);
Q_ASSERT(t1->transitionDistance(i) == d);

// If the new path is shorter, record the new distance
if (d == -1 || d0 + d1 < d) {
d = d0 + d1;
t0->setTransitionDistance(j, d);
t1->setTransitionDistance(i, d);

// We're making progress, flag for another iteration...
bNewConnections = true;
}
}
}
}

// Repeat while we are still making new connections (could take a number of iterations for distant terrains to connect)
} while (bNewConnections);
}
60 changes: 58 additions & 2 deletions src/libtiled/tileset.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include <QColor>
#include <QList>
#include <QVector>
#include <QPoint>
#include <QString>

Expand All @@ -42,25 +43,70 @@ class QImage;
namespace Tiled {

class Tile;
class Tileset;

class TerrainType
{
public:
TerrainType(int id, QString name, int imageTile):
TerrainType(int id, Tileset *tileset, QString name, int imageTile):
mId(id),
mTileset(tileset),
mName(name),
mImageTile(imageTile)
{
}

/**
* Returns ID of this tile terrain type.
*/
int id() const { return mId; }

/**
* Returns the tileset this terrain type belongs to.
*/
Tileset *tileset() const { return mTileset; }

/**
* Returns the name of this terrain type.
*/
QString name() const { return mName; }

/**
* Returns a tile index that represents this terrain type in the terrain palette.
*/
int paletteImageTile() const { return mImageTile; }

/**
* Returns a Tile that represents this terrain type in the terrain palette.
*/
// Tile *paletteImage() const { return mTileset->tileAt(mImageTile); }

/**
* Returns true if this terrain type already has transition distances calculated.
*/
bool hasTransitionDistances() const { return !mTransitionDistance.isEmpty(); }

/**
* Returns the transition penalty(/distance) from this terrain type to another terrain type.
*/
int transitionDistance(int targetTerrainType) const { return mTransitionDistance[targetTerrainType + 1]; }

/**
* Sets the transition penalty(/distance) from this terrain type to another terrain type.
*/
void setTransitionDistance(int targetTerrainType, int distance) { mTransitionDistance[targetTerrainType + 1] = distance; }

/**
* Returns the array of terrain penalties(/distances).
*/
void setTransitionDistances(QVector<int> &transitionDistances) { mTransitionDistance = transitionDistances; }

private:
int mId;
Tileset *mTileset;
QString mName;
int mImageTile;
QVector<int> mTransitionDistance;
};

/**
Expand Down Expand Up @@ -246,7 +292,17 @@ class TILEDSHARED_EXPORT Tileset : public Object
/**
* Add a new terrain type.
*/
void addTerrainType(QString name, int imageTile);
void addTerrainType(QString name, int imageTile, QString distances);

/**
* Calculates the transition distance matrix for all terrain types.
*/
void calculateTerrainDistances();

/**
* Returns the transition penalty(/distance) between 2 terrains. -1 if no transition is possible.
*/
int terrainTransitionPenalty(int terrainType0, int terrainType1);

private:
QString mName;
Expand Down
Loading

0 comments on commit 9dc152d

Please sign in to comment.