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