aboutsummaryrefslogtreecommitdiff
path: root/libtiled/tilelayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'libtiled/tilelayer.cpp')
-rw-r--r--libtiled/tilelayer.cpp425
1 files changed, 425 insertions, 0 deletions
diff --git a/libtiled/tilelayer.cpp b/libtiled/tilelayer.cpp
new file mode 100644
index 0000000..98675a7
--- /dev/null
+++ b/libtiled/tilelayer.cpp
@@ -0,0 +1,425 @@
+/*
+ * tilelayer.cpp
+ * Copyright 2008-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009, Jeff Bland <jksb@member.fsf.org>
+ *
+ * This file is part of libtiled.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "tilelayer.h"
+
+#include "map.h"
+#include "tile.h"
+#include "tileset.h"
+
+using namespace Tiled;
+
+TileLayer::TileLayer(const QString &name, int x, int y, int width, int height):
+ Layer(name, x, y, width, height),
+ mMaxTileSize(0, 0),
+ mGrid(width * height)
+{
+ Q_ASSERT(width >= 0);
+ Q_ASSERT(height >= 0);
+}
+
+QRegion TileLayer::region() const
+{
+ QRegion region;
+
+ for (int y = 0; y < mHeight; ++y) {
+ for (int x = 0; x < mWidth; ++x) {
+ if (!cellAt(x, y).isEmpty()) {
+ const int rangeStart = x;
+ for (++x; x <= mWidth; ++x) {
+ if (x == mWidth || cellAt(x, y).isEmpty()) {
+ const int rangeEnd = x;
+ region += QRect(rangeStart + mX, y + mY,
+ rangeEnd - rangeStart, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ 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));
+
+ if (cell.tile) {
+ int width = cell.tile->width();
+ int height = cell.tile->height();
+
+ if (cell.flippedAntiDiagonally)
+ std::swap(width, height);
+
+ 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;
+}
+
+TileLayer *TileLayer::copy(const QRegion &region) const
+{
+ const QRegion area = region.intersected(QRect(0, 0, width(), height()));
+ const QRect bounds = region.boundingRect();
+ const QRect areaBounds = area.boundingRect();
+ const int offsetX = qMax(0, areaBounds.x() - bounds.x());
+ const int offsetY = qMax(0, areaBounds.y() - bounds.y());
+
+ TileLayer *copied = new TileLayer(QString(),
+ 0, 0,
+ bounds.width(), bounds.height());
+
+ foreach (const QRect &rect, area.rects())
+ for (int x = rect.left(); x <= rect.right(); ++x)
+ for (int y = rect.top(); y <= rect.bottom(); ++y)
+ copied->setCell(x - areaBounds.x() + offsetX,
+ y - areaBounds.y() + offsetY,
+ cellAt(x, y));
+
+ return copied;
+}
+
+void TileLayer::merge(const QPoint &pos, const TileLayer *layer)
+{
+ // Determine the overlapping area
+ QRect area = QRect(pos, QSize(layer->width(), layer->height()));
+ area &= QRect(0, 0, width(), height());
+
+ for (int y = area.top(); y <= area.bottom(); ++y) {
+ for (int x = area.left(); x <= area.right(); ++x) {
+ const Cell &cell = layer->cellAt(x - area.left(),
+ y - area.top());
+ if (!cell.isEmpty())
+ setCell(x, y, cell);
+ }
+ }
+}
+
+void TileLayer::setCells(int x, int y, TileLayer *layer,
+ const QRegion &mask)
+{
+ // Determine the overlapping area
+ QRegion area = QRect(x, y, layer->width(), layer->height());
+ area &= QRect(0, 0, width(), height());
+
+ if (!mask.isEmpty())
+ area &= mask;
+
+ foreach (const QRect &rect, area.rects())
+ for (int _x = rect.left(); _x <= rect.right(); ++_x)
+ for (int _y = rect.top(); _y <= rect.bottom(); ++_y)
+ setCell(_x, _y, layer->cellAt(_x - x, _y - y));
+}
+
+void TileLayer::flip(FlipDirection direction)
+{
+ QVector<Cell> newGrid(mWidth * mHeight);
+
+ Q_ASSERT(direction == FlipHorizontally || direction == FlipVertically);
+
+ for (int y = 0; y < mHeight; ++y) {
+ for (int x = 0; x < mWidth; ++x) {
+ Cell &dest = newGrid[x + y * mWidth];
+ if (direction == FlipHorizontally) {
+ const Cell &source = cellAt(mWidth - x - 1, y);
+ dest = source;
+ dest.flippedHorizontally = !source.flippedHorizontally;
+ } else if (direction == FlipVertically) {
+ const Cell &source = cellAt(x, mHeight - y - 1);
+ dest = source;
+ dest.flippedVertically = !source.flippedVertically;
+ }
+ }
+ }
+
+ mGrid = newGrid;
+}
+
+void TileLayer::rotate(RotateDirection direction)
+{
+ static const char rotateRightMask[8] = { 5, 4, 1, 0, 7, 6, 3, 2 };
+ static const char rotateLeftMask[8] = { 3, 2, 7, 6, 1, 0, 5, 4 };
+
+ const char (&rotateMask)[8] =
+ (direction == RotateRight) ? rotateRightMask : rotateLeftMask;
+
+ int newWidth = mHeight;
+ int newHeight = mWidth;
+ QVector<Cell> newGrid(newWidth * newHeight);
+
+ for (int y = 0; y < mHeight; ++y) {
+ for (int x = 0; x < mWidth; ++x) {
+ const Cell &source = cellAt(x, y);
+ Cell dest = source;
+
+ unsigned char mask =
+ (dest.flippedHorizontally << 2) |
+ (dest.flippedVertically << 1) |
+ (dest.flippedAntiDiagonally << 0);
+
+ mask = rotateMask[mask];
+
+ dest.flippedHorizontally = (mask & 4) != 0;
+ dest.flippedVertically = (mask & 2) != 0;
+ dest.flippedAntiDiagonally = (mask & 1) != 0;
+
+ if (direction == RotateRight)
+ newGrid[x * newWidth + (mHeight - y - 1)] = dest;
+ else
+ newGrid[(mWidth - x - 1) * newWidth + y] = dest;
+ }
+ }
+
+ std::swap(mMaxTileSize.rwidth(),
+ mMaxTileSize.rheight());
+
+ mWidth = newWidth;
+ mHeight = newHeight;
+ mGrid = newGrid;
+}
+
+
+QSet<Tileset*> TileLayer::usedTilesets() const
+{
+ QSet<Tileset*> tilesets;
+
+ for (int i = 0, i_end = mGrid.size(); i < i_end; ++i)
+ if (const Tile *tile = mGrid.at(i).tile)
+ tilesets.insert(tile->tileset());
+
+ return tilesets;
+}
+
+bool TileLayer::referencesTileset(const Tileset *tileset) const
+{
+ for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) {
+ const Tile *tile = mGrid.at(i).tile;
+ if (tile && tile->tileset() == tileset)
+ return true;
+ }
+ return false;
+}
+
+QRegion TileLayer::tilesetReferences(Tileset *tileset) const
+{
+ QRegion region;
+
+ for (int y = 0; y < mHeight; ++y)
+ for (int x = 0; x < mWidth; ++x)
+ if (const Tile *tile = cellAt(x, y).tile)
+ if (tile->tileset() == tileset)
+ region += QRegion(x + mX, y + mY, 1, 1);
+
+ return region;
+}
+
+void TileLayer::removeReferencesToTileset(Tileset *tileset)
+{
+ for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) {
+ const Tile *tile = mGrid.at(i).tile;
+ if (tile && tile->tileset() == tileset)
+ mGrid.replace(i, Cell());
+ }
+}
+
+void TileLayer::replaceReferencesToTileset(Tileset *oldTileset,
+ Tileset *newTileset)
+{
+ for (int i = 0, i_end = mGrid.size(); i < i_end; ++i) {
+ const Tile *tile = mGrid.at(i).tile;
+ if (tile && tile->tileset() == oldTileset)
+ mGrid[i].tile = newTileset->tileAt(tile->id());
+ }
+}
+
+void TileLayer::resize(const QSize &size, const QPoint &offset)
+{
+ QVector<Cell> newGrid(size.width() * size.height());
+
+ // Copy over the preserved part
+ const int startX = qMax(0, -offset.x());
+ const int startY = qMax(0, -offset.y());
+ const int endX = qMin(mWidth, size.width() - offset.x());
+ const int endY = qMin(mHeight, size.height() - offset.y());
+
+ for (int y = startY; y < endY; ++y) {
+ for (int x = startX; x < endX; ++x) {
+ const int index = x + offset.x() + (y + offset.y()) * size.width();
+ newGrid[index] = cellAt(x, y);
+ }
+ }
+
+ mGrid = newGrid;
+ Layer::resize(size, offset);
+}
+
+void TileLayer::offset(const QPoint &offset,
+ const QRect &bounds,
+ bool wrapX, bool wrapY)
+{
+ QVector<Cell> newGrid(mWidth * mHeight);
+
+ for (int y = 0; y < mHeight; ++y) {
+ for (int x = 0; x < mWidth; ++x) {
+ // Skip out of bounds tiles
+ if (!bounds.contains(x, y)) {
+ newGrid[x + y * mWidth] = cellAt(x, y);
+ continue;
+ }
+
+ // Get position to pull tile value from
+ int oldX = x - offset.x();
+ int oldY = y - offset.y();
+
+ // Wrap x value that will be pulled from
+ if (wrapX && bounds.width() > 0) {
+ while (oldX < bounds.left())
+ oldX += bounds.width();
+ while (oldX > bounds.right())
+ oldX -= bounds.width();
+ }
+
+ // Wrap y value that will be pulled from
+ if (wrapY && bounds.height() > 0) {
+ while (oldY < bounds.top())
+ oldY += bounds.height();
+ while (oldY > bounds.bottom())
+ oldY -= bounds.height();
+ }
+
+ // Set the new tile
+ if (contains(oldX, oldY) && bounds.contains(oldX, oldY))
+ newGrid[x + y * mWidth] = cellAt(oldX, oldY);
+ else
+ newGrid[x + y * mWidth] = Cell();
+ }
+ }
+
+ mGrid = newGrid;
+}
+
+bool TileLayer::canMergeWith(Layer *other) const
+{
+ return dynamic_cast<TileLayer*>(other) != 0;
+}
+
+Layer *TileLayer::mergedWith(Layer *other) const
+{
+ Q_ASSERT(canMergeWith(other));
+
+ const TileLayer *o = static_cast<TileLayer*>(other);
+ const QRect unitedBounds = bounds().united(o->bounds());
+ const QPoint offset = position() - unitedBounds.topLeft();
+
+ TileLayer *merged = static_cast<TileLayer*>(clone());
+ merged->resize(unitedBounds.size(), offset);
+ merged->merge(o->position() - unitedBounds.topLeft(), o);
+ return merged;
+}
+
+QRegion TileLayer::computeDiffRegion(const TileLayer *other) const
+{
+ QRegion ret;
+
+ const int dx = other->x() - mX;
+ const int dy = other->y() - mY;
+ QRect r = QRect(0, 0, width(), height());
+ r &= QRect(dx, dy, other->width(), other->height());
+
+ for (int y = r.top(); y <= r.bottom(); ++y) {
+ for (int x = r.left(); x <= r.right(); ++x) {
+ if (cellAt(x, y) != other->cellAt(x - dx, y - dy)) {
+ const int rangeStart = x;
+ while (x <= r.right() &&
+ cellAt(x, y) != other->cellAt(x - dx, y - dy)) {
+ ++x;
+ }
+ const int rangeEnd = x;
+ ret += QRect(rangeStart, y, rangeEnd - rangeStart, 1);
+ }
+ }
+ }
+
+ return ret;
+}
+
+bool TileLayer::isEmpty() const
+{
+ for (int i = 0, i_end = mGrid.size(); i < i_end; ++i)
+ if (!mGrid.at(i).isEmpty())
+ return false;
+
+ return true;
+}
+
+/**
+ * Returns a duplicate of this TileLayer.
+ *
+ * \sa Layer::clone()
+ */
+Layer *TileLayer::clone() const
+{
+ return initializeClone(new TileLayer(mName, mX, mY, mWidth, mHeight));
+}
+
+TileLayer *TileLayer::initializeClone(TileLayer *clone) const
+{
+ Layer::initializeClone(clone);
+ clone->mGrid = mGrid;
+ clone->mMaxTileSize = mMaxTileSize;
+ clone->mOffsetMargins = mOffsetMargins;
+ return clone;
+}