1 /*
2 SPDX-FileCopyrightText: 2010 Stefan Majewsky <majewsky@gmx.net>
3
4 SPDX-License-Identifier: LGPL-2.0-only
5 */
6
7 #include "kgamerenderedobjectitem.h"
8
9 // own
10 #include "kgamerenderer.h"
11 // Qt
12 #include <QtMath>
13 #include <QGraphicsView>
14
15 class KGameRenderedObjectItemPrivate : public QGraphicsPixmapItem
16 {
17 public:
18 KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent);
19 bool adjustRenderSize(); //returns whether an adjustment was made; WARNING: only call when m_primaryView != 0
20 void adjustTransform();
21
22 //QGraphicsItem reimplementations (see comment below for why we need all of this)
23 bool contains(const QPointF& point) const override;
24 bool isObscuredBy(const QGraphicsItem* item) const override;
25 QPainterPath opaqueArea() const override;
26 void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override;
27 QPainterPath shape() const override;
28 public:
29 KGameRenderedObjectItem* m_parent;
30 QGraphicsView* m_primaryView;
31 QSize m_correctRenderSize;
32 QSizeF m_fixedSize;
33 };
34
KGameRenderedObjectItemPrivate(KGameRenderedObjectItem * parent)35 KGameRenderedObjectItemPrivate::KGameRenderedObjectItemPrivate(KGameRenderedObjectItem* parent)
36 : QGraphicsPixmapItem(parent)
37 , m_parent(parent)
38 , m_primaryView(nullptr)
39 , m_correctRenderSize(0, 0)
40 , m_fixedSize(-1, -1)
41 {
42 }
43
vectorLength(const QPointF & point)44 static inline int vectorLength(const QPointF& point)
45 {
46 return qSqrt(point.x() * point.x() + point.y() * point.y());
47 }
48
adjustRenderSize()49 bool KGameRenderedObjectItemPrivate::adjustRenderSize()
50 {
51 Q_ASSERT(m_primaryView);
52 //create a polygon from the item's boundingRect
53 const QRectF itemRect = m_parent->boundingRect();
54 QPolygonF itemPolygon(3);
55 itemPolygon[0] = itemRect.topLeft();
56 itemPolygon[1] = itemRect.topRight();
57 itemPolygon[2] = itemRect.bottomLeft();
58 //determine correct render size
59 const QPolygonF scenePolygon = m_parent->sceneTransform().map(itemPolygon);
60 const QPolygon viewPolygon = m_primaryView->mapFromScene(scenePolygon);
61 m_correctRenderSize.setWidth(qMax(vectorLength(viewPolygon[1] - viewPolygon[0]), 1));
62 m_correctRenderSize.setHeight(qMax(vectorLength(viewPolygon[2] - viewPolygon[0]), 1));
63 //ignore fluctuations in the render size which result from rounding errors
64 const QSize diff = m_parent->renderSize() - m_correctRenderSize;
65 if (qAbs(diff.width()) <= 1 && qAbs(diff.height()) <= 1)
66 {
67 return false;
68 }
69 m_parent->setRenderSize(m_correctRenderSize);
70 adjustTransform();
71 return true;
72 }
73
adjustTransform()74 void KGameRenderedObjectItemPrivate::adjustTransform()
75 {
76 //calculate new transform for this item
77 QTransform t;
78 t.scale(m_fixedSize.width() / m_correctRenderSize.width(), m_fixedSize.height() / m_correctRenderSize.height());
79 //render item
80 m_parent->prepareGeometryChange();
81 setTransform(t);
82 m_parent->update();
83 }
84
KGameRenderedObjectItem(KGameRenderer * renderer,const QString & spriteKey,QGraphicsItem * parent)85 KGameRenderedObjectItem::KGameRenderedObjectItem(KGameRenderer* renderer, const QString& spriteKey, QGraphicsItem* parent)
86 : QGraphicsObject(parent)
87 , KGameRendererClient(renderer, spriteKey)
88 , d(new KGameRenderedObjectItemPrivate(this))
89 {
90 setPrimaryView(renderer->defaultPrimaryView());
91 }
92
93 KGameRenderedObjectItem::~KGameRenderedObjectItem() = default;
94
offset() const95 QPointF KGameRenderedObjectItem::offset() const
96 {
97 return d->pos();
98 }
99
setOffset(const QPointF & offset)100 void KGameRenderedObjectItem::setOffset(const QPointF& offset)
101 {
102 if (d->pos() != offset)
103 {
104 prepareGeometryChange();
105 d->setPos(offset);
106 update();
107 }
108 }
109
setOffset(qreal x,qreal y)110 void KGameRenderedObjectItem::setOffset(qreal x, qreal y)
111 {
112 setOffset(QPointF(x, y));
113 }
114
fixedSize() const115 QSizeF KGameRenderedObjectItem::fixedSize() const
116 {
117 return d->m_fixedSize;
118 }
119
setFixedSize(const QSizeF & fixedSize)120 void KGameRenderedObjectItem::setFixedSize(const QSizeF& fixedSize)
121 {
122 if (d->m_primaryView)
123 {
124 d->m_fixedSize = fixedSize.expandedTo(QSize(1, 1));
125 d->adjustTransform();
126 }
127 }
128
primaryView() const129 QGraphicsView* KGameRenderedObjectItem::primaryView() const
130 {
131 return d->m_primaryView;
132 }
133
setPrimaryView(QGraphicsView * view)134 void KGameRenderedObjectItem::setPrimaryView(QGraphicsView* view)
135 {
136 if (d->m_primaryView != view)
137 {
138 d->m_primaryView = view;
139 if (view)
140 {
141 if (!d->m_fixedSize.isValid())
142 {
143 d->m_fixedSize = QSize(1, 1);
144 }
145 //determine render size and adjust coordinate system
146 d->m_correctRenderSize = QSize(-10, -10); //force adjustment to be made
147 d->adjustRenderSize();
148 }
149 else
150 {
151 d->m_fixedSize = QSize(-1, -1);
152 //reset transform to make coordinate systems of this item and the private item equal
153 prepareGeometryChange();
154 d->setTransform(QTransform());
155 update();
156 }
157 }
158 }
159
receivePixmap(const QPixmap & pixmap)160 void KGameRenderedObjectItem::receivePixmap(const QPixmap& pixmap)
161 {
162 prepareGeometryChange();
163 d->setPixmap(pixmap);
164 update();
165 }
166
167 //We want to make sure that all interactional events are sent ot this item, and
168 //not to the contained QGraphicsPixmapItem which provides the visual
169 //representation (and the metrics calculations).
170 //At the same time, we do not want the contained QGraphicsPixmapItem to slow
171 //down operations like QGraphicsScene::collidingItems().
172 //So the strategy is to use the QGraphicsPixmapItem implementation from
173 //KGameRenderedObjectItemPrivate for KGameRenderedObjectItem.
174 //Then the relevant methods in KGameRenderedObjectItemPrivate are reimplemented empty
175 //to effectively clear the item and hide it from any collision detection. This
176 //strategy allows us to use the nifty QGraphicsPixmapItem logic without exposing
177 //a QGraphicsPixmapItem subclass (which would conflict with QGraphicsObject).
178
179 //BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItem
180
boundingRect() const181 QRectF KGameRenderedObjectItem::boundingRect() const
182 {
183 return d->mapRectToParent(d->QGraphicsPixmapItem::boundingRect());
184 }
185
contains(const QPointF & point) const186 bool KGameRenderedObjectItem::contains(const QPointF& point) const
187 {
188 return d->QGraphicsPixmapItem::contains(d->mapFromParent(point));
189 }
190
isObscuredBy(const QGraphicsItem * item) const191 bool KGameRenderedObjectItem::isObscuredBy(const QGraphicsItem* item) const
192 {
193 return d->QGraphicsPixmapItem::isObscuredBy(item);
194 }
195
opaqueArea() const196 QPainterPath KGameRenderedObjectItem::opaqueArea() const
197 {
198 return d->mapToParent(d->QGraphicsPixmapItem::opaqueArea());
199 }
200
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)201 void KGameRenderedObjectItem::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
202 {
203 Q_UNUSED(painter) Q_UNUSED(option) Q_UNUSED(widget)
204 }
205
shape() const206 QPainterPath KGameRenderedObjectItem::shape() const
207 {
208 return d->mapToParent(d->QGraphicsPixmapItem::shape());
209 }
210
211 //END QGraphicsItem reimplementation of KGameRenderedObjectItem
212 //BEGIN QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate
213
contains(const QPointF & point) const214 bool KGameRenderedObjectItemPrivate::contains(const QPointF& point) const
215 {
216 Q_UNUSED(point)
217 return false;
218 }
219
isObscuredBy(const QGraphicsItem * item) const220 bool KGameRenderedObjectItemPrivate::isObscuredBy(const QGraphicsItem* item) const
221 {
222 Q_UNUSED(item)
223 return false;
224 }
225
opaqueArea() const226 QPainterPath KGameRenderedObjectItemPrivate::opaqueArea() const
227 {
228 return QPainterPath();
229 }
230
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)231 void KGameRenderedObjectItemPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
232 {
233 //Trivial stuff up to now. The fun stuff starts here. ;-)
234 //There is no way to get informed when the viewport's coordinate system
235 //(relative to this item's coordinate system) has changed, so we're checking
236 //the renderSize in each paintEvent coming from the primary view.
237 if (m_primaryView)
238 {
239 if (m_primaryView == widget || m_primaryView->isAncestorOf(widget))
240 {
241 const bool isSimpleTransformation = !painter->transform().isRotating();
242 //If an adjustment was made, do not paint now, but wait for the next
243 //painting. However, paint directly if the transformation is
244 //complex, in order to avoid flicker.
245 if (adjustRenderSize())
246 {
247 if (isSimpleTransformation)
248 {
249 return;
250 }
251 }
252 if (isSimpleTransformation)
253 {
254 //draw pixmap directly in physical coordinates
255 const QPoint basePos = painter->transform().map(QPointF()).toPoint();
256 painter->save();
257 painter->setTransform(QTransform());
258 painter->drawPixmap(basePos, pixmap());
259 painter->restore();
260 return;
261 }
262 }
263 }
264 QGraphicsPixmapItem::paint(painter, option, widget);
265 }
266
shape() const267 QPainterPath KGameRenderedObjectItemPrivate::shape() const
268 {
269 return QPainterPath();
270 }
271
272 //END QGraphicsItem reimplementation of KGameRenderedObjectItemPrivate
273
274
275