diff options
| author | Samu Laaksonen <laaksonen.sj@gmail.com> | 2012-09-25 20:00:41 +0300 |
|---|---|---|
| committer | Samu Laaksonen <laaksonen.sj@gmail.com> | 2012-09-25 20:00:41 +0300 |
| commit | 685fe05def77b039221edf06c74af74915d536c5 (patch) | |
| tree | 9c1a14b8f68bc0f801bdec3edc447d04fdbf7a4c | |
| parent | 29eabac0670574efd384182c065f53d08c42a483 (diff) | |
| download | prism-685fe05def77b039221edf06c74af74915d536c5.tar.gz prism-685fe05def77b039221edf06c74af74915d536c5.zip | |
Initial code commit
Added some stuff for project base
- tiled sources
- few Qt based classes for gfx
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 ®ion, + 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 ®ion, + 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 ®ion, + 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 ®ion, + 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 ®ion, + 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 ®ion) 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 ®ion) 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 |
