File indexing completed on 2024-04-28 04:05:03

0001 /*
0002     SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-only
0005 */
0006 
0007 #include "kgamerenderedgraphicsobject.h"
0008 
0009 // own
0010 #include "kgamegraphicsviewrenderer.h"
0011 // Qt
0012 #include <QGraphicsView>
0013 #include <QtMath>
0014 
0015 class KGameRenderedGraphicsObjectPrivate : public QGraphicsPixmapItem
0016 {
0017 public:
0018     explicit KGameRenderedGraphicsObjectPrivate(KGameRenderedGraphicsObject *parent);
0019 
0020     bool adjustRenderSize(); // returns whether an adjustment was made; WARNING: only call when m_primaryView != 0
0021     void adjustTransform();
0022 
0023     // QGraphicsItem reimplementations (see comment below for why we need all of this)
0024     bool contains(const QPointF &point) const override;
0025     bool isObscuredBy(const QGraphicsItem *item) const override;
0026     QPainterPath opaqueArea() const override;
0027     void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override;
0028     QPainterPath shape() const override;
0029 
0030 public:
0031     KGameRenderedGraphicsObject *const m_parent;
0032 
0033     QGraphicsView *m_primaryView = nullptr;
0034     QSize m_correctRenderSize = {0, 0};
0035     QSizeF m_fixedSize = {-1, -1};
0036 };
0037 
0038 KGameRenderedGraphicsObjectPrivate::KGameRenderedGraphicsObjectPrivate(KGameRenderedGraphicsObject *parent)
0039     : QGraphicsPixmapItem(parent)
0040     , m_parent(parent)
0041 {
0042 }
0043 
0044 static inline int vectorLength(QPointF point)
0045 {
0046     return qSqrt(point.x() * point.x() + point.y() * point.y());
0047 }
0048 
0049 bool KGameRenderedGraphicsObjectPrivate::adjustRenderSize()
0050 {
0051     Q_ASSERT(m_primaryView);
0052     // create a polygon from the item's boundingRect
0053     const QRectF itemRect = m_parent->boundingRect();
0054     QPolygonF itemPolygon(3);
0055     itemPolygon[0] = itemRect.topLeft();
0056     itemPolygon[1] = itemRect.topRight();
0057     itemPolygon[2] = itemRect.bottomLeft();
0058     // determine correct render size
0059     const QPolygonF scenePolygon = m_parent->sceneTransform().map(itemPolygon);
0060     const QPolygon viewPolygon = m_primaryView->mapFromScene(scenePolygon);
0061     m_correctRenderSize.setWidth(qMax(vectorLength(viewPolygon[1] - viewPolygon[0]), 1));
0062     m_correctRenderSize.setHeight(qMax(vectorLength(viewPolygon[2] - viewPolygon[0]), 1));
0063     // ignore fluctuations in the render size which result from rounding errors
0064     const QSize diff = m_parent->renderSize() - m_correctRenderSize;
0065     if (qAbs(diff.width()) <= 1 && qAbs(diff.height()) <= 1) {
0066         return false;
0067     }
0068     m_parent->setRenderSize(m_correctRenderSize);
0069     adjustTransform();
0070     return true;
0071 }
0072 
0073 void KGameRenderedGraphicsObjectPrivate::adjustTransform()
0074 {
0075     // calculate new transform for this item
0076     QTransform t;
0077     t.scale(m_fixedSize.width() / m_correctRenderSize.width(), m_fixedSize.height() / m_correctRenderSize.height());
0078     // render item
0079     m_parent->prepareGeometryChange();
0080     setTransform(t);
0081     m_parent->update();
0082 }
0083 
0084 KGameRenderedGraphicsObject::KGameRenderedGraphicsObject(KGameGraphicsViewRenderer *renderer, const QString &spriteKey, QGraphicsItem *parent)
0085     : QGraphicsObject(parent)
0086     , KGameRendererClient(renderer, spriteKey)
0087     , d_ptr(new KGameRenderedGraphicsObjectPrivate(this))
0088 {
0089     setPrimaryView(renderer->defaultPrimaryView());
0090 }
0091 
0092 KGameRenderedGraphicsObject::~KGameRenderedGraphicsObject() = default;
0093 
0094 QPointF KGameRenderedGraphicsObject::offset() const
0095 {
0096     Q_D(const KGameRenderedGraphicsObject);
0097 
0098     return d->pos();
0099 }
0100 
0101 void KGameRenderedGraphicsObject::setOffset(QPointF offset)
0102 {
0103     Q_D(KGameRenderedGraphicsObject);
0104 
0105     if (d->pos() != offset) {
0106         prepareGeometryChange();
0107         d->setPos(offset);
0108         update();
0109     }
0110 }
0111 
0112 void KGameRenderedGraphicsObject::setOffset(qreal x, qreal y)
0113 {
0114     setOffset(QPointF(x, y));
0115 }
0116 
0117 QSizeF KGameRenderedGraphicsObject::fixedSize() const
0118 {
0119     Q_D(const KGameRenderedGraphicsObject);
0120 
0121     return d->m_fixedSize;
0122 }
0123 
0124 void KGameRenderedGraphicsObject::setFixedSize(QSizeF fixedSize)
0125 {
0126     Q_D(KGameRenderedGraphicsObject);
0127 
0128     if (d->m_primaryView) {
0129         d->m_fixedSize = fixedSize.expandedTo(QSize(1, 1));
0130         d->adjustTransform();
0131     }
0132 }
0133 
0134 QGraphicsView *KGameRenderedGraphicsObject::primaryView() const
0135 {
0136     Q_D(const KGameRenderedGraphicsObject);
0137 
0138     return d->m_primaryView;
0139 }
0140 
0141 void KGameRenderedGraphicsObject::setPrimaryView(QGraphicsView *view)
0142 {
0143     Q_D(KGameRenderedGraphicsObject);
0144 
0145     if (d->m_primaryView != view) {
0146         d->m_primaryView = view;
0147         if (view) {
0148             if (!d->m_fixedSize.isValid()) {
0149                 d->m_fixedSize = QSize(1, 1);
0150             }
0151             // determine render size and adjust coordinate system
0152             d->m_correctRenderSize = QSize(-10, -10); // force adjustment to be made
0153             d->adjustRenderSize();
0154         } else {
0155             d->m_fixedSize = QSize(-1, -1);
0156             // reset transform to make coordinate systems of this item and the private item equal
0157             prepareGeometryChange();
0158             d->setTransform(QTransform());
0159             update();
0160         }
0161     }
0162 }
0163 
0164 void KGameRenderedGraphicsObject::receivePixmap(const QPixmap &pixmap)
0165 {
0166     Q_D(KGameRenderedGraphicsObject);
0167 
0168     prepareGeometryChange();
0169     d->setPixmap(pixmap);
0170     update();
0171 }
0172 
0173 // We want to make sure that all interactional events are sent ot this item, and
0174 // not to the contained QGraphicsPixmapItem which provides the visual
0175 // representation (and the metrics calculations).
0176 // At the same time, we do not want the contained QGraphicsPixmapItem to slow
0177 // down operations like QGraphicsScene::collidingItems().
0178 // So the strategy is to use the QGraphicsPixmapItem implementation from
0179 // KGameRenderedGraphicsObjectPrivate for KGameRenderedGraphicsObject.
0180 // Then the relevant methods in KGameRenderedGraphicsObjectPrivate are reimplemented empty
0181 // to effectively clear the item and hide it from any collision detection. This
0182 // strategy allows us to use the nifty QGraphicsPixmapItem logic without exposing
0183 // a QGraphicsPixmapItem subclass (which would conflict with QGraphicsObject).
0184 
0185 // BEGIN QGraphicsItem reimplementation of KGameRenderedGraphicsObject
0186 
0187 QRectF KGameRenderedGraphicsObject::boundingRect() const
0188 {
0189     Q_D(const KGameRenderedGraphicsObject);
0190 
0191     return d->mapRectToParent(d->QGraphicsPixmapItem::boundingRect());
0192 }
0193 
0194 bool KGameRenderedGraphicsObject::contains(const QPointF &point) const
0195 {
0196     Q_D(const KGameRenderedGraphicsObject);
0197 
0198     return d->QGraphicsPixmapItem::contains(d->mapFromParent(point));
0199 }
0200 
0201 bool KGameRenderedGraphicsObject::isObscuredBy(const QGraphicsItem *item) const
0202 {
0203     Q_D(const KGameRenderedGraphicsObject);
0204 
0205     return d->QGraphicsPixmapItem::isObscuredBy(item);
0206 }
0207 
0208 QPainterPath KGameRenderedGraphicsObject::opaqueArea() const
0209 {
0210     Q_D(const KGameRenderedGraphicsObject);
0211 
0212     return d->mapToParent(d->QGraphicsPixmapItem::opaqueArea());
0213 }
0214 
0215 void KGameRenderedGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0216 {
0217     Q_UNUSED(painter)
0218     Q_UNUSED(option)
0219     Q_UNUSED(widget)
0220 }
0221 
0222 QPainterPath KGameRenderedGraphicsObject::shape() const
0223 {
0224     Q_D(const KGameRenderedGraphicsObject);
0225 
0226     return d->mapToParent(d->QGraphicsPixmapItem::shape());
0227 }
0228 
0229 // END QGraphicsItem reimplementation of KGameRenderedGraphicsObject
0230 // BEGIN QGraphicsItem reimplementation of KGameRenderedGraphicsObjectPrivate
0231 
0232 bool KGameRenderedGraphicsObjectPrivate::contains(const QPointF &point) const
0233 {
0234     Q_UNUSED(point)
0235     return false;
0236 }
0237 
0238 bool KGameRenderedGraphicsObjectPrivate::isObscuredBy(const QGraphicsItem *item) const
0239 {
0240     Q_UNUSED(item)
0241     return false;
0242 }
0243 
0244 QPainterPath KGameRenderedGraphicsObjectPrivate::opaqueArea() const
0245 {
0246     return QPainterPath();
0247 }
0248 
0249 void KGameRenderedGraphicsObjectPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
0250 {
0251     // Trivial stuff up to now. The fun stuff starts here. ;-)
0252     // There is no way to get informed when the viewport's coordinate system
0253     //(relative to this item's coordinate system) has changed, so we're checking
0254     // the renderSize in each paintEvent coming from the primary view.
0255     if (m_primaryView) {
0256         if (m_primaryView == widget || m_primaryView->isAncestorOf(widget)) {
0257             const bool isSimpleTransformation = !painter->transform().isRotating();
0258             // If an adjustment was made, do not paint now, but wait for the next
0259             // painting. However, paint directly if the transformation is
0260             // complex, in order to avoid flicker.
0261             if (adjustRenderSize()) {
0262                 if (isSimpleTransformation) {
0263                     return;
0264                 }
0265             }
0266             if (isSimpleTransformation) {
0267                 // draw pixmap directly in physical coordinates
0268                 const QPoint basePos = painter->transform().map(QPointF()).toPoint();
0269                 painter->save();
0270                 painter->setTransform(QTransform());
0271                 painter->drawPixmap(basePos, pixmap());
0272                 painter->restore();
0273                 return;
0274             }
0275         }
0276     }
0277     QGraphicsPixmapItem::paint(painter, option, widget);
0278 }
0279 
0280 QPainterPath KGameRenderedGraphicsObjectPrivate::shape() const
0281 {
0282     return QPainterPath();
0283 }
0284 
0285 // END QGraphicsItem reimplementation of KGameRenderedGraphicsObjectPrivate
0286 
0287 #include "moc_kgamerenderedgraphicsobject.cpp"