File indexing completed on 2024-05-19 04:07:50
0001 /* 0002 SPDX-FileCopyrightText: 2009, 2010 Stefan Majewsky <majewsky@gmx.net> 0003 SPDX-FileCopyrightText: 2010 Johannes Löhnert <loehnert.kde@gmx.de> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "mergegroup.h" 0009 #include "settings.h" 0010 0011 #include <QGraphicsScene> 0012 #include <QParallelAnimationGroup> 0013 #include <QPropertyAnimation> 0014 #include <QSet> 0015 0016 static bool arePiecesPhysicallyNeighboring(Palapeli::Piece* piece1, Palapeli::Piece* piece2) 0017 { 0018 const qreal snappingPrecision = qreal(Settings::snappingPrecision()) / 100.0; 0019 const QSizeF snappingSize = snappingPrecision * piece1->atomicSize().expandedTo(piece2->atomicSize()); 0020 const QPointF posDifference = piece1->pos() - piece2->pos(); 0021 return qAbs(posDifference.x()) <= snappingSize.width() && qAbs(posDifference.y()) <= snappingSize.height(); 0022 } 0023 0024 QList<Palapeli::Piece*> Palapeli::MergeGroup::tryGrowMergeGroup(Palapeli::Piece* piece) 0025 { 0026 //definitions in this context: 0027 // logical neighbors = two pieces that occupy adjacent positions in the result image 0028 // physical neighbors = logical neighbors that have very similar coordinate systems in the current system 0029 QList<Palapeli::Piece*> resultList; 0030 QSet<Palapeli::Piece*> resultSet; 0031 resultList << piece; //this is our return value 0032 resultSet << piece; //this is for fast contains() checks 0033 bool addedSomethingInThisLoop = false; 0034 do 0035 { 0036 addedSomethingInThisLoop = false; //not yet 0037 //check all neighbors of the currently included pieces 0038 const auto currentResultList = resultList; 0039 for (Palapeli::Piece* piece : currentResultList) 0040 { 0041 const auto logicalNeighbors = piece->logicalNeighbors(); 0042 for (Palapeli::Piece* logicalNeighbor : logicalNeighbors) 0043 { 0044 if (piece->scene() != logicalNeighbor->scene()) 0045 continue; 0046 //no need to check the located physical neighbors again 0047 if (resultSet.contains(logicalNeighbor)) 0048 continue; 0049 if (arePiecesPhysicallyNeighboring(piece, logicalNeighbor)) 0050 { 0051 resultList << logicalNeighbor; 0052 resultSet << logicalNeighbor; 0053 addedSomethingInThisLoop = true; 0054 } 0055 } 0056 } 0057 } 0058 while (addedSomethingInThisLoop); 0059 return resultList; 0060 } 0061 0062 Palapeli::MergeGroup::MergeGroup(const QList<Palapeli::Piece*>& pieces, QGraphicsScene* scene, const QSizeF& pieceAreaSize, bool animated) 0063 : m_animated(animated) 0064 , m_pieces(pieces) 0065 , m_mergedPiece(nullptr) 0066 , m_scene(scene) 0067 , m_pieceAreaSize(pieceAreaSize) 0068 { 0069 //find united coordinate system (UCS) -> large pieces contribute more than smaller pieces 0070 int totalWeight = 0; 0071 for (Palapeli::Piece* piece : pieces) 0072 { 0073 const int weight = piece->representedAtomicPieces().count(); 0074 m_ucsPosition += weight * piece->pos(); 0075 totalWeight += weight; 0076 } 0077 m_ucsPosition /= totalWeight; 0078 } 0079 0080 void Palapeli::MergeGroup::start() 0081 { 0082 //if no animation is needed, continue directly 0083 if (!m_animated) 0084 createMergedPiece(); 0085 else 0086 { 0087 //create animations for merging the piece coordinate systems into the UCS 0088 QParallelAnimationGroup* masterAnimator = new QParallelAnimationGroup(this); 0089 for (Palapeli::Piece* piece : std::as_const(m_pieces)) 0090 { 0091 QPropertyAnimation* pieceAnimator = new QPropertyAnimation(piece, "pos", nullptr); 0092 pieceAnimator->setStartValue(piece->pos()); 0093 pieceAnimator->setEndValue(m_ucsPosition); 0094 pieceAnimator->setDuration(200); 0095 pieceAnimator->setEasingCurve(QEasingCurve::InCubic); 0096 masterAnimator->addAnimation(pieceAnimator); 0097 } 0098 masterAnimator->start(QAbstractAnimation::DeleteWhenStopped); 0099 connect(masterAnimator, &QParallelAnimationGroup::finished, this, &MergeGroup::createMergedPiece); 0100 } 0101 } 0102 0103 Palapeli::Piece* Palapeli::MergeGroup::mergedPiece() const 0104 { 0105 return m_mergedPiece; 0106 } 0107 0108 void Palapeli::MergeGroup::createMergedPiece() 0109 { 0110 //collect pixmaps for merging (also shadows if possible) 0111 QList<Palapeli::PieceVisuals> pieceVisuals; 0112 QList<Palapeli::PieceVisuals> shadowVisuals; 0113 QList<Palapeli::PieceVisuals> highlightVisuals; 0114 bool allPiecesHaveShadows = true; 0115 for (Palapeli::Piece* piece : std::as_const(m_pieces)) 0116 { 0117 pieceVisuals << piece->pieceVisuals(); 0118 if (allPiecesHaveShadows) //we stop collecting shadow samples when one piece has no shadow 0119 { 0120 const Palapeli::PieceVisuals shadowSample = piece->shadowVisuals(); 0121 if (shadowSample.isNull()) 0122 allPiecesHaveShadows = false; 0123 else 0124 shadowVisuals << shadowSample; 0125 } 0126 // Single pieces are assigned highlight items lazily (i.e. if 0127 // they happen to get selected), but when they are merged, each 0128 // one must have a highlight pixmap that can be merged into a 0129 // combined highlight pixmap for the new multi-part piece. 0130 if (!piece->hasHighlight()) { 0131 piece->createHighlight(m_pieceAreaSize); 0132 } 0133 highlightVisuals << piece->highlightVisuals(); 0134 } 0135 //merge pixmap and create piece 0136 Palapeli::PieceVisuals combinedPieceVisuals = Palapeli::mergeVisuals(pieceVisuals); 0137 Palapeli::PieceVisuals combinedShadowVisuals, combinedHighlightVisuals; 0138 if (allPiecesHaveShadows) 0139 combinedShadowVisuals = Palapeli::mergeVisuals(shadowVisuals); 0140 combinedHighlightVisuals = Palapeli::mergeVisuals(highlightVisuals); 0141 m_mergedPiece = new Palapeli::Piece(combinedPieceVisuals, 0142 combinedShadowVisuals, combinedHighlightVisuals); 0143 //apply UCS 0144 if (m_animated) { // If loading the scene, we add the piece later. 0145 m_scene->addItem(m_mergedPiece); 0146 } 0147 m_mergedPiece->setPos(m_ucsPosition); 0148 //transfer information from old pieces to new piece, then destroy old pieces 0149 for (Palapeli::Piece* piece : std::as_const(m_pieces)) 0150 { 0151 m_mergedPiece->addRepresentedAtomicPieces(piece->representedAtomicPieces()); 0152 m_mergedPiece->addLogicalNeighbors(piece->logicalNeighbors()); 0153 m_mergedPiece->addAtomicSize(piece->atomicSize()); 0154 if (piece->isSelected()) 0155 m_mergedPiece->setSelected(true); 0156 m_mergedPiece->setZValue(qMax(m_mergedPiece->zValue(), piece->zValue())); 0157 piece->announceReplaced(m_mergedPiece); //make sure that interactors know about the change, and delete the piece 0158 } 0159 m_mergedPiece->rewriteLogicalNeighbors(m_pieces, nullptr); //0 = these neighbors should be dropped 0160 const auto logicalNeighbors = m_mergedPiece->logicalNeighbors(); 0161 for (Palapeli::Piece* logicalNeighbor : logicalNeighbors) 0162 logicalNeighbor->rewriteLogicalNeighbors(m_pieces, m_mergedPiece); //these neighbors are now represented by m_mergedPiece 0163 //transaction done 0164 Q_EMIT pieceInstanceTransaction(m_pieces, QList<Palapeli::Piece*>() << m_mergedPiece); 0165 0166 // Do not highlight the merged piece, esp. not the solution-in-progress. 0167 m_mergedPiece->setSelected(false); 0168 deleteLater(); 0169 } 0170 0171 #include "moc_mergegroup.cpp"