Skip to content

Commit

Permalink
Introduced horizontal and vertical flipping of tiles
Browse files Browse the repository at this point in the history
This commit changes the TileLayer class to store Cell instances rather
than directly referring to Tile*. The Cell class contains the Tile* plus
information on whether the tile is horizontally or vertically flipped.

The MapReader and MapWriter have been adapted to store and restore the
flipped state as flags in the last bits of the global IDs (this means
older Tiled versions will refuse to load maps with flipped tiles).

The OrthogonalRenderer and IsometricRenderer have been adapted to render
the flipped tiles correctly.

This implements the first part of issue #3

Sponsored-by: Zipline Games, Inc.
  • Loading branch information
bjorn committed May 25, 2011
1 parent 33a854c commit 42f4048
Show file tree
Hide file tree
Showing 19 changed files with 330 additions and 247 deletions.
16 changes: 13 additions & 3 deletions src/libtiled/isometricrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,19 @@ void IsometricRenderer::drawTileLayer(QPainter *painter,

for (int x = startPos.x(); x < rect.right(); x += tileWidth) {
if (layer->contains(columnItr)) {
if (const Tile *tile = layer->tileAt(columnItr)) {
const QPixmap &img = tile->image();
painter->drawPixmap(x, y - img.height(), img);
const Cell &cell = layer->cellAt(columnItr);
if (!cell.isEmpty()) {
const QPixmap &img = cell.tile->image();
int flipX = cell.flippedHorizontally ? -1 : 1;
int flipY = cell.flippedVertically ? -1 : 1;
int offsetX = cell.flippedHorizontally ? img.width() : 0;
int offsetY = cell.flippedVertically ? 0 : img.height();

painter->scale(flipX, flipY);
painter->drawPixmap(x * flipX - offsetX,
y * flipY - offsetY,
img);
painter->scale(flipX, flipY);
}
}

Expand Down
75 changes: 41 additions & 34 deletions src/libtiled/mapreader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@
using namespace Tiled;
using namespace Tiled::Internal;

// Bits on the far end of the 32-bit global tile ID are used for tile flags
const int FlippedHorizontallyFlag = 0x80000000;
const int FlippedVerticallyFlag = 0x40000000;

namespace Tiled {
namespace Internal {

Expand Down Expand Up @@ -86,15 +90,15 @@ class MapReaderPrivate
void decodeCSVLayerData(TileLayer *tileLayer, const QString &text);

/**
* Returns the tile for the given global tile ID. When an error occurs,
* Returns the cell for the given global tile ID. When an error occurs,
* \a ok is set to false and an error is raised.
*
* @param gid the global tile ID, must be at least 0
* @param gid the global tile ID
* @param ok returns whether the conversion went ok
* @return the tile associated with the given global tile ID, or 0 if
* not found
* @return the cell data associated with the given global tile ID, or an
* empty cell if not found
*/
Tile *tileForGid(int gid, bool &ok);
Cell cellForGid(uint gid, bool &ok);

ObjectGroup *readObjectGroup();
MapObject *readObject();
Expand All @@ -107,7 +111,7 @@ class MapReaderPrivate
QString mError;
QString mPath;
Map *mMap;
QMap<int, Tileset*> mGidsToTileset;
QMap<uint, Tileset*> mGidsToTileset;
bool mReadingExternalTileset;

QXmlStreamReader xml;
Expand Down Expand Up @@ -251,8 +255,8 @@ Tileset *MapReaderPrivate::readTileset()

const QXmlStreamAttributes atts = xml.attributes();
const QString source = atts.value(QLatin1String("source")).toString();
const int firstGid =
atts.value(QLatin1String("firstgid")).toString().toInt();
const uint firstGid =
atts.value(QLatin1String("firstgid")).toString().toUInt();

Tileset *tileset = 0;

Expand All @@ -269,7 +273,7 @@ Tileset *MapReaderPrivate::readTileset()
atts.value(QLatin1String("margin")).toString().toInt();

if (tileWidth <= 0 || tileHeight <= 0
|| (firstGid <= 0 && !mReadingExternalTileset)) {
|| (firstGid == 0 && !mReadingExternalTileset)) {
xml.raiseError(tr("Invalid tileset parameters for tileset"
" '%1'").arg(name));
} else {
Expand Down Expand Up @@ -415,11 +419,11 @@ void MapReaderPrivate::readLayerData(TileLayer *tileLayer)
}

const QXmlStreamAttributes atts = xml.attributes();
int gid = atts.value(QLatin1String("gid")).toString().toInt();
uint gid = atts.value(QLatin1String("gid")).toString().toUInt();
bool ok;
Tile *tile = tileForGid(gid, ok);
Cell cell = cellForGid(gid, ok);
if (ok)
tileLayer->setTile(x, y, tile);
tileLayer->setCell(x, y, cell);
else
xml.raiseError(tr("Invalid tile: %1").arg(gid));

Expand Down Expand Up @@ -483,15 +487,15 @@ void MapReaderPrivate::decodeBinaryLayerData(TileLayer *tileLayer,
int y = 0;

for (int i = 0; i < size - 3; i += 4) {
const int gid = data[i] |
data[i + 1] << 8 |
data[i + 2] << 16 |
data[i + 3] << 24;
const uint gid = data[i] |
data[i + 1] << 8 |
data[i + 2] << 16 |
data[i + 3] << 24;

bool ok;
Tile *tile = tileForGid(gid, ok);
Cell cell = cellForGid(gid, ok);
if (ok)
tileLayer->setTile(x, y, tile);
tileLayer->setCell(x, y, cell);
else {
xml.raiseError(tr("Invalid tile: %1").arg(gid));
return;
Expand Down Expand Up @@ -519,46 +523,49 @@ void MapReaderPrivate::decodeCSVLayerData(TileLayer *tileLayer, const QString &t
for (int y = 0; y < tileLayer->height(); y++) {
for (int x = 0; x < tileLayer->width(); x++) {
bool conversionOk;
const int gid = tiles.at(y * tileLayer->width() + x)
.toInt(&conversionOk);
const uint gid = tiles.at(y * tileLayer->width() + x)
.toUInt(&conversionOk);
if (!conversionOk) {
xml.raiseError(
tr("Unable to parse tile at (%1,%2) on layer '%3'")
.arg(x + 1).arg(y + 1).arg(tileLayer->name()));
return;
}
bool gidOk;
Tile *tile = tileForGid(gid, gidOk);
Cell cell = cellForGid(gid, gidOk);
if (gidOk)
tileLayer->setTile(x, y, tile);
tileLayer->setCell(x, y, cell);
else {
xml.raiseError(tr("Invalid tile: %1").arg(gid));
}
}
}
}

Tile *MapReaderPrivate::tileForGid(int gid, bool &ok)
Cell MapReaderPrivate::cellForGid(uint gid, bool &ok)
{
Tile *result = 0;
Cell result;

if (gid < 0) {
xml.raiseError(tr("Invalid global tile id (less than 0): %1")
.arg(gid));
ok = false;
} else if (gid == 0) {
if (gid == 0) {
ok = true;
} else if (mGidsToTileset.isEmpty()) {
xml.raiseError(tr("Tile used but no tilesets specified"));
ok = false;
} else {
// Read out the flags
result.flippedHorizontally = (gid & FlippedHorizontallyFlag);
result.flippedVertically = (gid & FlippedVerticallyFlag);

// Clear the flags
gid &= ~(FlippedHorizontallyFlag | FlippedVerticallyFlag);

// Find the tileset containing this tile
QMap<int, Tileset*>::const_iterator i = mGidsToTileset.upperBound(gid);
QMap<uint, Tileset*>::const_iterator i = mGidsToTileset.upperBound(gid);
--i; // Navigate one tileset back since upper bound finds the next
const int tileId = gid - i.key();
const Tileset *tileset = i.value();

result = tileset ? tileset->tileAt(tileId) : 0;
result.tile = tileset ? tileset->tileAt(tileId) : 0;
ok = true;
}

Expand Down Expand Up @@ -601,7 +608,7 @@ MapObject *MapReaderPrivate::readObject()

const QXmlStreamAttributes atts = xml.attributes();
const QString name = atts.value(QLatin1String("name")).toString();
const int gid = atts.value(QLatin1String("gid")).toString().toInt();
const uint gid = atts.value(QLatin1String("gid")).toString().toUInt();
const int x = atts.value(QLatin1String("x")).toString().toInt();
const int y = atts.value(QLatin1String("y")).toString().toInt();
const int width = atts.value(QLatin1String("width")).toString().toInt();
Expand Down Expand Up @@ -631,9 +638,9 @@ MapObject *MapReaderPrivate::readObject()

if (gid) {
bool ok;
Tile *tile = tileForGid(gid, ok);
Cell cell = cellForGid(gid, ok);
if (ok) {
object->setTile(tile);
object->setTile(cell.tile);
} else {
xml.raiseError(tr("Invalid tile: %1").arg(gid));
}
Expand Down
49 changes: 31 additions & 18 deletions src/libtiled/mapwriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@
using namespace Tiled;
using namespace Tiled::Internal;

// Bits on the far end of the 32-bit global tile ID are used for tile flags
const int FlippedHorizontallyFlag = 0x80000000;
const int FlippedVerticallyFlag = 0x40000000;

namespace Tiled {
namespace Internal {

Expand All @@ -71,17 +75,17 @@ class MapWriterPrivate
private:
void writeMap(QXmlStreamWriter &w, const Map *map);
void writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
int firstGid);
uint firstGid);
void writeTileLayer(QXmlStreamWriter &w, const TileLayer *tileLayer);
void writeLayerAttributes(QXmlStreamWriter &w, const Layer *layer);
int gidForTile(const Tile *tile) const;
uint gidForCell(const Cell &cell) const;
void writeObjectGroup(QXmlStreamWriter &w, const ObjectGroup *objectGroup);
void writeObject(QXmlStreamWriter &w, const MapObject *mapObject);
void writeProperties(QXmlStreamWriter &w,
const Properties &properties);

QDir mMapDir; // The directory in which the map is being saved
QMap<int, const Tileset*> mFirstGidToTileset;
QMap<uint, const Tileset*> mFirstGidToTileset;
bool mUseAbsolutePaths;
};

Expand Down Expand Up @@ -186,7 +190,7 @@ void MapWriterPrivate::writeMap(QXmlStreamWriter &w, const Map *map)
writeProperties(w, map->properties());

mFirstGidToTileset.clear();
int firstGid = 1;
uint firstGid = 1;
foreach (const Tileset *tileset, map->tilesets()) {
writeTileset(w, tileset, firstGid);
mFirstGidToTileset.insert(firstGid, tileset);
Expand All @@ -204,7 +208,7 @@ void MapWriterPrivate::writeMap(QXmlStreamWriter &w, const Map *map)
}

void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
int firstGid)
uint firstGid)
{
w.writeStartElement(QLatin1String("tileset"));
if (firstGid > 0)
Expand Down Expand Up @@ -306,7 +310,7 @@ void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w,
if (mLayerDataFormat == MapWriter::XML) {
for (int y = 0; y < tileLayer->height(); ++y) {
for (int x = 0; x < tileLayer->width(); ++x) {
const int gid = gidForTile(tileLayer->tileAt(x, y));
const uint gid = gidForCell(tileLayer->cellAt(x, y));
w.writeStartElement(QLatin1String("tile"));
w.writeAttribute(QLatin1String("gid"), QString::number(gid));
w.writeEndElement();
Expand All @@ -317,7 +321,7 @@ void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w,

for (int y = 0; y < tileLayer->height(); ++y) {
for (int x = 0; x < tileLayer->width(); ++x) {
const int gid = gidForTile(tileLayer->tileAt(x, y));
const uint gid = gidForCell(tileLayer->cellAt(x, y));
tileData.append(QString::number(gid));
if (x != tileLayer->width() - 1
|| y != tileLayer->height() - 1)
Expand All @@ -334,7 +338,7 @@ void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w,

for (int y = 0; y < tileLayer->height(); ++y) {
for (int x = 0; x < tileLayer->width(); ++x) {
const int gid = gidForTile(tileLayer->tileAt(x, y));
const uint gid = gidForCell(tileLayer->cellAt(x, y));
tileData.append((char) (gid));
tileData.append((char) (gid >> 8));
tileData.append((char) (gid >> 16));
Expand Down Expand Up @@ -377,30 +381,39 @@ void MapWriterPrivate::writeLayerAttributes(QXmlStreamWriter &w,
}

/**
* Returns the global tile ID for the given tile. Only valid after the
* Returns the global tile ID for the given cell. Only valid after the
* firstGidToTileset map has been initialized.
*
* @param tile the tile to return the global ID for
* @param cell the cell to return the global ID for
* @return the appropriate global tile ID, or 0 if not found
*/
int MapWriterPrivate::gidForTile(const Tile *tile) const
uint MapWriterPrivate::gidForCell(const Cell &cell) const
{
if (!tile)
if (cell.isEmpty())
return 0;

const Tileset *tileset = tile->tileset();
const Tileset *tileset = cell.tile->tileset();

// Find the first GID for the tileset
QMap<int, const Tileset*>::const_iterator i = mFirstGidToTileset.begin();
QMap<int, const Tileset*>::const_iterator i_end = mFirstGidToTileset.end();
QMap<uint, const Tileset*>::const_iterator i = mFirstGidToTileset.begin();
QMap<uint, const Tileset*>::const_iterator i_end = mFirstGidToTileset.end();
while (i != i_end && i.value() != tileset)
++i;

return (i != i_end) ? i.key() + tile->id() : 0;
if (i == i_end) // tileset not found
return 0;

uint gid = i.key() + cell.tile->id();
if (cell.flippedHorizontally)
gid |= FlippedHorizontallyFlag;
if (cell.flippedVertically)
gid |= FlippedVerticallyFlag;

return gid;
}

void MapWriterPrivate::writeObjectGroup(QXmlStreamWriter &w,
const ObjectGroup *objectGroup)
const ObjectGroup *objectGroup)
{
w.writeStartElement(QLatin1String("objectgroup"));

Expand Down Expand Up @@ -429,7 +442,7 @@ void MapWriterPrivate::writeObject(QXmlStreamWriter &w,
w.writeAttribute(QLatin1String("type"), type);

if (mapObject->tile()) {
const int gid = gidForTile(mapObject->tile());
const uint gid = gidForCell(Cell(mapObject->tile()));
w.writeAttribute(QLatin1String("gid"), QString::number(gid));
}

Expand Down
17 changes: 12 additions & 5 deletions src/libtiled/orthogonalrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,14 +160,21 @@ void OrthogonalRenderer::drawTileLayer(QPainter *painter,

for (int y = startY; y < endY; ++y) {
for (int x = startX; x < endX; ++x) {
const Tile *tile = layer->tileAt(x, y);
if (!tile)
const Cell &cell = layer->cellAt(x, y);
if (cell.isEmpty())
continue;

const QPixmap &img = tile->image();
painter->drawPixmap(x * tileWidth,
(y + 1) * tileHeight - img.height(),
const QPixmap &img = cell.tile->image();
const int flipX = cell.flippedHorizontally ? -1 : 1;
const int flipY = cell.flippedVertically ? -1 : 1;
const int offsetX = cell.flippedHorizontally ? img.width() : 0;
const int offsetY = cell.flippedVertically ? 0 : img.height();

painter->scale(flipX, flipY);
painter->drawPixmap(x * tileWidth * flipX - offsetX,
(y + 1) * tileHeight * flipY - offsetY,
img);
painter->scale(flipX, flipY);
}
}

Expand Down
Loading

0 comments on commit 42f4048

Please sign in to comment.