1 /*
2 * adjusttileindexes.cpp
3 * Copyright 2015, Thorbjørn Lindeijer <bjorn@lindeijer.nl>
4 *
5 * This file is part of Tiled.
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 "adjusttileindexes.h"
22
23 #include "changemapobject.h"
24 #include "changeproperties.h"
25 #include "changetileanimation.h"
26 #include "changetileobjectgroup.h"
27 #include "changetileprobability.h"
28 #include "changetilewangid.h"
29 #include "changewangcolordata.h"
30 #include "changewangsetdata.h"
31 #include "map.h"
32 #include "mapdocument.h"
33 #include "mapobject.h"
34 #include "objectgroup.h"
35 #include "painttilelayer.h"
36 #include "tile.h"
37 #include "tilelayer.h"
38 #include "tilesetdocument.h"
39 #include "wangset.h"
40
41 #include <QCoreApplication>
42
43 namespace Tiled {
44
AdjustTileIndexes(MapDocument * mapDocument,const Tileset & tileset)45 AdjustTileIndexes::AdjustTileIndexes(MapDocument *mapDocument,
46 const Tileset &tileset)
47 : QUndoCommand(QCoreApplication::translate("Undo Commands",
48 "Adjust Tile Indexes"))
49 {
50 int oldColumnCount = tileset.expectedColumnCount();
51 int newColumnCount = tileset.columnCount();
52
53 auto isFromTileset = [&](const Cell &cell) -> bool {
54 return cell.tileset() == &tileset;
55 };
56
57 auto adjustCell = [&](Cell cell) -> Cell {
58 int tileIndex = cell.tileId();
59 int row = tileIndex / oldColumnCount;
60 int column = tileIndex % oldColumnCount;
61
62 if (column < newColumnCount) {
63 int newTileIndex = row * newColumnCount + column;
64 cell.setTile(cell.tileset(), newTileIndex);
65 } else {
66 cell.setTile(nullptr);
67 }
68
69 return cell;
70 };
71
72 QVector<MapObjectCell> objectChanges;
73
74 // Adjust tile references from map layers
75 LayerIterator iterator(mapDocument->map());
76 while (Layer *layer = iterator.next()) {
77 switch (layer->layerType()) {
78 case Layer::TileLayerType: {
79 TileLayer *tileLayer = static_cast<TileLayer*>(layer);
80 const QRegion region = tileLayer->region(isFromTileset).translated(-layer->position());
81
82 if (!region.isEmpty()) {
83 const QRect boundingRect(region.boundingRect());
84 auto changedLayer = new TileLayer(QString(), 0, 0,
85 boundingRect.width(),
86 boundingRect.height());
87
88 #if QT_VERSION < 0x050800
89 const auto rects = region.rects();
90 for (const QRect &rect : rects) {
91 #else
92 for (const QRect &rect : region) {
93 #endif
94 for (int x = rect.left(); x <= rect.right(); ++x) {
95 for (int y = rect.top(); y <= rect.bottom(); ++y) {
96 Cell cell = adjustCell(tileLayer->cellAt(x, y));
97 changedLayer->setCell(x - boundingRect.x(),
98 y - boundingRect.y(),
99 cell);
100 }
101 }
102 }
103
104 new PaintTileLayer(mapDocument, tileLayer,
105 boundingRect.x() + tileLayer->x(),
106 boundingRect.y() + tileLayer->y(),
107 changedLayer,
108 this);
109
110 delete changedLayer;
111 }
112
113 break;
114 }
115
116 case Layer::ObjectGroupType:
117 for (MapObject *mapObject : *static_cast<ObjectGroup*>(layer)) {
118 if ((!mapObject->isTemplateInstance() || mapObject->propertyChanged(MapObject::CellProperty))
119 && isFromTileset(mapObject->cell())) {
120 MapObjectCell change;
121 change.object = mapObject;
122 change.cell = adjustCell(mapObject->cell());
123 objectChanges.append(change);
124 }
125 }
126 break;
127
128 case Layer::ImageLayerType:
129 case Layer::GroupLayerType:
130 break;
131 }
132 }
133
134 if (!objectChanges.isEmpty())
135 new ChangeMapObjectCells(mapDocument, objectChanges, this);
136 }
137
138 AdjustTileMetaData::AdjustTileMetaData(TilesetDocument *tilesetDocument)
139 : QUndoCommand(QCoreApplication::translate("Undo Commands",
140 "Adjust Tile Indexes"))
141 {
142 const Tileset &tileset = *tilesetDocument->tileset();
143
144 int oldColumnCount = tileset.expectedColumnCount();
145 int newColumnCount = tileset.columnCount();
146
147 auto adjustTile = [&](const Tile *tile) -> Tile* {
148 int tileIndex = tile->id();
149 int row = tileIndex / oldColumnCount;
150 int column = tileIndex % oldColumnCount;
151
152 if (column < newColumnCount) {
153 int newTileIndex = row * newColumnCount + column;
154 return tileset.findTile(newTileIndex);
155 }
156
157 return nullptr;
158 };
159
160 // Adjust tile meta data
161 QList<Tile*> tilesChangingProbability;
162 QList<qreal> tileProbabilities;
163 QSet<Tile*> tilesToReset;
164
165 auto adjustAnimationFrames = [&](const QVector<Frame> &frames) -> QVector<Frame> {
166 QVector<Frame> newFrames;
167 for (const Frame &frame : frames) {
168 if (Tile *tile = tileset.findTile(frame.tileId)) {
169 if (Tile *newTile = adjustTile(tile))
170 newFrames.append(Frame { newTile->id(), frame.duration });
171 }
172 }
173 return newFrames;
174 };
175
176 auto applyMetaData = [&](Tile *toTile,
177 const Properties &properties,
178 qreal probability,
179 std::unique_ptr<ObjectGroup> objectGroup,
180 const QVector<Frame> &frames)
181 {
182 if (properties != toTile->properties()) {
183 new ChangeProperties(tilesetDocument,
184 QCoreApplication::translate("MapDocument", "Tile"),
185 toTile,
186 properties,
187 this);
188 }
189
190 if (probability != toTile->probability()) {
191 tilesChangingProbability.append(toTile);
192 tileProbabilities.append(probability);
193 }
194
195 if (objectGroup.get() != toTile->objectGroup())
196 new ChangeTileObjectGroup(tilesetDocument, toTile, std::move(objectGroup), this);
197
198 if (frames != toTile->frames())
199 new ChangeTileAnimation(tilesetDocument, toTile, frames, this);
200 };
201
202 auto moveMetaData = [&](Tile *fromTile) {
203 Tile *toTile = adjustTile(fromTile);
204 if (toTile == fromTile)
205 return;
206
207 tilesToReset.insert(fromTile); // We may still need to reset "fromTile"
208
209 if (!toTile)
210 return;
211
212 tilesToReset.remove(toTile); // "toTile" no longer needs to get reset
213
214 // Copy meta data from "fromTile" to "toTile"
215
216 std::unique_ptr<ObjectGroup> objectGroup;
217 if (fromTile->objectGroup())
218 objectGroup.reset(fromTile->objectGroup()->clone());
219
220 applyMetaData(toTile,
221 fromTile->properties(),
222 fromTile->probability(),
223 std::move(objectGroup),
224 adjustAnimationFrames(fromTile->frames()));
225 };
226
227 QMapIterator<int, Tile *> iterator { tileset.tilesById() };
228
229 if (newColumnCount > oldColumnCount) {
230 // Increasing column count means information is copied to higher tiles,
231 // so we need to iterate backwards.
232 iterator.toBack();
233 while (iterator.hasPrevious())
234 moveMetaData(iterator.previous().value());
235 } else {
236 while (iterator.hasNext())
237 moveMetaData(iterator.next().value());
238 }
239
240 // Reset meta data on tiles that nothing was copied to
241 QSetIterator<Tile*> resetIterator(tilesToReset);
242 while (resetIterator.hasNext()) {
243 applyMetaData(resetIterator.next(),
244 Properties(), 1.0, nullptr, QVector<Frame>());
245 }
246
247 // Translate tile references in Wang sets and Wang colors
248 for (WangSet *wangSet : tileset.wangSets()) {
249 // WangSet tile image
250 if (Tile *fromTile = tileset.findTile(wangSet->imageTileId())) {
251 if (Tile *newTile = adjustTile(fromTile))
252 if (fromTile != newTile)
253 new SetWangSetImage(tilesetDocument, wangSet, newTile->id(), this);
254 }
255
256 // WangColor tile images
257 for (const QSharedPointer<WangColor> &wangColor : wangSet->colors()) {
258 if (Tile *fromTile = tileset.findTile(wangColor->imageId()))
259 if (Tile *newTile = adjustTile(fromTile))
260 if (fromTile != newTile)
261 new ChangeWangColorImage(tilesetDocument, wangColor.data(), newTile->id(), this);
262 }
263
264 QVector<ChangeTileWangId::WangIdChange> changes;
265
266 // Move all WangIds to their new tiles
267 QHashIterator<int, WangId> it(wangSet->wangIdByTileId());
268 while (it.hasNext()) {
269 it.next();
270
271 if (Tile *fromTile = tileset.findTile(it.key())) {
272 if (Tile *newTile = adjustTile(fromTile)) {
273 const WangId fromWangId = wangSet->wangIdOfTile(newTile);
274 const WangId toWangId = it.value();
275 changes.append(ChangeTileWangId::WangIdChange(fromWangId, toWangId, newTile->id()));
276 }
277 }
278 }
279
280 // Clear WangIds from other tiles
281 it.toFront();
282 while (it.hasNext()) {
283 it.next();
284
285 if (Tile *fromTile = tileset.findTile(it.key())) {
286 auto matchesTile = [fromTileId = it.key()](const ChangeTileWangId::WangIdChange &change) {
287 return change.tileId == fromTileId;
288 };
289 if (!std::any_of(changes.begin(), changes.end(), matchesTile)) {
290 const WangId fromWangId = it.value();
291 changes.append(ChangeTileWangId::WangIdChange(fromWangId, WangId(), fromTile->id()));
292 }
293 }
294 }
295
296 if (!changes.isEmpty())
297 new ChangeTileWangId(tilesetDocument, wangSet, changes, this);
298 }
299
300 if (!tilesChangingProbability.isEmpty()) {
301 new ChangeTileProbability(tilesetDocument,
302 tilesChangingProbability,
303 tileProbabilities,
304 this);
305 }
306 }
307
308 } // namespace Tiled
309