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