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"