1 /*
2 * tilelayeritem.cpp
3 * Copyright 2014, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4 *
5 * This file is part of Tiled Quick.
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along with
18 * this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "tilelayeritem.h"
22
23 #include "tile.h"
24 #include "tilelayer.h"
25 #include "tileset.h"
26 #include "map.h"
27 #include "maprenderer.h"
28
29 #include "mapitem.h"
30 #include "tilesnode.h"
31
32 #include <QtMath>
33 #include <QQuickWindow>
34
35 using namespace Tiled;
36 using namespace TiledQuick;
37
38 namespace {
39
40 /**
41 * Returns the texture of a given tileset, or 0 if the image has not been
42 * loaded yet.
43 */
tilesetTexture(Tileset * tileset,QQuickWindow * window)44 static inline QSGTexture *tilesetTexture(Tileset *tileset,
45 QQuickWindow *window)
46 {
47 static QHash<Tileset *, QSGTexture *> cache;
48
49 QSGTexture *texture = cache.value(tileset);
50 if (!texture) {
51 const QString imagePath(Tiled::urlToLocalFileOrQrc(tileset->imageSource()));
52 texture = window->createTextureFromImage(QImage(imagePath));
53 cache.insert(tileset, texture);
54 }
55 return texture;
56 }
57
58 /**
59 * This helper class exists mainly to avoid redoing calculations that only need
60 * to be done once per tileset.
61 */
62 struct TilesetHelper
63 {
TilesetHelper__anond78ec3e60111::TilesetHelper64 TilesetHelper(const MapItem *mapItem)
65 : mWindow(mapItem->window())
66 , mTileset(nullptr)
67 , mTexture(nullptr)
68 , mMargin(0)
69 , mTileHSpace(0)
70 , mTileVSpace(0)
71 , mTilesPerRow(0)
72 {
73 }
74
tileset__anond78ec3e60111::TilesetHelper75 Tileset *tileset() const { return mTileset; }
texture__anond78ec3e60111::TilesetHelper76 QSGTexture *texture() const { return mTexture; }
77
setTileset__anond78ec3e60111::TilesetHelper78 void setTileset(Tileset *tileset)
79 {
80 mTileset = tileset;
81 mTexture = tilesetTexture(tileset, mWindow);
82 if (!mTexture)
83 return;
84
85 const int tileSpacing = tileset->tileSpacing();
86 mMargin = tileset->margin();
87 mTileHSpace = tileset->tileWidth() + tileSpacing;
88 mTileVSpace = tileset->tileHeight() + tileSpacing;
89
90 const QSize tilesetSize = mTexture->textureSize();
91 const int availableWidth = tilesetSize.width() + tileSpacing - mMargin;
92 mTilesPerRow = qMax(availableWidth / mTileHSpace, 1);
93 }
94
setTextureCoordinates__anond78ec3e60111::TilesetHelper95 void setTextureCoordinates(TileData &data, const Cell &cell) const
96 {
97 const int tileId = cell.tileId();
98 const int column = tileId % mTilesPerRow;
99 const int row = tileId / mTilesPerRow;
100
101 data.tx = column * mTileHSpace + mMargin;
102 data.ty = row * mTileVSpace + mMargin;
103 }
104
105 private:
106 QQuickWindow *mWindow;
107 Tileset *mTileset;
108 QSGTexture *mTexture;
109 int mMargin;
110 int mTileHSpace;
111 int mTileVSpace;
112 int mTilesPerRow;
113 };
114
115 } // anonymous namespace
116
117
TileLayerItem(TileLayer * layer,MapRenderer * renderer,MapItem * parent)118 TileLayerItem::TileLayerItem(TileLayer *layer, MapRenderer *renderer,
119 MapItem *parent)
120 : QQuickItem(parent)
121 , mLayer(layer)
122 , mRenderer(renderer)
123 , mVisibleArea(parent->visibleArea())
124 {
125 setFlag(ItemHasContents);
126 layerVisibilityChanged();
127
128 syncWithTileLayer();
129 setOpacity(mLayer->opacity());
130 }
131
syncWithTileLayer()132 void TileLayerItem::syncWithTileLayer()
133 {
134 const QRectF boundingRect = mRenderer->boundingRect(mLayer->rect());
135 setPosition(boundingRect.topLeft());
136 setSize(boundingRect.size());
137 }
138
139
140
updatePaintNode(QSGNode * node,QQuickItem::UpdatePaintNodeData *)141 QSGNode *TileLayerItem::updatePaintNode(QSGNode *node,
142 QQuickItem::UpdatePaintNodeData *)
143 {
144 delete node;
145 node = new QSGNode;
146 node->setFlag(QSGNode::OwnedByParent);
147
148 TilesetHelper helper(static_cast<MapItem*>(parentItem()));
149
150 QVector<TileData> tileData;
151 tileData.reserve(TilesNode::MaxTileCount);
152
153 /**
154 * Draws the tiles by adding nodes to the scene graph. When sequentially
155 * drawn tiles are using the same tileset, they will share a single
156 * geometry node.
157 */
158 auto tileRenderFunction = [&](QPoint tilePos, const QPointF &screenPos) {
159 const Cell &cell = mLayer->cellAt(tilePos);
160 Tileset *tileset = cell.tileset();
161 if (!tileset)
162 return;
163
164 if (tileset != helper.tileset() || tileData.size() == TilesNode::MaxTileCount) {
165 if (!tileData.isEmpty()) {
166 node->appendChildNode(new TilesNode(helper.texture(), tileData));
167 tileData.resize(0);
168 }
169
170 helper.setTileset(tileset);
171 }
172
173 if (!helper.texture())
174 return;
175
176 // todo: render "missing tile" marker
177 // if (!cell.tile()) {
178 // return;
179 // }
180
181 const auto offset = tileset->tileOffset();
182 const auto tile = tileset->findTile(cell.tileId());
183 const QSize size = (tile && !tile->image().isNull()) ? tile->size() : mRenderer->map()->tileSize();
184
185 TileData data;
186 data.x = static_cast<float>(screenPos.x()) + offset.x();
187 data.y = static_cast<float>(screenPos.y() - size.height()) + offset.y();
188 data.width = static_cast<float>(size.width());
189 data.height = static_cast<float>(size.height());
190 data.flippedHorizontally = cell.flippedHorizontally();
191 data.flippedVertically = cell.flippedVertically();
192 helper.setTextureCoordinates(data, cell);
193 tileData.append(data);
194 };
195
196 mRenderer->drawTileLayer(tileRenderFunction, mVisibleArea);
197
198 if (!tileData.isEmpty())
199 node->appendChildNode(new TilesNode(helper.texture(), tileData));
200
201 return node;
202 }
203
updateVisibleTiles()204 void TileLayerItem::updateVisibleTiles()
205 {
206 const MapItem *mapItem = static_cast<MapItem*>(parentItem());
207
208 QRectF rect = mapItem->visibleArea();
209
210 QMargins drawMargins = mLayer->drawMargins();
211 drawMargins.setTop(drawMargins.top() - mRenderer->map()->tileHeight());
212 drawMargins.setRight(drawMargins.right() - mRenderer->map()->tileWidth());
213
214 rect.adjust(-drawMargins.right(),
215 -drawMargins.bottom(),
216 drawMargins.left(),
217 drawMargins.top());
218
219 rect &= mRenderer->boundingRect(mLayer->localBounds());
220
221 if (mVisibleArea != rect) {
222 mVisibleArea = rect;
223 update();
224 }
225 }
226
layerVisibilityChanged()227 void TileLayerItem::layerVisibilityChanged()
228 {
229 const bool visible = mLayer->isVisible();
230 setVisible(visible);
231
232 MapItem *parent = qobject_cast<MapItem*>(parentItem());
233 if (visible) {
234 updateVisibleTiles();
235
236 if (parent)
237 connect(parent, &MapItem::visibleAreaChanged, this, &TileLayerItem::updateVisibleTiles);
238 } else {
239 if (parent)
240 disconnect(parent, &MapItem::visibleAreaChanged, this, &TileLayerItem::updateVisibleTiles);
241 }
242 }
243
244
TileItem(const Cell & cell,QPoint position,MapItem * parent)245 TileItem::TileItem(const Cell &cell, QPoint position, MapItem *parent)
246 : QQuickItem(parent)
247 , mCell(cell)
248 , mPosition(position)
249 {
250 setFlag(ItemHasContents);
251 setZ(position.y() * parent->map().mMap->tileHeight());
252 }
253
updatePaintNode(QSGNode * node,QQuickItem::UpdatePaintNodeData *)254 QSGNode *TileItem::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *)
255 {
256 if (!node) {
257 const MapItem *mapItem = static_cast<MapItem*>(parent());
258
259 TilesetHelper helper(mapItem);
260 Tileset *tileset = mCell.tileset();
261 helper.setTileset(tileset);
262
263 if (!helper.texture())
264 return nullptr;
265
266 Tile *tile = mCell.tile();
267 if (!tile)
268 return nullptr; // todo: render "missing tile" marker
269
270 const Map *map = mapItem->map();
271 const int tileWidth = map->tileWidth();
272 const int tileHeight = map->tileHeight();
273
274 const QSize size = tile->size();
275 const QPoint offset = tileset->tileOffset();
276
277 QVector<TileData> data(1);
278 data[0].x = mPosition.x() * tileWidth + offset.x();
279 data[0].y = (mPosition.y() + 1) * tileHeight - tileset->tileHeight() + offset.y();
280 data[0].width = size.width();
281 data[0].height = size.height();
282 helper.setTextureCoordinates(data[0], mCell);
283
284 node = new TilesNode(helper.texture(), data);
285 }
286
287 return node;
288 }
289