aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamu Laaksonen <laaksonen.sj@gmail.com>2012-09-25 20:00:41 +0300
committerSamu Laaksonen <laaksonen.sj@gmail.com>2012-09-25 20:00:41 +0300
commit685fe05def77b039221edf06c74af74915d536c5 (patch)
tree9c1a14b8f68bc0f801bdec3edc447d04fdbf7a4c
parent29eabac0670574efd384182c065f53d08c42a483 (diff)
downloadprism-685fe05def77b039221edf06c74af74915d536c5.tar.gz
prism-685fe05def77b039221edf06c74af74915d536c5.zip
Initial code commit
Added some stuff for project base - tiled sources - few Qt based classes for gfx
-rw-r--r--.gitignore5
-rw-r--r--libtiled/compression.cpp162
-rw-r--r--libtiled/compression.h72
-rw-r--r--libtiled/gidmapper.cpp125
-rw-r--r--libtiled/gidmapper.h89
-rw-r--r--libtiled/isometricrenderer.cpp430
-rw-r--r--libtiled/isometricrenderer.h82
-rw-r--r--libtiled/layer.cpp69
-rw-r--r--libtiled/layer.h215
-rw-r--r--libtiled/libtiled.pri3
-rw-r--r--libtiled/libtiled.pro58
-rw-r--r--libtiled/map.cpp207
-rw-r--r--libtiled/map.h278
-rw-r--r--libtiled/mapobject.cpp63
-rw-r--r--libtiled/mapobject.h244
-rw-r--r--libtiled/mapreader.cpp772
-rw-r--r--libtiled/mapreader.h129
-rw-r--r--libtiled/maprenderer.cpp46
-rw-r--r--libtiled/maprenderer.h152
-rw-r--r--libtiled/mapwriter.cpp580
-rw-r--r--libtiled/mapwriter.h129
-rw-r--r--libtiled/object.h85
-rw-r--r--libtiled/objectgroup.cpp198
-rw-r--r--libtiled/objectgroup.h164
-rw-r--r--libtiled/orthogonalrenderer.cpp372
-rw-r--r--libtiled/orthogonalrenderer.h74
-rw-r--r--libtiled/properties.cpp42
-rw-r--r--libtiled/properties.h47
-rw-r--r--libtiled/tile.h93
-rw-r--r--libtiled/tiled_global.h40
-rw-r--r--libtiled/tilelayer.cpp425
-rw-r--r--libtiled/tilelayer.h279
-rw-r--r--libtiled/tileset.cpp114
-rw-r--r--libtiled/tileset.h234
-rw-r--r--platformer.pri18
-rw-r--r--platformer.pro11
-rw-r--r--src/ActionScene.cpp150
-rw-r--r--src/ActionScene.h83
-rw-r--r--src/BasicEnemy.cpp38
-rw-r--r--src/BasicEnemy.h21
-rw-r--r--src/Character.cpp113
-rw-r--r--src/Character.h65
-rw-r--r--src/Collectible.cpp25
-rw-r--r--src/Collectible.h19
-rw-r--r--src/CreditsScene.cpp64
-rw-r--r--src/CreditsScene.h25
-rw-r--r--src/GameScene.cpp96
-rw-r--r--src/GameScene.h55
-rw-r--r--src/GameView.cpp174
-rw-r--r--src/GameView.h57
-rw-r--r--src/GraphicsButtonObject.cpp137
-rw-r--r--src/GraphicsButtonObject.h59
-rw-r--r--src/GraphicsPixmapObject.cpp7
-rw-r--r--src/GraphicsPixmapObject.h30
-rw-r--r--src/Hero.cpp41
-rw-r--r--src/Hero.h25
-rw-r--r--src/LevelSelectionScene.cpp98
-rw-r--r--src/LevelSelectionScene.h36
-rw-r--r--src/MainWindow.cpp17
-rw-r--r--src/MainWindow.h23
-rw-r--r--src/MenuScene.cpp53
-rw-r--r--src/MenuScene.h32
-rw-r--r--src/ParallaxScrollerItem.cpp47
-rw-r--r--src/ParallaxScrollerItem.h24
-rw-r--r--src/ParallaxScrollerStatic.cpp29
-rw-r--r--src/ParallaxScrollerStatic.h26
-rw-r--r--src/SceneChanger.cpp107
-rw-r--r--src/SceneChanger.h74
-rw-r--r--src/Sprite.cpp108
-rw-r--r--src/Sprite.h70
-rw-r--r--src/main.cpp33
-rw-r--r--src/src.pro55
72 files changed, 8222 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c2ef6a2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+Makefile
+moc_*
+*.o
+*.pro.user
+
diff --git a/libtiled/compression.cpp b/libtiled/compression.cpp
new file mode 100644
index 0000000..3cf6f46
--- /dev/null
+++ b/libtiled/compression.cpp
@@ -0,0 +1,162 @@
+/*
+ * compression.cpp
+ * Copyright 2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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 "compression.h"
+
+#include <zlib.h>
+#include <QByteArray>
+#include <QDebug>
+
+using namespace Tiled;
+
+// TODO: Improve error reporting by showing these errors in the user interface
+static void logZlibError(int error)
+{
+ switch (error)
+ {
+ case Z_MEM_ERROR:
+ qDebug() << "Out of memory while (de)compressing data!";
+ break;
+ case Z_VERSION_ERROR:
+ qDebug() << "Incompatible zlib version!";
+ break;
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR:
+ qDebug() << "Incorrect zlib compressed data!";
+ break;
+ default:
+ qDebug() << "Unknown error while (de)compressing data!";
+ }
+}
+
+QByteArray Tiled::decompress(const QByteArray &data, int expectedSize)
+{
+ QByteArray out;
+ out.resize(expectedSize);
+ z_stream strm;
+
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.next_in = (Bytef *) data.data();
+ strm.avail_in = data.length();
+ strm.next_out = (Bytef *) out.data();
+ strm.avail_out = out.size();
+
+ int ret = inflateInit2(&strm, 15 + 32);
+
+ if (ret != Z_OK) {
+ logZlibError(ret);
+ return QByteArray();
+ }
+
+ do {
+ ret = inflate(&strm, Z_SYNC_FLUSH);
+
+ switch (ret) {
+ case Z_NEED_DICT:
+ case Z_STREAM_ERROR:
+ ret = Z_DATA_ERROR;
+ case Z_DATA_ERROR:
+ case Z_MEM_ERROR:
+ inflateEnd(&strm);
+ logZlibError(ret);
+ return QByteArray();
+ }
+
+ if (ret != Z_STREAM_END) {
+ int oldSize = out.size();
+ out.resize(out.size() * 2);
+
+ strm.next_out = (Bytef *)(out.data() + oldSize);
+ strm.avail_out = oldSize;
+ }
+ }
+ while (ret != Z_STREAM_END);
+
+ if (strm.avail_in != 0) {
+ logZlibError(Z_DATA_ERROR);
+ return QByteArray();
+ }
+
+ const int outLength = out.size() - strm.avail_out;
+ inflateEnd(&strm);
+
+ out.resize(outLength);
+ return out;
+}
+
+QByteArray Tiled::compress(const QByteArray &data, CompressionMethod method)
+{
+ QByteArray out;
+ out.resize(1024);
+ int err;
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.next_in = (Bytef *) data.data();
+ strm.avail_in = data.length();
+ strm.next_out = (Bytef *) out.data();
+ strm.avail_out = out.size();
+
+ const int windowBits = (method == Gzip) ? 15 + 16 : 15;
+
+ err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits,
+ 8, Z_DEFAULT_STRATEGY);
+ if (err != Z_OK) {
+ logZlibError(err);
+ return QByteArray();
+ }
+
+ do {
+ err = deflate(&strm, Z_FINISH);
+ Q_ASSERT(err != Z_STREAM_ERROR);
+
+ if (err == Z_OK) {
+ // More output space needed
+ int oldSize = out.size();
+ out.resize(out.size() * 2);
+
+ strm.next_out = (Bytef *)(out.data() + oldSize);
+ strm.avail_out = oldSize;
+ }
+ } while (err == Z_OK);
+
+ if (err != Z_STREAM_END) {
+ logZlibError(err);
+ deflateEnd(&strm);
+ return QByteArray();
+ }
+
+ const int outLength = out.size() - strm.avail_out;
+ deflateEnd(&strm);
+
+ out.resize(outLength);
+ return out;
+}
diff --git a/libtiled/compression.h b/libtiled/compression.h
new file mode 100644
index 0000000..04071ee
--- /dev/null
+++ b/libtiled/compression.h
@@ -0,0 +1,72 @@
+/*
+ * compression.h
+ * Copyright 2008, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef COMPRESSION_H
+#define COMPRESSION_H
+
+#include "tiled_global.h"
+
+class QByteArray;
+
+namespace Tiled {
+
+enum CompressionMethod {
+ Gzip,
+ Zlib
+};
+
+/**
+ * Decompresses either zlib or gzip compressed memory. Returns a null
+ * QByteArray if decompressing failed.
+ *
+ * Needed because qUncompress does not support gzip compressed data. Also,
+ * this method does not need the expected size to be prepended to the data,
+ * but it can be passed as optional parameter.
+ *
+ * @param data the compressed data
+ * @param expectedSize the expected size of the uncompressed data in bytes
+ * @return the uncompressed data, or a null QByteArray if decompressing failed
+ */
+QByteArray TILEDSHARED_EXPORT decompress(const QByteArray &data,
+ int expectedSize = 1024);
+
+/**
+ * Compresses the give data in either gzip or zlib format. Returns a null
+ * QByteArray if compression failed.
+ *
+ * Needed because qCompress does not support gzip compression.
+ *
+ * @param data the uncompressed data
+ * @return the compressed data, or a null QByteArray if compression failed
+ */
+QByteArray TILEDSHARED_EXPORT compress(const QByteArray &data,
+ CompressionMethod method = Zlib);
+
+} // namespace Tiled
+
+#endif // COMPRESSION_H
diff --git a/libtiled/gidmapper.cpp b/libtiled/gidmapper.cpp
new file mode 100644
index 0000000..aca1a7f
--- /dev/null
+++ b/libtiled/gidmapper.cpp
@@ -0,0 +1,125 @@
+/*
+ * gidmapper.cpp
+ * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * This file is part of libtiled.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "gidmapper.h"
+
+#include "tile.h"
+#include "tileset.h"
+#include "map.h"
+
+using namespace Tiled;
+
+// 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;
+const int FlippedAntiDiagonallyFlag = 0x20000000;
+
+GidMapper::GidMapper()
+{
+}
+
+GidMapper::GidMapper(const QList<Tileset *> &tilesets)
+{
+ uint firstGid = 1;
+ foreach (Tileset *tileset, tilesets) {
+ insert(firstGid, tileset);
+ firstGid += tileset->tileCount();
+ }
+}
+
+Cell GidMapper::gidToCell(uint gid, bool &ok) const
+{
+ Cell result;
+
+ // Read out the flags
+ result.flippedHorizontally = (gid & FlippedHorizontallyFlag);
+ result.flippedVertically = (gid & FlippedVerticallyFlag);
+ result.flippedAntiDiagonally = (gid & FlippedAntiDiagonallyFlag);
+
+ // Clear the flags
+ gid &= ~(FlippedHorizontallyFlag |
+ FlippedVerticallyFlag |
+ FlippedAntiDiagonallyFlag);
+
+ if (gid == 0) {
+ ok = true;
+ } else if (isEmpty()) {
+ ok = false;
+ } else {
+ // Find the tileset containing this tile
+ QMap<uint, Tileset*>::const_iterator i = mFirstGidToTileset.upperBound(gid);
+ --i; // Navigate one tileset back since upper bound finds the next
+ int tileId = gid - i.key();
+ const Tileset *tileset = i.value();
+
+ if (tileset) {
+ const int columnCount = mTilesetColumnCounts.value(tileset);
+ if (columnCount > 0 && columnCount != tileset->columnCount()) {
+ // Correct tile index for changes in image width
+ const int row = tileId / columnCount;
+ const int column = tileId % columnCount;
+ tileId = row * tileset->columnCount() + column;
+ }
+
+ result.tile = tileset->tileAt(tileId);
+ } else {
+ result.tile = 0;
+ }
+
+ ok = true;
+ }
+
+ return result;
+}
+
+uint GidMapper::cellToGid(const Cell &cell) const
+{
+ if (cell.isEmpty())
+ return 0;
+
+ const Tileset *tileset = cell.tile->tileset();
+
+ // Find the first GID for the tileset
+ QMap<uint, Tileset*>::const_iterator i = mFirstGidToTileset.begin();
+ QMap<uint, Tileset*>::const_iterator i_end = mFirstGidToTileset.end();
+ while (i != i_end && i.value() != tileset)
+ ++i;
+
+ 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;
+ if (cell.flippedAntiDiagonally)
+ gid |= FlippedAntiDiagonallyFlag;
+
+ return gid;
+}
+
+void GidMapper::setTilesetWidth(const Tileset *tileset, int width)
+{
+ if (tileset->tileWidth() == 0)
+ return;
+
+ mTilesetColumnCounts.insert(tileset, tileset->columnCountForWidth(width));
+}
diff --git a/libtiled/gidmapper.h b/libtiled/gidmapper.h
new file mode 100644
index 0000000..803db67
--- /dev/null
+++ b/libtiled/gidmapper.h
@@ -0,0 +1,89 @@
+/*
+ * gidmapper.h
+ * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * This file is part of libtiled.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef TILED_GIDMAPPER_H
+#define TILED_GIDMAPPER_H
+
+#include "tilelayer.h"
+
+#include <QMap>
+
+namespace Tiled {
+
+/**
+ * A class that maps cells to global IDs (gids) and back.
+ */
+class TILEDSHARED_EXPORT GidMapper
+{
+public:
+ /**
+ * Default constructor. Use \l insert to initialize the gid mapper
+ * incrementally.
+ */
+ GidMapper();
+
+ /**
+ * Constructor that initializes the gid mapper using the given \a tilesets.
+ */
+ GidMapper(const QList<Tileset *> &tilesets);
+
+ /**
+ * Insert the given \a tileset with \a firstGid as its first global ID.
+ */
+ void insert(uint firstGid, Tileset *tileset)
+ { mFirstGidToTileset.insert(firstGid, tileset); }
+
+ /**
+ * Clears the gid mapper, so that it can be reused.
+ */
+ void clear() { mFirstGidToTileset.clear(); }
+
+ /**
+ * Returns true when no tilesets are known to this gid mapper.
+ */
+ bool isEmpty() const { return mFirstGidToTileset.isEmpty(); }
+
+ /**
+ * Returns the cell data matched by the given \a gid. The \a ok parameter
+ * indicates whether an error occurred.
+ */
+ Cell gidToCell(uint gid, bool &ok) const;
+
+ /**
+ * Returns the global tile ID for the given \a cell. Returns 0 when the
+ * cell is empty or when its tileset isn't known.
+ */
+ uint cellToGid(const Cell &cell) const;
+
+ /**
+ * This sets the original tileset width. In case the image size has
+ * changed, the tile indexes will be adjusted automatically when using
+ * gidToCell().
+ */
+ void setTilesetWidth(const Tileset *tileset, int width);
+
+private:
+ QMap<uint, Tileset*> mFirstGidToTileset;
+ QMap<const Tileset*, int> mTilesetColumnCounts;
+};
+
+} // namespace Tiled
+
+#endif // TILED_GIDMAPPER_H
diff --git a/libtiled/isometricrenderer.cpp b/libtiled/isometricrenderer.cpp
new file mode 100644
index 0000000..a7e182b
--- /dev/null
+++ b/libtiled/isometricrenderer.cpp
@@ -0,0 +1,430 @@
+/*
+ * isometricrenderer.cpp
+ * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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 "isometricrenderer.h"
+
+#include "map.h"
+#include "mapobject.h"
+#include "tile.h"
+#include "tilelayer.h"
+#include "tileset.h"
+
+#include <cmath>
+
+using namespace Tiled;
+
+QSize IsometricRenderer::mapSize() const
+{
+ // Map width and height contribute equally in both directions
+ const int side = map()->height() + map()->width();
+ return QSize(side * map()->tileWidth() / 2,
+ side * map()->tileHeight() / 2);
+}
+
+QRect IsometricRenderer::boundingRect(const QRect &rect) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ const int originX = map()->height() * tileWidth / 2;
+ const QPoint pos((rect.x() - (rect.y() + rect.height()))
+ * tileWidth / 2 + originX,
+ (rect.x() + rect.y()) * tileHeight / 2);
+
+ const int side = rect.height() + rect.width();
+ const QSize size(side * tileWidth / 2,
+ side * tileHeight / 2);
+
+ return QRect(pos, size);
+}
+
+QRectF IsometricRenderer::boundingRect(const MapObject *object) const
+{
+ if (object->tile()) {
+ const QPointF bottomCenter = tileToPixelCoords(object->position());
+ const QPixmap &img = object->tile()->image();
+ return QRectF(bottomCenter.x() - img.width() / 2,
+ bottomCenter.y() - img.height(),
+ img.width(),
+ img.height()).adjusted(-1, -1, 1, 1);
+ } else if (!object->polygon().isEmpty()) {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ const QPolygonF screenPolygon = tileToPixelCoords(polygon);
+ return screenPolygon.boundingRect().adjusted(-2, -2, 3, 3);
+ } else {
+ // Take the bounding rect of the projected object, and then add a few
+ // pixels on all sides to correct for the line width.
+ const QRectF base = tileRectToPolygon(object->bounds()).boundingRect();
+ return base.adjusted(-2, -3, 2, 2);
+ }
+}
+
+QPainterPath IsometricRenderer::shape(const MapObject *object) const
+{
+ QPainterPath path;
+ if (object->tile()) {
+ path.addRect(boundingRect(object));
+ } else {
+ switch (object->shape()) {
+ case MapObject::Rectangle:
+ path.addPolygon(tileRectToPolygon(object->bounds()));
+ break;
+ case MapObject::Polygon:
+ case MapObject::Polyline: {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ const QPolygonF screenPolygon = tileToPixelCoords(polygon);
+ if (object->shape() == MapObject::Polygon) {
+ path.addPolygon(screenPolygon);
+ } else {
+ for (int i = 1; i < screenPolygon.size(); ++i) {
+ path.addPolygon(lineToPolygon(screenPolygon[i - 1],
+ screenPolygon[i]));
+ }
+ path.setFillRule(Qt::WindingFill);
+ }
+ break;
+ }
+ }
+ }
+ return path;
+}
+
+void IsometricRenderer::drawGrid(QPainter *painter, const QRectF &rect) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ QRect r = rect.toAlignedRect();
+ r.adjust(-tileWidth / 2, -tileHeight / 2,
+ tileWidth / 2, tileHeight / 2);
+
+ const int startX = qMax(qreal(0), pixelToTileCoords(r.topLeft()).x());
+ const int startY = qMax(qreal(0), pixelToTileCoords(r.topRight()).y());
+ const int endX = qMin(qreal(map()->width()),
+ pixelToTileCoords(r.bottomRight()).x());
+ const int endY = qMin(qreal(map()->height()),
+ pixelToTileCoords(r.bottomLeft()).y());
+
+ QColor gridColor(Qt::black);
+ gridColor.setAlpha(128);
+
+ QPen gridPen(gridColor);
+ gridPen.setDashPattern(QVector<qreal>() << 2 << 2);
+ painter->setPen(gridPen);
+
+ for (int y = startY; y <= endY; ++y) {
+ const QPointF start = tileToPixelCoords(startX, y);
+ const QPointF end = tileToPixelCoords(endX, y);
+ painter->drawLine(start, end);
+ }
+ for (int x = startX; x <= endX; ++x) {
+ const QPointF start = tileToPixelCoords(x, startY);
+ const QPointF end = tileToPixelCoords(x, endY);
+ painter->drawLine(start, end);
+ }
+}
+
+void IsometricRenderer::drawTileLayer(QPainter *painter,
+ const TileLayer *layer,
+ const QRectF &exposed) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ if (tileWidth <= 0 || tileHeight <= 1)
+ return;
+
+ QRect rect = exposed.toAlignedRect();
+ if (rect.isNull())
+ rect = boundingRect(layer->bounds());
+
+ 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());
+ QPoint rowItr = QPoint((int) std::floor(tilePos.x()),
+ (int) std::floor(tilePos.y()));
+ QPointF startPos = tileToPixelCoords(rowItr);
+ startPos.rx() -= tileWidth / 2;
+ startPos.ry() += tileHeight;
+
+ // Compensate for the layer position
+ rowItr -= QPoint(layer->x(), layer->y());
+
+ /* Determine in which half of the tile the top-left corner of the area we
+ * need to draw is. If we're in the upper half, we need to start one row
+ * up due to those tiles being visible as well. How we go up one row
+ * depends on whether we're in the left or right half of the tile.
+ */
+ const bool inUpperHalf = startPos.y() - rect.y() > tileHeight / 2;
+ const bool inLeftHalf = rect.x() - startPos.x() < tileWidth / 2;
+
+ if (inUpperHalf) {
+ if (inLeftHalf) {
+ --rowItr.rx();
+ startPos.rx() -= tileWidth / 2;
+ } else {
+ --rowItr.ry();
+ startPos.rx() += tileWidth / 2;
+ }
+ startPos.ry() -= tileHeight / 2;
+ }
+
+ // Determine whether the current row is shifted half a tile to the right
+ bool shifted = inUpperHalf ^ inLeftHalf;
+
+ QTransform baseTransform = painter->transform();
+
+ for (int y = startPos.y(); y - tileHeight < rect.bottom();
+ y += tileHeight / 2)
+ {
+ QPoint columnItr = rowItr;
+
+ for (int x = startPos.x(); x < rect.right(); x += tileWidth) {
+ if (layer->contains(columnItr)) {
+ 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 = offset.x() + x;
+ qreal dy = offset.y() + y - img.height();
+
+ if (cell.flippedAntiDiagonally) {
+ // Use shearing to swap the X/Y axis
+ m11 = 0;
+ m12 = 1;
+ m21 = 1;
+ m22 = 0;
+
+ // Compensate for the swap of image dimensions
+ dy += img.height() - img.width();
+ }
+ if (cell.flippedHorizontally) {
+ m11 = -m11;
+ m21 = -m21;
+ dx += cell.flippedAntiDiagonally ? img.height()
+ : img.width();
+ }
+ if (cell.flippedVertically) {
+ m12 = -m12;
+ m22 = -m22;
+ dy += cell.flippedAntiDiagonally ? img.width()
+ : img.height();
+ }
+
+ const QTransform transform(m11, m12, m21, m22, dx, dy);
+ painter->setTransform(transform * baseTransform);
+
+ painter->drawPixmap(0, 0, img);
+ }
+ }
+
+ // Advance to the next column
+ ++columnItr.rx();
+ --columnItr.ry();
+ }
+
+ // Advance to the next row
+ if (!shifted) {
+ ++rowItr.rx();
+ startPos.rx() += tileWidth / 2;
+ shifted = true;
+ } else {
+ ++rowItr.ry();
+ startPos.rx() -= tileWidth / 2;
+ shifted = false;
+ }
+ }
+
+ painter->setTransform(baseTransform);
+}
+
+void IsometricRenderer::drawTileSelection(QPainter *painter,
+ const QRegion &region,
+ const QColor &color,
+ const QRectF &exposed) const
+{
+ painter->setBrush(color);
+ painter->setPen(Qt::NoPen);
+ foreach (const QRect &r, region.rects()) {
+ QPolygonF polygon = tileRectToPolygon(r);
+ if (QRectF(polygon.boundingRect()).intersects(exposed))
+ painter->drawConvexPolygon(polygon);
+ }
+}
+
+void IsometricRenderer::drawMapObject(QPainter *painter,
+ const MapObject *object,
+ const QColor &color) const
+{
+ painter->save();
+
+ QPen pen(Qt::black);
+
+ if (object->tile()) {
+ const QPixmap &img = object->tile()->image();
+ QPointF paintOrigin(-img.width() / 2, -img.height());
+ paintOrigin += tileToPixelCoords(object->position()).toPoint();
+ painter->drawPixmap(paintOrigin, img);
+
+ pen.setStyle(Qt::SolidLine);
+ painter->setPen(pen);
+ painter->drawRect(QRectF(paintOrigin, img.size()));
+ pen.setStyle(Qt::DotLine);
+ pen.setColor(color);
+ painter->setPen(pen);
+ painter->drawRect(QRectF(paintOrigin, img.size()));
+ } else {
+ QColor brushColor = color;
+ brushColor.setAlpha(50);
+ QBrush brush(brushColor);
+
+ pen.setJoinStyle(Qt::RoundJoin);
+ pen.setCapStyle(Qt::RoundCap);
+ pen.setWidth(2);
+
+ painter->setPen(pen);
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ // TODO: Draw the object name
+ // TODO: Do something sensible to make null-sized objects usable
+
+ switch (object->shape()) {
+ case MapObject::Rectangle: {
+ QPolygonF polygon = tileRectToPolygon(object->bounds());
+ painter->drawPolygon(polygon);
+
+ pen.setColor(color);
+ painter->setPen(pen);
+ painter->setBrush(brush);
+ polygon.translate(0, -1);
+
+ painter->drawPolygon(polygon);
+ break;
+ }
+ case MapObject::Polygon: {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ QPolygonF screenPolygon = tileToPixelCoords(polygon);
+
+ painter->drawPolygon(screenPolygon);
+
+ pen.setColor(color);
+ painter->setPen(pen);
+ painter->setBrush(brush);
+ screenPolygon.translate(0, -1);
+
+ painter->drawPolygon(screenPolygon);
+ break;
+ }
+ case MapObject::Polyline: {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ QPolygonF screenPolygon = tileToPixelCoords(polygon);
+
+ painter->drawPolyline(screenPolygon);
+
+ pen.setColor(color);
+ painter->setPen(pen);
+ screenPolygon.translate(0, -1);
+
+ painter->drawPolyline(screenPolygon);
+ break;
+ }
+ }
+ }
+
+ painter->restore();
+}
+
+QPointF IsometricRenderer::pixelToTileCoords(qreal x, qreal y) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+ const qreal ratio = (qreal) tileWidth / tileHeight;
+
+ x -= map()->height() * tileWidth / 2;
+ const qreal mx = y + (x / ratio);
+ const qreal my = y - (x / ratio);
+
+ return QPointF(mx / tileHeight,
+ my / tileHeight);
+}
+
+QPointF IsometricRenderer::tileToPixelCoords(qreal x, qreal y) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+ const int originX = map()->height() * tileWidth / 2;
+
+ return QPointF((x - y) * tileWidth / 2 + originX,
+ (x + y) * tileHeight / 2);
+}
+
+QPolygonF IsometricRenderer::tileRectToPolygon(const QRect &rect) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ const QPointF topRight = tileToPixelCoords(rect.topRight());
+ const QPointF bottomRight = tileToPixelCoords(rect.bottomRight());
+ const QPointF bottomLeft = tileToPixelCoords(rect.bottomLeft());
+
+ QPolygonF polygon;
+ polygon << QPointF(tileToPixelCoords(rect.topLeft()));
+ polygon << QPointF(topRight.x() + tileWidth / 2,
+ topRight.y() + tileHeight / 2);
+ polygon << QPointF(bottomRight.x(), bottomRight.y() + tileHeight);
+ polygon << QPointF(bottomLeft.x() - tileWidth / 2,
+ bottomLeft.y() + tileHeight / 2);
+ return polygon;
+}
+
+QPolygonF IsometricRenderer::tileRectToPolygon(const QRectF &rect) const
+{
+ QPolygonF polygon;
+ polygon << QPointF(tileToPixelCoords(rect.topLeft()));
+ polygon << QPointF(tileToPixelCoords(rect.topRight()));
+ polygon << QPointF(tileToPixelCoords(rect.bottomRight()));
+ polygon << QPointF(tileToPixelCoords(rect.bottomLeft()));
+ return polygon;
+}
diff --git a/libtiled/isometricrenderer.h b/libtiled/isometricrenderer.h
new file mode 100644
index 0000000..5cb5d35
--- /dev/null
+++ b/libtiled/isometricrenderer.h
@@ -0,0 +1,82 @@
+/*
+ * isometricrenderer.h
+ * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef ISOMETRICRENDERER_H
+#define ISOMETRICRENDERER_H
+
+#include "maprenderer.h"
+
+namespace Tiled {
+
+/**
+ * An isometric map renderer.
+ *
+ * Isometric maps have diamond shaped tiles. This map renderer renders them in
+ * such a way that the map will also be diamond shaped. The X axis points to
+ * the bottom right while the Y axis points to the bottom left.
+ */
+class TILEDSHARED_EXPORT IsometricRenderer : public MapRenderer
+{
+public:
+ IsometricRenderer(const Map *map) : MapRenderer(map) {}
+
+ QSize mapSize() const;
+
+ QRect boundingRect(const QRect &rect) const;
+
+ QRectF boundingRect(const MapObject *object) const;
+ QPainterPath shape(const MapObject *object) const;
+
+ void drawGrid(QPainter *painter, const QRectF &rect) const;
+
+ void drawTileLayer(QPainter *painter, const TileLayer *layer,
+ const QRectF &exposed = QRectF()) const;
+
+ void drawTileSelection(QPainter *painter,
+ const QRegion &region,
+ const QColor &color,
+ const QRectF &exposed) const;
+
+ void drawMapObject(QPainter *painter,
+ const MapObject *object,
+ const QColor &color) const;
+
+ using MapRenderer::pixelToTileCoords;
+ QPointF pixelToTileCoords(qreal x, qreal y) const;
+
+ using MapRenderer::tileToPixelCoords;
+ QPointF tileToPixelCoords(qreal x, qreal y) const;
+
+private:
+ QPolygonF tileRectToPolygon(const QRect &rect) const;
+ QPolygonF tileRectToPolygon(const QRectF &rect) const;
+};
+
+} // namespace Tiled
+
+#endif // ISOMETRICRENDERER_H
diff --git a/libtiled/layer.cpp b/libtiled/layer.cpp
new file mode 100644
index 0000000..a019086
--- /dev/null
+++ b/libtiled/layer.cpp
@@ -0,0 +1,69 @@
+/*
+ * layer.cpp
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009, Jeff Bland <jeff@teamphobic.com>
+ *
+ * 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 "layer.h"
+
+using namespace Tiled;
+
+Layer::Layer(const QString &name, int x, int y, int width, int height):
+ mName(name),
+ mX(x),
+ mY(y),
+ mWidth(width),
+ mHeight(height),
+ mOpacity(1.0f),
+ mVisible(true),
+ mMap(0)
+{
+}
+
+void Layer::resize(const QSize &size, const QPoint & /* offset */)
+{
+ mWidth = size.width();
+ mHeight = size.height();
+}
+
+/**
+ * A helper function for initializing the members of the given instance to
+ * those of this layer. Used by subclasses when cloning.
+ *
+ * Layer name, position and size are not cloned, since they are assumed to have
+ * already been passed to the constructor. Also, map ownership is not cloned,
+ * since the clone is not added to the map.
+ *
+ * \return the initialized clone (the same instance that was passed in)
+ * \sa clone()
+ */
+Layer *Layer::initializeClone(Layer *clone) const
+{
+ clone->mOpacity = mOpacity;
+ clone->mVisible = mVisible;
+ clone->setProperties(properties());
+ return clone;
+}
diff --git a/libtiled/layer.h b/libtiled/layer.h
new file mode 100644
index 0000000..2aeac27
--- /dev/null
+++ b/libtiled/layer.h
@@ -0,0 +1,215 @@
+/*
+ * layer.h
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009, Jeff Bland <jeff@teamphobic.com>
+ *
+ * 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.
+ */
+
+#ifndef LAYER_H
+#define LAYER_H
+
+#include "object.h"
+
+#include <QPixmap>
+#include <QRect>
+#include <QSet>
+#include <QString>
+#include <QVector>
+
+namespace Tiled {
+
+class Map;
+class ObjectGroup;
+class TileLayer;
+class Tileset;
+
+/**
+ * A map layer.
+ */
+class TILEDSHARED_EXPORT Layer : public Object
+{
+public:
+ /**
+ * Constructor.
+ */
+ Layer(const QString &name, int x, int y, int width, int height);
+
+ /**
+ * Returns the name of this layer.
+ */
+ const QString &name() const { return mName; }
+
+ /**
+ * Sets the name of this layer.
+ */
+ void setName(const QString &name) { mName = name; }
+
+ /**
+ * Returns the opacity of this layer.
+ */
+ float opacity() const { return mOpacity; }
+
+ /**
+ * Sets the opacity of this layer.
+ */
+ void setOpacity(float opacity) { mOpacity = opacity; }
+
+ /**
+ * Returns the visibility of this layer.
+ */
+ bool isVisible() const { return mVisible; }
+
+ /**
+ * Sets the visibility of this layer.
+ */
+ void setVisible(bool visible) { mVisible = visible; }
+
+ /**
+ * Returns the map this layer is part of.
+ */
+ Map *map() const { return mMap; }
+
+ /**
+ * Sets the map this layer is part of. Should only be called from the
+ * Map class.
+ */
+ void setMap(Map *map) { mMap = map; }
+
+ /**
+ * Returns the x position of this layer (in tiles).
+ */
+ int x() const { return mX; }
+
+ /**
+ * Sets the x position of this layer (in tiles).
+ */
+ void setX(int x) { mX = x; }
+
+ /**
+ * Returns the y position of this layer (in tiles).
+ */
+ int y() const { return mY; }
+
+ /**
+ * Sets the y position of this layer (in tiles).
+ */
+ void setY(int y) { mY = y; }
+
+ /**
+ * Returns the position of this layer (in tiles).
+ */
+ QPoint position() const { return QPoint(mX, mY); }
+
+ /**
+ * Sets the position of this layer (in tiles).
+ */
+ void setPosition(QPoint pos) { setPosition(pos.x(), pos.y()); }
+ void setPosition(int x, int y) { mX = x; mY = y; }
+
+ /**
+ * Returns the width of this layer.
+ */
+ int width() const { return mWidth; }
+
+ /**
+ * Returns the height of this layer.
+ */
+ int height() const { return mHeight; }
+
+ /**
+ * Returns the bounds of this layer.
+ */
+ QRect bounds() const { return QRect(mX, mY, mWidth, mHeight); }
+
+ /**
+ * Computes and returns the set of tilesets used by this layer.
+ */
+ virtual QSet<Tileset*> usedTilesets() const = 0;
+
+ /**
+ * Returns whether this layer is referencing the given tileset.
+ */
+ virtual bool referencesTileset(const Tileset *tileset) const = 0;
+
+ /**
+ * Replaces all references to tiles from \a oldTileset with tiles from
+ * \a newTileset.
+ */
+ virtual void replaceReferencesToTileset(Tileset *oldTileset,
+ Tileset *newTileset) = 0;
+
+ /**
+ * Resizes this layer to \a size, while shifting its contents by \a offset.
+ * Note that the position of the layer remains unaffected.
+ */
+ virtual void resize(const QSize &size, const QPoint &offset);
+
+ /**
+ * Offsets the layer by the given amount, and optionally wraps it around.
+ */
+ virtual void offset(const QPoint &offset, const QRect &bounds,
+ bool wrapX, bool wrapY) = 0;
+
+ /**
+ * Returns whether this layer can merge together with the \a other layer.
+ */
+ virtual bool canMergeWith(Layer *other) const = 0;
+
+ /**
+ * Returns a newly allocated layer that is the result of merging this layer
+ * with the \a other layer. Where relevant, the other layer is considered
+ * to be on top of this one.
+ *
+ * Should only be called when canMergeWith returns true.
+ */
+ virtual Layer *mergedWith(Layer *other) const = 0;
+
+ /**
+ * Returns a duplicate of this layer. The caller is responsible for the
+ * ownership of this newly created layer.
+ */
+ virtual Layer *clone() const = 0;
+
+ // These functions allow checking whether this Layer is an instance of the
+ // given subclass without relying on a dynamic_cast.
+ virtual TileLayer *asTileLayer() { return 0; }
+ virtual ObjectGroup *asObjectGroup() { return 0; }
+
+protected:
+ Layer *initializeClone(Layer *clone) const;
+
+ QString mName;
+ int mX;
+ int mY;
+ int mWidth;
+ int mHeight;
+ float mOpacity;
+ bool mVisible;
+ Map *mMap;
+};
+
+} // namespace Tiled
+
+#endif // LAYER_H
diff --git a/libtiled/libtiled.pri b/libtiled/libtiled.pri
new file mode 100644
index 0000000..bdce5f2
--- /dev/null
+++ b/libtiled/libtiled.pri
@@ -0,0 +1,3 @@
+INCLUDEPATH += $$PWD
+DEPENDPATH += $$PWD
+LIBS *= -ltiled
diff --git a/libtiled/libtiled.pro b/libtiled/libtiled.pro
new file mode 100644
index 0000000..84341d4
--- /dev/null
+++ b/libtiled/libtiled.pro
@@ -0,0 +1,58 @@
+include(../platformer.pri)
+
+TEMPLATE = lib
+TARGET = tiled
+target.path = $${LIBDIR}
+INSTALLS += target
+macx {
+ DESTDIR = ../bin/Tiled.app/Contents/Frameworks
+ QMAKE_LFLAGS_SONAME = -Wl,-install_name,@executable_path/../Frameworks/
+} else {
+ DESTDIR = ../lib
+}
+DLLDESTDIR = ..
+
+#win32:INCLUDEPATH += $$(QTDIR)/src/3rdparty/zlib
+win32:INCLUDEPATH += G:/QtSDK/QtSources/4.8.1/src/3rdparty/zlib
+else:LIBS += -lz
+
+DEFINES += QT_NO_CAST_FROM_ASCII \
+ QT_NO_CAST_TO_ASCII
+DEFINES += TILED_LIBRARY
+contains(QT_CONFIG, reduce_exports): CONFIG += hide_symbols
+OBJECTS_DIR = .obj
+SOURCES += compression.cpp \
+ isometricrenderer.cpp \
+ layer.cpp \
+ map.cpp \
+ mapobject.cpp \
+ mapreader.cpp \
+ maprenderer.cpp \
+ mapwriter.cpp \
+ objectgroup.cpp \
+ orthogonalrenderer.cpp \
+ properties.cpp \
+ tilelayer.cpp \
+ tileset.cpp \
+ gidmapper.cpp
+HEADERS += compression.h \
+ isometricrenderer.h \
+ layer.h \
+ map.h \
+ mapobject.h \
+ mapreader.h \
+ maprenderer.h \
+ mapwriter.h \
+ object.h \
+ objectgroup.h \
+ orthogonalrenderer.h \
+ properties.h \
+ tile.h \
+ tiled_global.h \
+ tilelayer.h \
+ tileset.h \
+ gidmapper.h
+macx {
+ contains(QT_CONFIG, ppc):CONFIG += x86 \
+ ppc
+}
diff --git a/libtiled/map.cpp b/libtiled/map.cpp
new file mode 100644
index 0000000..07abd81
--- /dev/null
+++ b/libtiled/map.cpp
@@ -0,0 +1,207 @@
+/*
+ * map.cpp
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ * Copyright 2010, Andrew G. Crowell <overkill9999@gmail.com>
+ *
+ * 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 "map.h"
+
+#include "layer.h"
+#include "tile.h"
+#include "tilelayer.h"
+#include "tileset.h"
+
+using namespace Tiled;
+
+Map::Map(Orientation orientation,
+ int width, int height, int tileWidth, int tileHeight):
+ mOrientation(orientation),
+ mWidth(width),
+ mHeight(height),
+ mTileWidth(tileWidth),
+ mTileHeight(tileHeight)
+{
+}
+
+Map::~Map()
+{
+ qDeleteAll(mLayers);
+}
+
+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 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
+{
+ int count = 0;
+ foreach (Layer *layer, mLayers)
+ if (layer->asTileLayer())
+ count++;
+ return count;
+}
+
+int Map::objectGroupCount() const
+{
+ int count = 0;
+ foreach (Layer *layer, mLayers)
+ if (layer->asObjectGroup())
+ count++;
+ return count;
+}
+
+void Map::addLayer(Layer *layer)
+{
+ adoptLayer(layer);
+ mLayers.append(layer);
+}
+
+int Map::indexOfLayer(const QString &layerName) const
+{
+ for (int index = 0; index < mLayers.size(); index++)
+ if (layerAt(index)->name() == layerName)
+ return index;
+
+ return -1;
+}
+
+void Map::insertLayer(int index, Layer *layer)
+{
+ adoptLayer(layer);
+ mLayers.insert(index, layer);
+}
+
+void Map::adoptLayer(Layer *layer)
+{
+ layer->setMap(this);
+
+ if (TileLayer *tileLayer = dynamic_cast<TileLayer*>(layer))
+ adjustDrawMargins(tileLayer->drawMargins());
+}
+
+Layer *Map::takeLayerAt(int index)
+{
+ Layer *layer = mLayers.takeAt(index);
+ layer->setMap(0);
+ return layer;
+}
+
+void Map::addTileset(Tileset *tileset)
+{
+ mTilesets.append(tileset);
+}
+
+void Map::insertTileset(int index, Tileset *tileset)
+{
+ mTilesets.insert(index, tileset);
+}
+
+int Map::indexOfTileset(Tileset *tileset) const
+{
+ return mTilesets.indexOf(tileset);
+}
+
+void Map::removeTilesetAt(int index)
+{
+ mTilesets.removeAt(index);
+}
+
+void Map::replaceTileset(Tileset *oldTileset, Tileset *newTileset)
+{
+ const int index = mTilesets.indexOf(oldTileset);
+ Q_ASSERT(index != -1);
+
+ foreach (Layer *layer, mLayers)
+ layer->replaceReferencesToTileset(oldTileset, newTileset);
+
+ mTilesets.replace(index, newTileset);
+}
+
+bool Map::isTilesetUsed(Tileset *tileset) const
+{
+ foreach (const Layer *layer, mLayers)
+ if (layer->referencesTileset(tileset))
+ return true;
+
+ return false;
+}
+
+Map *Map::clone() const
+{
+ Map *o = new Map(mOrientation, mWidth, mHeight, mTileWidth, mTileHeight);
+ o->mDrawMargins = mDrawMargins;
+ foreach (const Layer *layer, mLayers)
+ o->addLayer(layer->clone());
+ o->mTilesets = mTilesets;
+ o->setProperties(properties());
+ return o;
+}
+
+
+QString Tiled::orientationToString(Map::Orientation orientation)
+{
+ switch (orientation) {
+ default:
+ case Map::Unknown:
+ return QLatin1String("unknown");
+ break;
+ case Map::Orthogonal:
+ return QLatin1String("orthogonal");
+ break;
+ case Map::Isometric:
+ return QLatin1String("isometric");
+ break;
+ }
+}
+
+Map::Orientation Tiled::orientationFromString(const QString &string)
+{
+ Map::Orientation orientation = Map::Unknown;
+ if (string == QLatin1String("orthogonal")) {
+ orientation = Map::Orthogonal;
+ } else if (string == QLatin1String("isometric")) {
+ orientation = Map::Isometric;
+ }
+ return orientation;
+}
diff --git a/libtiled/map.h b/libtiled/map.h
new file mode 100644
index 0000000..d209816
--- /dev/null
+++ b/libtiled/map.h
@@ -0,0 +1,278 @@
+/*
+ * map.h
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ * Copyright 2010, Andrew G. Crowell <overkill9999@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef MAP_H
+#define MAP_H
+
+#include "object.h"
+
+#include <QList>
+#include <QMargins>
+#include <QSize>
+
+namespace Tiled {
+
+class Layer;
+class Tile;
+class Tileset;
+class ObjectGroup;
+
+/**
+ * A tile map. Consists of a stack of layers, each can be either a TileLayer
+ * or an ObjectGroup.
+ *
+ * It also keeps track of the list of referenced tilesets.
+ */
+class TILEDSHARED_EXPORT Map : public Object
+{
+public:
+ /**
+ * The orientation of the map determines how it should be rendered. An
+ * Orthogonal map is using rectangular tiles that are aligned on a
+ * straight grid. An Isometric map uses diamond shaped tiles that are
+ * aligned on an isometric projected grid. A Hexagonal map uses hexagon
+ * shaped tiles that fit into each other by shifting every other row.
+ *
+ * Only Orthogonal and Isometric maps are supported by this version of
+ * Tiled.
+ */
+ enum Orientation {
+ Unknown,
+ Orthogonal,
+ Isometric
+ };
+
+ /**
+ * Constructor, taking map orientation, size and tile size as parameters.
+ */
+ Map(Orientation orientation,
+ int width, int height,
+ int tileWidth, int tileHeight);
+
+ /**
+ * Destructor.
+ */
+ ~Map();
+
+ /**
+ * Returns the orientation of the map.
+ */
+ Orientation orientation() const { return mOrientation; }
+
+ /**
+ * Sets the orientation of the map.
+ */
+ void setOrientation(Orientation orientation)
+ { mOrientation = orientation; }
+
+ /**
+ * Returns the width of this map.
+ */
+ int width() const { return mWidth; }
+
+ /**
+ * Sets the width of this map.
+ */
+ void setWidth(int width) { mWidth = width; }
+
+ /**
+ * Returns the height of this map.
+ */
+ int height() const { return mHeight; }
+
+ /**
+ * Sets the height of this map.
+ */
+ void setHeight(int height) { mHeight = height; }
+
+ /**
+ * Returns the size of this map. Provided for convenience.
+ */
+ QSize size() const { return QSize(mWidth, mHeight); }
+
+ /**
+ * Returns the tile width of this map.
+ */
+ int tileWidth() const { return mTileWidth; }
+
+ /**
+ * Returns the tile height used by this map.
+ */
+ int tileHeight() const { return mTileHeight; }
+
+ /**
+ * Adjusts the draw margins to be at least as big as the given margins.
+ * Called from tile layers when their tiles change.
+ */
+ void adjustDrawMargins(const QMargins &margins);
+
+ /**
+ * 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 TileLayer::drawMargins
+ */
+ QMargins drawMargins() const { return mDrawMargins; }
+
+ /**
+ * Returns the number of layers of this map.
+ */
+ int layerCount() const
+ { return mLayers.size(); }
+
+ /**
+ * Convenience function that returns the number of layers of this map that
+ * are tile layers.
+ */
+ int tileLayerCount() const;
+
+ /**
+ * Convenience function that returns the number of layers of this map that
+ * are object groups.
+ */
+ int objectGroupCount() const;
+
+ /**
+ * Returns the layer at the specified index.
+ */
+ Layer *layerAt(int index) const
+ { return mLayers.at(index); }
+
+ /**
+ * Returns the list of layers of this map. This is useful when you want to
+ * use foreach.
+ */
+ const QList<Layer*> &layers() const { return mLayers; }
+
+ /**
+ * Adds a layer to this map.
+ */
+ void addLayer(Layer *layer);
+
+ /**
+ * Returns the index of the layer given by \a layerName, or -1 if no
+ * layer with that name is found.
+ */
+ int indexOfLayer(const QString &layerName) const;
+
+ /**
+ * Adds a layer to this map, inserting it at the given index.
+ */
+ void insertLayer(int index, Layer *layer);
+
+ /**
+ * Removes the layer at the given index from this map and returns it.
+ * The caller becomes responsible for the lifetime of this layer.
+ */
+ Layer *takeLayerAt(int index);
+
+ /**
+ * Adds a tileset to this map. The map does not take ownership over its
+ * tilesets, this is merely for keeping track of which tilesets are used by
+ * the map, and their saving order.
+ *
+ * @param tileset the tileset to add
+ */
+ void addTileset(Tileset *tileset);
+
+ /**
+ * Inserts \a tileset at \a index in the list of tilesets used by this map.
+ */
+ void insertTileset(int index, Tileset *tileset);
+
+ /**
+ * Returns the index of the given \a tileset, or -1 if it is not used in
+ * this map.
+ */
+ int indexOfTileset(Tileset *tileset) const;
+
+ /**
+ * Removes the tileset at \a index from this map.
+ *
+ * \warning Does not make sure that this map no longer refers to tiles from
+ * the removed tileset!
+ *
+ * \sa addTileset
+ */
+ void removeTilesetAt(int index);
+
+ /**
+ * Replaces all tiles from \a oldTileset with tiles from \a newTileset.
+ * Also replaces the old tileset with the new tileset in the list of
+ * tilesets.
+ */
+ void replaceTileset(Tileset *oldTileset, Tileset *newTileset);
+
+ /**
+ * Returns the tilesets that the tiles on this map are using.
+ */
+ const QList<Tileset*> &tilesets() const { return mTilesets; }
+
+ /**
+ * Returns whether the given \a tileset is used by any tile layer of this
+ * map.
+ */
+ bool isTilesetUsed(Tileset *tileset) const;
+
+ Map *clone() const;
+
+private:
+ void adoptLayer(Layer *layer);
+
+ Orientation mOrientation;
+ int mWidth;
+ int mHeight;
+ int mTileWidth;
+ int mTileHeight;
+ QMargins mDrawMargins;
+ QList<Layer*> mLayers;
+ QList<Tileset*> mTilesets;
+};
+
+/**
+ * Helper function that converts the map orientation to a string value. Useful
+ * for map writers.
+ *
+ * @return The map orientation as a lowercase string.
+ */
+TILEDSHARED_EXPORT QString orientationToString(Map::Orientation);
+
+/**
+ * Helper function that converts a string to a map orientation enumerator.
+ * Useful for map readers.
+ *
+ * @return The map orientation matching the given string, or Map::Unknown if
+ * the string is unrecognized.
+ */
+TILEDSHARED_EXPORT Map::Orientation orientationFromString(const QString &);
+
+} // namespace Tiled
+
+#endif // MAP_H
diff --git a/libtiled/mapobject.cpp b/libtiled/mapobject.cpp
new file mode 100644
index 0000000..3a05073
--- /dev/null
+++ b/libtiled/mapobject.cpp
@@ -0,0 +1,63 @@
+/*
+ * mapobject.cpp
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ *
+ * 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 "mapobject.h"
+
+using namespace Tiled;
+
+MapObject::MapObject():
+ mSize(0, 0),
+ mShape(Rectangle),
+ mTile(0),
+ mObjectGroup(0)
+{
+}
+
+MapObject::MapObject(const QString &name, const QString &type,
+ const QPointF &pos,
+ const QSizeF &size):
+ mName(name),
+ mType(type),
+ mPos(pos),
+ mSize(size),
+ mShape(Rectangle),
+ mTile(0),
+ mObjectGroup(0)
+{
+}
+
+MapObject *MapObject::clone() const
+{
+ MapObject *o = new MapObject(mName, mType, mPos, mSize);
+ o->setProperties(properties());
+ o->setPolygon(mPolygon);
+ o->setShape(mShape);
+ o->setTile(mTile);
+ return o;
+}
diff --git a/libtiled/mapobject.h b/libtiled/mapobject.h
new file mode 100644
index 0000000..aa14190
--- /dev/null
+++ b/libtiled/mapobject.h
@@ -0,0 +1,244 @@
+/*
+ * mapobject.h
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ * Copyright 2009, Jeff Bland <jeff@teamphobic.com>
+ *
+ * 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.
+ */
+
+#ifndef MAPOBJECT_H
+#define MAPOBJECT_H
+
+#include "object.h"
+
+#include <QPolygonF>
+#include <QSizeF>
+#include <QString>
+#include <QRectF>
+
+namespace Tiled {
+
+class ObjectGroup;
+class Tile;
+
+/**
+ * An object on a map. Objects are positioned and scaled using floating point
+ * values, ensuring they are not limited to the tile grid. They are suitable
+ * for adding any kind of annotation to your maps, as well as free placement of
+ * images.
+ *
+ * Common usages of objects include defining portals, monsters spawn areas,
+ * ambient effects, scripted areas, etc.
+ */
+class TILEDSHARED_EXPORT MapObject : public Object
+{
+public:
+ /**
+ * Enumerates the different object shapes. Rectangle is the default shape.
+ * When a polygon is set, the shape determines whether it should be
+ * interpreted as a filled polygon or a line.
+ */
+ enum Shape {
+ Rectangle,
+ Polygon,
+ Polyline
+ };
+
+ /**
+ * Default constructor.
+ */
+ MapObject();
+
+ /**
+ * Constructor.
+ */
+ MapObject(const QString &name, const QString &type,
+ const QPointF &pos,
+ const QSizeF &size);
+
+ /**
+ * Destructor.
+ */
+ ~MapObject() {}
+
+ /**
+ * Returns the name of this object. The name is usually just used for
+ * identification of the object in the editor.
+ */
+ const QString &name() const { return mName; }
+
+ /**
+ * Sets the name of this object.
+ */
+ void setName(const QString &name) { mName = name; }
+
+ /**
+ * Returns the type of this object. The type usually says something about
+ * how the object is meant to be interpreted by the engine.
+ */
+ const QString &type() const { return mType; }
+
+ /**
+ * Sets the type of this object.
+ */
+ void setType(const QString &type) { mType = type; }
+
+ /**
+ * Returns the position of this object.
+ */
+ const QPointF &position() const { return mPos; }
+
+ /**
+ * Sets the position of this object.
+ */
+ void setPosition(const QPointF &pos) { mPos = pos; }
+
+ /**
+ * Returns the x position of this object.
+ */
+ qreal x() const { return mPos.x(); }
+
+ /**
+ * Sets the x position of this object.
+ */
+ void setX(qreal x) { mPos.setX(x); }
+
+ /**
+ * Returns the y position of this object.
+ */
+ qreal y() const { return mPos.y(); }
+
+ /**
+ * Sets the x position of this object.
+ */
+ void setY(qreal y) { mPos.setY(y); }
+
+ /**
+ * Returns the size of this object.
+ */
+ const QSizeF &size() const { return mSize; }
+
+ /**
+ * Sets the size of this object.
+ */
+ void setSize(const QSizeF &size) { mSize = size; }
+
+ void setSize(qreal width, qreal height)
+ { setSize(QSizeF(width, height)); }
+
+ /**
+ * Returns the width of this object.
+ */
+ qreal width() const { return mSize.width(); }
+
+ /**
+ * Sets the width of this object.
+ */
+ void setWidth(qreal width) { mSize.setWidth(width); }
+
+ /**
+ * Returns the height of this object.
+ */
+ qreal height() const { return mSize.height(); }
+
+ /**
+ * Sets the height of this object.
+ */
+ void setHeight(qreal height) { mSize.setHeight(height); }
+
+ /**
+ * Sets the polygon associated with this object. The polygon is only used
+ * when the object shape is set to either Polygon or Polyline.
+ *
+ * \sa setShape()
+ */
+ void setPolygon(const QPolygonF &polygon) { mPolygon = polygon; }
+
+ /**
+ * Returns the polygon associated with this object. Returns an empty
+ * polygon when no polygon is associated with this object.
+ */
+ const QPolygonF &polygon() const { return mPolygon; }
+
+ /**
+ * Sets the shape of the object.
+ */
+ void setShape(Shape shape) { mShape = shape; }
+
+ /**
+ * Returns the shape of the object.
+ */
+ Shape shape() const { return mShape; }
+
+ /**
+ * Shortcut to getting a QRectF from position() and size().
+ */
+ QRectF bounds() const { return QRectF(mPos, mSize); }
+
+ /**
+ * Sets the tile that is associated with this object. The object will
+ * display as the tile image.
+ *
+ * \warning The object shape is ignored for tile objects!
+ */
+ void setTile(Tile *tile) { mTile = tile; }
+
+ /**
+ * Returns the tile associated with this object.
+ */
+ Tile *tile() const { return mTile; }
+
+ /**
+ * Returns the object group this object belongs to.
+ */
+ ObjectGroup *objectGroup() const { return mObjectGroup; }
+
+ /**
+ * Sets the object group this object belongs to. Should only be called
+ * from the ObjectGroup class.
+ */
+ void setObjectGroup(ObjectGroup *objectGroup)
+ { mObjectGroup = objectGroup; }
+
+ /**
+ * Returns a duplicate of this object. The caller is responsible for the
+ * ownership of this newly created object.
+ */
+ MapObject *clone() const;
+
+private:
+ QString mName;
+ QString mType;
+ QPointF mPos;
+ QSizeF mSize;
+ QPolygonF mPolygon;
+ Shape mShape;
+ Tile *mTile;
+ ObjectGroup *mObjectGroup;
+};
+
+} // namespace Tiled
+
+#endif // MAPOBJECT_H
diff --git a/libtiled/mapreader.cpp b/libtiled/mapreader.cpp
new file mode 100644
index 0000000..ca8e19c
--- /dev/null
+++ b/libtiled/mapreader.cpp
@@ -0,0 +1,772 @@
+/*
+ * mapreader.cpp
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2010, Jeff Bland <jksb@member.fsf.org>
+ * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com>
+ *
+ * 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 "mapreader.h"
+
+#include "compression.h"
+#include "gidmapper.h"
+#include "objectgroup.h"
+#include "map.h"
+#include "mapobject.h"
+#include "tile.h"
+#include "tilelayer.h"
+#include "tileset.h"
+
+#include <QCoreApplication>
+#include <QDebug>
+#include <QDir>
+#include <QFileInfo>
+#include <QXmlStreamReader>
+
+using namespace Tiled;
+using namespace Tiled::Internal;
+
+namespace Tiled {
+namespace Internal {
+
+class MapReaderPrivate
+{
+ Q_DECLARE_TR_FUNCTIONS(MapReader)
+
+public:
+ MapReaderPrivate(MapReader *mapReader):
+ p(mapReader),
+ mMap(0),
+ mReadingExternalTileset(false)
+ {}
+
+ Map *readMap(QIODevice *device, const QString &path);
+ Tileset *readTileset(QIODevice *device, const QString &path);
+
+ bool openFile(QFile *file);
+
+ QString errorString() const;
+
+private:
+ void readUnknownElement();
+
+ Map *readMap();
+
+ Tileset *readTileset();
+ void readTilesetTile(Tileset *tileset);
+ void readTilesetImage(Tileset *tileset);
+
+ TileLayer *readLayer();
+ void readLayerData(TileLayer *tileLayer);
+ void decodeBinaryLayerData(TileLayer *tileLayer,
+ const QStringRef &text,
+ const QStringRef &compression);
+ void decodeCSVLayerData(TileLayer *tileLayer, const QString &text);
+
+ /**
+ * Returns the cell for the given global tile ID. Errors are raised with
+ * the QXmlStreamReader.
+ *
+ * @param gid the global tile ID
+ * @return the cell data associated with the given global tile ID, or an
+ * empty cell if not found
+ */
+ Cell cellForGid(uint gid);
+
+ ObjectGroup *readObjectGroup();
+ MapObject *readObject();
+ QPolygonF readPolygon();
+
+ Properties readProperties();
+ void readProperty(Properties *properties);
+
+ MapReader *p;
+
+ QString mError;
+ QString mPath;
+ Map *mMap;
+ GidMapper mGidMapper;
+ bool mReadingExternalTileset;
+
+ QXmlStreamReader xml;
+};
+
+} // namespace Internal
+} // namespace Tiled
+
+Map *MapReaderPrivate::readMap(QIODevice *device, const QString &path)
+{
+ mError.clear();
+ mPath = path;
+ Map *map = 0;
+
+ xml.setDevice(device);
+
+ if (xml.readNextStartElement() && xml.name() == "map") {
+ map = readMap();
+ } else {
+ xml.raiseError(tr("Not a map file."));
+ }
+
+ mGidMapper.clear();
+ return map;
+}
+
+Tileset *MapReaderPrivate::readTileset(QIODevice *device, const QString &path)
+{
+ mError.clear();
+ mPath = path;
+ Tileset *tileset = 0;
+ mReadingExternalTileset = true;
+
+ xml.setDevice(device);
+
+ if (xml.readNextStartElement() && xml.name() == "tileset")
+ tileset = readTileset();
+ else
+ xml.raiseError(tr("Not a tileset file."));
+
+ mReadingExternalTileset = false;
+ return tileset;
+}
+
+QString MapReaderPrivate::errorString() const
+{
+ if (!mError.isEmpty()) {
+ return mError;
+ } else {
+ return tr("%3\n\nLine %1, column %2")
+ .arg(xml.lineNumber())
+ .arg(xml.columnNumber())
+ .arg(xml.errorString());
+ }
+}
+
+bool MapReaderPrivate::openFile(QFile *file)
+{
+ if (!file->exists()) {
+ mError = tr("File not found: %1").arg(file->fileName());
+ return false;
+ } else if (!file->open(QFile::ReadOnly | QFile::Text)) {
+ mError = tr("Unable to read file: %1").arg(file->fileName());
+ return false;
+ }
+
+ return true;
+}
+
+void MapReaderPrivate::readUnknownElement()
+{
+ qDebug() << "Unknown element (fixme):" << xml.name();
+ xml.skipCurrentElement();
+}
+
+Map *MapReaderPrivate::readMap()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "map");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const int mapWidth =
+ atts.value(QLatin1String("width")).toString().toInt();
+ const int mapHeight =
+ atts.value(QLatin1String("height")).toString().toInt();
+ const int tileWidth =
+ atts.value(QLatin1String("tilewidth")).toString().toInt();
+ const int tileHeight =
+ atts.value(QLatin1String("tileheight")).toString().toInt();
+
+ const QString orientationString =
+ atts.value(QLatin1String("orientation")).toString();
+ const Map::Orientation orientation =
+ orientationFromString(orientationString);
+
+ if (orientation == Map::Unknown) {
+ xml.raiseError(tr("Unsupported map orientation: \"%1\"")
+ .arg(orientationString));
+ }
+
+ mMap = new Map(orientation, mapWidth, mapHeight, tileWidth, tileHeight);
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "properties")
+ mMap->mergeProperties(readProperties());
+ else if (xml.name() == "tileset")
+ mMap->addTileset(readTileset());
+ else if (xml.name() == "layer")
+ mMap->addLayer(readLayer());
+ else if (xml.name() == "objectgroup")
+ mMap->addLayer(readObjectGroup());
+ else
+ readUnknownElement();
+ }
+
+ // Clean up in case of error
+ if (xml.hasError()) {
+ // The tilesets are not owned by the map
+ qDeleteAll(mMap->tilesets());
+
+ delete mMap;
+ mMap = 0;
+ }
+
+ return mMap;
+}
+
+Tileset *MapReaderPrivate::readTileset()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "tileset");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const QString source = atts.value(QLatin1String("source")).toString();
+ const uint firstGid =
+ atts.value(QLatin1String("firstgid")).toString().toUInt();
+
+ Tileset *tileset = 0;
+
+ if (source.isEmpty()) { // Not an external tileset
+ const QString name =
+ atts.value(QLatin1String("name")).toString();
+ const int tileWidth =
+ atts.value(QLatin1String("tilewidth")).toString().toInt();
+ const int tileHeight =
+ atts.value(QLatin1String("tileheight")).toString().toInt();
+ const int tileSpacing =
+ atts.value(QLatin1String("spacing")).toString().toInt();
+ const int margin =
+ atts.value(QLatin1String("margin")).toString().toInt();
+
+ if (tileWidth <= 0 || tileHeight <= 0
+ || (firstGid == 0 && !mReadingExternalTileset)) {
+ xml.raiseError(tr("Invalid tileset parameters for tileset"
+ " '%1'").arg(name));
+ } else {
+ tileset = new Tileset(name, tileWidth, tileHeight,
+ tileSpacing, margin);
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "tile") {
+ readTilesetTile(tileset);
+ } 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") {
+ readTilesetImage(tileset);
+ } else {
+ readUnknownElement();
+ }
+ }
+ }
+ } else { // External tileset
+ const QString absoluteSource = p->resolveReference(source, mPath);
+ QString error;
+ tileset = p->readExternalTileset(absoluteSource, &error);
+
+ if (!tileset) {
+ xml.raiseError(tr("Error while loading tileset '%1': %2")
+ .arg(absoluteSource, error));
+ }
+
+ xml.skipCurrentElement();
+ }
+
+ if (tileset && !mReadingExternalTileset)
+ mGidMapper.insert(firstGid, tileset);
+
+ return tileset;
+}
+
+void MapReaderPrivate::readTilesetTile(Tileset *tileset)
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "tile");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const int id = atts.value(QLatin1String("id")).toString().toInt();
+
+ if (id < 0 || id >= tileset->tileCount()) {
+ xml.raiseError(tr("Invalid tile ID: %1").arg(id));
+ return;
+ }
+
+ // TODO: Add support for individual tiles (then it needs to be added here)
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "properties") {
+ Tile *tile = tileset->tileAt(id);
+ tile->mergeProperties(readProperties());
+ } else {
+ readUnknownElement();
+ }
+ }
+}
+
+void MapReaderPrivate::readTilesetImage(Tileset *tileset)
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "image");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ QString source = atts.value(QLatin1String("source")).toString();
+ QString trans = atts.value(QLatin1String("trans")).toString();
+
+ if (!trans.isEmpty()) {
+ if (!trans.startsWith(QLatin1Char('#')))
+ trans.prepend(QLatin1Char('#'));
+ tileset->setTransparentColor(QColor(trans));
+ }
+
+ source = p->resolveReference(source, mPath);
+
+ // Set the width that the tileset had when the map was saved
+ const int width = atts.value(QLatin1String("width")).toString().toInt();
+ mGidMapper.setTilesetWidth(tileset, width);
+
+ const QImage tilesetImage = p->readExternalImage(source);
+ if (!tileset->loadFromImage(tilesetImage, source))
+ xml.raiseError(tr("Error loading tileset image:\n'%1'").arg(source));
+
+ xml.skipCurrentElement();
+}
+
+static void readLayerAttributes(Layer *layer,
+ const QXmlStreamAttributes &atts)
+{
+ const QStringRef opacityRef = atts.value(QLatin1String("opacity"));
+ const QStringRef visibleRef = atts.value(QLatin1String("visible"));
+
+ bool ok;
+ const float opacity = opacityRef.toString().toFloat(&ok);
+ if (ok)
+ layer->setOpacity(opacity);
+
+ const int visible = visibleRef.toString().toInt(&ok);
+ if (ok)
+ layer->setVisible(visible);
+}
+
+TileLayer *MapReaderPrivate::readLayer()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "layer");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const QString name = atts.value(QLatin1String("name")).toString();
+ 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();
+ const int height = atts.value(QLatin1String("height")).toString().toInt();
+
+ TileLayer *tileLayer = new TileLayer(name, x, y, width, height);
+ readLayerAttributes(tileLayer, atts);
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "properties")
+ tileLayer->mergeProperties(readProperties());
+ else if (xml.name() == "data")
+ readLayerData(tileLayer);
+ else
+ readUnknownElement();
+ }
+
+ return tileLayer;
+}
+
+void MapReaderPrivate::readLayerData(TileLayer *tileLayer)
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "data");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ QStringRef encoding = atts.value(QLatin1String("encoding"));
+ QStringRef compression = atts.value(QLatin1String("compression"));
+
+ int x = 0;
+ int y = 0;
+
+ while (xml.readNext() != QXmlStreamReader::Invalid) {
+ if (xml.isEndElement())
+ break;
+ else if (xml.isStartElement()) {
+ if (xml.name() == QLatin1String("tile")) {
+ if (y >= tileLayer->height()) {
+ xml.raiseError(tr("Too many <tile> elements"));
+ continue;
+ }
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ uint gid = atts.value(QLatin1String("gid")).toString().toUInt();
+ tileLayer->setCell(x, y, cellForGid(gid));
+
+ x++;
+ if (x >= tileLayer->width()) {
+ x = 0;
+ y++;
+ }
+
+ xml.skipCurrentElement();
+ } else {
+ readUnknownElement();
+ }
+ } else if (xml.isCharacters() && !xml.isWhitespace()) {
+ if (encoding == QLatin1String("base64")) {
+ decodeBinaryLayerData(tileLayer,
+ xml.text(),
+ compression);
+ } else if (encoding == QLatin1String("csv")) {
+ decodeCSVLayerData(tileLayer, xml.text().toString());
+ } else {
+ xml.raiseError(tr("Unknown encoding: %1")
+ .arg(encoding.toString()));
+ continue;
+ }
+ }
+ }
+}
+
+void MapReaderPrivate::decodeBinaryLayerData(TileLayer *tileLayer,
+ const QStringRef &text,
+ const QStringRef &compression)
+{
+#if QT_VERSION < 0x040800
+ const QString textData = QString::fromRawData(text.unicode(), text.size());
+ const QByteArray latin1Text = textData.toLatin1();
+#else
+ const QByteArray latin1Text = text.toLatin1();
+#endif
+ QByteArray tileData = QByteArray::fromBase64(latin1Text);
+ const int size = (tileLayer->width() * tileLayer->height()) * 4;
+
+ if (compression == QLatin1String("zlib")
+ || compression == QLatin1String("gzip")) {
+ tileData = decompress(tileData, size);
+ } else if (!compression.isEmpty()) {
+ xml.raiseError(tr("Compression method '%1' not supported")
+ .arg(compression.toString()));
+ return;
+ }
+
+ if (size != tileData.length()) {
+ xml.raiseError(tr("Corrupt layer data for layer '%1'")
+ .arg(tileLayer->name()));
+ return;
+ }
+
+ const unsigned char *data =
+ reinterpret_cast<const unsigned char*>(tileData.constData());
+ int x = 0;
+ int y = 0;
+
+ for (int i = 0; i < size - 3; i += 4) {
+ const uint gid = data[i] |
+ data[i + 1] << 8 |
+ data[i + 2] << 16 |
+ data[i + 3] << 24;
+
+ tileLayer->setCell(x, y, cellForGid(gid));
+
+ x++;
+ if (x == tileLayer->width()) {
+ x = 0;
+ y++;
+ }
+ }
+}
+
+void MapReaderPrivate::decodeCSVLayerData(TileLayer *tileLayer, const QString &text)
+{
+ QString trimText = text.trimmed();
+ QStringList tiles = trimText.split(QLatin1Char(','));
+
+ if (tiles.length() != tileLayer->width() * tileLayer->height()) {
+ xml.raiseError(tr("Corrupt layer data for layer '%1'")
+ .arg(tileLayer->name()));
+ return;
+ }
+
+ for (int y = 0; y < tileLayer->height(); y++) {
+ for (int x = 0; x < tileLayer->width(); x++) {
+ bool 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;
+ }
+ tileLayer->setCell(x, y, cellForGid(gid));
+ }
+ }
+}
+
+Cell MapReaderPrivate::cellForGid(uint gid)
+{
+ bool ok;
+ const Cell result = mGidMapper.gidToCell(gid, ok);
+
+ if (!ok) {
+ if (mGidMapper.isEmpty())
+ xml.raiseError(tr("Tile used but no tilesets specified"));
+ else
+ xml.raiseError(tr("Invalid tile: %1").arg(gid));
+ }
+
+ return result;
+}
+
+ObjectGroup *MapReaderPrivate::readObjectGroup()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "objectgroup");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const QString name = atts.value(QLatin1String("name")).toString();
+ 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();
+ const int height = atts.value(QLatin1String("height")).toString().toInt();
+
+ ObjectGroup *objectGroup = new ObjectGroup(name, x, y, width, height);
+ readLayerAttributes(objectGroup, atts);
+
+ const QString color = atts.value(QLatin1String("color")).toString();
+ if (!color.isEmpty())
+ objectGroup->setColor(color);
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "object")
+ objectGroup->addObject(readObject());
+ else if (xml.name() == "properties")
+ objectGroup->mergeProperties(readProperties());
+ else
+ readUnknownElement();
+ }
+
+ return objectGroup;
+}
+
+static QPointF pixelToTileCoordinates(Map *map, int x, int y)
+{
+ const int tileHeight = map->tileHeight();
+ const int tileWidth = map->tileWidth();
+
+ if (map->orientation() == Map::Isometric) {
+ // Isometric needs special handling, since the pixel values are based
+ // solely on the tile height.
+ return QPointF((qreal) x / tileHeight,
+ (qreal) y / tileHeight);
+ } else {
+ return QPointF((qreal) x / tileWidth,
+ (qreal) y / tileHeight);
+ }
+}
+
+MapObject *MapReaderPrivate::readObject()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "object");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const QString name = atts.value(QLatin1String("name")).toString();
+ 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();
+ const int height = atts.value(QLatin1String("height")).toString().toInt();
+ const QString type = atts.value(QLatin1String("type")).toString();
+
+ const QPointF pos = pixelToTileCoordinates(mMap, x, y);
+ const QPointF size = pixelToTileCoordinates(mMap, width, height);
+
+ MapObject *object = new MapObject(name, type, pos, QSizeF(size.x(),
+ size.y()));
+
+ if (gid) {
+ const Cell cell = cellForGid(gid);
+ object->setTile(cell.tile);
+ }
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "properties") {
+ object->mergeProperties(readProperties());
+ } else if (xml.name() == "polygon") {
+ object->setPolygon(readPolygon());
+ object->setShape(MapObject::Polygon);
+ } else if (xml.name() == "polyline") {
+ object->setPolygon(readPolygon());
+ object->setShape(MapObject::Polyline);
+ } else {
+ readUnknownElement();
+ }
+ }
+
+ return object;
+}
+
+QPolygonF MapReaderPrivate::readPolygon()
+{
+ Q_ASSERT(xml.isStartElement() && (xml.name() == "polygon" ||
+ xml.name() == "polyline"));
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ const QString points = atts.value(QLatin1String("points")).toString();
+ const QStringList pointsList = points.split(QLatin1Char(' '),
+ QString::SkipEmptyParts);
+
+ QPolygonF polygon;
+ bool ok = true;
+
+ foreach (const QString &point, pointsList) {
+ const int commaPos = point.indexOf(QLatin1Char(','));
+ if (commaPos == -1) {
+ ok = false;
+ break;
+ }
+
+ const int x = point.left(commaPos).toInt(&ok);
+ if (!ok)
+ break;
+ const int y = point.mid(commaPos + 1).toInt(&ok);
+ if (!ok)
+ break;
+
+ polygon.append(pixelToTileCoordinates(mMap, x, y));
+ }
+
+ if (!ok)
+ xml.raiseError(tr("Invalid points data for polygon"));
+
+ xml.skipCurrentElement();
+ return polygon;
+}
+
+Properties MapReaderPrivate::readProperties()
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "properties");
+
+ Properties properties;
+
+ while (xml.readNextStartElement()) {
+ if (xml.name() == "property")
+ readProperty(&properties);
+ else
+ readUnknownElement();
+ }
+
+ return properties;
+}
+
+void MapReaderPrivate::readProperty(Properties *properties)
+{
+ Q_ASSERT(xml.isStartElement() && xml.name() == "property");
+
+ const QXmlStreamAttributes atts = xml.attributes();
+ QString propertyName = atts.value(QLatin1String("name")).toString();
+ QString propertyValue = atts.value(QLatin1String("value")).toString();
+
+ while (xml.readNext() != QXmlStreamReader::Invalid) {
+ if (xml.isEndElement()) {
+ break;
+ } else if (xml.isCharacters() && !xml.isWhitespace()) {
+ if (propertyValue.isEmpty())
+ propertyValue = xml.text().toString();
+ } else if (xml.isStartElement()) {
+ readUnknownElement();
+ }
+ }
+
+ properties->insert(propertyName, propertyValue);
+}
+
+
+MapReader::MapReader()
+ : d(new MapReaderPrivate(this))
+{
+}
+
+MapReader::~MapReader()
+{
+ delete d;
+}
+
+Map *MapReader::readMap(QIODevice *device, const QString &path)
+{
+ return d->readMap(device, path);
+}
+
+Map *MapReader::readMap(const QString &fileName)
+{
+ QFile file(fileName);
+ if (!d->openFile(&file))
+ return 0;
+
+ return readMap(&file, QFileInfo(fileName).absolutePath());
+}
+
+Tileset *MapReader::readTileset(QIODevice *device, const QString &path)
+{
+ return d->readTileset(device, path);
+}
+
+Tileset *MapReader::readTileset(const QString &fileName)
+{
+ QFile file(fileName);
+ if (!d->openFile(&file))
+ return 0;
+
+ Tileset *tileset = readTileset(&file, QFileInfo(fileName).absolutePath());
+ if (tileset)
+ tileset->setFileName(fileName);
+
+ return tileset;
+}
+
+QString MapReader::errorString() const
+{
+ return d->errorString();
+}
+
+QString MapReader::resolveReference(const QString &reference,
+ const QString &mapPath)
+{
+ if (QDir::isRelativePath(reference))
+ return mapPath + QLatin1Char('/') + reference;
+ else
+ return reference;
+}
+
+QImage MapReader::readExternalImage(const QString &source)
+{
+ return QImage(source);
+}
+
+Tileset *MapReader::readExternalTileset(const QString &source,
+ QString *error)
+{
+ MapReader reader;
+ Tileset *tileset = reader.readTileset(source);
+ if (!tileset)
+ *error = reader.errorString();
+ return tileset;
+}
diff --git a/libtiled/mapreader.h b/libtiled/mapreader.h
new file mode 100644
index 0000000..39db882
--- /dev/null
+++ b/libtiled/mapreader.h
@@ -0,0 +1,129 @@
+/*
+ * mapreader.h
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef MAPREADER_H
+#define MAPREADER_H
+
+#include "tiled_global.h"
+
+#include <QImage>
+
+class QFile;
+
+namespace Tiled {
+
+class Map;
+class Tileset;
+
+namespace Internal {
+class MapReaderPrivate;
+}
+
+/**
+ * A fast QXmlStreamReader based reader for the TMX and TSX formats.
+ *
+ * Can be subclassed when special handling of external images and tilesets is
+ * needed.
+ */
+class TILEDSHARED_EXPORT MapReader
+{
+public:
+ MapReader();
+ ~MapReader();
+
+ /**
+ * Reads a TMX map from the given \a device. Optionally a \a path can
+ * be given, which will be used to resolve relative references to external
+ * images and tilesets.
+ *
+ * Returns 0 and sets errorString() when reading failed.
+ *
+ * The caller takes ownership over the newly created map.
+ */
+ Map *readMap(QIODevice *device, const QString &path = QString());
+
+ /**
+ * Reads a TMX map from the given \a fileName.
+ * \overload
+ */
+ Map *readMap(const QString &fileName);
+
+ /**
+ * Reads a TSX tileset from the given \a device. Optionally a \a path can
+ * be given, which will be used to resolve relative references to external
+ * images.
+ *
+ * Returns 0 and sets errorString() when reading failed.
+ *
+ * The caller takes ownership over the newly created tileset.
+ */
+ Tileset *readTileset(QIODevice *device, const QString &path = QString());
+
+ /**
+ * Reads a TSX tileset from the given \a fileName.
+ * \overload
+ */
+ Tileset *readTileset(const QString &fileName);
+
+ /**
+ * Returns the error message for the last occurred error.
+ */
+ QString errorString() const;
+
+protected:
+ /**
+ * Called for each \a reference to an external file. Should return the path
+ * to be used when loading this file. \a mapPath contains the path to the
+ * map or tileset that is currently being loaded.
+ */
+ virtual QString resolveReference(const QString &reference,
+ const QString &mapPath);
+
+ /**
+ * Called when an external image is encountered while a tileset is loaded.
+ */
+ virtual QImage readExternalImage(const QString &source);
+
+ /**
+ * Called when an external tileset is encountered while a map is loaded.
+ * The default implementation just calls readTileset() on a new MapReader.
+ *
+ * If an error occurred, the \a error parameter should be set to the error
+ * message.
+ */
+ virtual Tileset *readExternalTileset(const QString &source,
+ QString *error);
+
+private:
+ friend class Internal::MapReaderPrivate;
+ Internal::MapReaderPrivate *d;
+};
+
+} // namespace Tiled
+
+#endif // MAPREADER_H
diff --git a/libtiled/maprenderer.cpp b/libtiled/maprenderer.cpp
new file mode 100644
index 0000000..719bd42
--- /dev/null
+++ b/libtiled/maprenderer.cpp
@@ -0,0 +1,46 @@
+/*
+ * maprenderer.cpp
+ * Copyright 2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * This file is part of Tiled.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "maprenderer.h"
+
+#include <QVector2D>
+
+using namespace Tiled;
+
+/**
+ * Converts a line running from \a start to \a end to a polygon which
+ * extends 5 pixels from the line in all directions.
+ */
+QPolygonF MapRenderer::lineToPolygon(const QPointF &start, const QPointF &end)
+{
+ QPointF direction = QVector2D(end - start).normalized().toPointF();
+ QPointF perpendicular(-direction.y(), direction.x());
+
+ const qreal thickness = 5.0f; // 5 pixels on each side
+ direction *= thickness;
+ perpendicular *= thickness;
+
+ QPolygonF polygon(4);
+ polygon[0] = start + perpendicular - direction;
+ polygon[1] = start - perpendicular - direction;
+ polygon[2] = end - perpendicular + direction;
+ polygon[3] = end + perpendicular + direction;
+ return polygon;
+}
diff --git a/libtiled/maprenderer.h b/libtiled/maprenderer.h
new file mode 100644
index 0000000..0943df0
--- /dev/null
+++ b/libtiled/maprenderer.h
@@ -0,0 +1,152 @@
+/*
+ * maprenderer.h
+ * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef MAPRENDERER_H
+#define MAPRENDERER_H
+
+#include "tiled_global.h"
+
+#include <QPainter>
+
+namespace Tiled {
+
+class Layer;
+class Map;
+class MapObject;
+class TileLayer;
+
+/**
+ * This interface is used for rendering tile layers and retrieving associated
+ * metrics. The different implementations deal with different map
+ * orientations.
+ */
+class TILEDSHARED_EXPORT MapRenderer
+{
+public:
+ MapRenderer(const Map *map) : mMap(map) {}
+ virtual ~MapRenderer() {}
+
+ /**
+ * Returns the size in pixels of the map associated with this renderer.
+ */
+ virtual QSize mapSize() const = 0;
+
+ /**
+ * Returns the bounding rectangle in pixels of the given \a rect given in
+ * tile coordinates.
+ *
+ * This is useful for calculating the bounding rect of a tile layer or of
+ * a region of tiles that was changed.
+ */
+ virtual QRect boundingRect(const QRect &rect) const = 0;
+
+ /**
+ * Returns the bounding rectangle in pixels of the given \a object, as it
+ * would be drawn by drawMapObject().
+ */
+ virtual QRectF boundingRect(const MapObject *object) const = 0;
+
+ /**
+ * Returns the shape in pixels of the given \a object. This is used for
+ * mouse interaction and should match the rendered object as closely as
+ * possible.
+ */
+ virtual QPainterPath shape(const MapObject *object) const = 0;
+
+ /**
+ * Draws the tile grid in the specified \a rect using the given
+ * \a painter.
+ */
+ virtual void drawGrid(QPainter *painter, const QRectF &rect) const = 0;
+
+ /**
+ * Draws the given \a layer using the given \a painter.
+ *
+ * Optionally, you can pass in the \a exposed rect (of pixels), so that
+ * only tiles that can be visible in this area will be drawn.
+ */
+ virtual void drawTileLayer(QPainter *painter, const TileLayer *layer,
+ const QRectF &exposed = QRectF()) const = 0;
+
+ /**
+ * Draws the tile selection given by \a region in the specified \a color.
+ *
+ * The implementation can be optimized by taking into account the
+ * \a exposed rectangle, to avoid drawing too much.
+ */
+ virtual void drawTileSelection(QPainter *painter,
+ const QRegion &region,
+ const QColor &color,
+ const QRectF &exposed) const = 0;
+
+ /**
+ * Draws the \a object in the given \a color using the \a painter.
+ */
+ virtual void drawMapObject(QPainter *painter,
+ const MapObject *object,
+ const QColor &color) const = 0;
+
+ /**
+ * Returns the tile coordinates matching the given pixel position.
+ */
+ virtual QPointF pixelToTileCoords(qreal x, qreal y) const = 0;
+
+ inline QPointF pixelToTileCoords(const QPointF &point) const
+ { return pixelToTileCoords(point.x(), point.y()); }
+
+ /**
+ * Returns the pixel coordinates matching the given tile coordinates.
+ */
+ virtual QPointF tileToPixelCoords(qreal x, qreal y) const = 0;
+
+ inline QPointF tileToPixelCoords(const QPointF &point) const
+ { return tileToPixelCoords(point.x(), point.y()); }
+
+ QPolygonF tileToPixelCoords(const QPolygonF &polygon) const
+ {
+ QPolygonF screenPolygon(polygon.size());
+ for (int i = polygon.size() - 1; i >= 0; --i)
+ screenPolygon[i] = tileToPixelCoords(polygon[i]);
+ return screenPolygon;
+ }
+
+ static QPolygonF lineToPolygon(const QPointF &start, const QPointF &end);
+
+protected:
+ /**
+ * Returns the map this renderer is associated with.
+ */
+ const Map *map() const { return mMap; }
+
+private:
+ const Map *mMap;
+};
+
+} // namespace Tiled
+
+#endif // MAPRENDERER_H
diff --git a/libtiled/mapwriter.cpp b/libtiled/mapwriter.cpp
new file mode 100644
index 0000000..4242bfb
--- /dev/null
+++ b/libtiled/mapwriter.cpp
@@ -0,0 +1,580 @@
+/*
+ * mapwriter.cpp
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2010, Jeff Bland <jksb@member.fsf.org>
+ * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com>
+ *
+ * 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 "mapwriter.h"
+
+#include "compression.h"
+#include "gidmapper.h"
+#include "map.h"
+#include "mapobject.h"
+#include "objectgroup.h"
+#include "tile.h"
+#include "tilelayer.h"
+#include "tileset.h"
+
+#include <QCoreApplication>
+#include <QDir>
+#include <QXmlStreamWriter>
+
+using namespace Tiled;
+using namespace Tiled::Internal;
+
+namespace Tiled {
+namespace Internal {
+
+class MapWriterPrivate
+{
+ Q_DECLARE_TR_FUNCTIONS(MapReader)
+
+public:
+ MapWriterPrivate();
+
+ void writeMap(const Map *map, QIODevice *device,
+ const QString &path);
+
+ void writeTileset(const Tileset *tileset, QIODevice *device,
+ const QString &path);
+
+ bool openFile(QFile *file);
+
+ QString mError;
+ MapWriter::LayerDataFormat mLayerDataFormat;
+ bool mDtdEnabled;
+
+private:
+ void writeMap(QXmlStreamWriter &w, const Map *map);
+ void writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
+ uint firstGid);
+ void writeTileLayer(QXmlStreamWriter &w, const TileLayer *tileLayer);
+ void writeLayerAttributes(QXmlStreamWriter &w, const Layer *layer);
+ 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
+ GidMapper mGidMapper;
+ bool mUseAbsolutePaths;
+};
+
+} // namespace Internal
+} // namespace Tiled
+
+
+MapWriterPrivate::MapWriterPrivate()
+ : mLayerDataFormat(MapWriter::Base64Gzip)
+ , mDtdEnabled(false)
+ , mUseAbsolutePaths(false)
+{
+}
+
+bool MapWriterPrivate::openFile(QFile *file)
+{
+ if (!file->open(QIODevice::WriteOnly)) {
+ mError = tr("Could not open file for writing.");
+ return false;
+ }
+
+ return true;
+}
+
+static QXmlStreamWriter *createWriter(QIODevice *device)
+{
+ QXmlStreamWriter *writer = new QXmlStreamWriter(device);
+ writer->setAutoFormatting(true);
+ writer->setAutoFormattingIndent(1);
+ return writer;
+}
+
+void MapWriterPrivate::writeMap(const Map *map, QIODevice *device,
+ const QString &path)
+{
+ mMapDir = QDir(path);
+ mUseAbsolutePaths = path.isEmpty();
+
+ QXmlStreamWriter *writer = createWriter(device);
+ writer->writeStartDocument();
+
+ if (mDtdEnabled) {
+ writer->writeDTD(QLatin1String("<!DOCTYPE map SYSTEM \""
+ "http://mapeditor.org/dtd/1.0/"
+ "map.dtd\">"));
+ }
+
+ writeMap(*writer, map);
+ writer->writeEndDocument();
+ delete writer;
+}
+
+void MapWriterPrivate::writeTileset(const Tileset *tileset, QIODevice *device,
+ const QString &path)
+{
+ mMapDir = QDir(path);
+ mUseAbsolutePaths = path.isEmpty();
+
+ QXmlStreamWriter *writer = createWriter(device);
+ writer->writeStartDocument();
+
+ if (mDtdEnabled) {
+ writer->writeDTD(QLatin1String("<!DOCTYPE tileset SYSTEM \""
+ "http://mapeditor.org/dtd/1.0/"
+ "map.dtd\">"));
+ }
+
+ writeTileset(*writer, tileset, 0);
+ writer->writeEndDocument();
+ delete writer;
+}
+
+void MapWriterPrivate::writeMap(QXmlStreamWriter &w, const Map *map)
+{
+ w.writeStartElement(QLatin1String("map"));
+
+ const QString orientation = orientationToString(map->orientation());
+
+ w.writeAttribute(QLatin1String("version"), QLatin1String("1.0"));
+ w.writeAttribute(QLatin1String("orientation"), orientation);
+ w.writeAttribute(QLatin1String("width"), QString::number(map->width()));
+ w.writeAttribute(QLatin1String("height"), QString::number(map->height()));
+ w.writeAttribute(QLatin1String("tilewidth"),
+ QString::number(map->tileWidth()));
+ w.writeAttribute(QLatin1String("tileheight"),
+ QString::number(map->tileHeight()));
+
+ writeProperties(w, map->properties());
+
+ mGidMapper.clear();
+ uint firstGid = 1;
+ foreach (Tileset *tileset, map->tilesets()) {
+ writeTileset(w, tileset, firstGid);
+ mGidMapper.insert(firstGid, tileset);
+ firstGid += tileset->tileCount();
+ }
+
+ foreach (const Layer *layer, map->layers()) {
+ if (dynamic_cast<const TileLayer*>(layer) != 0)
+ writeTileLayer(w, static_cast<const TileLayer*>(layer));
+ else if (dynamic_cast<const ObjectGroup*>(layer) != 0)
+ writeObjectGroup(w, static_cast<const ObjectGroup*>(layer));
+ }
+
+ w.writeEndElement();
+}
+
+void MapWriterPrivate::writeTileset(QXmlStreamWriter &w, const Tileset *tileset,
+ uint firstGid)
+{
+ w.writeStartElement(QLatin1String("tileset"));
+ if (firstGid > 0)
+ w.writeAttribute(QLatin1String("firstgid"), QString::number(firstGid));
+
+ const QString &fileName = tileset->fileName();
+ if (!fileName.isEmpty()) {
+ QString source = fileName;
+ if (!mUseAbsolutePaths)
+ source = mMapDir.relativeFilePath(source);
+ w.writeAttribute(QLatin1String("source"), source);
+
+ // Tileset is external, so no need to write any of the stuff below
+ w.writeEndElement();
+ return;
+ }
+
+ w.writeAttribute(QLatin1String("name"), tileset->name());
+ w.writeAttribute(QLatin1String("tilewidth"),
+ QString::number(tileset->tileWidth()));
+ w.writeAttribute(QLatin1String("tileheight"),
+ QString::number(tileset->tileHeight()));
+ const int tileSpacing = tileset->tileSpacing();
+ const int margin = tileset->margin();
+ if (tileSpacing != 0)
+ w.writeAttribute(QLatin1String("spacing"),
+ QString::number(tileSpacing));
+ 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());
+
+ // Write the image element
+ const QString &imageSource = tileset->imageSource();
+ if (!imageSource.isEmpty()) {
+ w.writeStartElement(QLatin1String("image"));
+ QString source = imageSource;
+ if (!mUseAbsolutePaths)
+ source = mMapDir.relativeFilePath(source);
+ w.writeAttribute(QLatin1String("source"), source);
+
+ const QColor transColor = tileset->transparentColor();
+ if (transColor.isValid())
+ w.writeAttribute(QLatin1String("trans"), transColor.name().mid(1));
+
+ if (tileset->imageWidth() > 0)
+ w.writeAttribute(QLatin1String("width"),
+ QString::number(tileset->imageWidth()));
+ if (tileset->imageHeight() > 0)
+ w.writeAttribute(QLatin1String("height"),
+ QString::number(tileset->imageHeight()));
+
+ w.writeEndElement();
+ }
+
+ // Write the properties for those tiles that have them
+ for (int i = 0; i < tileset->tileCount(); ++i) {
+ const Tile *tile = tileset->tileAt(i);
+ const Properties properties = tile->properties();
+ if (!properties.isEmpty()) {
+ w.writeStartElement(QLatin1String("tile"));
+ w.writeAttribute(QLatin1String("id"), QString::number(i));
+ writeProperties(w, properties);
+ w.writeEndElement();
+ }
+ }
+
+ w.writeEndElement();
+}
+
+void MapWriterPrivate::writeTileLayer(QXmlStreamWriter &w,
+ const TileLayer *tileLayer)
+{
+ w.writeStartElement(QLatin1String("layer"));
+ writeLayerAttributes(w, tileLayer);
+ writeProperties(w, tileLayer->properties());
+
+ QString encoding;
+ QString compression;
+
+ if (mLayerDataFormat == MapWriter::Base64
+ || mLayerDataFormat == MapWriter::Base64Gzip
+ || mLayerDataFormat == MapWriter::Base64Zlib) {
+
+ encoding = QLatin1String("base64");
+
+ if (mLayerDataFormat == MapWriter::Base64Gzip)
+ compression = QLatin1String("gzip");
+ else if (mLayerDataFormat == MapWriter::Base64Zlib)
+ compression = QLatin1String("zlib");
+
+ } else if (mLayerDataFormat == MapWriter::CSV)
+ encoding = QLatin1String("csv");
+
+ w.writeStartElement(QLatin1String("data"));
+ if (!encoding.isEmpty())
+ w.writeAttribute(QLatin1String("encoding"), encoding);
+ if (!compression.isEmpty())
+ w.writeAttribute(QLatin1String("compression"), compression);
+
+ if (mLayerDataFormat == MapWriter::XML) {
+ for (int y = 0; y < tileLayer->height(); ++y) {
+ for (int x = 0; x < tileLayer->width(); ++x) {
+ const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y));
+ w.writeStartElement(QLatin1String("tile"));
+ w.writeAttribute(QLatin1String("gid"), QString::number(gid));
+ w.writeEndElement();
+ }
+ }
+ } else if (mLayerDataFormat == MapWriter::CSV) {
+ QString tileData;
+
+ for (int y = 0; y < tileLayer->height(); ++y) {
+ for (int x = 0; x < tileLayer->width(); ++x) {
+ const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y));
+ tileData.append(QString::number(gid));
+ if (x != tileLayer->width() - 1
+ || y != tileLayer->height() - 1)
+ tileData.append(QLatin1String(","));
+ }
+ tileData.append(QLatin1String("\n"));
+ }
+
+ w.writeCharacters(QLatin1String("\n"));
+ w.writeCharacters(tileData);
+ } else {
+ QByteArray tileData;
+ tileData.reserve(tileLayer->height() * tileLayer->width() * 4);
+
+ for (int y = 0; y < tileLayer->height(); ++y) {
+ for (int x = 0; x < tileLayer->width(); ++x) {
+ const uint gid = mGidMapper.cellToGid(tileLayer->cellAt(x, y));
+ tileData.append((char) (gid));
+ tileData.append((char) (gid >> 8));
+ tileData.append((char) (gid >> 16));
+ tileData.append((char) (gid >> 24));
+ }
+ }
+
+ if (mLayerDataFormat == MapWriter::Base64Gzip)
+ tileData = compress(tileData, Gzip);
+ else if (mLayerDataFormat == MapWriter::Base64Zlib)
+ tileData = compress(tileData, Zlib);
+
+ w.writeCharacters(QLatin1String("\n "));
+ w.writeCharacters(QString::fromLatin1(tileData.toBase64()));
+ w.writeCharacters(QLatin1String("\n "));
+ }
+
+ w.writeEndElement(); // </data>
+ w.writeEndElement(); // </layer>
+}
+
+void MapWriterPrivate::writeLayerAttributes(QXmlStreamWriter &w,
+ const Layer *layer)
+{
+ w.writeAttribute(QLatin1String("name"), layer->name());
+ w.writeAttribute(QLatin1String("width"), QString::number(layer->width()));
+ w.writeAttribute(QLatin1String("height"),
+ QString::number(layer->height()));
+ const int x = layer->x();
+ const int y = layer->y();
+ const qreal opacity = layer->opacity();
+ if (x != 0)
+ w.writeAttribute(QLatin1String("x"), QString::number(x));
+ if (y != 0)
+ w.writeAttribute(QLatin1String("y"), QString::number(y));
+ if (!layer->isVisible())
+ w.writeAttribute(QLatin1String("visible"), QLatin1String("0"));
+ if (opacity != qreal(1))
+ w.writeAttribute(QLatin1String("opacity"), QString::number(opacity));
+}
+
+void MapWriterPrivate::writeObjectGroup(QXmlStreamWriter &w,
+ const ObjectGroup *objectGroup)
+{
+ w.writeStartElement(QLatin1String("objectgroup"));
+
+ if (objectGroup->color().isValid())
+ w.writeAttribute(QLatin1String("color"),
+ objectGroup->color().name());
+
+ writeLayerAttributes(w, objectGroup);
+ writeProperties(w, objectGroup->properties());
+
+ foreach (const MapObject *mapObject, objectGroup->objects())
+ writeObject(w, mapObject);
+
+ w.writeEndElement();
+}
+
+class TileToPixelCoordinates
+{
+public:
+ TileToPixelCoordinates(Map *map)
+ {
+ if (map->orientation() == Map::Isometric) {
+ // Isometric needs special handling, since the pixel values are
+ // based solely on the tile height.
+ mMultiplierX = map->tileHeight();
+ mMultiplierY = map->tileHeight();
+ } else {
+ mMultiplierX = map->tileWidth();
+ mMultiplierY = map->tileHeight();
+ }
+ }
+
+ QPoint operator() (qreal x, qreal y) const
+ {
+ return QPoint(qRound(x * mMultiplierX),
+ qRound(y * mMultiplierY));
+ }
+
+private:
+ int mMultiplierX;
+ int mMultiplierY;
+};
+
+void MapWriterPrivate::writeObject(QXmlStreamWriter &w,
+ const MapObject *mapObject)
+{
+ w.writeStartElement(QLatin1String("object"));
+ const QString &name = mapObject->name();
+ const QString &type = mapObject->type();
+ if (!name.isEmpty())
+ w.writeAttribute(QLatin1String("name"), name);
+ if (!type.isEmpty())
+ w.writeAttribute(QLatin1String("type"), type);
+
+ if (mapObject->tile()) {
+ const uint gid = mGidMapper.cellToGid(Cell(mapObject->tile()));
+ w.writeAttribute(QLatin1String("gid"), QString::number(gid));
+ }
+
+ // Convert from tile to pixel coordinates
+ const ObjectGroup *objectGroup = mapObject->objectGroup();
+ const TileToPixelCoordinates toPixel(objectGroup->map());
+
+ QPoint pos = toPixel(mapObject->x(), mapObject->y());
+ QPoint size = toPixel(mapObject->width(), mapObject->height());
+
+ w.writeAttribute(QLatin1String("x"), QString::number(pos.x()));
+ w.writeAttribute(QLatin1String("y"), QString::number(pos.y()));
+
+ if (size.x() != 0)
+ w.writeAttribute(QLatin1String("width"), QString::number(size.x()));
+ if (size.y() != 0)
+ w.writeAttribute(QLatin1String("height"), QString::number(size.y()));
+
+ writeProperties(w, mapObject->properties());
+
+ const QPolygonF &polygon = mapObject->polygon();
+ if (!polygon.isEmpty()) {
+ if (mapObject->shape() == MapObject::Polygon)
+ w.writeStartElement(QLatin1String("polygon"));
+ else
+ w.writeStartElement(QLatin1String("polyline"));
+
+ QString points;
+ foreach (const QPointF &point, polygon) {
+ const QPoint pos = toPixel(point.x(), point.y());
+ points.append(QString::number(pos.x()));
+ points.append(QLatin1Char(','));
+ points.append(QString::number(pos.y()));
+ points.append(QLatin1Char(' '));
+ }
+ points.chop(1);
+ w.writeAttribute(QLatin1String("points"), points);
+ w.writeEndElement();
+ }
+
+ w.writeEndElement();
+}
+
+void MapWriterPrivate::writeProperties(QXmlStreamWriter &w,
+ const Properties &properties)
+{
+ if (properties.isEmpty())
+ return;
+
+ w.writeStartElement(QLatin1String("properties"));
+
+ Properties::const_iterator it = properties.constBegin();
+ Properties::const_iterator it_end = properties.constEnd();
+ for (; it != it_end; ++it) {
+ w.writeStartElement(QLatin1String("property"));
+ w.writeAttribute(QLatin1String("name"), it.key());
+
+ const QString &value = it.value();
+ if (value.contains(QLatin1Char('\n'))) {
+ w.writeCharacters(value);
+ } else {
+ w.writeAttribute(QLatin1String("value"), it.value());
+ }
+ w.writeEndElement();
+ }
+
+ w.writeEndElement();
+}
+
+
+MapWriter::MapWriter()
+ : d(new MapWriterPrivate)
+{
+}
+
+MapWriter::~MapWriter()
+{
+ delete d;
+}
+
+void MapWriter::writeMap(const Map *map, QIODevice *device,
+ const QString &path)
+{
+ d->writeMap(map, device, path);
+}
+
+bool MapWriter::writeMap(const Map *map, const QString &fileName)
+{
+ QFile file(fileName);
+ if (!d->openFile(&file))
+ return false;
+
+ writeMap(map, &file, QFileInfo(fileName).absolutePath());
+
+ if (file.error() != QFile::NoError) {
+ d->mError = file.errorString();
+ return false;
+ }
+
+ return true;
+}
+
+void MapWriter::writeTileset(const Tileset *tileset, QIODevice *device,
+ const QString &path)
+{
+ d->writeTileset(tileset, device, path);
+}
+
+bool MapWriter::writeTileset(const Tileset *tileset, const QString &fileName)
+{
+ QFile file(fileName);
+ if (!d->openFile(&file))
+ return false;
+
+ writeTileset(tileset, &file, QFileInfo(fileName).absolutePath());
+
+ if (file.error() != QFile::NoError) {
+ d->mError = file.errorString();
+ return false;
+ }
+
+ return true;
+}
+
+QString MapWriter::errorString() const
+{
+ return d->mError;
+}
+
+void MapWriter::setLayerDataFormat(MapWriter::LayerDataFormat format)
+{
+ d->mLayerDataFormat = format;
+}
+
+MapWriter::LayerDataFormat MapWriter::layerDataFormat() const
+{
+ return d->mLayerDataFormat;
+}
+
+void MapWriter::setDtdEnabled(bool enabled)
+{
+ d->mDtdEnabled = enabled;
+}
+
+bool MapWriter::isDtdEnabled() const
+{
+ return d->mDtdEnabled;
+}
diff --git a/libtiled/mapwriter.h b/libtiled/mapwriter.h
new file mode 100644
index 0000000..6e346c8
--- /dev/null
+++ b/libtiled/mapwriter.h
@@ -0,0 +1,129 @@
+/*
+ * mapwriter.h
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2010, Dennis Honeyman <arcticuno@gmail.com>
+ *
+ * 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.
+ */
+
+#ifndef MAPWRITER_H
+#define MAPWRITER_H
+
+#include "tiled_global.h"
+
+#include <QString>
+
+class QIODevice;
+
+namespace Tiled {
+
+class Map;
+class Tileset;
+
+namespace Internal {
+class MapWriterPrivate;
+}
+
+/**
+ * A QXmlStreamWriter based writer for the TMX and TSX formats.
+ */
+class TILEDSHARED_EXPORT MapWriter
+{
+public:
+ MapWriter();
+ ~MapWriter();
+
+ /**
+ * Writes a TMX map to the given \a device. Optionally a \a path can
+ * be given, which will be used to create relative references to external
+ * images and tilesets.
+ *
+ * Error checking will need to be done on the \a device after calling this
+ * function.
+ */
+ void writeMap(const Map *map, QIODevice *device,
+ const QString &path = QString());
+
+ /**
+ * Writes a TMX map to the given \a fileName.
+ *
+ * Returns false and sets errorString() when reading failed.
+ * \overload
+ */
+ bool writeMap(const Map *map, const QString &fileName);
+
+ /**
+ * Writes a TSX tileset to the given \a device. Optionally a \a path can
+ * be given, which will be used to create relative references to external
+ * images.
+ *
+ * Error checking will need to be done on the \a device after calling this
+ * function.
+ */
+ void writeTileset(const Tileset *tileset, QIODevice *device,
+ const QString &path = QString());
+
+ /**
+ * Writes a TSX tileset to the given \a fileName.
+ *
+ * Returns false and sets errorString() when reading failed.
+ * \overload
+ */
+ bool writeTileset(const Tileset *tileset, const QString &fileName);
+
+ /**
+ * Returns the error message for the last occurred error.
+ */
+ QString errorString() const;
+
+ /**
+ * The different formats in which the tile layer data can be stored.
+ */
+ enum LayerDataFormat {
+ XML = 0,
+ Base64 = 1,
+ Base64Gzip = 2,
+ Base64Zlib = 3,
+ CSV = 4
+ };
+
+ /**
+ * Sets the format in which the tile layer data is stored.
+ */
+ void setLayerDataFormat(LayerDataFormat format);
+ LayerDataFormat layerDataFormat() const;
+
+ /**
+ * Sets whether the DTD reference is written when saving the map.
+ */
+ void setDtdEnabled(bool enabled);
+ bool isDtdEnabled() const;
+
+private:
+ Internal::MapWriterPrivate *d;
+};
+
+} // namespace Tiled
+
+#endif // MAPWRITER_H
diff --git a/libtiled/object.h b/libtiled/object.h
new file mode 100644
index 0000000..3a1d9fd
--- /dev/null
+++ b/libtiled/object.h
@@ -0,0 +1,85 @@
+/*
+ * object.h
+ * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef OBJECT_H
+#define OBJECT_H
+
+#include "properties.h"
+
+namespace Tiled {
+
+/**
+ * The base class for anything that can hold properties.
+ */
+class TILEDSHARED_EXPORT Object
+{
+public:
+ /**
+ * Virtual destructor.
+ */
+ virtual ~Object() {}
+
+ /**
+ * Returns the properties of this object.
+ */
+ const Properties &properties() const { return mProperties; }
+
+ /**
+ * Replaces all existing properties with a new set of properties.
+ */
+ void setProperties(const Properties &properties)
+ { mProperties = properties; }
+
+ /**
+ * Merges \a properties with the existing properties. Properties with the
+ * same name will be overridden.
+ *
+ * \sa Properties::merge
+ */
+ void mergeProperties(const Properties &properties)
+ { mProperties.merge(properties); }
+
+ /**
+ * Returns the value of the object's \a name property.
+ */
+ QString property(const QString &name) const
+ { return mProperties.value(name); }
+
+ /**
+ * Sets the value of the object's \a name property to \a value.
+ */
+ void setProperty(const QString &name, const QString &value)
+ { mProperties.insert(name, value); }
+
+private:
+ Properties mProperties;
+};
+
+} // namespace Tiled
+
+#endif // OBJECT_H
diff --git a/libtiled/objectgroup.cpp b/libtiled/objectgroup.cpp
new file mode 100644
index 0000000..d0adc60
--- /dev/null
+++ b/libtiled/objectgroup.cpp
@@ -0,0 +1,198 @@
+/*
+ * objectgroup.cpp
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009-2010, 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 "objectgroup.h"
+
+#include "map.h"
+#include "mapobject.h"
+#include "tile.h"
+#include "tileset.h"
+
+using namespace Tiled;
+
+ObjectGroup::ObjectGroup()
+ : Layer(QString(), 0, 0, 0, 0)
+{
+}
+
+ObjectGroup::ObjectGroup(const QString &name,
+ int x, int y, int width, int height)
+ : Layer(name, x, y, width, height)
+{
+}
+
+ObjectGroup::~ObjectGroup()
+{
+ qDeleteAll(mObjects);
+}
+
+void ObjectGroup::addObject(MapObject *object)
+{
+ mObjects.append(object);
+ object->setObjectGroup(this);
+}
+
+void ObjectGroup::insertObject(int index, MapObject *object)
+{
+ mObjects.insert(index, object);
+ object->setObjectGroup(this);
+}
+
+int ObjectGroup::removeObject(MapObject *object)
+{
+ const int index = mObjects.indexOf(object);
+ Q_ASSERT(index != -1);
+
+ mObjects.removeAt(index);
+ object->setObjectGroup(0);
+ return index;
+}
+
+QRectF ObjectGroup::objectsBoundingRect() const
+{
+ QRectF boundingRect;
+ foreach (const MapObject *object, mObjects)
+ boundingRect = boundingRect.united(object->bounds());
+ return boundingRect;
+}
+
+QSet<Tileset*> ObjectGroup::usedTilesets() const
+{
+ QSet<Tileset*> tilesets;
+
+ foreach (const MapObject *object, mObjects)
+ if (const Tile *tile = object->tile())
+ tilesets.insert(tile->tileset());
+
+ return tilesets;
+}
+
+bool ObjectGroup::referencesTileset(const Tileset *tileset) const
+{
+ foreach (const MapObject *object, mObjects) {
+ const Tile *tile = object->tile();
+ if (tile && tile->tileset() == tileset)
+ return true;
+ }
+
+ return false;
+}
+
+void ObjectGroup::replaceReferencesToTileset(Tileset *oldTileset,
+ Tileset *newTileset)
+{
+ foreach (MapObject *object, mObjects) {
+ const Tile *tile = object->tile();
+ if (tile && tile->tileset() == oldTileset)
+ object->setTile(newTileset->tileAt(tile->id()));
+ }
+}
+
+void ObjectGroup::resize(const QSize &size, const QPoint &offset)
+{
+ Layer::resize(size, offset);
+
+ foreach (MapObject *object, mObjects) {
+ QPointF pos = object->position();
+ pos.rx() += offset.x();
+ pos.ry() += offset.y();
+ object->setPosition(pos);
+ }
+}
+
+void ObjectGroup::offset(const QPoint &offset,
+ const QRect &bounds,
+ bool wrapX, bool wrapY)
+{
+ foreach (MapObject *object, mObjects) {
+ const QRectF objectBounds = object->bounds();
+ if (!QRectF(bounds).contains(objectBounds.center()))
+ continue;
+
+ QPointF newPos(objectBounds.left() + offset.x(),
+ objectBounds.top () + offset.y());
+
+ if (wrapX && bounds.width() > 0) {
+ while (newPos.x() + objectBounds.width() / 2
+ < qreal(bounds.left()))
+ newPos.rx() += qreal(bounds.width());
+ while (newPos.x() + objectBounds.width() / 2
+ > qreal(bounds.left() + bounds.width()))
+ newPos.rx() -= qreal(bounds.width());
+ }
+
+ if (wrapY && bounds.height() > 0) {
+ while (newPos.y() + objectBounds.height() / 2
+ < qreal(bounds.top()))
+ newPos.ry() += qreal(bounds.height());
+ while (newPos.y() + objectBounds.height() / 2
+ > qreal(bounds.top() + bounds.height()))
+ newPos.ry() -= qreal(bounds.height());
+ }
+
+ object->setPosition(newPos);
+ }
+}
+
+bool ObjectGroup::canMergeWith(Layer *other) const
+{
+ return dynamic_cast<ObjectGroup*>(other) != 0;
+}
+
+Layer *ObjectGroup::mergedWith(Layer *other) const
+{
+ Q_ASSERT(canMergeWith(other));
+
+ const ObjectGroup *og = static_cast<ObjectGroup*>(other);
+
+ ObjectGroup *merged = static_cast<ObjectGroup*>(clone());
+ foreach (const MapObject *mapObject, og->objects())
+ merged->addObject(mapObject->clone());
+ return merged;
+}
+
+/**
+ * Returns a duplicate of this ObjectGroup.
+ *
+ * \sa Layer::clone()
+ */
+Layer *ObjectGroup::clone() const
+{
+ return initializeClone(new ObjectGroup(mName, mX, mY, mWidth, mHeight));
+}
+
+ObjectGroup *ObjectGroup::initializeClone(ObjectGroup *clone) const
+{
+ Layer::initializeClone(clone);
+ foreach (const MapObject *object, mObjects)
+ clone->addObject(object->clone());
+ clone->setColor(mColor);
+ return clone;
+}
diff --git a/libtiled/objectgroup.h b/libtiled/objectgroup.h
new file mode 100644
index 0000000..423a5d7
--- /dev/null
+++ b/libtiled/objectgroup.h
@@ -0,0 +1,164 @@
+/*
+ * objectgroup.h
+ * Copyright 2008, Roderic Morris <roderic@ccs.neu.edu>
+ * Copyright 2008-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009-2010, 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.
+ */
+
+#ifndef OBJECTGROUP_H
+#define OBJECTGROUP_H
+
+#include "tiled_global.h"
+
+#include "layer.h"
+
+#include <QList>
+#include <QColor>
+
+namespace Tiled {
+
+class MapObject;
+
+/**
+ * A group of objects on a map.
+ */
+class TILEDSHARED_EXPORT ObjectGroup : public Layer
+{
+public:
+ /**
+ * Default constructor.
+ */
+ ObjectGroup();
+
+ /**
+ * Constructor with some parameters.
+ */
+ ObjectGroup(const QString &name, int x, int y, int width, int height);
+
+ /**
+ * Destructor.
+ */
+ ~ObjectGroup();
+
+ /**
+ * Returns a pointer to the list of objects in this object group.
+ */
+ const QList<MapObject*> &objects() const { return mObjects; }
+
+ /**
+ * Returns the number of objects in this object group.
+ */
+ int objectCount() const { return mObjects.size(); }
+
+ /**
+ * Adds an object to this object group.
+ */
+ void addObject(MapObject *object);
+
+ /**
+ * Inserts an object at the specified index. This is only used for undoing
+ * the removal of an object at the moment, to make sure not to change the
+ * saved order of the objects.
+ */
+ void insertObject(int index, MapObject *object);
+
+ /**
+ * Removes an object from this object group. Ownership of the object is
+ * transferred to the caller.
+ *
+ * @return the index at which the specified object was removed
+ */
+ int removeObject(MapObject *object);
+
+ /**
+ * Returns the bounding rect around all objects in this object group.
+ */
+ QRectF objectsBoundingRect() const;
+
+ /**
+ * Computes and returns the set of tilesets used by this object group.
+ */
+ QSet<Tileset*> usedTilesets() const;
+
+ /**
+ * Returns whether any tile objects in this object group reference tiles
+ * in the given tileset.
+ */
+ bool referencesTileset(const Tileset *tileset) const;
+
+ /**
+ * Replaces all references to tiles from \a oldTileset with tiles from
+ * \a newTileset.
+ */
+ void replaceReferencesToTileset(Tileset *oldTileset, Tileset *newTileset);
+
+ /**
+ * Resizes this object group to \a size, while shifting all objects by
+ * \a offset tiles.
+ *
+ * \sa Layer::resize()
+ */
+ virtual void resize(const QSize &size, const QPoint &offset);
+
+ /**
+ * Offsets all objects within the group, and optionally wraps them. The
+ * object's center must be within \a bounds, and wrapping occurs if the
+ * displaced center is out of the bounds.
+ *
+ * \sa Layer::offset()
+ */
+ virtual void offset(const QPoint &offset, const QRect &bounds,
+ bool wrapX, bool wrapY);
+
+ bool canMergeWith(Layer *other) const;
+ Layer *mergedWith(Layer *other) const;
+
+ /**
+ * Returns the color of the object group, or an invalid color if no color
+ * is set.
+ */
+ const QColor &color() const { return mColor; }
+
+ /**
+ * Sets the display color of the object group.
+ */
+ void setColor(const QColor &color) { mColor = color; }
+
+ Layer *clone() const;
+
+ virtual ObjectGroup *asObjectGroup() { return this; }
+
+protected:
+ ObjectGroup *initializeClone(ObjectGroup *clone) const;
+
+private:
+ QList<MapObject*> mObjects;
+ QColor mColor;
+};
+
+} // namespace Tiled
+
+#endif // OBJECTGROUP_H
diff --git a/libtiled/orthogonalrenderer.cpp b/libtiled/orthogonalrenderer.cpp
new file mode 100644
index 0000000..6f82590
--- /dev/null
+++ b/libtiled/orthogonalrenderer.cpp
@@ -0,0 +1,372 @@
+/*
+ * orthogonalrenderer.cpp
+ * Copyright 2009-2011, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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 "orthogonalrenderer.h"
+
+#include "map.h"
+#include "mapobject.h"
+#include "tile.h"
+#include "tilelayer.h"
+#include "tileset.h"
+
+#include <cmath>
+
+using namespace Tiled;
+
+QSize OrthogonalRenderer::mapSize() const
+{
+ return QSize(map()->width() * map()->tileWidth(),
+ map()->height() * map()->tileHeight());
+}
+
+QRect OrthogonalRenderer::boundingRect(const QRect &rect) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ return QRect(rect.x() * tileWidth,
+ rect.y() * tileHeight,
+ rect.width() * tileWidth,
+ rect.height() * tileHeight);
+}
+
+QRectF OrthogonalRenderer::boundingRect(const MapObject *object) const
+{
+ const QRectF bounds = object->bounds();
+ const QRectF rect(tileToPixelCoords(bounds.topLeft()),
+ tileToPixelCoords(bounds.bottomRight()));
+
+ QRectF boundingRect;
+
+ if (object->tile()) {
+ const QPointF bottomLeft = rect.topLeft();
+ const QPixmap &img = object->tile()->image();
+ boundingRect = QRectF(bottomLeft.x(),
+ bottomLeft.y() - img.height(),
+ img.width(),
+ img.height()).adjusted(-1, -1, 1, 1);
+ } else {
+ // The -2 and +3 are to account for the pen width and shadow
+ switch (object->shape()) {
+ case MapObject::Rectangle:
+ if (rect.isNull()) {
+ boundingRect = rect.adjusted(-10 - 2, -10 - 2, 10 + 3, 10 + 3);
+ } else {
+ const int nameHeight = object->name().isEmpty() ? 0 : 15;
+ boundingRect = rect.adjusted(-2, -nameHeight - 2, 3, 3);
+ }
+ break;
+
+ case MapObject::Polygon:
+ case MapObject::Polyline: {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ QPolygonF screenPolygon = tileToPixelCoords(polygon);
+ boundingRect = screenPolygon.boundingRect().adjusted(-2, -2, 3, 3);
+ break;
+ }
+ }
+ }
+
+ return boundingRect;
+}
+
+QPainterPath OrthogonalRenderer::shape(const MapObject *object) const
+{
+ QPainterPath path;
+
+ if (object->tile()) {
+ path.addRect(boundingRect(object));
+ } else {
+ switch (object->shape()) {
+ case MapObject::Rectangle: {
+ const QRectF bounds = object->bounds();
+ const QRectF rect(tileToPixelCoords(bounds.topLeft()),
+ tileToPixelCoords(bounds.bottomRight()));
+
+ if (rect.isNull()) {
+ path.addEllipse(rect.topLeft(), 20, 20);
+ } else {
+ path.addRoundedRect(rect, 10, 10);
+ }
+ break;
+ }
+ case MapObject::Polygon:
+ case MapObject::Polyline: {
+ const QPointF &pos = object->position();
+ const QPolygonF polygon = object->polygon().translated(pos);
+ const QPolygonF screenPolygon = tileToPixelCoords(polygon);
+ if (object->shape() == MapObject::Polygon) {
+ path.addPolygon(screenPolygon);
+ } else {
+ for (int i = 1; i < screenPolygon.size(); ++i) {
+ path.addPolygon(lineToPolygon(screenPolygon[i - 1],
+ screenPolygon[i]));
+ }
+ path.setFillRule(Qt::WindingFill);
+ }
+ break;
+ }
+ }
+ }
+
+ return path;
+}
+
+void OrthogonalRenderer::drawGrid(QPainter *painter, const QRectF &rect) const
+{
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+
+ if (tileWidth <= 0 || tileHeight <= 0)
+ return;
+
+ const int startX = qMax(0, (int) (rect.x() / tileWidth) * tileWidth);
+ const int startY = qMax(0, (int) (rect.y() / tileHeight) * tileHeight);
+ const int endX = qMin((int) std::ceil(rect.right()),
+ map()->width() * tileWidth + 1);
+ const int endY = qMin((int) std::ceil(rect.bottom()),
+ map()->height() * tileHeight + 1);
+
+ QColor gridColor(Qt::black);
+ gridColor.setAlpha(128);
+
+ QPen gridPen(gridColor);
+ gridPen.setDashPattern(QVector<qreal>() << 2 << 2);
+
+ if (startY < endY) {
+ gridPen.setDashOffset(startY);
+ painter->setPen(gridPen);
+ for (int x = startX; x < endX; x += tileWidth)
+ painter->drawLine(x, startY, x, endY - 1);
+ }
+
+ if (startX < endX) {
+ gridPen.setDashOffset(startX);
+ painter->setPen(gridPen);
+ for (int y = startY; y < endY; y += tileHeight)
+ painter->drawLine(startX, y, endX - 1, y);
+ }
+}
+
+void OrthogonalRenderer::drawTileLayer(QPainter *painter,
+ const TileLayer *layer,
+ const QRectF &exposed) const
+{
+ QTransform savedTransform = painter->transform();
+
+ const int tileWidth = map()->tileWidth();
+ const int tileHeight = map()->tileHeight();
+ const QPointF layerPos(layer->x() * tileWidth,
+ layer->y() * tileHeight);
+
+ painter->translate(layerPos);
+
+ int startX = 0;
+ int startY = 0;
+ int endX = layer->width();
+ int endY = layer->height();
+
+ if (!exposed.isNull()) {
+ 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);
+ startY = qMax((int) rect.y() / tileHeight, 0);
+ endX = qMin((int) std::ceil(rect.right()) / tileWidth + 1, endX);
+ endY = qMin((int) std::ceil(rect.bottom()) / tileHeight + 1, endY);
+ }
+
+ QTransform baseTransform = painter->transform();
+
+ for (int y = startY; y < endY; ++y) {
+ for (int x = startX; x < endX; ++x) {
+ const Cell &cell = layer->cellAt(x, y);
+ if (cell.isEmpty())
+ 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 = offset.x() + x * tileWidth;
+ qreal dy = offset.y() + (y + 1) * tileHeight - img.height();
+
+ if (cell.flippedAntiDiagonally) {
+ // Use shearing to swap the X/Y axis
+ m11 = 0;
+ m12 = 1;
+ m21 = 1;
+ m22 = 0;
+
+ // Compensate for the swap of image dimensions
+ dy += img.height() - img.width();
+ }
+ if (cell.flippedHorizontally) {
+ m11 = -m11;
+ m21 = -m21;
+ dx += cell.flippedAntiDiagonally ? img.height() : img.width();
+ }
+ if (cell.flippedVertically) {
+ m12 = -m12;
+ m22 = -m22;
+ dy += cell.flippedAntiDiagonally ? img.width() : img.height();
+ }
+
+ const QTransform transform(m11, m12, m21, m22, dx, dy);
+ painter->setTransform(transform * baseTransform);
+
+ painter->drawPixmap(0, 0, img);
+ }
+ }
+
+ painter->setTransform(savedTransform);
+}
+
+void OrthogonalRenderer::drawTileSelection(QPainter *painter,
+ const QRegion &region,
+ const QColor &color,
+ const QRectF &exposed) const
+{
+ foreach (const QRect &r, region.rects()) {
+ const QRectF toFill = QRectF(boundingRect(r)).intersected(exposed);
+ if (!toFill.isEmpty())
+ painter->fillRect(toFill, color);
+ }
+}
+
+void OrthogonalRenderer::drawMapObject(QPainter *painter,
+ const MapObject *object,
+ const QColor &color) const
+{
+ painter->save();
+
+ const QRectF bounds = object->bounds();
+ QRectF rect(tileToPixelCoords(bounds.topLeft()),
+ tileToPixelCoords(bounds.bottomRight()));
+
+ painter->translate(rect.topLeft());
+ rect.moveTopLeft(QPointF(0, 0));
+
+ if (object->tile()) {
+ const QPixmap &img = object->tile()->image();
+ const QPoint paintOrigin(0, -img.height());
+ painter->drawPixmap(paintOrigin, img);
+
+ QPen pen(Qt::SolidLine);
+ painter->setPen(pen);
+ painter->drawRect(QRect(paintOrigin, img.size()));
+ pen.setStyle(Qt::DotLine);
+ pen.setColor(color);
+ painter->setPen(pen);
+ painter->drawRect(QRect(paintOrigin, img.size()));
+ } else {
+ const QPen linePen(color, 2);
+ const QPen shadowPen(Qt::black, 2);
+
+ QColor brushColor = color;
+ brushColor.setAlpha(50);
+ const QBrush fillBrush(brushColor);
+
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ switch (object->shape()) {
+ case MapObject::Rectangle: {
+ if (rect.isNull())
+ rect = QRectF(QPointF(-10, -10), QSizeF(20, 20));
+
+ const QFontMetrics fm = painter->fontMetrics();
+ QString name = fm.elidedText(object->name(), Qt::ElideRight,
+ rect.width() + 2);
+
+ // Draw the shadow
+ painter->setPen(shadowPen);
+ painter->drawRect(rect.translated(QPointF(1, 1)));
+ if (!name.isEmpty())
+ painter->drawText(QPoint(1, -5 + 1), name);
+
+ painter->setPen(linePen);
+ painter->setBrush(fillBrush);
+ painter->drawRect(rect);
+ if (!name.isEmpty())
+ painter->drawText(QPoint(0, -5), name);
+
+ break;
+ }
+
+ case MapObject::Polyline: {
+ QPolygonF screenPolygon = tileToPixelCoords(object->polygon());
+
+ painter->setPen(shadowPen);
+ painter->drawPolyline(screenPolygon.translated(1, 1));
+
+ painter->setPen(linePen);
+ painter->setBrush(fillBrush);
+ painter->drawPolyline(screenPolygon);
+ break;
+ }
+
+ case MapObject::Polygon: {
+ QPolygonF screenPolygon = tileToPixelCoords(object->polygon());
+
+ painter->setPen(shadowPen);
+ painter->drawPolygon(screenPolygon.translated(1, 1));
+
+ painter->setPen(linePen);
+ painter->setBrush(fillBrush);
+ painter->drawPolygon(screenPolygon);
+ break;
+ }
+ }
+ }
+
+ painter->restore();
+}
+
+QPointF OrthogonalRenderer::pixelToTileCoords(qreal x, qreal y) const
+{
+ return QPointF(x / map()->tileWidth(),
+ y / map()->tileHeight());
+}
+
+QPointF OrthogonalRenderer::tileToPixelCoords(qreal x, qreal y) const
+{
+ return QPointF(x * map()->tileWidth(),
+ y * map()->tileHeight());
+}
diff --git a/libtiled/orthogonalrenderer.h b/libtiled/orthogonalrenderer.h
new file mode 100644
index 0000000..d6afb01
--- /dev/null
+++ b/libtiled/orthogonalrenderer.h
@@ -0,0 +1,74 @@
+/*
+ * orthogonalrenderer.h
+ * Copyright 2009-2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef ORTHOGONALRENDERER_H
+#define ORTHOGONALRENDERER_H
+
+#include "maprenderer.h"
+
+namespace Tiled {
+
+/**
+ * The orthogonal map renderer. This is the most basic map renderer,
+ * dealing with maps that use rectangular tiles.
+ */
+class TILEDSHARED_EXPORT OrthogonalRenderer : public MapRenderer
+{
+public:
+ OrthogonalRenderer(const Map *map) : MapRenderer(map) {}
+
+ QSize mapSize() const;
+
+ QRect boundingRect(const QRect &rect) const;
+
+ QRectF boundingRect(const MapObject *object) const;
+ QPainterPath shape(const MapObject *object) const;
+
+ void drawGrid(QPainter *painter, const QRectF &rect) const;
+
+ void drawTileLayer(QPainter *painter, const TileLayer *layer,
+ const QRectF &exposed = QRectF()) const;
+
+ void drawTileSelection(QPainter *painter,
+ const QRegion &region,
+ const QColor &color,
+ const QRectF &exposed) const;
+
+ void drawMapObject(QPainter *painter,
+ const MapObject *object,
+ const QColor &color) const;
+
+ QPointF pixelToTileCoords(qreal x, qreal y) const;
+
+ using MapRenderer::tileToPixelCoords;
+ QPointF tileToPixelCoords(qreal x, qreal y) const;
+};
+
+} // namespace Tiled
+
+#endif // ORTHOGONALRENDERER_H
diff --git a/libtiled/properties.cpp b/libtiled/properties.cpp
new file mode 100644
index 0000000..53e483d
--- /dev/null
+++ b/libtiled/properties.cpp
@@ -0,0 +1,42 @@
+/*
+ * properties.cpp
+ * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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 "properties.h"
+
+using namespace Tiled;
+
+void Properties::merge(const Properties &other)
+{
+ // Based on QMap::unite, but using insert instead of insertMulti
+ const_iterator it = other.constEnd();
+ const const_iterator b = other.constBegin();
+ while (it != b) {
+ --it;
+ insert(it.key(), it.value());
+ }
+}
diff --git a/libtiled/properties.h b/libtiled/properties.h
new file mode 100644
index 0000000..4c34087
--- /dev/null
+++ b/libtiled/properties.h
@@ -0,0 +1,47 @@
+/*
+ * properties.h
+ * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef PROPERTIES_H
+#define PROPERTIES_H
+
+#include "tiled_global.h"
+
+#include <QMap>
+#include <QString>
+
+namespace Tiled {
+
+class TILEDSHARED_EXPORT Properties : public QMap<QString,QString>
+{
+public:
+ void merge(const Properties &other);
+};
+
+} // namespace Tiled
+
+#endif // PROPERTIES_H
diff --git a/libtiled/tile.h b/libtiled/tile.h
new file mode 100644
index 0000000..b512622
--- /dev/null
+++ b/libtiled/tile.h
@@ -0,0 +1,93 @@
+/*
+ * tile.h
+ * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyright 2009, Edward Hutchins <eah1@yahoo.com>
+ *
+ * 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.
+ */
+
+#ifndef TILE_H
+#define TILE_H
+
+#include "object.h"
+
+#include <QPixmap>
+
+namespace Tiled {
+
+class Tileset;
+
+class TILEDSHARED_EXPORT Tile : public Object
+{
+public:
+ Tile(const QPixmap &image, int id, Tileset *tileset):
+ mId(id),
+ mTileset(tileset),
+ mImage(image)
+ {}
+
+ /**
+ * Returns ID of this tile within its tileset.
+ */
+ int id() const { return mId; }
+
+ /**
+ * Returns the tileset that this tile is part of.
+ */
+ Tileset *tileset() const { return mTileset; }
+
+ /**
+ * Returns the image of this tile.
+ */
+ const QPixmap &image() const { return mImage; }
+
+ /**
+ * Sets the image of this tile.
+ */
+ void setImage(const QPixmap &image) { mImage = image; }
+
+ /**
+ * Returns the width of this tile.
+ */
+ int width() const { return mImage.width(); }
+
+ /**
+ * Returns the height of this tile.
+ */
+ int height() const { return mImage.height(); }
+
+ /**
+ * Returns the size of this tile.
+ */
+ QSize size() const { return mImage.size(); }
+
+private:
+ int mId;
+ Tileset *mTileset;
+ QPixmap mImage;
+};
+
+} // namespace Tiled
+
+#endif // TILE_H
diff --git a/libtiled/tiled_global.h b/libtiled/tiled_global.h
new file mode 100644
index 0000000..3dbf164
--- /dev/null
+++ b/libtiled/tiled_global.h
@@ -0,0 +1,40 @@
+/*
+ * tiled_global.h
+ * Copyright 2010, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ *
+ * 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.
+ */
+
+#ifndef TILED_GLOBAL_H
+#define TILED_GLOBAL_H
+
+#include <QtCore/qglobal.h>
+
+#if defined(TILED_LIBRARY)
+# define TILEDSHARED_EXPORT Q_DECL_EXPORT
+#else
+# define TILEDSHARED_EXPORT Q_DECL_IMPORT
+#endif
+
+#endif // TILED_GLOBAL_H
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;
+}
diff --git a/libtiled/tilelayer.h b/libtiled/tilelayer.h
new file mode 100644
index 0000000..eca8e88
--- /dev/null
+++ b/libtiled/tilelayer.h
@@ -0,0 +1,279 @@
+/*
+ * tilelayer.h
+ * 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.
+ */
+
+#ifndef TILELAYER_H
+#define TILELAYER_H
+
+#include "tiled_global.h"
+
+#include "layer.h"
+
+#include <QMargins>
+#include <QString>
+#include <QVector>
+
+namespace Tiled {
+
+class Tile;
+class Tileset;
+
+/**
+ * A cell on a tile layer grid.
+ */
+class Cell
+{
+public:
+ Cell() :
+ tile(0),
+ flippedHorizontally(false),
+ flippedVertically(false),
+ flippedAntiDiagonally(false)
+ {}
+
+ explicit Cell(Tile *tile) :
+ tile(tile),
+ flippedHorizontally(false),
+ flippedVertically(false),
+ flippedAntiDiagonally(false)
+ {}
+
+ bool isEmpty() const { return tile == 0; }
+
+ bool operator == (const Cell &other) const
+ {
+ return tile == other.tile
+ && flippedHorizontally == other.flippedHorizontally
+ && flippedVertically == other.flippedVertically
+ && flippedAntiDiagonally == other.flippedAntiDiagonally;
+ }
+
+ bool operator != (const Cell &other) const
+ {
+ return tile != other.tile
+ || flippedHorizontally != other.flippedHorizontally
+ || flippedVertically != other.flippedVertically
+ || flippedAntiDiagonally != other.flippedAntiDiagonally;
+ }
+
+ Tile *tile;
+ bool flippedHorizontally;
+ bool flippedVertically;
+ bool flippedAntiDiagonally;
+};
+
+/**
+ * A tile layer is a grid of cells. Each cell refers to a specific tile, and
+ * stores how the tile is flipped.
+ *
+ * Coordinates and regions passed to function parameters are in local
+ * coordinates and do not take into account the position of the layer.
+ */
+class TILEDSHARED_EXPORT TileLayer : public Layer
+{
+public:
+ enum FlipDirection {
+ FlipHorizontally,
+ FlipVertically,
+ FlipDiagonally
+ };
+
+ enum RotateDirection {
+ RotateLeft,
+ RotateRight
+ };
+
+ /**
+ * Constructor.
+ */
+ TileLayer(const QString &name, int x, int y, int width, int height);
+
+ /**
+ * 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.
+ */
+ bool contains(int x, int y) const
+ { return x >= 0 && y >= 0 && x < mWidth && y < mHeight; }
+
+ bool contains(const QPoint &point) const
+ { return contains(point.x(), point.y()); }
+
+ /**
+ * Calculates the region occupied by the tiles of this layer. Similar to
+ * Layer::bounds(), but leaves out the regions without tiles.
+ */
+ QRegion region() const;
+
+ /**
+ * Returns a read-only reference to the cell at the given coordinates. The
+ * coordinates have to be within this layer.
+ */
+ const Cell &cellAt(int x, int y) const
+ { return mGrid.at(x + y * mWidth); }
+
+ const Cell &cellAt(const QPoint &point) const
+ { return cellAt(point.x(), point.y()); }
+
+ /**
+ * Sets the cell at the given coordinates.
+ */
+ void setCell(int x, int y, const Cell &cell);
+
+ /**
+ * Returns a copy of the area specified by the given \a region. The
+ * caller is responsible for the returned tile layer.
+ */
+ TileLayer *copy(const QRegion &region) const;
+
+ TileLayer *copy(int x, int y, int width, int height) const
+ { return copy(QRegion(x, y, width, height)); }
+
+ /**
+ * Merges the given \a layer onto this layer at position \a pos. Parts that
+ * fall outside of this layer will be lost and empty tiles in the given
+ * layer will have no effect.
+ */
+ void merge(const QPoint &pos, const TileLayer *layer);
+
+ /**
+ * Sets the cells starting at the given position to the cells in the given
+ * \a tileLayer. Parts that fall outside of this layer will be ignored.
+ *
+ * When a \a mask is given, only cells that fall within this mask are set.
+ * The mask is applied in local coordinates.
+ */
+ void setCells(int x, int y, TileLayer *tileLayer,
+ const QRegion &mask = QRegion());
+
+ /**
+ * Flip this tile layer in the given \a direction. Direction must be
+ * horizontal or vertical. This doesn't change the dimensions of the
+ * tile layer.
+ */
+ void flip(FlipDirection direction);
+
+ /**
+ * Rotate this tile layer by 90 degrees left or right. The tile positions
+ * are rotated within the layer, and the tiles themselves are rotated. The
+ * dimensions of the tile layer are swapped.
+ */
+ void rotate(RotateDirection direction);
+
+ /**
+ * Computes and returns the set of tilesets used by this tile layer.
+ */
+ QSet<Tileset*> usedTilesets() const;
+
+ /**
+ * Returns whether this tile layer is referencing the given tileset.
+ */
+ bool referencesTileset(const Tileset *tileset) const;
+
+ /**
+ * Returns the region of tiles coming from the given \a tileset.
+ */
+ QRegion tilesetReferences(Tileset *tileset) const;
+
+ /**
+ * Removes all references to the given tileset. This sets all tiles on this
+ * layer that are from the given tileset to null.
+ */
+ void removeReferencesToTileset(Tileset *tileset);
+
+ /**
+ * Replaces all tiles from \a oldTileset with tiles from \a newTileset.
+ */
+ void replaceReferencesToTileset(Tileset *oldTileset, Tileset *newTileset);
+
+ /**
+ * Resizes this tile layer to \a size, while shifting all tiles by
+ * \a offset.
+ *
+ * \sa Layer::resize()
+ */
+ virtual void resize(const QSize &size, const QPoint &offset);
+
+ /**
+ * Offsets the objects in this group by \a offset, within \bounds
+ * and optionally wraps it.
+ *
+ * \sa Layer::offset()
+ */
+ virtual void offset(const QPoint &offset,
+ const QRect &bounds,
+ bool wrapX, bool wrapY);
+
+ bool canMergeWith(Layer *other) const;
+ Layer *mergedWith(Layer *other) const;
+
+ /**
+ * Returns the region where this tile layer and the given tile layer
+ * are different. The relative positions of the layers are taken into
+ * account. The returned region is relative to this tile layer.
+ */
+ QRegion computeDiffRegion(const TileLayer *other) const;
+
+ /**
+ * Returns true if all tiles in the layer are empty.
+ */
+ bool isEmpty() const;
+
+ virtual Layer *clone() const;
+
+ virtual TileLayer *asTileLayer() { return this; }
+
+protected:
+ TileLayer *initializeClone(TileLayer *clone) const;
+
+private:
+ QSize mMaxTileSize;
+ QMargins mOffsetMargins;
+ QVector<Cell> mGrid;
+};
+
+} // namespace Tiled
+
+#endif // TILELAYER_H
diff --git a/libtiled/tileset.cpp b/libtiled/tileset.cpp
new file mode 100644
index 0000000..54e71e9
--- /dev/null
+++ b/libtiled/tileset.cpp
@@ -0,0 +1,114 @@
+/*
+ * tileset.cpp
+ * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyrigth 2009, Edward Hutchins <eah1@yahoo.com>
+ *
+ * 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 "tileset.h"
+#include "tile.h"
+
+#include <QBitmap>
+
+using namespace Tiled;
+
+Tileset::~Tileset()
+{
+ qDeleteAll(mTiles);
+}
+
+Tile *Tileset::tileAt(int id) const
+{
+ return (id < mTiles.size()) ? mTiles.at(id) : 0;
+}
+
+bool Tileset::loadFromImage(const QImage &image, const QString &fileName)
+{
+ Q_ASSERT(mTileWidth > 0 && mTileHeight > 0);
+
+ if (image.isNull())
+ return false;
+
+ const int stopWidth = image.width() - mTileWidth;
+ const int stopHeight = image.height() - mTileHeight;
+
+ int oldTilesetSize = mTiles.size();
+ int tileNum = 0;
+
+ for (int y = mMargin; y <= stopHeight; y += mTileHeight + mTileSpacing) {
+ for (int x = mMargin; x <= stopWidth; x += mTileWidth + mTileSpacing) {
+ const QImage tileImage = image.copy(x, y, mTileWidth, mTileHeight);
+ QPixmap tilePixmap = QPixmap::fromImage(tileImage);
+
+ if (mTransparentColor.isValid()) {
+ const QImage mask =
+ tileImage.createMaskFromColor(mTransparentColor.rgb());
+ tilePixmap.setMask(QBitmap::fromImage(mask));
+ }
+
+ if (tileNum < oldTilesetSize) {
+ mTiles.at(tileNum)->setImage(tilePixmap);
+ } else {
+ mTiles.append(new Tile(tilePixmap, tileNum, this));
+ }
+ ++tileNum;
+ }
+ }
+
+ // Blank out any remaining tiles to avoid confusion
+ while (tileNum < oldTilesetSize) {
+ QPixmap tilePixmap = QPixmap(mTileWidth, mTileHeight);
+ tilePixmap.fill();
+ mTiles.at(tileNum)->setImage(tilePixmap);
+ ++tileNum;
+ }
+
+ mImageWidth = image.width();
+ mImageHeight = image.height();
+ mColumnCount = columnCountForWidth(mImageWidth);
+ mImageSource = fileName;
+ return true;
+}
+
+Tileset *Tileset::findSimilarTileset(const QList<Tileset*> &tilesets) const
+{
+ foreach (Tileset *candidate, tilesets) {
+ if (candidate != this
+ && candidate->imageSource() == imageSource()
+ && candidate->tileWidth() == tileWidth()
+ && candidate->tileHeight() == tileHeight()
+ && candidate->tileSpacing() == tileSpacing()
+ && candidate->margin() == margin()) {
+ return candidate;
+ }
+ }
+ return 0;
+}
+
+int Tileset::columnCountForWidth(int width) const
+{
+ Q_ASSERT(mTileWidth > 0);
+ return (width - mMargin + mTileSpacing) / (mTileWidth + mTileSpacing);
+}
diff --git a/libtiled/tileset.h b/libtiled/tileset.h
new file mode 100644
index 0000000..99c7ba6
--- /dev/null
+++ b/libtiled/tileset.h
@@ -0,0 +1,234 @@
+/*
+ * tileset.h
+ * Copyright 2008-2009, Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
+ * Copyrigth 2009, Edward Hutchins <eah1@yahoo.com>
+ *
+ * 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.
+ */
+
+#ifndef TILESET_H
+#define TILESET_H
+
+#include "object.h"
+
+#include <QColor>
+#include <QList>
+#include <QPoint>
+#include <QString>
+
+class QImage;
+
+namespace Tiled {
+
+class Tile;
+
+/**
+ * A tileset, representing a set of tiles.
+ *
+ * This class currently only supports loading tiles from a tileset image, using
+ * loadFromImage(). There is no way to add or remove arbitrary tiles.
+ */
+class TILEDSHARED_EXPORT Tileset : public Object
+{
+public:
+ /**
+ * Constructor.
+ *
+ * @param name the name of the tileset
+ * @param tileWidth the width of the tiles in the tileset
+ * @param tileHeight the height of the tiles in the tileset
+ * @param tileSpacing the spacing between the tiles in the tileset image
+ * @param margin the margin around the tiles in the tileset image
+ */
+ Tileset(const QString &name, int tileWidth, int tileHeight,
+ int tileSpacing = 0, int margin = 0):
+ mName(name),
+ mTileWidth(tileWidth),
+ mTileHeight(tileHeight),
+ mTileSpacing(tileSpacing),
+ mMargin(margin),
+ mImageWidth(0),
+ mImageHeight(0),
+ mColumnCount(0)
+ {
+ Q_ASSERT(tileSpacing >= 0);
+ Q_ASSERT(margin >= 0);
+ }
+
+ /**
+ * Destructor.
+ */
+ ~Tileset();
+
+ /**
+ * Returns the name of this tileset.
+ */
+ const QString &name() const { return mName; }
+
+ /**
+ * Sets the name of this tileset.
+ */
+ void setName(const QString &name) { mName = name; }
+
+ /**
+ * Returns the file name of this tileset. When the tileset isn't an
+ * external tileset, the file name is empty.
+ */
+ const QString &fileName() const { return mFileName; }
+
+ /**
+ * Sets the filename of this tileset.
+ */
+ void setFileName(const QString &fileName) { mFileName = fileName; }
+
+ /**
+ * Returns whether this tileset is external.
+ */
+ bool isExternal() const { return !mFileName.isEmpty(); }
+
+ /**
+ * Returns the width of the tiles in this tileset.
+ */
+ int tileWidth() const { return mTileWidth; }
+
+ /**
+ * Returns the height of the tiles in this tileset.
+ */
+ int tileHeight() const { return mTileHeight; }
+
+ /**
+ * Returns the spacing between the tiles in the tileset image.
+ */
+ int tileSpacing() const { return mTileSpacing; }
+
+ /**
+ * Returns the margin around the tiles in the tileset image.
+ */
+ 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
+ * [0, tileCount() - 1].
+ */
+ Tile *tileAt(int id) const;
+
+ /**
+ * Returns the number of tiles in this tileset.
+ */
+ int tileCount() const { return mTiles.size(); }
+
+ /**
+ * Returns the number of tile columns in the tileset image.
+ */
+ int columnCount() const { return mColumnCount; }
+
+ /**
+ * Returns the width of the tileset image.
+ */
+ int imageWidth() const { return mImageWidth; }
+
+ /**
+ * Returns the height of the tileset image.
+ */
+ int imageHeight() const { return mImageHeight; }
+
+ /**
+ * Returns the transparent color, or an invalid color if no transparent
+ * color is used.
+ */
+ QColor transparentColor() const { return mTransparentColor; }
+
+ /**
+ * Sets the transparent color. Pixels with this color will be masked out
+ * when loadFromImage() is called.
+ */
+ void setTransparentColor(const QColor &c) { mTransparentColor = c; }
+
+ /**
+ * Load this tileset from the given tileset \a image. This will replace
+ * existing tile images in this tileset with new ones. If the new image
+ * contains more tiles than exist in the tileset new tiles will be
+ * appended, if there are fewer tiles the excess images will be blanked.
+ *
+ * The tile width and height of this tileset must be higher than 0.
+ *
+ * @param image the image to load the tiles from
+ * @param fileName the file name of the image, which will be remembered
+ * as the image source of this tileset.
+ * @return <code>true</code> if loading was successful, otherwise
+ * returns <code>false</code>
+ */
+ bool loadFromImage(const QImage &image, const QString &fileName);
+
+ /**
+ * This checks if there is a similar tileset in the given list.
+ * It is needed for replacing this tileset by its similar copy.
+ */
+ Tileset *findSimilarTileset(const QList<Tileset*> &tilesets) const;
+
+ /**
+ * Returns the file name of the external image that contains the tiles in
+ * this tileset. Is an empty string when this tileset doesn't have a
+ * tileset image.
+ */
+ const QString &imageSource() const { return mImageSource; }
+
+ /**
+ * Returns the column count that this tileset would have if the tileset
+ * image would have the given \a width. This takes into account the tile
+ * size, margin and spacing.
+ */
+ int columnCountForWidth(int width) const;
+
+private:
+ QString mName;
+ QString mFileName;
+ QString mImageSource;
+ QColor mTransparentColor;
+ int mTileWidth;
+ int mTileHeight;
+ int mTileSpacing;
+ int mMargin;
+ QPoint mTileOffset;
+ int mImageWidth;
+ int mImageHeight;
+ int mColumnCount;
+ QList<Tile*> mTiles;
+};
+
+} // namespace Tiled
+
+#endif // TILESET_H
diff --git a/platformer.pri b/platformer.pri
new file mode 100644
index 0000000..1fef270
--- /dev/null
+++ b/platformer.pri
@@ -0,0 +1,18 @@
+unix: {
+isEmpty(PREFIX):PREFIX = /usr/local
+isEmpty(LIBDIR):LIBDIR = $${PREFIX}/lib
+isEmpty(BINDIR):BINDIR = $${PREFIX}/bin
+isEmpty(DATADIR):DATADIR = $${PREFIX}/share/walrush
+isEmpty(LEVELDIR):LEVELDIR = $${DATADIR}/levels
+
+DEFINES += DATADIR=\"\\\"$${DATADIR}\\\"\" \
+ LEVELDIR=\"\\\"$${LEVELDIR}\\\"\"
+
+}
+
+win32: {
+
+}
+
+RESOURCES += \
+ ../resources.qrc
diff --git a/platformer.pro b/platformer.pro
new file mode 100644
index 0000000..9035e94
--- /dev/null
+++ b/platformer.pro
@@ -0,0 +1,11 @@
+#-------------------------------------------------
+#
+# Project created by QtCreator 2011-02-20T21:55:55
+#
+#-------------------------------------------------
+
+TEMPLATE = subdirs
+CONFIG += ordered
+
+SUBDIRS = libtiled \
+ src
diff --git a/src/ActionScene.cpp b/src/ActionScene.cpp
new file mode 100644
index 0000000..9d7013c
--- /dev/null
+++ b/src/ActionScene.cpp
@@ -0,0 +1,150 @@
+#include <QPainter>
+#include <QPainterPath>
+#include <QColor>
+#include <QBrush>
+#include <QDebug>
+#include <QGraphicsPixmapItem>
+#include <QGraphicsSceneMouseEvent>
+
+#include <QSettings>
+
+#include <QApplication>
+#include <QTime>
+#include <QFileInfo>
+#include <QMessageBox>
+
+#include "MainWindow.h"
+#include "GameView.h"
+#include "Hero.h"
+#include "ParallaxScrollerStatic.h"
+#include "Collectible.h"
+
+#include "layer.h"
+#include "objectgroup.h"
+#include "mapobject.h"
+#include "tile.h"
+
+#include "ActionScene.h"
+
+using Tiled::Map;
+using Tiled::MapReader;
+using Tiled::OrthogonalRenderer;
+using Tiled::Layer;
+using Tiled::ObjectGroup;
+using Tiled::Tile;
+
+ActionScene::ActionScene(const QString &name, const QRectF &rect, GameView *parent)
+ : GameScene(name, parent)
+{
+ setSceneRect(rect);
+ m_clearAlert = false;
+ m_mapReader = new MapReader;
+
+ qsrand(QTime::currentTime().msec());
+
+ m_hpText = new QGraphicsTextItem("HP: ", 0, this);
+ m_hpText->setPos(100, 10);
+ m_scoreText = new QGraphicsTextItem("Score: 0", 0, this);
+ m_scoreText->setPos(100, 20);
+
+ m_hero = new Hero(this, QPointF(100, 300));
+ connect(m_hero, SIGNAL(removeMe()), this, SLOT(removeSprite()));
+}
+
+ActionScene::~ActionScene()
+{
+ delete m_map;
+ delete m_mapReader;
+ delete m_mapRenderer;
+}
+
+void ActionScene::updateLogic()
+{
+ if(!m_clearAlert)
+ {
+ advance();
+ update();
+ }
+}
+
+void ActionScene::keyPressEvent(QKeyEvent *event)
+{
+ QGraphicsScene::keyPressEvent(event);
+}
+
+void ActionScene::loadMap(QString target)
+{
+ QFileInfo f(target);
+ m_levelName = f.fileName();
+
+ QSettings set;
+ m_levelScore = set.value(m_levelName).toInt();
+
+ m_map = m_mapReader->readMap(target);
+
+ if (m_map == NULL)
+ {
+ qDebug() << "error:" << m_mapReader->errorString();
+ return;
+ }
+
+ m_mapRenderer = new OrthogonalRenderer(m_map);
+
+ qDebug() << "size" << m_map->width() << "x" << m_map->height();
+ qDebug() << "layers" << m_map->layerCount();
+
+ QImage img(m_map->width() * m_map->tileWidth(),
+ m_map->height() * m_map->tileHeight(),
+ QImage::Format_ARGB32);
+
+ QPainter painter(&img);
+ m_mapRenderer->drawTileLayer(&painter, m_map->layerAt(0)->asTileLayer());
+
+ m_mapPixmap = QPixmap::fromImage(img);
+
+ qDebug() << "hasAlpha" << m_mapPixmap.hasAlpha() << "\n"
+ << "hasAlphaChannel" << m_mapPixmap.hasAlphaChannel();
+
+ m_mapPixmapItem = addPixmap(m_mapPixmap);
+ m_mapPixmapItem->setPos(0, 0);
+ m_mapPixmapItem->setData(ITEM_OBJECTNAME, QString("SolidGround"));
+ m_mapPixmapItem->setShapeMode(QGraphicsPixmapItem::MaskShape);
+ m_mapPixmapItem->setZValue(1);
+
+ m_mapPixmapItem->setPixmap(m_mapPixmap);
+
+ ObjectGroup* fish = NULL;
+
+ if(m_map->indexOfLayer("fish") >= 0)
+ fish = m_map->layerAt(m_map->indexOfLayer("fish"))->asObjectGroup();
+
+ if(fish)
+ {
+ Q_FOREACH(Tiled::MapObject *obj, fish->objects())
+ {
+ Collectible *fish = new Collectible(0, this);
+ fish->setData(ITEM_OBJECTNAME, QString("fish"));
+ connect(fish, SIGNAL(removeMe()), this, SLOT(removeSprite()));
+
+ fish->setPos((obj->x()) * m_map->tileWidth(),
+ (obj->y() - 1) * m_map->tileHeight());
+
+ fish->setZValue(2);
+
+ qDebug() << obj->position() << fish->pos();
+ }
+ }
+ m_clearAlert = false;
+}
+
+void ActionScene::removeSprite()
+{
+ qDebug() << "removing a sprite";
+
+ Sprite* sp = (Sprite*) sender();
+ if(sp)
+ {
+ removeItem(sp);
+ sp->deleteLater();
+ }
+}
diff --git a/src/ActionScene.h b/src/ActionScene.h
new file mode 100644
index 0000000..49fdb20
--- /dev/null
+++ b/src/ActionScene.h
@@ -0,0 +1,83 @@
+#ifndef ActionScene_H
+#define ActionScene_H
+
+#include <QObject>
+#include <QRectF>
+#include <QGraphicsRectItem>
+
+#include "GameScene.h"
+#include "Sprite.h"
+
+#include "map.h"
+#include "mapreader.h"
+#include "orthogonalrenderer.h"
+
+class QGraphicsPixmapItem;
+class Hero;
+
+class ActionScene : public GameScene
+{
+ Q_OBJECT
+
+public:
+ explicit ActionScene(const QString &name, const QRectF &rect, GameView *parent = 0);
+ virtual ~ActionScene();
+
+ void updateLogic();
+ void keyPressEvent(QKeyEvent *event);
+
+ /**
+ * Loads level from target location.
+ */
+ void loadMap(QString target);
+
+private:
+
+ Tiled::Map *m_map;
+ Tiled::MapReader *m_mapReader;
+ Tiled::OrthogonalRenderer *m_mapRenderer;
+
+ //! Level name used for records.
+ QString m_levelName;
+
+ //! Levelscore used for records.
+ int m_levelScore;
+
+ //! Map layer is drawn to this pixmap
+ QPixmap m_mapPixmap;
+
+ //! Item for map layer
+ QGraphicsPixmapItem *m_mapPixmapItem;
+
+ //! What portion of the map to draw
+ QSize m_mapWindow;
+
+ //! Background pixmap
+ QPixmap m_bgPixmap;
+ QGraphicsPixmapItem *m_bgPixmapItem;
+
+ //! What portion of the bg pixmap to draw
+ QRectF m_bgWindow;
+
+ //! Pointer for forwarding commands to main character
+ Hero* m_hero;
+
+ //! Stops graphics rendering while scene is cleared.
+ bool m_clearAlert;
+
+ //! HP text item
+ QGraphicsTextItem* m_hpText;
+ //! Score text item
+ QGraphicsTextItem* m_scoreText;
+
+signals:
+ void gameOver();
+
+public slots:
+ /**
+ * Removes a sprite from the scene
+ */
+ void removeSprite();
+};
+
+#endif // ActionScene_H
diff --git a/src/BasicEnemy.cpp b/src/BasicEnemy.cpp
new file mode 100644
index 0000000..c42f961
--- /dev/null
+++ b/src/BasicEnemy.cpp
@@ -0,0 +1,38 @@
+#include <QGraphicsScene>
+#include <QApplication>
+#include <QDebug>
+
+#include "BasicEnemy.h"
+
+BasicEnemy::BasicEnemy(QGraphicsScene* scene, QPointF pos, QGraphicsItem* parent)
+ : Character(parent, scene)
+{
+ m_velocityY = 0;
+ m_velocityX = 1;
+
+ setPos(pos);
+ setZValue(2);
+ setShapeMode(QGraphicsPixmapItem::MaskShape);
+}
+
+BasicEnemy::~BasicEnemy()
+{
+
+}
+
+void BasicEnemy::advance(int phase)
+{
+ if(phase == 0)
+ return;
+
+ if(m_state == STATE_DEAD)
+ {
+ qDebug() << "this baddy be dead now";
+ removeMe();
+ }
+
+ if(phase == 1)
+ {
+ return;
+ }
+}
diff --git a/src/BasicEnemy.h b/src/BasicEnemy.h
new file mode 100644
index 0000000..37ce25e
--- /dev/null
+++ b/src/BasicEnemy.h
@@ -0,0 +1,21 @@
+#ifndef BasicEnemy_h
+#define BasicEnemy_h
+
+#include <QGraphicsPixmapItem>
+#include <QGraphicsScene>
+
+#include "Character.h"
+
+class BasicEnemy : public Character
+{
+public:
+ BasicEnemy(QGraphicsScene* scene, QPointF pos, QGraphicsItem* parent = 0);
+ virtual ~BasicEnemy();
+
+ void advance(int phase);
+
+private:
+
+};
+
+#endif // BasicEnemy_h
diff --git a/src/Character.cpp b/src/Character.cpp
new file mode 100644
index 0000000..83b18ce
--- /dev/null
+++ b/src/Character.cpp
@@ -0,0 +1,113 @@
+#include "Character.h"
+
+Character::Character(QGraphicsItem *parent, QGraphicsScene *scene)
+ : Sprite(parent, scene),
+ m_healthPoints(10),
+ m_shieldCapacity(0),
+ m_meleeDamage(5),
+ m_rangedDamage(0),
+ m_velocityX(0),
+ m_velocityY(0),
+ m_state(STATE_IDLE)
+{
+
+}
+
+Character::~Character()
+{
+}
+
+void Character::increaseHealthPoints(int hp)
+{
+ m_healthPoints += hp;
+}
+
+void Character::decreaseHealthPoints(int hp)
+{
+ m_healthPoints -= hp;
+
+ if(m_healthPoints <= 0)
+ {
+ m_state = STATE_DEAD;
+ }
+}
+
+void Character::setHealthPoints(int hp)
+{
+ m_healthPoints = hp;
+}
+
+int Character::getHealthPoints()
+{
+ return m_healthPoints;
+}
+
+void Character::increaseShieldCapacity(int shieldCapacity)
+{
+ m_shieldCapacity += shieldCapacity;
+}
+
+void Character::decreaseShieldCapacity(int shieldCapacity)
+{
+ m_shieldCapacity -= shieldCapacity;
+}
+
+void Character::setShieldCapacity(int shieldCapacity)
+{
+ m_shieldCapacity = shieldCapacity;
+}
+
+int Character::getShieldCapacity()
+{
+ return m_shieldCapacity;
+}
+
+void Character::setMeleeDamage(int damage)
+{
+ m_meleeDamage = damage;
+}
+
+int Character::getMeleeDamage()
+{
+ return m_meleeDamage;
+}
+
+void Character::setRangedDamage(int damage)
+{
+ m_rangedDamage = damage;
+}
+
+int Character::getRangedDamage()
+{
+ return m_rangedDamage;
+}
+
+void Character::setVelocityX(int x)
+{
+ m_velocityX = x;
+}
+
+void Character::setVelocityY(int y)
+{
+ m_velocityY = y;
+}
+
+int Character::getVelocityX()
+{
+ return m_velocityX;
+}
+
+int Character::getVelocityY()
+{
+ return m_velocityY;
+}
+
+void Character::setState(State s)
+{
+ m_state = s;
+}
+
+State Character::getState()
+{
+ return m_state;
+}
diff --git a/src/Character.h b/src/Character.h
new file mode 100644
index 0000000..b246169
--- /dev/null
+++ b/src/Character.h
@@ -0,0 +1,65 @@
+#ifndef Character_h
+#define Character_h
+
+#include "Sprite.h"
+
+enum State
+{
+ STATE_IDLE,
+ STATE_MOVING_RIGHT,
+ STATE_MOVING_LEFT,
+ STATE_DEAD,
+ STATE_JUMPING,
+ STATE_LANDING,
+ STATE_JUMPING_DOUBLE,
+ STATE_LANDING_DOUBLE_FORCE,
+ STATE_SPRINTING_RIGHT,
+ STATE_SPRINTING_LEFT,
+ STATE_CLIMB_UP,
+ STATE_CLIMG_DOWN
+};
+
+class Character: public Sprite
+{
+public:
+ explicit Character(QGraphicsItem *parent = 0, QGraphicsScene *scene = 0);
+ virtual ~Character();
+
+ void increaseHealthPoints(int);
+ void decreaseHealthPoints(int);
+ void setHealthPoints(int);
+ int getHealthPoints();
+
+ void increaseShieldCapacity(int);
+ void decreaseShieldCapacity(int);
+ void setShieldCapacity(int);
+ int getShieldCapacity();
+
+ void setMeleeDamage(int);
+ int getMeleeDamage();
+
+ void setRangedDamage(int);
+ int getRangedDamage();
+
+ void setVelocityX(int);
+ void setVelocityY(int);
+ int getVelocityX();
+ int getVelocityY();
+
+ void setState(State);
+ State getState();
+
+protected:
+ int m_healthPoints;
+ int m_shieldCapacity;
+
+ int m_meleeDamage;
+ int m_rangedDamage;
+
+ int m_velocityX;
+ int m_velocityY;
+
+ State m_state;
+};
+
+#endif // Character_h
diff --git a/src/Collectible.cpp b/src/Collectible.cpp
new file mode 100644
index 0000000..3bf298c
--- /dev/null
+++ b/src/Collectible.cpp
@@ -0,0 +1,25 @@
+#include "Collectible.h"
+
+#include <QSettings>
+
+Collectible::Collectible(QGraphicsItem *parent, QGraphicsScene *scene)
+ : Sprite(parent, scene)
+{
+
+}
+
+void Collectible::collected()
+{
+ setData(0, "colllected");
+ removeMe();
+}
+
+void Collectible::setValue(int value)
+{
+ m_value = value;
+}
+
+int Collectible::getValue() const
+{
+ return m_value;
+}
diff --git a/src/Collectible.h b/src/Collectible.h
new file mode 100644
index 0000000..7ac79d3
--- /dev/null
+++ b/src/Collectible.h
@@ -0,0 +1,19 @@
+#ifndef Collectible_H
+#define Collectible_H
+
+#include "Sprite.h"
+
+class Collectible : public Sprite
+{
+public:
+ Collectible(QGraphicsItem *parent = 0, QGraphicsScene *scene = 0);
+ void collected();
+
+ void setValue(int value);
+ int getValue() const;
+
+private:
+ int m_value;
+};
+
+#endif // Collectible_H
diff --git a/src/CreditsScene.cpp b/src/CreditsScene.cpp
new file mode 100644
index 0000000..cd1558c
--- /dev/null
+++ b/src/CreditsScene.cpp
@@ -0,0 +1,64 @@
+#include <QApplication>
+#include <QGraphicsTextItem>
+
+#include <QDebug>
+
+#include "GameView.h"
+#include "GraphicsButtonObject.h"
+#include "ParallaxScrollerStatic.h"
+
+#include "CreditsScene.h"
+
+CreditsScene::CreditsScene(const QString &name, const QRectF &rect, GameView *parent)
+ : GameScene(name, parent)
+{
+ setSceneRect(rect);
+
+ QString appDir = qApp->applicationDirPath();
+
+ m_background = new ParallaxScrollerStatic(this);
+ m_background->setLayerWidth(rect.width());
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer1.png"), QPointF(0,0), -2, 6);
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer2.png"), QPointF(0,0), -1, 4);
+
+ initializeScene();
+}
+
+CreditsScene::~CreditsScene()
+{
+ delete m_background;
+}
+
+void CreditsScene::initializeScene()
+{
+ addTitle("Credits");
+
+ QGraphicsTextItem* creditText = new QGraphicsTextItem(0, this);
+ creditText->setHtml("<b>WalRush</b> (v. 1.0) <br><br>"\
+ "WalRush was originally a project "\
+ "for course in TAMK.<br><br>"\
+ "Programming by:<br>"\
+ "Samu Laaksonen<br>"\
+ "Oskari Timperi<br><br>"\
+ "Graphics by: <br>"\
+ "Lauri Paakinaho<br><br>"\
+ "Copyright (c) 2010 of aforementioned persons. All rights reserved.<br>");
+ creditText->setPos(140, 130);
+ creditText->setFont(QFont("Arial", 14));
+
+ GraphicsButtonObject *btn;
+ btn = new GraphicsButtonObject(QPixmap(qApp->applicationDirPath() + "/gfx/buttons/back-arrow1.png"),
+ 0, this);
+ btn->setPressedPixmap(QPixmap(qApp->applicationDirPath() + "/gfx/buttons/back-arrow2.png"));
+ btn->setPos(720, 400);
+ btn->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
+ btn->setZValue(2);
+
+ connect(btn, SIGNAL(clicked()), gameView(), SLOT(showMenuScene()));
+}
+
+void CreditsScene::updateLogic()
+{
+ advance();
+ update();
+}
diff --git a/src/CreditsScene.h b/src/CreditsScene.h
new file mode 100644
index 0000000..144d135
--- /dev/null
+++ b/src/CreditsScene.h
@@ -0,0 +1,25 @@
+#ifndef CreditsScene_h
+#define CreditsScene_h
+
+#include "GameScene.h"
+
+#include "ParallaxScrollerStatic.h"
+
+class CreditsScene : public GameScene
+{
+public:
+ CreditsScene(const QString &name, const QRectF &rect, GameView *parent = 0);
+ virtual ~CreditsScene();
+
+ void updateLogic();
+
+ /**
+ * Initializes resources used on scene.
+ */
+ void initializeScene();
+
+private:
+ ParallaxScrollerStatic* m_background;
+};
+
+#endif // CreditsScene_h
diff --git a/src/GameScene.cpp b/src/GameScene.cpp
new file mode 100644
index 0000000..ed011c1
--- /dev/null
+++ b/src/GameScene.cpp
@@ -0,0 +1,96 @@
+#include <QPainter>
+#include <QGraphicsPixmapItem>
+#include <QDebug>
+
+#include "GameView.h"
+
+#include "GameScene.h"
+
+GameScene::GameScene(const QString &name, GameView *parent) :
+ QGraphicsScene(parent)
+{
+ setObjectName(name);
+ m_gameView = parent;
+}
+
+GameView* GameScene::gameView() const
+{
+ return m_gameView;
+}
+
+void GameScene::enterScene(GameScene *)
+{
+ qDebug() << __FUNCTION__ << objectName();
+}
+
+void GameScene::leaveScene(GameScene *)
+{
+ qDebug() << __FUNCTION__ << objectName();
+}
+
+QGraphicsPixmapItem* GameScene::addTitle(const QString &title, int pointSize)
+{
+ // // Draw text with a black outline and using a gradient as a brush :-)
+ QLinearGradient grad;
+
+ grad.setCoordinateMode(QGradient::ObjectBoundingMode);
+
+ grad.setStart(0, 0);
+ grad.setFinalStop(0, 1);
+
+ /* the old gradient
+ grad.setColorAt(0, Qt::white);
+ grad.setColorAt(0.25, QColor(226, 174, 31));
+ grad.setColorAt(0.5, QColor(149, 113, 16));
+ grad.setColorAt(0.51, Qt::white);
+ grad.setColorAt(1, QColor(68, 153, 213));
+ */
+
+ grad.setColorAt(0, Qt::white);
+ grad.setColorAt(0.20, QColor(137, 175, 201));
+ grad.setColorAt(0.35, QColor(35, 136, 207));
+ grad.setColorAt(0.5, QColor(32, 98, 145));
+ grad.setColorAt(0.65, QColor(35, 136, 207));
+ grad.setColorAt(0.80, QColor(137, 175, 201));
+ grad.setColorAt(1, Qt::white);
+
+ QFont font("Arial", pointSize);
+ font.setUnderline(true);
+ font.setLetterSpacing(QFont::PercentageSpacing, 95);
+ font.setWordSpacing(-20);
+
+ QFontMetrics metrics(font);
+// QRect bbox = metrics.boundingRect(title);
+
+// qDebug() << metrics.boundingRect("MAZENNUS");
+// qDebug() << metrics.width("MAZENNUS") << metrics.height();
+
+ QPainterPath path;
+ // path.addText(pixmap.width()/2-bbox.width()/2, 20+metrics.ascent(), font, "MAZENNUS");
+ path.addText(0, metrics.ascent(), font, title);
+
+ QImage img(metrics.width(title), metrics.height()+3, QImage::Format_ARGB32);
+
+ QPainter painter(&img);
+
+ // set dst pixels to transparent regardless of src
+ painter.setCompositionMode(QPainter::CompositionMode_Clear);
+ painter.fillRect(img.rect(), Qt::white);
+
+ // switch back to normal
+ painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
+
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(QPen(Qt::black, 3));
+ painter.setBrush(grad);
+
+ painter.drawPath(path);
+
+ QPixmap pixmap = QPixmap::fromImage(img);
+
+ QGraphicsPixmapItem *i = addPixmap(pixmap);
+
+ i->setPos(sceneRect().width()/2 - pixmap.width()/2, 20);
+
+ return i;
+}
diff --git a/src/GameScene.h b/src/GameScene.h
new file mode 100644
index 0000000..0d1921a
--- /dev/null
+++ b/src/GameScene.h
@@ -0,0 +1,55 @@
+#ifndef GameScene_h
+#define GameScene_h
+
+#include <QGraphicsScene>
+
+class GameView;
+
+//! Used in QGraphicsItem::setData()/data()
+static const int ITEM_OBJECTNAME = 0;
+
+
+/**
+ * The base class for all scenes in the game. GameScene class offers some
+ * convenience methods for all its children. This also helps the management
+ * of the scenes in GameView.
+ */
+class GameScene : public QGraphicsScene
+{
+ Q_OBJECT
+public:
+ explicit GameScene(const QString &name, GameView *parent = 0);
+
+ /**
+ * This should be called when the scene is entered, i.e. the scene
+ * becomes the active scene.
+ */
+ virtual void enterScene(GameScene *prev = 0);
+
+ /**
+ * This should be called when the scene is losing its active status.
+ */
+ virtual void leaveScene(GameScene *next = 0);
+
+ /**
+ * Used for updating contents of scene.
+ */
+ virtual void updateLogic() = 0;
+
+ /**
+ * Convenience method to get the associated GameView instance.
+ */
+ GameView* gameView() const;
+
+ QGraphicsPixmapItem *addTitle(const QString &title, int pointSize = 75);
+
+protected:
+ GameView* m_gameView;
+
+signals:
+
+public slots:
+
+};
+
+#endif // GameScene_h
diff --git a/src/GameView.cpp b/src/GameView.cpp
new file mode 100644
index 0000000..3ba2631
--- /dev/null
+++ b/src/GameView.cpp
@@ -0,0 +1,174 @@
+#include <QScrollBar>
+#include <QDebug>
+#include <QtOpenGL/QGLWidget>
+
+#include "ActionScene.h"
+#include "LevelSelectionScene.h"
+#include "CreditsScene.h"
+#include "SceneChanger.h"
+#include "MenuScene.h"
+
+#include "GameView.h"
+
+GameView *GameView::m_instance = NULL;
+
+GameView::GameView(QWidget *parent) :
+ QGraphicsView(parent)
+{
+ setViewport(new QGLWidget);
+
+ m_actionScene = new ActionScene("ActionScene", QRectF(0, 0, 6400, 480), this);
+ connect(m_actionScene, SIGNAL(gameOver()), this, SLOT(gameOver()));
+
+ LevelSelectionScene* levelSelectionScene = new LevelSelectionScene("LevelSelectionScene", QRectF(0, 0, 800, 480), this);
+ CreditsScene* creditsScene = new CreditsScene("CreditsScene", QRectF(0, 0, 800, 480), this);
+ MenuScene* menuScene = new MenuScene("MenuScene", QRectF(0, 0, 800, 480), this);
+
+ m_scenes.append(m_actionScene);
+ m_scenes.append(levelSelectionScene);
+ m_scenes.append(creditsScene);
+ m_scenes.append(menuScene);
+
+ m_sceneChanger = new SceneChanger("ChangerScene", QRectF(0, 0, 800, 480), this);
+
+ setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+ setViewportUpdateMode(FullViewportUpdate);
+ setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing);
+
+ m_gameTimer = new QTimer(this);
+ connect(m_gameTimer, SIGNAL(timeout()), this, SLOT(updateSceneLogic()));
+
+ showScene(menuScene);
+}
+
+GameView *GameView::instance()
+{
+ if (m_instance == NULL)
+ {
+ m_instance = new GameView;
+ }
+
+ return m_instance;
+}
+
+GameScene *GameView::getScene(const QString &name)
+{
+ Q_FOREACH(GameScene *scene, m_scenes)
+ {
+ if (scene->objectName() == name)
+ {
+ return scene;
+ }
+ }
+
+ qDebug() << "GameScene" << name << "not found";
+
+ return NULL;
+}
+
+GameScene *GameView::getCurrentScene()
+{
+ return qobject_cast<GameScene *>(scene());
+}
+
+void GameView::showScene(const QString &name)
+{
+ GameScene *scene = NULL;
+
+ Q_FOREACH(GameScene *s, m_scenes)
+ {
+ if (s->objectName() == name)
+ {
+ scene = s;
+ break;
+ }
+ }
+
+ if (scene)
+ {
+ changeScene(scene);
+ }
+ else
+ {
+ qDebug() << "no such scene:" << name;
+ }
+}
+
+void GameView::changeScene(GameScene* scene)
+{
+ GameScene *current = getCurrentScene();
+ showScene(m_sceneChanger);
+
+ if( current->objectName() == "CreditsScene" && scene->objectName() == "MenuScene" )
+ m_sceneChanger->changeScene(current, scene, true);
+ else if( current->objectName() == "MenuScene" && scene->objectName() == "LevelSelectionScene" )
+ m_sceneChanger->changeScene(current, scene, true);
+ else if( current->objectName() == "LevelSelectionScene" && scene->objectName() == "ActionScene" )
+ m_sceneChanger->changeScene(current, scene, true);
+ else
+ m_sceneChanger->changeScene(current, scene, false);
+}
+
+void GameView::showScene(GameScene *scene)
+{
+ Q_ASSERT(scene != NULL);
+
+ GameScene *old = getCurrentScene();
+
+ if (scene == old)
+ return;
+
+ m_gameTimer->stop();
+
+ if (old)
+ old->leaveScene(scene);
+
+ setScene(scene);
+
+ scene->enterScene(old);
+
+ m_gameTimer->start(1000.0f / 60.0f);
+}
+
+void GameView::updateSceneLogic()
+{
+ GameScene *scene = getCurrentScene();
+
+ if (scene)
+ {
+ scene->updateLogic();
+ }
+}
+
+ActionScene* GameView::getActionScene()
+{
+ return m_actionScene;
+}
+
+void GameView::gameOver()
+{
+ qDebug() << __FUNCTION__;
+ // not so simple, man
+ showScene("LevelSelectionScene");
+}
+
+void GameView::showMenuScene()
+{
+ showScene("MenuScene");
+}
+
+void GameView::showLevelSelectionScene()
+{
+ showScene("LevelSelectionScene");
+}
+
+void GameView::showCreditsScene()
+{
+ showScene("CreditsScene");
+}
+
+void GameView::showActionScene()
+{
+ showScene("ActionScene");
+}
diff --git a/src/GameView.h b/src/GameView.h
new file mode 100644
index 0000000..b45dfb6
--- /dev/null
+++ b/src/GameView.h
@@ -0,0 +1,57 @@
+#ifndef GameView_h
+#define GameView_h
+
+#include <QGraphicsView>
+#include <QList>
+#include <QTimer>
+
+class ActionScene;
+class GameScene;
+class SceneChanger;
+
+class GameView : public QGraphicsView
+{
+ Q_OBJECT
+public:
+ static GameView *instance();
+
+ GameScene *getScene(const QString &);
+ GameScene *getCurrentScene();
+
+ ActionScene* getActionScene();
+
+signals:
+
+public slots:
+
+ void gameOver();
+
+ void showMenuScene();
+ void showLevelSelectionScene();
+ void showCreditsScene();
+ void showActionScene();
+
+ void changeScene(GameScene* );
+ void showScene(GameScene *);
+ void showScene(const QString &);
+
+private slots:
+
+ //! Calls GameScene::updateLogic of the current scene
+ void updateSceneLogic();
+
+private:
+ explicit GameView(QWidget *parent = 0);
+
+ static GameView *m_instance;
+
+ ActionScene* m_actionScene;
+ SceneChanger* m_sceneChanger;
+
+ QList<GameScene *> m_scenes;
+
+ //! Timer used to scene updates
+ QTimer *m_gameTimer;
+};
+
+#endif // GameView_h
diff --git a/src/GraphicsButtonObject.cpp b/src/GraphicsButtonObject.cpp
new file mode 100644
index 0000000..d5697fa
--- /dev/null
+++ b/src/GraphicsButtonObject.cpp
@@ -0,0 +1,137 @@
+#include <QDebug>
+#include <QGraphicsSceneMouseEvent>
+#include <QPainter>
+#include <QPainterPath>
+#include <QFontMetrics>
+
+#include "GraphicsButtonObject.h"
+
+QPixmap *GraphicsButtonObject::s_tmpGfx = 0;
+int GraphicsButtonObject::s_ref = 0;
+
+GraphicsButtonObject::GraphicsButtonObject(const QPixmap &releasedPixmap,
+ QGraphicsPixmapItem *parent,
+ QGraphicsScene *scene)
+ : QObject(0),
+ QGraphicsPixmapItem(releasedPixmap, parent, scene)
+
+{
+ s_ref++;
+ m_releasedGfx = releasedPixmap;
+}
+
+GraphicsButtonObject::GraphicsButtonObject(QGraphicsPixmapItem *parent,
+ QGraphicsScene *scene)
+ : QObject(0),
+ QGraphicsPixmapItem(parent, scene)
+
+{
+ s_ref++;
+}
+
+GraphicsButtonObject::GraphicsButtonObject(const QString &str,
+ QGraphicsPixmapItem *parent,
+ QGraphicsScene *scene)
+ : QObject(0),
+ QGraphicsPixmapItem(parent, scene)
+{
+ s_ref++;
+
+ int pw = 280;
+ int ph = 60;
+
+ // Draw the gradients only once
+ if (!s_tmpGfx)
+ {
+ //DBG("generating button gradients");
+
+ s_tmpGfx = new QPixmap(pw, ph);
+ QPainter painter(s_tmpGfx);
+
+ painter.setPen(Qt::NoPen);
+
+ painter.setBrush(Qt::white);
+ painter.drawRect(s_tmpGfx->rect());
+
+ QLinearGradient grad;
+
+ grad.setCoordinateMode(QGradient::ObjectBoundingMode);
+
+ grad.setStart(0, 0);
+ grad.setFinalStop(0, 1);
+
+ grad.setColorAt(0, Qt::white);
+ grad.setColorAt(0.20, QColor(137, 175, 201));
+ grad.setColorAt(0.35, QColor(35, 136, 207));
+ grad.setColorAt(0.5, QColor(32, 98, 145));
+ grad.setColorAt(0.65, QColor(35, 136, 207));
+ grad.setColorAt(0.80, QColor(137, 175, 201));
+ grad.setColorAt(1, Qt::white);
+
+ painter.setBrush(QBrush(grad));
+ painter.drawRect(4, 4, pw-8, ph-8);
+ }
+
+ QPixmap released = QPixmap(*s_tmpGfx);
+ QPainter painter(&released);
+
+ QFont font("Arial", 36);
+ font.setLetterSpacing(QFont::PercentageSpacing, 95);
+
+ QFontMetrics metrics(font);
+ QRect bbox = metrics.boundingRect(str);
+
+ QPainterPath path;
+
+ int x = (ph - metrics.height()) / 2;
+ path.addText(pw/2-bbox.width()/2, x + metrics.ascent(), font, str);
+
+ painter.setRenderHint(QPainter::Antialiasing);
+ painter.setPen(QPen(Qt::black, 3));
+ painter.setBrush(Qt::white);
+ painter.drawPath(path);
+
+ QPixmap pressed(released);
+
+ QPainter painter2(&pressed);
+ painter2.setBrush(QColor(0, 0, 0, 127));
+ painter2.drawRect(pressed.rect());
+
+ m_releasedGfx = released;
+ m_pressedGfx = pressed;
+
+ setPixmap(released);
+}
+
+GraphicsButtonObject::~GraphicsButtonObject()
+{
+ s_ref--;
+
+ if (s_tmpGfx && s_ref == 0)
+ {
+ delete s_tmpGfx;
+ s_tmpGfx = 0;
+ }
+}
+
+void GraphicsButtonObject::setPressedPixmap(const QPixmap &pixmap)
+{
+ m_pressedGfx = pixmap;
+}
+
+void GraphicsButtonObject::setReleasedPixmap(const QPixmap &pixmap)
+{
+ m_releasedGfx = pixmap;
+}
+
+void GraphicsButtonObject::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
+{
+ setPixmap(m_releasedGfx);
+ emit clicked();
+}
+
+void GraphicsButtonObject::mousePressEvent(QGraphicsSceneMouseEvent *event)
+{
+ setPixmap(m_pressedGfx);
+}
+
diff --git a/src/GraphicsButtonObject.h b/src/GraphicsButtonObject.h
new file mode 100644
index 0000000..dbbcaa3
--- /dev/null
+++ b/src/GraphicsButtonObject.h
@@ -0,0 +1,59 @@
+#ifndef GraphicsButtonObject_h
+#define GraphicsButtonObject_h
+
+#include <QObject>
+#include <QGraphicsPixmapItem>
+#include <QGraphicsLayoutItem>
+#include <QPixmap>
+
+/**
+ * Used for creating nice buttons to menus.
+ */
+class GraphicsButtonObject :
+ public QObject,
+ public QGraphicsPixmapItem
+{
+ Q_OBJECT
+
+public:
+ explicit GraphicsButtonObject(const QPixmap &releasedPixmap,
+ QGraphicsPixmapItem *parent = 0,
+ QGraphicsScene *scene = 0);
+
+ explicit GraphicsButtonObject(QGraphicsPixmapItem *parent = 0,
+ QGraphicsScene *scene = 0);
+
+ GraphicsButtonObject(const QString &str,
+ QGraphicsPixmapItem *parent = 0,
+ QGraphicsScene *scene = 0);
+
+ ~GraphicsButtonObject();
+
+ /**
+ * Sets gfx to indicate pressed-state on button.
+ * @param &pixmap The pixmap used as gfx
+ */
+ void setPressedPixmap(const QPixmap &pixmap);
+
+ /**
+ * Sets gfx to indicate released/normal-state on button.
+ * @param &pixmap The pixmap used as gfx
+ */
+ void setReleasedPixmap(const QPixmap &pixmap);
+
+protected:
+ void mousePressEvent(QGraphicsSceneMouseEvent *event);
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
+
+private:
+ QPixmap m_pressedGfx;
+ QPixmap m_releasedGfx;
+
+ static QPixmap *s_tmpGfx;
+ static int s_ref;
+
+signals:
+ void clicked();
+};
+
+#endif // GraphicsButtonObject_h
diff --git a/src/GraphicsPixmapObject.cpp b/src/GraphicsPixmapObject.cpp
new file mode 100644
index 0000000..b916bfc
--- /dev/null
+++ b/src/GraphicsPixmapObject.cpp
@@ -0,0 +1,7 @@
+#include "GraphicsPixmapObject.h"
+
+GraphicsPixmapObject::GraphicsPixmapObject(const QPixmap &pixmap,
+ QGraphicsItem *parent) :
+ QGraphicsPixmapItem(pixmap, parent)
+{
+}
diff --git a/src/GraphicsPixmapObject.h b/src/GraphicsPixmapObject.h
new file mode 100644
index 0000000..4ca8005
--- /dev/null
+++ b/src/GraphicsPixmapObject.h
@@ -0,0 +1,30 @@
+#ifndef GraphicsPixmapObject_h
+#define GraphicsPixmapObject_h
+
+#include <QObject>
+#include <QGraphicsPixmapItem>
+
+/**
+ * Used for the animation in SceneChanger. As QPropertyAnimation uses
+ * Qt's property system, we have to derive from QObject to use properties.
+ * So GraphicsPixmapObject derives from QObject and QGraphicsPixmapItem
+ * and publishes the opacity as a property.
+ */
+class GraphicsPixmapObject : public QObject, public QGraphicsPixmapItem
+{
+ Q_OBJECT
+
+ Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity);
+ Q_PROPERTY(QPointF pos READ pos WRITE setPos);
+
+public:
+ explicit GraphicsPixmapObject(const QPixmap &pixmap,
+ QGraphicsItem *parent = 0);
+
+signals:
+
+public slots:
+
+};
+
+#endif // GraphicsPixmapObject_h
diff --git a/src/Hero.cpp b/src/Hero.cpp
new file mode 100644
index 0000000..5af9b6a
--- /dev/null
+++ b/src/Hero.cpp
@@ -0,0 +1,41 @@
+#include <QApplication>
+#include <QGraphicsScene>
+#include <QGraphicsPixmapItem>
+#include <QSettings>
+#include <QDebug>
+
+#include "Hero.h"
+#include "GameScene.h"
+
+Hero::Hero(QGraphicsScene* scene, QPointF pos, QGraphicsItem* parent)
+ : Character(parent, scene)
+{
+ m_state = STATE_IDLE;
+
+ // something small for testing purposes
+ setHealthPoints(15);
+
+ setPos(pos);
+ setZValue(2);
+
+ setShapeMode(QGraphicsPixmapItem::MaskShape);
+}
+
+Hero::~Hero()
+{
+}
+
+void Hero::advance(int phase)
+{
+ Character::advance(phase);
+
+ if (phase == 0)
+ return;
+
+ // err, no good.
+ if(m_state == STATE_DEAD)
+ {
+ gameOver();
+ //removeMe();
+ }
+}
diff --git a/src/Hero.h b/src/Hero.h
new file mode 100644
index 0000000..ec25944
--- /dev/null
+++ b/src/Hero.h
@@ -0,0 +1,25 @@
+#ifndef Hero_h
+#define Hero_h
+
+#include "Character.h"
+
+class Hero: public Character
+{
+ Q_OBJECT
+
+public:
+ Hero(QGraphicsScene* scene, QPointF pos, QGraphicsItem* parent = 0);
+ virtual ~Hero();
+
+ void advance(int phase);
+
+signals:
+ void gameOver();
+ void levelComplete();
+ void updateUI();
+
+private:
+
+};
+
+#endif // Hero_h
diff --git a/src/LevelSelectionScene.cpp b/src/LevelSelectionScene.cpp
new file mode 100644
index 0000000..02516b1
--- /dev/null
+++ b/src/LevelSelectionScene.cpp
@@ -0,0 +1,98 @@
+#include <QApplication>
+#include <QDir>
+#include <QSettings>
+#include <QDebug>
+
+#include "ParallaxScrollerStatic.h"
+#include "GraphicsButtonObject.h"
+#include "GameView.h"
+#include "ActionScene.h"
+
+#include "LevelSelectionScene.h"
+
+LevelSelectionScene::LevelSelectionScene(const QString &name, const QRectF &rect, GameView *parent)
+ : GameScene(name, parent)
+{
+ setSceneRect(rect);
+
+ QString appDir = qApp->applicationDirPath();
+
+ m_background = new ParallaxScrollerStatic(this);
+ m_background->setLayerWidth(rect.width());
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer1.png"), QPointF(0,0), -2, 6);
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer2.png"), QPointF(0,0), -1, 4);
+
+ initializeScene();
+}
+
+LevelSelectionScene::~LevelSelectionScene()
+{
+ delete m_background;
+}
+
+void LevelSelectionScene::initializeScene()
+{
+ addTitle("Level Selection");
+
+ QSettings settings;
+
+ QDir levelDirectory(settings.value("path/levels").toString());
+
+ /* TODO: check levels directory for levels (should be 6) and create corresponding buttons
+ that react when pressed and lauch the level
+ */
+ int yoff = 200;
+ int xoff = 50;
+ int count = 1;
+
+ Q_FOREACH(QString f, levelDirectory.entryList(QDir::Files, QDir::Name))
+ {
+ QString lvlNum;
+ lvlNum.setNum(count);
+ GraphicsButtonObject *btn = new GraphicsButtonObject(lvlNum, 0, this);
+ btn->setPos(210 + xoff, yoff);
+ btn->setProperty("level_path", levelDirectory.filePath(f));
+
+ qDebug() << levelDirectory.filePath(f);
+
+ connect(btn, SIGNAL(clicked()), this, SLOT(levelSelection()));
+
+// if(count % 4 == 0)
+// {
+// yoff += btn->boundingRect().bottom() + 20;
+// xoff = 50;
+// }
+
+ count++;
+ //xoff += btn->boundingRect().bottom() + 20;
+ yoff += btn->boundingRect().bottom() + 10;
+ }
+
+ GraphicsButtonObject *btn;
+ btn = new GraphicsButtonObject(QPixmap(QApplication::applicationDirPath() + "/gfx/buttons/back-arrow1.png"), 0, this);
+ btn->setPressedPixmap(QPixmap(QApplication::applicationDirPath() + "/gfx/buttons/back-arrow2.png"));
+ btn->setPos(720, 400);
+ btn->setShapeMode(QGraphicsPixmapItem::BoundingRectShape);
+ btn->setZValue(2);
+
+ connect(btn, SIGNAL(clicked()), gameView(), SLOT(showMenuScene()));
+}
+
+void LevelSelectionScene::levelSelection()
+{
+ QVariant level_path = sender()->property("level_path");
+
+ Q_ASSERT(level_path.isValid());
+
+ QString levelPath = level_path.toString();
+
+ gameView()->getActionScene()->loadMap(levelPath);
+ gameView()->showActionScene();
+}
+
+void LevelSelectionScene::updateLogic()
+{
+ advance();
+ update();
+}
+
diff --git a/src/LevelSelectionScene.h b/src/LevelSelectionScene.h
new file mode 100644
index 0000000..61eb70a
--- /dev/null
+++ b/src/LevelSelectionScene.h
@@ -0,0 +1,36 @@
+#ifndef LevelSelectionScene_h
+#define LevelSelectionScene_h
+
+#include "GameScene.h"
+
+#include "ParallaxScrollerStatic.h"
+
+class LevelSelectionScene : public GameScene
+{
+ Q_OBJECT
+public:
+ LevelSelectionScene(const QString &name, const QRectF &rect, GameView *parent = 0);
+ virtual ~LevelSelectionScene();
+
+ /**
+ * Updates graphics on scene. Mainly background but levelselectionbuttons too.
+ */
+ void updateLogic();
+
+ /**
+ * Loads level data and creates selection grid
+ */
+ void initializeScene();
+
+private slots:
+ /**
+ * This function calls ActionScene with information about what level to load.
+ */
+ void levelSelection();
+
+private:
+ ParallaxScrollerStatic* m_background;
+
+};
+
+#endif // LevelSelectionScene_h
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
new file mode 100644
index 0000000..cac85c1
--- /dev/null
+++ b/src/MainWindow.cpp
@@ -0,0 +1,17 @@
+#include "GameView.h"
+
+#include "MainWindow.h"
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent)
+{
+ resize(800, 480);
+ setWindowTitle("Generic platformer base, version 0.0.1 alpha");
+
+ setCentralWidget(m_gameView->instance());
+}
+
+MainWindow::~MainWindow()
+{
+
+}
diff --git a/src/MainWindow.h b/src/MainWindow.h
new file mode 100644
index 0000000..d6246f8
--- /dev/null
+++ b/src/MainWindow.h
@@ -0,0 +1,23 @@
+#ifndef MainWindow_h
+#define MainWindow_h
+
+#include <QMainWindow>
+
+class GameView;
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+public:
+ explicit MainWindow(QWidget *parent = 0);
+ virtual ~MainWindow();
+
+signals:
+
+public slots:
+
+private:
+ GameView* m_gameView;
+};
+
+#endif // MainWindow_h
diff --git a/src/MenuScene.cpp b/src/MenuScene.cpp
new file mode 100644
index 0000000..1deaa66
--- /dev/null
+++ b/src/MenuScene.cpp
@@ -0,0 +1,53 @@
+#include <QApplication>
+
+#include "ParallaxScrollerStatic.h"
+#include "GraphicsButtonObject.h"
+#include "GameView.h"
+
+#include "MenuScene.h"
+
+MenuScene::MenuScene(const QString &name, const QRectF &rect, GameView *parent)
+ : GameScene(name, parent)
+{
+ setSceneRect(rect);
+
+ QString appDir = qApp->applicationDirPath();
+
+ m_background = new ParallaxScrollerStatic(this);
+ m_background->setLayerWidth(rect.width());
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer1.png"), QPointF(0,0), -2, 6);
+ m_background->addParallaxScrollItem(QString(appDir + "/gfx/bg/layer2.png"), QPointF(0,0), -1, 4);
+
+ initializeScene();
+}
+
+MenuScene::~MenuScene()
+{
+ delete m_background;
+}
+
+void MenuScene::updateLogic()
+{
+ advance();
+ update();
+}
+
+void MenuScene::initializeScene()
+{
+ int yoff = 200;
+
+ GraphicsButtonObject *btn1 = new GraphicsButtonObject("Play", 0, this);
+ btn1->setPos(260, yoff);
+
+ GraphicsButtonObject *btn2 = new GraphicsButtonObject("Credits", 0, this);
+ btn2->setPos(260, btn1->pos().y() + btn1->boundingRect().bottom() + 20);
+
+ GraphicsButtonObject *btn3 = new GraphicsButtonObject("Quit", 0, this);
+ btn3->setPos(260, btn2->pos().y() + btn2->boundingRect().bottom() + 20);
+
+ connect(btn1, SIGNAL(clicked()), gameView(), SLOT(showLevelSelectionScene()));
+ connect(btn2, SIGNAL(clicked()), gameView(), SLOT(showCreditsScene()));
+ connect(btn3, SIGNAL(clicked()), qApp, SLOT(quit()));
+
+ addTitle("Platformer");
+}
diff --git a/src/MenuScene.h b/src/MenuScene.h
new file mode 100644
index 0000000..501550b
--- /dev/null
+++ b/src/MenuScene.h
@@ -0,0 +1,32 @@
+#ifndef MenuScene_h
+#define MenuScene_h
+
+#include "GameScene.h"
+
+#include "ParallaxScrollerStatic.h"
+
+class GameView;
+
+/**
+ * Scene for first view displayed when application launches.
+ */
+class MenuScene : public GameScene
+{
+ Q_OBJECT
+public:
+ MenuScene(const QString &name, const QRectF &rect, GameView *parent = 0);
+ virtual ~MenuScene();
+
+ void initializeScene();
+
+ void updateLogic();
+
+signals:
+
+public slots:
+
+private:
+ ParallaxScrollerStatic* m_background;
+};
+
+#endif // MenuScene_h
diff --git a/src/ParallaxScrollerItem.cpp b/src/ParallaxScrollerItem.cpp
new file mode 100644
index 0000000..0480b29
--- /dev/null
+++ b/src/ParallaxScrollerItem.cpp
@@ -0,0 +1,47 @@
+#include "ParallaxScrollerItem.h"
+
+#include <QGraphicsView>
+#include <QGraphicsScene>
+#include <QScrollBar>
+
+#include <QPainter>
+#include <QDebug>
+
+ParallaxScrollerItem::ParallaxScrollerItem(QString layer, QPointF pos,
+ qreal depthFactor, qreal speed,
+ qreal factor, QGraphicsScene *scene,
+ QGraphicsItem *parent)
+ : QGraphicsPixmapItem(parent, scene), m_scrollSpeed(speed), m_factor(factor)
+{
+ m_scrollingSpeedSlowingFactor = 0;
+ m_layer.load(layer);
+ setPixmap(m_layer);
+ setPos(pos);
+ setZValue(depthFactor);
+}
+
+ParallaxScrollerItem::~ParallaxScrollerItem()
+{
+
+}
+
+void ParallaxScrollerItem::advance(int phase)
+{
+ if(phase == 1)
+ {
+ m_scrollingSpeedSlowingFactor++;
+ if(m_scrollingSpeedSlowingFactor % m_factor == 0)
+ {
+ m_scrollingSpeedSlowingFactor = 0;
+ setPos(x()-m_scrollSpeed, y());
+
+ QGraphicsView* v = scene()->views().first();
+ QPointF p = v->mapToScene(v->viewport()->x(), 0);
+
+ if(x() <= p.x()-pixmap().width())
+ {
+ setPos(p.x()+pixmap().width()-m_scrollSpeed, y());
+ }
+ }
+ }
+}
diff --git a/src/ParallaxScrollerItem.h b/src/ParallaxScrollerItem.h
new file mode 100644
index 0000000..13269c8
--- /dev/null
+++ b/src/ParallaxScrollerItem.h
@@ -0,0 +1,24 @@
+#ifndef ParallaxScrollerItem_h
+#define ParallaxScrollerItem_h
+
+#include <QGraphicsPixmapItem>
+
+class ParallaxScrollerItem : public QGraphicsPixmapItem
+{
+public:
+ ParallaxScrollerItem(QString layer, QPointF pos,
+ qreal depthFactor, qreal speed,
+ qreal factor, QGraphicsScene* scene,
+ QGraphicsItem* parent = 0);
+ virtual ~ParallaxScrollerItem();
+
+ void advance(int phase);
+
+private:
+ int m_scrollingSpeedSlowingFactor;
+ int m_factor;
+ qreal m_scrollSpeed;
+ QPixmap m_layer;
+};
+
+#endif // ParallaxScrollerItem_h
diff --git a/src/ParallaxScrollerStatic.cpp b/src/ParallaxScrollerStatic.cpp
new file mode 100644
index 0000000..c2642e9
--- /dev/null
+++ b/src/ParallaxScrollerStatic.cpp
@@ -0,0 +1,29 @@
+#include "ParallaxScrollerItem.h"
+
+#include "ParallaxScrollerStatic.h"
+
+ParallaxScrollerStatic::ParallaxScrollerStatic(QGraphicsScene *scene)
+{
+ m_parent = scene;
+}
+
+ParallaxScrollerStatic::~ParallaxScrollerStatic()
+{
+
+}
+
+void ParallaxScrollerStatic::setLayerWidth(int width)
+{
+ m_layerWidth = width;
+}
+
+void ParallaxScrollerStatic::addParallaxScrollItem(const QString &layer, QPointF pos, qreal depth, qreal slowingFactor)
+{
+ ParallaxScrollerItem* firstPartOfLayer = new ParallaxScrollerItem(layer, QPointF(pos.x(), pos.y()),
+ depth, 1, slowingFactor, m_parent);
+ ParallaxScrollerItem* secondPartOfLayer = new ParallaxScrollerItem(layer, QPointF(pos.x()+m_layerWidth, 0),
+ depth, 1, slowingFactor, m_parent);
+
+ m_layersFirstWave.append(firstPartOfLayer);
+ m_layerSecondWave.append(secondPartOfLayer);
+}
diff --git a/src/ParallaxScrollerStatic.h b/src/ParallaxScrollerStatic.h
new file mode 100644
index 0000000..04e9547
--- /dev/null
+++ b/src/ParallaxScrollerStatic.h
@@ -0,0 +1,26 @@
+#ifndef ParallaxScrollerStatic_h
+#define ParallaxScrollerStatic_h
+
+#include <QStringList>
+#include <QPointF>
+#include <QGraphicsScene>
+
+class ParallaxScrollerItem;
+
+class ParallaxScrollerStatic
+{
+public:
+ ParallaxScrollerStatic(QGraphicsScene* scene);
+ virtual ~ParallaxScrollerStatic();
+
+ void setLayerWidth(int width);
+ void addParallaxScrollItem(const QString& layer, QPointF pos, qreal depth, qreal slowingFactor);
+
+private:
+ QGraphicsScene* m_parent;
+ QVector<ParallaxScrollerItem*> m_layersFirstWave;
+ QVector<ParallaxScrollerItem*> m_layerSecondWave;
+ int m_layerWidth;
+};
+
+#endif // ParallaxScrollerStatic_h
diff --git a/src/SceneChanger.cpp b/src/SceneChanger.cpp
new file mode 100644
index 0000000..2aa8b86
--- /dev/null
+++ b/src/SceneChanger.cpp
@@ -0,0 +1,107 @@
+#include "SceneChanger.h"
+#include "GameScene.h"
+#include "GameView.h"
+#include "GraphicsPixmapObject.h"
+
+#include <QTimer>
+#include <QParallelAnimationGroup>
+#include <QPropertyAnimation>
+
+SceneChanger::SceneChanger(QString name, const QRectF &rect, GameView *parent) :
+ GameScene(name, parent)
+{
+ setSceneRect(rect);
+
+ m_anim = new QParallelAnimationGroup(this);
+ connect(m_anim, SIGNAL(finished()), this, SLOT(animationFinished()));
+ setDuration(500);
+
+ m_updateTimer = new QTimer(this);
+ connect(m_updateTimer, SIGNAL(timeout()), this, SLOT(update()));
+}
+
+void SceneChanger::updateLogic()
+{
+ advance();
+ update();
+}
+
+void SceneChanger::setDuration(int msecs)
+{
+ m_duration = msecs;
+}
+
+void SceneChanger::changeScene(GameScene *from, GameScene *to, bool rightToLeft)
+{
+ m_fromScene = from;
+ m_toScene = to;
+
+ int width = from->sceneRect().width();
+ int height = from->sceneRect().height();
+
+ clear();
+ m_anim->clear();
+
+ QPixmap from_pixmap(width, height);
+ QPixmap to_pixmap(width, height);
+
+ QPainter from_painter(&from_pixmap);
+ QPainter to_painter(&to_pixmap);
+
+ from->render(&from_painter);
+ to->render(&to_painter);
+
+ GraphicsPixmapObject *from_obj = new GraphicsPixmapObject(from_pixmap);
+ from_obj->setPos(0, 0);
+
+ GraphicsPixmapObject *to_obj = new GraphicsPixmapObject(to_pixmap);
+ to_obj->setPos(0, 0);
+
+ addItem(from_obj);
+ addItem(to_obj);
+
+ QPropertyAnimation *from_anim = new QPropertyAnimation(from_obj, "pos",
+ this);
+
+ QPropertyAnimation *to_anim = new QPropertyAnimation(to_obj, "pos",
+ this);
+
+ if( rightToLeft )
+ {
+ from_anim->setDuration(m_duration);
+ from_anim->setStartValue(QPointF(0.0, 0.0));
+ from_anim->setEndValue(QPointF(-(float)width, 0.0));
+ from_anim->setEasingCurve(QEasingCurve::OutCubic); //or OutBounce, OutQuint...
+
+ to_anim->setDuration(m_duration);
+ to_anim->setStartValue(QPointF((float)width, 0.0));
+ to_anim->setEndValue(QPointF(0.0, 0.0));
+ to_anim->setEasingCurve(QEasingCurve::OutCubic); //or OutBounce, OutQuint...
+ }
+ else
+ {
+ from_anim->setDuration(m_duration);
+ from_anim->setStartValue(QPointF(0.0, 0.0));
+ from_anim->setEndValue(QPointF((float)width, 0.0));
+ from_anim->setEasingCurve(QEasingCurve::OutCubic); //or OutBounce, OutQuint...
+
+ to_anim->setDuration(m_duration);
+ to_anim->setStartValue(QPointF(-(float)width, 0.0));
+ to_anim->setEndValue(QPointF(0.0, 0.0));
+ to_anim->setEasingCurve(QEasingCurve::OutCubic); //or OutBounce, OutQuint...
+
+ }
+ m_anim->addAnimation(from_anim);
+ m_anim->addAnimation(to_anim);
+
+ // Use a little more frequent update than normally
+ m_updateTimer->start(1000 / 100);
+ m_anim->start();
+}
+
+void SceneChanger::animationFinished()
+{
+ m_updateTimer->stop();
+
+ gameView()->showScene(m_toScene);
+}
diff --git a/src/SceneChanger.h b/src/SceneChanger.h
new file mode 100644
index 0000000..fee3b48
--- /dev/null
+++ b/src/SceneChanger.h
@@ -0,0 +1,74 @@
+#ifndef SceneChanger_h
+#define SceneChanger_h
+
+#include "GameScene.h"
+
+class QParallelAnimationGroup;
+class QTimer;
+class GameView;
+
+/**
+ * Animates the change from a scene to another. Start the change
+ * with SceneChanger::changeScene(). After the animation is done,
+ * SceneChanger calls MainView::showScene for the scene that is
+ * to be shown.
+ *
+ * Basically changeScene() just takes a "screenshot" of both the
+ * scenes, adds the shots to itself and uses QPropertyAnimation
+ * to do the animation.
+ */
+class SceneChanger : public GameScene
+{
+ Q_OBJECT
+public:
+ explicit SceneChanger(QString name, const QRectF &rect, GameView *parent = 0);
+
+ /**
+ * Start the scene change animation.
+ *
+ * @param from The scene we are changing from
+ * @param to The scene we are changing to
+ */
+ void changeScene(GameScene *from, GameScene *to, bool rightToLeft);
+
+ /**
+ * Sets the duration of the animation in milliseconds. Default
+ * duration is 250 milliseconds.
+ */
+ void setDuration(int msecs);
+
+ void updateLogic();
+
+private:
+
+ /**
+ * Center of all the animation.
+ */
+ QParallelAnimationGroup *m_anim;
+
+ GameScene *m_fromScene;
+ GameScene *m_toScene;
+
+ /**
+ * Time in ms how long it takes to animate from view to an other.
+ */
+ int m_duration;
+
+ /**
+ * Used for faster update on animating transition
+ * than normal rendering needs.
+ */
+ QTimer *m_updateTimer;
+
+signals:
+
+private slots:
+ /**
+ * Called after m_anim has finished the animation. Calls
+ * MainView::showScene() to show the new scene.
+ */
+ void animationFinished();
+
+};
+
+#endif // SceneChanger_h
diff --git a/src/Sprite.cpp b/src/Sprite.cpp
new file mode 100644
index 0000000..b921394
--- /dev/null
+++ b/src/Sprite.cpp
@@ -0,0 +1,108 @@
+#include "Sprite.h"
+
+Sprite::Sprite(QGraphicsItem *parent, QGraphicsScene *scene)
+ : QObject(0),
+ QGraphicsPixmapItem(parent, scene),
+ m_currentFrame(0),
+ m_interval(1000/20)
+{
+ addAnimation("default", FrameList());
+ setAnimation("default");
+
+ m_timer.invalidate();
+}
+
+void Sprite::addFrame(const QString &anim, QPixmap frame)
+{
+ if (!m_animations.contains(anim))
+ {
+ FrameList l;
+ m_animations[anim] = l;
+ }
+
+ m_animations[anim].append(frame);
+}
+
+void Sprite::addAnimation(const QString &anim, const FrameList &frames)
+{
+ if (!m_animations.contains(anim))
+ {
+ m_animations[anim] = frames;
+ }
+ else
+ {
+ m_animations[anim].append(frames);
+ }
+}
+
+void Sprite::setFrame(int frame)
+{
+ if (frame < 0 || frame >= getCurrentAnimation().size())
+ {
+ qWarning("invalid frame number %d", frame);
+ return;
+ }
+
+ m_currentFrame = frame;
+ setPixmap(getFramePixmap());
+}
+
+int Sprite::getFrame() const
+{
+ return m_currentFrame;
+}
+
+QPixmap Sprite::getFramePixmap() const
+{
+ return getCurrentAnimation()[m_currentFrame];
+}
+
+int Sprite::getFrameCount() const
+{
+ return getCurrentAnimation().size();
+}
+
+Sprite::FrameList Sprite::getCurrentAnimation() const
+{
+ return m_animations[m_currentAnimation];
+}
+
+void Sprite::nextFrame()
+{
+ m_currentFrame++;
+
+ if (m_currentFrame >= getCurrentAnimation().size())
+ m_currentFrame = 0;
+
+ setFrame(m_currentFrame);
+}
+
+void Sprite::setAnimation(const QString &anim)
+{
+ if (!m_animations.contains(anim))
+ {
+ qWarning("animation '%s' doesn't exist", anim.toUtf8().data());
+ return;
+ }
+
+ if (m_currentAnimation != anim)
+ m_currentAnimation = anim;
+}
+
+void Sprite::advance(int phase)
+{
+ if (phase == 1)
+ return;
+
+ if (m_timer.isValid() && m_timer.elapsed() >= m_interval)
+ {
+ nextFrame();
+ m_timer.start();
+ }
+
+ // this should be run the first time advance() is called
+ if (!m_timer.isValid())
+ {
+ m_timer.start();
+ }
+}
diff --git a/src/Sprite.h b/src/Sprite.h
new file mode 100644
index 0000000..88679b3
--- /dev/null
+++ b/src/Sprite.h
@@ -0,0 +1,70 @@
+#ifndef SPRITE_H
+#define SPRITE_H
+
+#include <QList>
+#include <QMap>
+#include <QString>
+#include <QPixmap>
+#include <QGraphicsPixmapItem>
+#include <QElapsedTimer>
+#include <QObject>
+
+class Sprite: public QObject, public QGraphicsPixmapItem
+{
+ Q_OBJECT
+
+public:
+ typedef QList<QPixmap> FrameList;
+
+ explicit Sprite(QGraphicsItem *parent = 0, QGraphicsScene *scene = 0);
+
+ //! Adds a frame to an animation
+ void addFrame(const QString &anim, QPixmap frame);
+
+ //! Adds a list of frames to an animation
+ void addAnimation(const QString &anim, const FrameList &frames);
+
+ //! Set current frame in the current animation
+ void setFrame(int frame);
+
+ //! Get current frame
+ int getFrame() const;
+
+ //! Change to next frame
+ void nextFrame();
+
+ QPixmap getFramePixmap() const;
+
+ //! Get frame count in current animation
+ int getFrameCount() const;
+
+ FrameList getCurrentAnimation() const;
+
+ void setAnimation(const QString &anim);
+
+ void setFrameInterval(int msecs) { m_interval = msecs; }
+ int frameInterval() const { return m_interval; }
+
+ virtual void advance(int phase);
+
+signals:
+ //! Marks this sprite for removal
+ void removeMe();
+
+protected:
+
+ //! Map of animations (i.e. running, idle, jumping, ...)
+ QMap<QString, FrameList> m_animations;
+
+ QString m_currentAnimation;
+
+ //! Current frame
+ int m_currentFrame;
+
+ //! How often frames should be changed
+ int m_interval;
+
+ QElapsedTimer m_timer;
+};
+
+#endif // SPRITE_H
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 0000000..5691abc
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,33 @@
+#include <QtGui/QApplication>
+#include <QSettings>
+
+#include "MainWindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication a(argc, argv);
+
+ QApplication::setApplicationName("prism");
+ QApplication::setOrganizationName("aarg");
+ QApplication::setOrganizationDomain("aarg.eu");
+
+ QSettings::setDefaultFormat(QSettings::IniFormat);
+
+ // set some default settings
+
+ QSettings s;
+
+ if (!s.contains("path/data"))
+ {
+ const QString &app_path = QApplication::applicationDirPath();
+
+ s.setValue("path/data", app_path);
+ s.setValue("path/gfx", app_path + "/gfx");
+ s.setValue("path/levels", app_path + "/levels");
+ }
+
+ MainWindow mw;
+ mw.show();
+
+ return a.exec();
+}
diff --git a/src/src.pro b/src/src.pro
new file mode 100644
index 0000000..ff63d79
--- /dev/null
+++ b/src/src.pro
@@ -0,0 +1,55 @@
+######################################################################
+# Automatically generated by qmake (2.01a) Thu Mar 24 21:28:35 2011
+######################################################################
+
+include(../platformer.pri)
+
+QT += core gui opengl
+TEMPLATE = app
+TARGET = platformer
+DEPENDPATH += .
+INCLUDEPATH += . ../libtiled
+INSTALLS += target
+target.path = $${BINDIR}
+unix:DESTDIR = ../bin
+unix:LIBS += -L../lib -L/usr/local/lib -L/usr/lib -ltiled
+win32:LIBS += G:/Projects/Qt/platformer/platformer-build-desktop/lib/tiled.dll
+
+OBJECTS_DIR = .obj
+
+HEADERS += MainWindow.h \
+ GameScene.h \
+ CreditsScene.h \
+ MenuScene.h \
+ LevelSelectionScene.h \
+ GraphicsPixmapObject.h \
+ GraphicsButtonObject.h \
+ ActionScene.h \
+ ParallaxScrollerStatic.h \
+ ParallaxScrollerItem.h \
+ SceneChanger.h \
+ GameView.h \
+ Character.h \
+ Sprite.h \
+ Collectible.h \
+ Hero.h \
+ BasicEnemy.h
+
+SOURCES += main.cpp \
+ MainWindow.cpp \
+ GameScene.cpp \
+ CreditsScene.cpp \
+ MenuScene.cpp \
+ LevelSelectionScene.cpp \
+ GraphicsPixmapObject.cpp \
+ GraphicsButtonObject.cpp \
+ ActionScene.cpp \
+ ParallaxScrollerStatic.cpp \
+ ParallaxScrollerItem.cpp \
+ SceneChanger.cpp \
+ GameView.cpp \
+ Character.cpp \
+ Sprite.cpp \
+ Collectible.cpp \
+ Hero.cpp \
+ BasicEnemy.cpp