From 694257b1fe83c4c6cbc9b2c2c247547f74422cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thorbj=C3=B8rn=20Lindeijer?= Date: Wed, 26 Oct 2011 23:36:00 +0200 Subject: [PATCH] Added support for specifying a tile drawing offset Each tileset can now define an offset that is applied when drawing its tiles. This is useful when combining tilesets with a different baseline, or lining up the base of the tiles with the tile grid. This new tileset property is used to improve the tile positions in the 'perspective walls' and 'isometric grass and water' examples. This closes #16 Sponsored-by: Clint Bellanger --- examples/isometric_grass_and_water.tmx | 1 + examples/perspective_walls.tmx | 2 +- examples/perspective_walls.tsx | 1 + src/libtiled/isometricrenderer.cpp | 18 +- src/libtiled/map.cpp | 30 ++- src/libtiled/map.h | 25 +- src/libtiled/mapreader.cpp | 15 +- src/libtiled/mapwriter.cpp | 8 + src/libtiled/orthogonalrenderer.cpp | 19 +- src/libtiled/tilelayer.cpp | 38 ++- src/libtiled/tilelayer.h | 19 +- src/libtiled/tileset.h | 13 + src/tiled/brushitem.cpp | 15 +- src/tiled/mapscene.cpp | 11 +- src/tiled/newtilesetdialog.cpp | 9 + src/tiled/newtilesetdialog.ui | 354 ++++++++++++++++--------- 16 files changed, 392 insertions(+), 186 deletions(-) diff --git a/examples/isometric_grass_and_water.tmx b/examples/isometric_grass_and_water.tmx index e9a896dad8..93cfc52c76 100644 --- a/examples/isometric_grass_and_water.tmx +++ b/examples/isometric_grass_and_water.tmx @@ -2,6 +2,7 @@ + diff --git a/examples/perspective_walls.tmx b/examples/perspective_walls.tmx index bd4d2b25f7..858eea843f 100644 --- a/examples/perspective_walls.tmx +++ b/examples/perspective_walls.tmx @@ -1,5 +1,5 @@ - + diff --git a/examples/perspective_walls.tsx b/examples/perspective_walls.tsx index 45d763d57a..98cdbec615 100644 --- a/examples/perspective_walls.tsx +++ b/examples/perspective_walls.tsx @@ -1,5 +1,6 @@ + diff --git a/src/libtiled/isometricrenderer.cpp b/src/libtiled/isometricrenderer.cpp index 7031dec1b2..c846667e02 100644 --- a/src/libtiled/isometricrenderer.cpp +++ b/src/libtiled/isometricrenderer.cpp @@ -32,6 +32,7 @@ #include "mapobject.h" #include "tile.h" #include "tilelayer.h" +#include "tileset.h" #include @@ -164,10 +165,14 @@ void IsometricRenderer::drawTileLayer(QPainter *painter, if (rect.isNull()) rect = boundingRect(layer->bounds()); - const QSize maxTileSize = layer->maxTileSize(); - const int extraWidth = maxTileSize.width() - tileWidth; - const int extraHeight = maxTileSize.height() - tileHeight; - rect.adjust(-extraWidth, 0, 0, extraHeight); + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + rect.adjust(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); // Determine the tile and pixel coordinates to start at QPointF tilePos = pixelToTileCoords(rect.x(), rect.y()); @@ -214,13 +219,14 @@ void IsometricRenderer::drawTileLayer(QPainter *painter, const Cell &cell = layer->cellAt(columnItr); if (!cell.isEmpty()) { const QPixmap &img = cell.tile->image(); + const QPoint offset = cell.tile->tileset()->tileOffset(); qreal m11 = 1; // Horizontal scaling factor qreal m12 = 0; // Vertical shearing factor qreal m21 = 0; // Horizontal shearing factor qreal m22 = 1; // Vertical scaling factor - qreal dx = x; - qreal dy = y - img.height(); + qreal dx = offset.x() + x; + qreal dy = offset.y() + y - img.height(); if (cell.flippedDiagonally) { // Use shearing to swap the X/Y axis diff --git a/src/libtiled/map.cpp b/src/libtiled/map.cpp index 10381b593d..a2393564b4 100644 --- a/src/libtiled/map.cpp +++ b/src/libtiled/map.cpp @@ -43,8 +43,7 @@ Map::Map(Orientation orientation, mWidth(width), mHeight(height), mTileWidth(tileWidth), - mTileHeight(tileHeight), - mMaxTileSize(tileWidth, tileHeight) + mTileHeight(tileHeight) { } @@ -53,12 +52,25 @@ Map::~Map() qDeleteAll(mLayers); } -void Map::adjustMaxTileSize(const QSize &size) +static QMargins maxMargins(const QMargins &a, + const QMargins &b) { - if (size.width() > mMaxTileSize.width()) - mMaxTileSize.setWidth(size.width()); - if (size.height() > mMaxTileSize.height()) - mMaxTileSize.setHeight(size.height()); + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + +void Map::adjustDrawMargins(const QMargins &margins) +{ + // The TileLayer includes the maximum tile size in its draw margins. So + // we need to subtract the tile size of the map, since that part does not + // contribute to additional margin. + mDrawMargins = maxMargins(QMargins(margins.left(), + margins.top() - mTileHeight, + margins.right() - mTileWidth, + margins.bottom()), + mDrawMargins); } int Map::tileLayerCount() const @@ -105,7 +117,7 @@ void Map::adoptLayer(Layer *layer) layer->setMap(this); if (TileLayer *tileLayer = dynamic_cast(layer)) - adjustMaxTileSize(tileLayer->maxTileSize()); + adjustDrawMargins(tileLayer->drawMargins()); } Layer *Map::takeLayerAt(int index) @@ -158,7 +170,7 @@ bool Map::isTilesetUsed(Tileset *tileset) const Map *Map::clone() const { Map *o = new Map(mOrientation, mWidth, mHeight, mTileWidth, mTileHeight); - o->mMaxTileSize = mMaxTileSize; + o->mDrawMargins = mDrawMargins; foreach (const Layer *layer, mLayers) o->addLayer(layer->clone()); o->mTilesets = mTilesets; diff --git a/src/libtiled/map.h b/src/libtiled/map.h index 6e1512f2c4..349e6fea39 100644 --- a/src/libtiled/map.h +++ b/src/libtiled/map.h @@ -34,6 +34,7 @@ #include "object.h" #include +#include #include namespace Tiled { @@ -128,26 +129,18 @@ class TILEDSHARED_EXPORT Map : public Object int tileHeight() const { return mTileHeight; } /** - * Returns the maximum tile size used by tile layers of this map. - * @see TileLayer::extraTileSize() + * Adjusts the draw margins to be at least as big as the given margins. + * Called from tile layers when their tiles change. */ - QSize maxTileSize() const { return mMaxTileSize; } + void adjustDrawMargins(const QMargins &margins); /** - * Adjusts the maximum tile size to be at least as much as the given - * size. Called from tile layers when their maximum tile size increases. - */ - void adjustMaxTileSize(const QSize &size); - - /** - * Convenience method for getting the extra tile size, which is the number - * of pixels that tiles may extend beyond the size of the tile grid. + * Returns the margins that have to be taken into account when figuring + * out which part of the map to repaint after changing some tiles. * - * @see maxTileSize() + * @see TileLayer::drawMargins */ - QSize extraTileSize() const - { return QSize(mMaxTileSize.width() - mTileWidth, - mMaxTileSize.height() - mTileHeight); } + QMargins drawMargins() const { return mDrawMargins; } /** * Returns the number of layers of this map. @@ -259,7 +252,7 @@ class TILEDSHARED_EXPORT Map : public Object int mHeight; int mTileWidth; int mTileHeight; - QSize mMaxTileSize; + QMargins mDrawMargins; QList mLayers; QList mTilesets; }; diff --git a/src/libtiled/mapreader.cpp b/src/libtiled/mapreader.cpp index fd994a1336..53f209e184 100644 --- a/src/libtiled/mapreader.cpp +++ b/src/libtiled/mapreader.cpp @@ -277,14 +277,21 @@ Tileset *MapReaderPrivate::readTileset() tileSpacing, margin); while (xml.readNextStartElement()) { - if (xml.name() == "tile") + if (xml.name() == "tile") { readTilesetTile(tileset); - else if (xml.name() == "properties") + } else if (xml.name() == "tileoffset") { + const QXmlStreamAttributes oa = xml.attributes(); + int x = oa.value(QLatin1String("x")).toString().toInt(); + int y = oa.value(QLatin1String("y")).toString().toInt(); + tileset->setTileOffset(QPoint(x, y)); + xml.skipCurrentElement(); + } else if (xml.name() == "properties") { tileset->mergeProperties(readProperties()); - else if (xml.name() == "image") + } else if (xml.name() == "image") { readTilesetImage(tileset); - else + } else { readUnknownElement(); + } } } } else { // External tileset diff --git a/src/libtiled/mapwriter.cpp b/src/libtiled/mapwriter.cpp index bde9f7f433..ca9231322e 100644 --- a/src/libtiled/mapwriter.cpp +++ b/src/libtiled/mapwriter.cpp @@ -234,6 +234,14 @@ void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset, if (margin != 0) w.writeAttribute(QLatin1String("margin"), QString::number(margin)); + const QPoint offset = tileset->tileOffset(); + if (!offset.isNull()) { + w.writeStartElement(QLatin1String("tileoffset")); + w.writeAttribute(QLatin1String("x"), QString::number(offset.x())); + w.writeAttribute(QLatin1String("y"), QString::number(offset.y())); + w.writeEndElement(); + } + // Write the tileset properties writeProperties(w, tileset->properties()); diff --git a/src/libtiled/orthogonalrenderer.cpp b/src/libtiled/orthogonalrenderer.cpp index 1186c4f480..9dad71fb8d 100644 --- a/src/libtiled/orthogonalrenderer.cpp +++ b/src/libtiled/orthogonalrenderer.cpp @@ -32,6 +32,7 @@ #include "mapobject.h" #include "tile.h" #include "tilelayer.h" +#include "tileset.h" #include @@ -192,10 +193,15 @@ void OrthogonalRenderer::drawTileLayer(QPainter *painter, int endY = layer->height(); if (!exposed.isNull()) { - const QSize maxTileSize = layer->maxTileSize(); - const int extraWidth = maxTileSize.width() - tileWidth; - const int extraHeight = maxTileSize.height() - tileHeight; - QRectF rect = exposed.adjusted(-extraWidth, 0, 0, extraHeight); + QMargins drawMargins = layer->drawMargins(); + drawMargins.setTop(drawMargins.top() - tileHeight); + drawMargins.setRight(drawMargins.right() - tileWidth); + + QRectF rect = exposed.adjusted(-drawMargins.right(), + -drawMargins.bottom(), + drawMargins.left(), + drawMargins.top()); + rect.translate(-layerPos); startX = qMax((int) rect.x() / tileWidth, 0); @@ -213,13 +219,14 @@ void OrthogonalRenderer::drawTileLayer(QPainter *painter, continue; const QPixmap &img = cell.tile->image(); + const QPoint offset = cell.tile->tileset()->tileOffset(); qreal m11 = 1; // Horizontal scaling factor qreal m12 = 0; // Vertical shearing factor qreal m21 = 0; // Horizontal shearing factor qreal m22 = 1; // Vertical scaling factor - qreal dx = x * tileWidth; - qreal dy = (y + 1) * tileHeight - img.height(); + qreal dx = offset.x() + x * tileWidth; + qreal dy = offset.y() + (y + 1) * tileHeight - img.height(); if (cell.flippedDiagonally) { // Use shearing to swap the X/Y axis diff --git a/src/libtiled/tilelayer.cpp b/src/libtiled/tilelayer.cpp index 4234c82e93..ea30f5fabd 100644 --- a/src/libtiled/tilelayer.cpp +++ b/src/libtiled/tilelayer.cpp @@ -67,6 +67,22 @@ QRegion TileLayer::region() const return region; } +static QSize maxSize(const QSize &a, + const QSize &b) +{ + return QSize(qMax(a.width(), b.width()), + qMax(a.height(), b.height())); +} + +static QMargins maxMargins(const QMargins &a, + const QMargins &b) +{ + return QMargins(qMax(a.left(), b.left()), + qMax(a.top(), b.top()), + qMax(a.right(), b.right()), + qMax(a.bottom(), b.bottom())); +} + void TileLayer::setCell(int x, int y, const Cell &cell) { Q_ASSERT(contains(x, y)); @@ -78,16 +94,17 @@ void TileLayer::setCell(int x, int y, const Cell &cell) if (cell.flippedDiagonally) std::swap(width, height); - if (width > mMaxTileSize.width()) { - mMaxTileSize.setWidth(width); - if (mMap) - mMap->adjustMaxTileSize(mMaxTileSize); - } - if (height > mMaxTileSize.height()) { - mMaxTileSize.setHeight(height); - if (mMap) - mMap->adjustMaxTileSize(mMaxTileSize); - } + const QPoint offset = cell.tile->tileset()->tileOffset(); + + mMaxTileSize = maxSize(QSize(width, height), mMaxTileSize); + mOffsetMargins = maxMargins(QMargins(-offset.x(), + -offset.y(), + offset.x(), + offset.y()), + mOffsetMargins); + + if (mMap) + mMap->adjustDrawMargins(drawMargins()); } mGrid[x + y * mWidth] = cell; @@ -403,5 +420,6 @@ TileLayer *TileLayer::initializeClone(TileLayer *clone) const Layer::initializeClone(clone); clone->mGrid = mGrid; clone->mMaxTileSize = mMaxTileSize; + clone->mOffsetMargins = mOffsetMargins; return clone; } diff --git a/src/libtiled/tilelayer.h b/src/libtiled/tilelayer.h index 7d67e07700..6671736e5d 100644 --- a/src/libtiled/tilelayer.h +++ b/src/libtiled/tilelayer.h @@ -34,6 +34,7 @@ #include "layer.h" +#include #include #include @@ -113,11 +114,24 @@ class TILEDSHARED_EXPORT TileLayer : public Layer TileLayer(const QString &name, int x, int y, int width, int height); /** - * Returns the maximum tile size of this layer. Used by the layer - * rendering code to determine the area that needs to be redrawn. + * Returns the maximum tile size of this layer. */ QSize maxTileSize() const { return mMaxTileSize; } + /** + * Returns the margins that have to be taken into account while drawing + * this tile layer. The margins depend on the maximum tile size and the + * offset applied to the tiles. + */ + QMargins drawMargins() const + { + return QMargins(mOffsetMargins.left(), + mOffsetMargins.top() + mMaxTileSize.height(), + mOffsetMargins.right() + mMaxTileSize.width(), + mOffsetMargins.bottom()); + } + + /** * Returns whether (x, y) is inside this map layer. */ @@ -256,6 +270,7 @@ class TILEDSHARED_EXPORT TileLayer : public Layer private: QSize mMaxTileSize; + QMargins mOffsetMargins; QVector mGrid; }; diff --git a/src/libtiled/tileset.h b/src/libtiled/tileset.h index 2f9b50aeb9..99c7ba67db 100644 --- a/src/libtiled/tileset.h +++ b/src/libtiled/tileset.h @@ -34,6 +34,7 @@ #include #include +#include #include class QImage; @@ -126,6 +127,17 @@ class TILEDSHARED_EXPORT Tileset : public Object */ int margin() const { return mMargin; } + /** + * Returns the offset that is applied when drawing the tiles in this + * tileset. + */ + QPoint tileOffset() const { return mTileOffset; } + + /** + * @see tileOffset + */ + void setTileOffset(QPoint offset) { mTileOffset = offset; } + /** * Returns the tile for the given tile ID. * The tile ID is local to this tileset, which means the IDs are in range @@ -210,6 +222,7 @@ class TILEDSHARED_EXPORT Tileset : public Object int mTileHeight; int mTileSpacing; int mMargin; + QPoint mTileOffset; int mImageWidth; int mImageHeight; int mColumnCount; diff --git a/src/tiled/brushitem.cpp b/src/tiled/brushitem.cpp index 9eed2df2ce..1b0c613534 100644 --- a/src/tiled/brushitem.cpp +++ b/src/tiled/brushitem.cpp @@ -136,11 +136,14 @@ void BrushItem::updateBoundingRect() // Adjust for amount of pixels tiles extend at the top and to the right if (mTileLayer) { const Map *map = mMapDocument->map(); - const int tileWidth = map->tileWidth(); - const int tileHeight = map->tileHeight(); - const QSize maxTileSize = mTileLayer->maxTileSize(); - const int extendTop = -qMax(0, maxTileSize.height() - tileHeight); - const int extendRight = qMax(0, maxTileSize.width() - tileWidth); - mBoundingRect.adjust(0, extendTop, extendRight, 0); + + QMargins drawMargins = mTileLayer->drawMargins(); + drawMargins.setTop(drawMargins.top() - map->tileHeight()); + drawMargins.setRight(drawMargins.right() - map->tileWidth()); + + mBoundingRect.adjust(-drawMargins.left(), + -drawMargins.top(), + drawMargins.right(), + drawMargins.bottom()); } } diff --git a/src/tiled/mapscene.cpp b/src/tiled/mapscene.cpp index 442be5e4c3..a50e82bdb0 100644 --- a/src/tiled/mapscene.cpp +++ b/src/tiled/mapscene.cpp @@ -232,11 +232,14 @@ void MapScene::updateCurrentLayerHighlight() void MapScene::repaintRegion(const QRegion ®ion) { const MapRenderer *renderer = mMapDocument->renderer(); - const QSize extra = mMapDocument->map()->extraTileSize(); + const QMargins margins = mMapDocument->map()->drawMargins(); - foreach (const QRect &r, region.rects()) - update(renderer->boundingRect(r) - .adjusted(0, -extra.height(), extra.width(), 0)); + foreach (const QRect &r, region.rects()) { + update(renderer->boundingRect(r).adjusted(-margins.left(), + -margins.top(), + margins.right(), + margins.bottom())); + } } void MapScene::enableSelectedTool() diff --git a/src/tiled/newtilesetdialog.cpp b/src/tiled/newtilesetdialog.cpp index b9526ea79e..aa59a06d54 100644 --- a/src/tiled/newtilesetdialog.cpp +++ b/src/tiled/newtilesetdialog.cpp @@ -37,6 +37,7 @@ static const char * const COLOR_ENABLED_KEY = "Tileset/UseTransparentColor"; static const char * const COLOR_KEY = "Tileset/TransparentColor"; static const char * const SPACING_KEY = "Tileset/Spacing"; static const char * const MARGIN_KEY = "Tileset/Margin"; +static const char * const OFFSET_KEY = "Tileset/Offset"; using namespace Tiled; using namespace Tiled::Internal; @@ -58,11 +59,14 @@ NewTilesetDialog::NewTilesetDialog(const QString &path, QWidget *parent) : QColor color = colorName.isEmpty() ? Qt::magenta : QColor(colorName); int spacing = s->value(QLatin1String(SPACING_KEY)).toInt(); int margin = s->value(QLatin1String(MARGIN_KEY)).toInt(); + QPoint offset = s->value(QLatin1String(OFFSET_KEY)).toPoint(); mUi->useTransparentColor->setChecked(colorEnabled); mUi->colorButton->setColor(color); mUi->spacing->setValue(spacing); mUi->margin->setValue(margin); + mUi->offsetX->setValue(offset.x()); + mUi->offsetY->setValue(offset.y()); connect(mUi->browseButton, SIGNAL(clicked()), SLOT(browse())); connect(mUi->name, SIGNAL(textEdited(QString)), SLOT(nameEdited(QString))); @@ -112,11 +116,15 @@ void NewTilesetDialog::tryAccept() const int tileHeight = mUi->tileHeight->value(); const int spacing = mUi->spacing->value(); const int margin = mUi->margin->value(); + const QPoint offset = QPoint(mUi->offsetX->value(), + mUi->offsetY->value()); std::auto_ptr tileset(new Tileset(name, tileWidth, tileHeight, spacing, margin)); + tileset->setTileOffset(offset); + if (useTransparentColor) tileset->setTransparentColor(transparentColor); @@ -141,6 +149,7 @@ void NewTilesetDialog::tryAccept() s->setValue(QLatin1String(COLOR_KEY), transparentColor.name()); s->setValue(QLatin1String(SPACING_KEY), spacing); s->setValue(QLatin1String(MARGIN_KEY), margin); + s->setValue(QLatin1String(OFFSET_KEY), offset); mNewTileset = tileset.release(); accept(); diff --git a/src/tiled/newtilesetdialog.ui b/src/tiled/newtilesetdialog.ui index d2ebf1c5fc..5f90c4d4ec 100644 --- a/src/tiled/newtilesetdialog.ui +++ b/src/tiled/newtilesetdialog.ui @@ -2,11 +2,19 @@ NewTilesetDialog + + + 0 + 0 + 342 + 369 + + New Tileset - - + + Tileset @@ -76,128 +84,230 @@ - - - - Tiles + + + + 0 - - - - - Tile width: - - - - - - - true - - - 1 - - - 9999 - - - 32 - - - - - - - Margin: - - - - - - - 0 - - - 99 - - - 0 - - - - - - - Tile height: - - - - - - - 1 - - - 9999 - - - 32 - - - - - - - Spacing: - - - - - - - 0 - - - 99 - - - 0 - - - - - - - Qt::Horizontal - - - QSizePolicy::MinimumExpanding - - - - 20 - 20 - - - - - - - - - - - Qt::Vertical - - - - 20 - 0 - - - + + + + Tiles + + + + + + Tile width: + + + + + + + true + + + 1 + + + 9999 + + + 32 + + + + + + + Tile height: + + + + + + + 1 + + + 9999 + + + 32 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 10 + 13 + + + + + + + + Margin: + + + + + + + 0 + + + 99 + + + 0 + + + + + + + Spacing: + + + + + + + 0 + + + 99 + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + + Drawing Offset + + + + + + X: + + + + + + + true + + + -9999 + + + 9999 + + + + + + + Y: + + + + + + + -9999 + + + 9999 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::MinimumExpanding + + + + 0 + 20 + + + + + - + Qt::Horizontal