1 /*
2  * wangset.cpp
3  * Copyright 2017, Benjamin Trotter <bdtrotte@ucsc.edu>
4  * This file is part of libtiled.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *    1. Redistributions of source code must retain the above copyright notice,
10  *       this list of conditions and the following disclaimer.
11  *
12  *    2. Redistributions in binary form must reproduce the above copyright
13  *       notice, this list of conditions and the following disclaimer in the
14  *       documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19  * EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "tilelayer.h"
29 #include "wangset.h"
30 
31 #include <QDebug>
32 #include <QStack>
33 #include <QtMath>
34 
35 #include "qtcompat_p.h"
36 
37 namespace Tiled {
38 
39 /**
40  * These return the color of the edge of the WangId.
41  * 0 being the top edge:
42  *
43  *       |0|
44  *      3|.|1
45  *       |2|
46  */
edgeColor(int index) const47 int WangId::edgeColor(int index) const
48 {
49     Q_ASSERT(index >= 0 && index < NumEdges);
50     return indexColor(index * 2);
51 }
52 
53 /**
54  * These return the color of the corner of the WangId.
55  * 0 being the top right corner:
56  *
57  *      3| |0
58  *       |.|
59  *      2| |1
60  */
cornerColor(int index) const61 int WangId::cornerColor(int index) const
62 {
63     Q_ASSERT(index >= 0 && index < NumCorners);
64     return indexColor(index * 2 + 1);
65 }
66 
67 /**
68  * Returns the color of a certain index 0 - 7.
69  *
70  *      7|0|1
71  *      6|.|2
72  *      5|4|3
73  */
indexColor(int index) const74 int WangId::indexColor(int index) const
75 {
76     Q_ASSERT(index >= 0 && index < NumIndexes);
77     return (mId >> (index * BITS_PER_INDEX)) & INDEX_MASK;
78 }
79 
setEdgeColor(int index,unsigned value)80 void WangId::setEdgeColor(int index, unsigned value)
81 {
82     Q_ASSERT(index >= 0 && index < NumEdges);
83     setIndexColor(index * 2, value);
84 }
85 
setCornerColor(int index,unsigned value)86 void WangId::setCornerColor(int index, unsigned value)
87 {
88     Q_ASSERT(index >= 0 && index < NumCorners);
89     setIndexColor(index * 2 + 1, value);
90 }
91 
92 /**
93  * Sets the color of a certain grid index:
94  *
95  *      y
96  *    x 0|1|2
97  *      1|.|.
98  *      2|.|.
99  */
setGridColor(int x,int y,unsigned value)100 void WangId::setGridColor(int x, int y, unsigned value)
101 {
102     const int index = indexByGrid(x, y);
103     if (index < NumIndexes)
104         setIndexColor(index, value);
105 }
106 
107 /**
108  * Sets the color of a certain index 0 - 7.
109  *
110  *      7|0|1
111  *      6|.|2
112  *      5|4|3
113  */
setIndexColor(int index,unsigned value)114 void WangId::setIndexColor(int index, unsigned value)
115 {
116     Q_ASSERT(index >= 0 && index < NumIndexes);
117     mId &= ~(INDEX_MASK << (index * BITS_PER_INDEX));
118     mId |= quint64(value & INDEX_MASK) << (index * BITS_PER_INDEX);
119 }
120 
121 /**
122  * Matches this WangId's edges/corners with an \a adjacent one.
123  * Where \a position is 0-7 with 0 being top, and 7 being top left:
124  *
125  *      7|0|1
126  *      6|.|2
127  *      5|4|3
128  */
updateToAdjacent(WangId adjacent,int position)129 void WangId::updateToAdjacent(WangId adjacent, int position)
130 {
131     setIndexColor(position, adjacent.indexColor(oppositeIndex(position)));
132 
133     if (!isCorner(position)) {
134         const int cornerIndex = position / 2;
135         setCornerColor(cornerIndex, adjacent.cornerColor((cornerIndex + 1) % NumCorners));
136         setCornerColor((cornerIndex + 3) % NumCorners, adjacent.cornerColor((cornerIndex + 2) % NumCorners));
137     }
138 }
139 
140 /**
141  * Returns true if one or more indexes have no color.
142  */
hasWildCards() const143 bool WangId::hasWildCards() const
144 {
145     for (int i = 0; i < NumIndexes; ++i)
146         if (!indexColor(i))
147             return true;
148 
149     return false;
150 }
151 
152 /**
153  * Returns true if one or more corners have no color.
154  */
hasCornerWildCards() const155 bool WangId::hasCornerWildCards() const
156 {
157     for (int i = 0; i < NumCorners; ++i)
158         if (!cornerColor(i))
159             return true;
160 
161     return false;
162 }
163 
164 /**
165  * Returns true if one or more edges have no color.
166  */
hasEdgeWildCards() const167 bool WangId::hasEdgeWildCards() const
168 {
169     for (int i = 0; i < NumEdges; ++i)
170         if (!edgeColor(i))
171             return true;
172 
173     return false;
174 }
175 
176 /**
177  * Returns a mask that is 0 for any indexes that have no color defined.
178  */
mask() const179 quint64 WangId::mask() const
180 {
181     quint64 mask = 0;
182     for (int i = 0; i < NumIndexes; ++i) {
183         if (indexColor(i))
184             mask |= INDEX_MASK << (i * BITS_PER_INDEX);
185     }
186     return mask;
187 }
188 
189 /**
190  * Returns a mask that is 0 for any indexes that don't match the given color.
191  */
mask(int value) const192 quint64 WangId::mask(int value) const
193 {
194     quint64 mask = 0;
195     for (int i = 0; i < NumIndexes; ++i) {
196         if (indexColor(i) == value)
197             mask |= INDEX_MASK << (i * BITS_PER_INDEX);
198     }
199     return mask;
200 }
201 
hasCornerWithColor(int value) const202 bool WangId::hasCornerWithColor(int value) const
203 {
204     for (int i = 0; i < NumCorners; ++i) {
205         if (cornerColor(i) == value)
206             return true;
207     }
208     return false;
209 }
210 
hasEdgeWithColor(int value) const211 bool WangId::hasEdgeWithColor(int value) const
212 {
213     for (int i = 0; i < NumEdges; ++i) {
214         if (edgeColor(i) == value)
215             return true;
216     }
217     return false;
218 }
219 
220 /**
221  * Rotates the wang Id clockwise by (90 * rotations) degrees.
222  * Meaning with one rotation, the top edge becomes the right edge,
223  * and the top right corner, becomes the top bottom.
224  */
rotate(int rotations)225 void WangId::rotate(int rotations)
226 {
227     *this = rotated(rotations);
228 }
229 
230 /**
231  * @see rotate
232  */
rotated(int rotations) const233 WangId WangId::rotated(int rotations) const
234 {
235     if (rotations < 0)
236         rotations = 4 + (rotations % 4);
237     else
238         rotations %= 4;
239 
240     quint64 rotated = mId << (rotations * BITS_PER_INDEX * 2);
241     rotated = rotated | (mId >> ((4 - rotations) * BITS_PER_INDEX * 2));
242 
243     return rotated;
244 }
245 
246 /**
247  * Flips the wang Id horizontally.
248  */
flipHorizontally()249 void WangId::flipHorizontally()
250 {
251     *this = flippedHorizontally();
252 }
253 
254 /**
255  * Flips the wang Id vertically.
256  */
flipVertically()257 void WangId::flipVertically()
258 {
259     flipHorizontally();
260     rotate(2);
261 }
262 
flippedHorizontally() const263 WangId WangId::flippedHorizontally() const
264 {
265     WangId newWangId = mId;
266 
267     newWangId.setIndexColor(WangId::Right, indexColor(WangId::Left));
268     newWangId.setIndexColor(WangId::Left, indexColor(WangId::Right));
269 
270     for (int i = 0; i < NumCorners; ++i)
271         newWangId.setCornerColor(i, cornerColor(NumCorners - 1 - i));
272 
273     return newWangId;
274 }
275 
flippedVertically() const276 WangId WangId::flippedVertically() const
277 {
278     WangId newWangId = mId;
279     newWangId.flipVertically();
280     return newWangId;
281 }
282 
indexByGrid(int x,int y)283 WangId::Index WangId::indexByGrid(int x, int y)
284 {
285     Q_ASSERT(x >= 0 && x < 3);
286     Q_ASSERT(y >= 0 && y < 3);
287 
288     static constexpr Index map[3][3] = {
289         { TopLeft,      Top,        TopRight },
290         { Left,         NumIndexes, Right },
291         { BottomLeft,   Bottom,     BottomRight },
292     };
293 
294     return map[y][x];
295 }
296 
297 /**
298  * Creates a WangId based on the 32-bit value, which uses 4 bits per index.
299  * Provided for compatibility.
300  */
fromUint(unsigned id)301 WangId WangId::fromUint(unsigned id)
302 {
303     quint64 id64 = 0;
304     for (int i = 0; i < NumIndexes; ++i) {
305         const quint64 color = (id >> (i * 4)) & 0xF;
306         id64 |= color << (i * BITS_PER_INDEX);
307     }
308     return id64;
309 }
310 
311 /**
312  * Converts the WangId to a 32-bit value, using 4 bits per index.
313  * Provided for compatibility.
314  */
toUint() const315 unsigned WangId::toUint() const
316 {
317     unsigned id = 0;
318     for (int i = 0; i < NumIndexes; ++i) {
319         const unsigned color = (mId >> (i * BITS_PER_INDEX)) & INDEX_MASK;
320         id |= color << (i * 4);
321     }
322     return id;
323 }
324 
fromString(QStringRef string,bool * ok)325 WangId WangId::fromString(QStringRef string, bool *ok)
326 {
327     WangId id;
328 
329     const auto parts = string.split(QLatin1Char(','));
330     if (parts.size() == NumIndexes) {
331         for (int i = 0; i < NumIndexes; ++i) {
332             unsigned color = parts[i].toUInt(ok);
333             if (ok && !(*ok))
334                 return id;
335 
336             if (color > WangId::MAX_COLOR_COUNT) {
337                 if (ok)
338                     *ok = false;
339                 return id;
340             }
341 
342             id.setIndexColor(i, color);
343         }
344     } else if (ok) {
345         *ok = false;
346     }
347 
348     return id;
349 }
350 
toString() const351 QString WangId::toString() const
352 {
353     QString result;
354     for (int i = 0; i < NumIndexes; ++i ) {
355         if (i > 0)
356             result += QLatin1Char(',');
357         result += QString::number(indexColor(i));
358     }
359     return result;
360 }
361 
362 
operator <<(QDebug debug,WangId wangId)363 QDebug operator<<(QDebug debug, WangId wangId)
364 {
365     QDebugStateSaver state(debug);
366     debug.nospace().noquote() << "WangId(" << wangId.toString() << ')';
367     return debug;
368 }
369 
operator <<(QDebug debug,const WangTile & wangTile)370 QDebug operator<<(QDebug debug, const WangTile &wangTile)
371 {
372     QDebugStateSaver state(debug);
373     debug.nospace() << "WangTile(" << wangTile.tileId() << ", " << wangTile.wangId() << ')';
374     return debug;
375 }
376 
377 
WangColor()378 WangColor::WangColor()
379     : WangColor(0, QString(), Qt::red, -1)
380 {}
381 
WangColor(int colorIndex,const QString & name,const QColor & color,int imageId,qreal probability)382 WangColor::WangColor(int colorIndex, const QString &name, const QColor &color, int imageId, qreal probability)
383     : Object(WangColorType)
384     , mColorIndex(colorIndex)
385     , mName(name)
386     , mColor(color)
387     , mImageId(imageId)
388     , mProbability(probability)
389 {}
390 
391 
392 static const QColor defaultWangColors[] = {
393     QColor(255, 0, 0),
394     QColor(0, 255, 0),
395     QColor(0, 0, 255),
396     QColor(255, 119, 0),
397     QColor(0, 233, 255),
398     QColor(255, 0, 216),
399     QColor(255, 255, 0),
400     QColor(160, 0, 255),
401     QColor(0, 255, 161),
402     QColor(255, 168, 168),
403     QColor(180, 168, 255),
404     QColor(150, 255, 167),
405     QColor(142, 120, 72),
406     QColor(90, 90, 90),
407     QColor(14, 122, 70)
408 };
409 
WangSet(Tileset * tileset,const QString & name,Type type,int imageTileId)410 WangSet::WangSet(Tileset *tileset,
411                  const QString &name,
412                  Type type,
413                  int imageTileId)
414     : Object(Object::WangSetType)
415     , mTileset(tileset)
416     , mName(name)
417     , mType(type)
418     , mImageTileId(imageTileId)
419 {
420 }
421 
422 /**
423  * Sets the color count.
424  *
425  * This can make wangIds already in the set invalid, so should only be used
426  * from ChangeWangSetColorCount.
427  */
setColorCount(int n)428 void WangSet::setColorCount(int n)
429 {
430     Q_ASSERT(n >= 0 && n <= WangId::MAX_COLOR_COUNT);
431 
432     if (n == colorCount())
433         return;
434 
435     if (n < colorCount()) {
436         mColors.resize(n);
437     } else {
438         while (mColors.size() < n) {
439             QColor color;
440             if (mColors.size() < 16)
441                 color = defaultWangColors[mColors.size()];
442             else
443                 color = QColor(rand() % 256, rand() % 256, rand() % 256);
444 
445             mColors.append(QSharedPointer<WangColor>::create(mColors.size() + 1,
446                                                              QString(),
447                                                              color));
448             mColors.last()->mWangSet = this;
449         }
450     }
451 }
452 
453 /**
454  * Inserts a given wangColor into the wangSet.
455  * If the color is greater than current count, it must only be one greater.
456  * For use in an undo command (does not adjust currently assigned tiles).
457  */
insertWangColor(const QSharedPointer<WangColor> & wangColor)458 void WangSet::insertWangColor(const QSharedPointer<WangColor> &wangColor)
459 {
460     Q_ASSERT(colorCount() + 1 >= wangColor->colorIndex());
461 
462     wangColor->mWangSet = this;
463     mColors.insert(wangColor->colorIndex() - 1, wangColor);
464 
465     for (int i = wangColor->colorIndex(); i < colorCount(); ++i)
466         mColors.at(i)->setColorIndex(i + 1);
467 
468     mColorDistancesDirty = true;
469 }
470 
471 /**
472  * Adds a \a wangColor to the set.
473  * The Wang colors color index may be changed.
474  */
addWangColor(const QSharedPointer<WangColor> & wangColor)475 void WangSet::addWangColor(const QSharedPointer<WangColor> &wangColor)
476 {
477     wangColor->setColorIndex(mColors.size() + 1);
478     insertWangColor(wangColor);
479 }
480 
481 /**
482  * Removes and returns a given \a color.
483  *
484  * This can make wangIds invalid, so should only be used from
485  * changewangsetdata.h
486  */
takeWangColorAt(int color)487 QSharedPointer<WangColor> WangSet::takeWangColorAt(int color)
488 {
489     Q_ASSERT(color > 0 && color - 1 < colorCount());
490 
491     auto wangColor = mColors.takeAt(color - 1);
492     wangColor->mWangSet = nullptr;
493 
494     for (int i = color - 1; i < colorCount(); ++i)
495         mColors.at(i)->setColorIndex(i + 1);
496 
497     mColorDistancesDirty = true;
498     return wangColor;
499 }
500 
501 /**
502  * Associates the given \a wangId with the given \a tileId.
503  *
504  * If the given WangTile is already in the set with a different wangId, then
505  * that reference is removed, and replaced with the new wangId. If the wangId
506  * provided is zero then the wangTile is removed if already in the set.
507  */
setWangId(int tileId,WangId wangId)508 void WangSet::setWangId(int tileId, WangId wangId)
509 {
510     Q_ASSERT(wangIdIsValid(wangId));
511 
512     if (WangId previousWangId = mTileIdToWangId.value(tileId)) {
513         // return when the same tile is already part of this set with the same WangId
514         if (previousWangId == wangId)
515             return;
516 
517         removeTileId(tileId);
518     }
519 
520     if (wangId == 0)
521         return;
522 
523     mTileIdToWangId.insert(tileId, wangId);
524     mColorDistancesDirty = true;
525     mCellsDirty = true;
526 }
527 
removeTileId(int tileId)528 void WangSet::removeTileId(int tileId)
529 {
530     mTileIdToWangId.remove(tileId);
531     mColorDistancesDirty = true;
532     mCellsDirty = true;
533 }
534 
wangIdsAndCells() const535 const QVector<WangSet::WangIdAndCell> &WangSet::wangIdsAndCells() const
536 {
537     if (cellsDirty())
538         const_cast<WangSet*>(this)->recalculateCells();
539     return mWangIdAndCells;
540 }
541 
recalculateCells()542 void WangSet::recalculateCells()
543 {
544     mWangIdAndCells.clear();
545     mCellsDirty = false;
546     mUniqueFullWangIdCount = 0;
547 
548     QSet<WangId> addedWangIds;
549 
550     // First insert all available tiles
551     QHashIterator<int, WangId> it(mTileIdToWangId);
552     while (it.hasNext()) {
553         it.next();
554         mUniqueFullWangIdCount += !it.value().hasWildCards() && !addedWangIds.contains(it.value());
555         addedWangIds.insert(it.value());
556         mWangIdAndCells.append({it.value(), Cell(mTileset, it.key())});
557     }
558 
559     const auto transformationFlags = tileset()->transformationFlags();
560     mLastSeenTranslationFlags = transformationFlags;
561 
562     if (!(transformationFlags & ~Tileset::PreferUntransformed))
563         return;
564 
565     // Then insert variations based on flipping
566     it.toFront();
567     while (it.hasNext()) {
568         it.next();
569 
570         Cell cells[8] = { Cell(mTileset, it.key()) };
571         WangId wangIds[8] = { it.value() };
572         int count = 1;
573         const bool hasWildCards = it.value().hasWildCards();
574 
575         // Add 90, 180 and 270 degree rotations if enabled
576         if (transformationFlags.testFlag(Tileset::AllowRotate)) {
577             for (int i = 0; i < 3; ++i) {
578                 cells[count + i] = cells[i];
579                 cells[count + i].rotate(RotateRight);
580                 wangIds[count + i] = wangIds[i].rotated(1);
581             }
582 
583             count = 4;
584         }
585 
586         if (transformationFlags.testFlag(Tileset::AllowFlipHorizontally)) {
587             for (int i = 0; i < count; ++i) {
588                 cells[count + i] = cells[i];
589                 cells[count + i].setFlippedHorizontally(!cells[count + i].flippedHorizontally());
590                 wangIds[count + i] = wangIds[i].flippedHorizontally();
591             }
592 
593             count *= 2;
594         }
595 
596         if (count <= 4 && transformationFlags.testFlag(Tileset::AllowFlipVertically)) {
597             for (int i = 0; i < count; ++i) {
598                 cells[count + i] = cells[i];
599                 cells[count + i].setFlippedVertically(!cells[count + i].flippedVertically());
600                 wangIds[count + i] = wangIds[i].flippedVertically();
601             }
602 
603             count *= 2;
604         }
605 
606         for (int i = 1; i < count; ++i) {
607             const bool exists = addedWangIds.contains(wangIds[i]);
608             if (transformationFlags.testFlag(Tileset::PreferUntransformed) && exists)
609                 continue;
610             mUniqueFullWangIdCount += !hasWildCards && !exists;
611             addedWangIds.insert(wangIds[i]);
612             mWangIdAndCells.append({wangIds[i], cells[i]});
613         }
614     }
615 }
616 
617 /**
618  * Calculates the distances between Wang colors.
619  *
620  * Distance between colors is the minimum number of tiles required before one
621  * color may meet another. Colors that have no transition path have a distance
622  * of -1.
623  */
recalculateColorDistances()624 void WangSet::recalculateColorDistances()
625 {
626     int maximumDistance = 1;
627 
628     for (int i = 1; i <= colorCount(); ++i) {
629         WangColor &color = *colorAt(i);
630         QVector<int> distance(colorCount() + 1, -1);
631 
632         // Check all tiles for transitions to other Wang colors
633         for (const WangId wangId : qAsConst(mTileIdToWangId)) {
634 
635             // Don't consider edges and corners to be connected. This helps
636             // avoid seeing transitions to "no color" for edge or corner
637             // based sets.
638 
639             if (wangId.hasCornerWithColor(i)) {
640                 for (int index = 0; index < 4; ++index)
641                     distance[wangId.cornerColor(index)] = 1;
642             }
643 
644             if (wangId.hasEdgeWithColor(i)) {
645                 for (int index = 0; index < 4; ++index)
646                     distance[wangId.edgeColor(index)] = 1;
647             }
648         }
649 
650         // Color has at least one tile of its own type
651         distance[i] = 0;
652 
653         color.mDistanceToColor = distance;
654     }
655 
656     // Calculate indirect transition distances
657     bool newConnections;
658     do {
659         newConnections = false;
660 
661         // For each combination of colors
662         for (int i = 1; i <= colorCount(); ++i) {
663             WangColor &colorI = *colorAt(i);
664 
665             for (int j = 1; j <= colorCount(); ++j) {
666                 if (i == j)
667                     continue;
668 
669                 WangColor &colorJ = *colorAt(j);
670 
671                 // Scan through each color, and see if we have any in common
672                 for (int t = 0; t <= colorCount(); ++t) {
673                     const int d0 = colorI.distanceToColor(t);
674                     const int d1 = colorJ.distanceToColor(t);
675                     if (d0 == -1 || d1 == -1)
676                         continue;
677 
678                     // We have found a common connection
679                     int d = colorI.distanceToColor(j);
680                     Q_ASSERT(colorJ.distanceToColor(i) == d);
681 
682                     // If the new path is shorter, record the new distance
683                     if (d == -1 || d0 + d1 < d) {
684                         d = d0 + d1;
685                         colorI.mDistanceToColor[j] = d;
686                         colorJ.mDistanceToColor[i] = d;
687                         maximumDistance = qMax(maximumDistance, d);
688 
689                         // We're making progress, flag for another iteration...
690                         newConnections = true;
691                     }
692                 }
693             }
694         }
695 
696         // Repeat while we are still making new connections (could take a
697         // number of iterations for distant colors to connect)
698     } while (newConnections);
699 
700     mMaximumColorDistance = maximumDistance;
701     mColorDistancesDirty = false;
702 }
703 
704 /**
705  * Returns a list of the wangTiles in this set, sorted by tileId.
706  */
sortedWangTiles() const707 QList<WangTile> WangSet::sortedWangTiles() const
708 {
709     QList<WangTile> wangTiles;
710     wangTiles.reserve(mTileIdToWangId.size());
711 
712     QHashIterator<int, WangId> it(mTileIdToWangId);
713     while (it.hasNext()) {
714         it.next();
715         wangTiles.append(WangTile(it.key(), it.value()));
716     }
717 
718     std::stable_sort(wangTiles.begin(), wangTiles.end());
719     return wangTiles;
720 }
721 
722 /**
723  * Returns a WangId matching that of the provided \a surroundingWangIds.
724  *
725  * This is based off a provided array, { 0, 1, 2, 3, 4, 5, 6, 7 },
726  * which corresponds to:
727  *
728  *      7|0|1
729  *      6|X|2
730  *      5|4|3
731  */
wangIdFromSurrounding(const WangId surroundingWangIds[]) const732 WangId WangSet::wangIdFromSurrounding(const WangId surroundingWangIds[]) const
733 {
734     quint64 id = 0;
735 
736     // Edges
737     for (int i = 0; i < WangId::NumEdges; ++i)
738         id |= quint64(surroundingWangIds[i*2].edgeColor((2 + i) % WangId::NumEdges)) << (i * WangId::BITS_PER_INDEX * 2);
739 
740     // Corners
741     for (int i = 0; i < WangId::NumCorners; ++i) {
742         int color = surroundingWangIds[i*2 + 1].cornerColor((2 + i) % WangId::NumCorners);
743 
744         if (!color)
745             color = surroundingWangIds[i*2].cornerColor((1 + i) % WangId::NumCorners);
746 
747         if (!color)
748             color = surroundingWangIds[(i*2 + 2) % WangId::NumIndexes].cornerColor((3 + i) % WangId::NumCorners);
749 
750         id |= quint64(color) << (WangId::BITS_PER_INDEX + i * WangId::BITS_PER_INDEX * 2);
751     }
752 
753     return id;
754 }
755 
756 /**
757  * Returns a wangId matching that of the provided surrounding tiles.
758  *
759  * This is based off a provided array, { 0, 1, 2, 3, 4, 5, 6, 7 },
760  * which corresponds to:
761  *
762  *      7|0|1
763  *      6|X|2
764  *      5|4|3
765  */
wangIdFromSurrounding(const Cell surroundingCells[]) const766 WangId WangSet::wangIdFromSurrounding(const Cell surroundingCells[]) const
767 {
768     WangId wangIds[WangId::NumIndexes];
769 
770     for (int i = 0; i < WangId::NumIndexes; ++i)
771         wangIds[i] = wangIdOfCell(surroundingCells[i]);
772 
773     return wangIdFromSurrounding(wangIds);
774 }
775 
776 /**
777  * Returns the WangId of a given \a tile.
778  *
779  * The tile is expected to be from the tileset to which this WangSet belongs.
780  */
wangIdOfTile(const Tile * tile) const781 WangId WangSet::wangIdOfTile(const Tile *tile) const
782 {
783     Q_ASSERT(tile->tileset() == mTileset);
784     return mTileIdToWangId.value(tile->id());
785 }
786 
787 /**
788  * Returns the WangId of a given \a cell.
789  *
790  * If the cell refers to a different tileset than the one to which this WangSet
791  * belongs, an empty WangId is returned.
792  */
wangIdOfCell(const Cell & cell) const793 WangId WangSet::wangIdOfCell(const Cell &cell) const
794 {
795     WangId wangId;
796 
797     if (cell.tileset() == mTileset) {
798         wangId = mTileIdToWangId.value(cell.tileId());
799 
800         if (cell.flippedAntiDiagonally()) {
801             wangId.rotate(1);
802             wangId.flipHorizontally();
803         }
804         if (cell.flippedHorizontally())
805             wangId.flipHorizontally();
806         if (cell.flippedVertically())
807             wangId.flipVertically();
808     }
809 
810     return wangId;
811 }
812 
813 /**
814  * The probability of a given WangId of being selected.
815  */
wangIdProbability(WangId wangId) const816 qreal WangSet::wangIdProbability(WangId wangId) const
817 {
818     qreal probability = 1.0;
819 
820     for (int i = 0; i < WangId::NumIndexes; ++i) {
821         if (int color = wangId.indexColor(i))
822             probability *= colorAt(color)->probability();
823     }
824 
825     return probability;
826 }
827 
828 /**
829  * Returns whether or not the given wangId is valid in the contex of the
830  * current wangSet
831  */
wangIdIsValid(WangId wangId) const832 bool WangSet::wangIdIsValid(WangId wangId) const
833 {
834     return wangIdIsValid(wangId, colorCount());
835 }
836 
wangIdIsValid(WangId wangId,int colorCount)837 bool WangSet::wangIdIsValid(WangId wangId, int colorCount)
838 {
839     for (int i = 0; i < WangId::NumIndexes; ++i)
840         if (wangId.indexColor(i) > colorCount)
841             return false;
842 
843     return true;
844 }
845 
846 /**
847  * Returns whether the given \a wangId is assigned to a WangTile.
848  *
849  * When \a mask is given, returns whether there is a WangId assigned to a
850  * WangTile matching the part of the \a wangId indicated by the mask.
851  */
wangIdIsUsed(WangId wangId,WangId mask) const852 bool WangSet::wangIdIsUsed(WangId wangId, WangId mask) const
853 {
854     const quint64 maskedWangId = wangId & mask;
855 
856     for (const auto &wangIdAndCell : wangIdsAndCells())
857         if ((wangIdAndCell.wangId & mask) == maskedWangId)
858             return true;
859 
860     return false;
861 }
862 
transitionPenalty(int colorA,int colorB) const863 int WangSet::transitionPenalty(int colorA, int colorB) const
864 {
865     if (mColorDistancesDirty)
866         const_cast<WangSet*>(this)->recalculateColorDistances();
867 
868     // Do some magic, since we don't have a transition array for no-color
869     if (colorA == 0 && colorB == 0)
870         return 0;
871 
872     if (colorA == 0)
873         return colorAt(colorB)->mDistanceToColor[colorA];
874 
875     return colorAt(colorA)->mDistanceToColor[colorB];
876 }
877 
maximumColorDistance() const878 int WangSet::maximumColorDistance() const
879 {
880     if (mColorDistancesDirty)
881         const_cast<WangSet*>(this)->recalculateColorDistances();
882 
883     return mMaximumColorDistance;
884 }
885 
886 /**
887  * Returns whether every template wangTile is filled.
888  */
isComplete() const889 bool WangSet::isComplete() const
890 {
891     if (cellsDirty())
892         const_cast<WangSet*>(this)->recalculateCells();
893 
894     return mUniqueFullWangIdCount == completeSetSize();
895 }
896 
897 /**
898  * Returns the amount of tiles expected in a complete Wang set.
899  */
completeSetSize() const900 quint64 WangSet::completeSetSize() const
901 {
902     quint64 c = static_cast<quint64>(colorCount());
903 
904     switch (mType) {
905     case Corner:
906     case Edge:
907         return c * c * c * c;
908     case Mixed:
909     default:
910         return c * c * c * c * c * c * c * c;
911     }
912 }
913 
914 /**
915  * Returns the Nth WangId starting at 0x11111111
916  * and, when C is the number of colors, ending at 0xCCCCCCCC.
917  *
918  * Note this does NOT include wildcards (no zeros).
919  */
templateWangIdAt(unsigned n) const920 WangId WangSet::templateWangIdAt(unsigned n) const
921 {
922     if (colorCount() <= 0)
923         return {};
924 
925     WangId wangId;
926 
927     switch (mType) {
928     case Corner:
929         for (int i = WangId::NumCorners - 1; i >= 0; --i) {
930             const int belowPermutations = qPow(colorCount(), i);
931             const int value = n / belowPermutations;
932 
933             n -= value * belowPermutations;
934 
935             wangId.setCornerColor(i, value + 1);
936         }
937         break;
938     case Edge:
939         for (int i = WangId::NumEdges - 1; i >= 0; --i) {
940             //this is the number of permutations possible bellow this point in the wangId
941             const int belowPermutations = qPow(colorCount(), i);
942             const int value = n / belowPermutations;
943 
944             n -= value * belowPermutations;
945 
946             wangId.setEdgeColor(i, value + 1);
947         }
948         break;
949     case Mixed:
950         for (int i = WangId::NumIndexes - 1; i >= 0; --i) {
951             const int belowPermutations = qPow(colorCount(), i);
952             const int value = n / belowPermutations;
953 
954             n -= value * belowPermutations;
955 
956             wangId.setIndexColor(i, value + 1);
957         }
958         break;
959     }
960 
961     return wangId;
962 }
963 
clone(Tileset * tileset) const964 WangSet *WangSet::clone(Tileset *tileset) const
965 {
966     // Caller is responsible for adding the WangSet to this tileset
967     WangSet *c = new WangSet(tileset, mName, mType, mImageTileId);
968 
969     c->mUniqueFullWangIdCount = mUniqueFullWangIdCount;
970     c->mColors = mColors;
971     c->mTileIdToWangId = mTileIdToWangId;
972     c->mWangIdAndCells = mWangIdAndCells;
973     c->mMaximumColorDistance = mMaximumColorDistance;
974     c->mColorDistancesDirty = mColorDistancesDirty;
975     c->mCellsDirty = mCellsDirty;
976     c->mLastSeenTranslationFlags = mLastSeenTranslationFlags;
977     c->setProperties(properties());
978 
979     // Avoid sharing Wang colors
980     for (QSharedPointer<WangColor> &wangColor : c->mColors) {
981         const auto properties = wangColor->properties();
982         const auto distanceToColor = wangColor->mDistanceToColor;
983 
984         wangColor = QSharedPointer<WangColor>::create(wangColor->colorIndex(),
985                                                       wangColor->name(),
986                                                       wangColor->color(),
987                                                       wangColor->imageId(),
988                                                       wangColor->probability());
989         wangColor->setProperties(properties);
990         wangColor->mWangSet = c;
991         wangColor->mDistanceToColor = distanceToColor;
992     }
993 
994     return c;
995 }
996 
wangSetTypeToString(WangSet::Type type)997 QString wangSetTypeToString(WangSet::Type type)
998 {
999     switch (type) {
1000     case WangSet::Corner:
1001         return QStringLiteral("corner");
1002     case WangSet::Edge:
1003         return QStringLiteral("edge");
1004     case WangSet::Mixed:
1005         return QStringLiteral("mixed");
1006     }
1007     return QString();
1008 }
1009 
wangSetTypeFromString(const QString & string)1010 WangSet::Type wangSetTypeFromString(const QString &string)
1011 {
1012     WangSet::Type type = WangSet::Mixed;
1013 
1014     if (string == QLatin1String("edge"))
1015         type = WangSet::Edge;
1016     else if (string == QLatin1String("corner"))
1017         type = WangSet::Corner;
1018 
1019     return type;
1020 }
1021 
1022 } // namespace Tiled
1023