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