1 /*
2 Copyright (C) 2002-2005, Jason Katz-Brown <jasonkb@mit.edu>
3 Copyright 2008, 2009, 2010 Stefan Majewsky <majewsky@gmx.net>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "canvasitem.h"
21 #include "game.h"
22 #include "landscape.h"
23 #include "overlay.h"
24 #include "shape.h"
25
26 #include <Box2D/Dynamics/b2Body.h>
27 #include <Box2D/Dynamics/b2World.h>
28
29 //this is how much a strut and the items on it are raised
30 static const int ZValueStep = 100;
31
CanvasItem(b2World * world)32 CanvasItem::CanvasItem(b2World* world)
33 : game(nullptr)
34 , m_zBehavior(CanvasItem::FixedZValue)
35 , m_zValue(0)
36 , m_strut(nullptr)
37 , m_staticStrut(nullptr)
38 , m_body(nullptr)
39 , m_overlay(nullptr)
40 , m_simulationType((CanvasItem::SimulationType) -1)
41 {
42 b2BodyDef bodyDef;
43 bodyDef.userData = this;
44 m_body = world->CreateBody(&bodyDef);
45 setSimulationType(CanvasItem::CollisionSimulation);
46 }
47
~CanvasItem()48 CanvasItem::~CanvasItem()
49 {
50 //disconnect struts
51 if (m_strut)
52 m_strut->m_struttedItems.removeAll(this);
53 for (CanvasItem* item : std::as_const(m_struttedItems))
54 item->m_strut = nullptr;
55 //The overlay is deleted first, because it might interact with all other parts of the object.
56 delete m_overlay;
57 //NOTE: Box2D objects will need to be destroyed in the following order:
58 //subobjects, shapes, own b2Body
59 qDeleteAll(m_shapes);
60 m_body->GetWorld()->DestroyBody(m_body);
61 }
62
setZBehavior(CanvasItem::ZBehavior behavior,qreal zValue)63 void CanvasItem::setZBehavior(CanvasItem::ZBehavior behavior, qreal zValue)
64 {
65 m_zBehavior = behavior;
66 m_zValue = zValue;
67 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(this);
68 if (qitem)
69 {
70 if (m_zBehavior == CanvasItem::FixedZValue)
71 qitem->setZValue(m_zValue);
72 else
73 updateZ(qitem);
74 }
75 }
76
setStaticStrut(CanvasItem * citem)77 void CanvasItem::setStaticStrut(CanvasItem* citem)
78 {
79 m_staticStrut = citem;
80 }
81
updateZ(QGraphicsItem * self)82 void CanvasItem::updateZ(QGraphicsItem* self)
83 {
84 //disconnect from old strut (if any)
85 //TODO: not if old strut is new strut (or did I forget some cornercases?)
86 if (m_strut)
87 {
88 m_strut->m_struttedItems.removeAll(this);
89 m_strut = nullptr;
90 }
91 //simple behavior
92 if (m_zBehavior == CanvasItem::FixedZValue)
93 {
94 self->setZValue(m_zValue);
95 return;
96 }
97 if (m_zBehavior == CanvasItem::IsStrut)
98 {
99 self->setZValue(ZValueStep);
100 return;
101 }
102 //determine new strut
103 if (m_staticStrut)
104 m_strut = m_staticStrut;
105 else
106 {
107 const auto collidingItems = self->collidingItems();
108 for (QGraphicsItem* qitem :collidingItems) {
109 CanvasItem* citem = dynamic_cast<CanvasItem*>(qitem);
110 if (citem && citem->m_zBehavior == CanvasItem::IsStrut)
111 {
112 //special condition for slopes: they must lie inside the strut's area, not only touch it
113 Kolf::Slope* slope = dynamic_cast<Kolf::Slope*>(this);
114 if (slope)
115 if (!slope->collidesWithItem(qitem, Qt::ContainsItemBoundingRect))
116 continue;
117 //strut found
118 m_strut = citem;
119 break;
120 }
121 }
122 }
123 //strut found?
124 if (m_strut)
125 {
126 m_strut->m_struttedItems << this;
127 self->setZValue(m_zValue + ZValueStep);
128 }
129 //no strut found -> set default zValue
130 else
131 self->setZValue(m_zValue);
132 }
133
moveItemsOnStrut(const QPointF & posDiff)134 void CanvasItem::moveItemsOnStrut(const QPointF& posDiff)
135 {
136 for (CanvasItem* citem : std::as_const(m_struttedItems)) {
137 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
138 if (!qitem || qitem->data(0) == Rtti_Putter)
139 continue;
140 citem->moveBy(posDiff.x(), posDiff.y());
141 Ball* ball = dynamic_cast<Ball*>(citem);
142 if (ball && game && !game->isEditing() && game->curBall() == ball)
143 game->ballMoved();
144 }
145 }
146
mayCollide(CanvasItem * citem1,CanvasItem * citem2)147 /*static*/ bool CanvasItem::mayCollide(CanvasItem* citem1, CanvasItem* citem2)
148 {
149 //which one is the ball?
150 Ball* ball = dynamic_cast<Ball*>(citem1);
151 CanvasItem* citem = citem2;
152 if (!ball)
153 {
154 ball = dynamic_cast<Ball*>(citem2);
155 citem = citem1;
156 }
157 if (!ball)
158 //huh, no ball involved? then don't restrict anything, because
159 //that likely introduces weird bugs later
160 return true;
161 //if both items are graphicsitems, restrict collisions of ball to those
162 //objects on same strut level or above (i.e. don't collide with
163 //stuff below the current strut)
164 const QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
165 if (!qitem)
166 return true;
167 const int ballStrutLevel = int(ball->zValue()) / ZValueStep;
168 const int itemStrutLevel = int(qitem->zValue()) / ZValueStep;
169 return ballStrutLevel <= itemStrutLevel;
170 }
171
moveBy(double dx,double dy)172 void CanvasItem::moveBy(double dx, double dy)
173 {
174 Q_UNUSED(dx) Q_UNUSED(dy)
175 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(this);
176 if (qitem)
177 updateZ(qitem);
178 }
179
save(KConfigGroup * cfgGroup)180 void CanvasItem::save(KConfigGroup *cfgGroup)
181 {
182 cfgGroup->writeEntry("dummykey", true);
183 }
184
editModeChanged(bool editing)185 void CanvasItem::editModeChanged(bool editing)
186 {
187 Kolf::Overlay* overlay = this->overlay();
188 if (overlay)
189 overlay->setVisible(editing);
190 }
191
world() const192 b2World* CanvasItem::world() const
193 {
194 return m_body->GetWorld();
195 }
196
addShape(Kolf::Shape * shape)197 void CanvasItem::addShape(Kolf::Shape* shape)
198 {
199 if (shape->attach(this)) //this will fail if the shape is already attached to some object
200 m_shapes << shape;
201 }
202
setSimulationType(CanvasItem::SimulationType type)203 void CanvasItem::setSimulationType(CanvasItem::SimulationType type)
204 {
205 if (m_simulationType != type)
206 {
207 m_simulationType = type;
208 //write type into b2Body
209 b2BodyType b2type; bool b2active;
210 switch (type)
211 {
212 case CanvasItem::NoSimulation:
213 b2type = b2_staticBody;
214 b2active = false;
215 break;
216 case CanvasItem::CollisionSimulation:
217 b2type = b2_staticBody;
218 b2active = true;
219 break;
220 case CanvasItem::KinematicSimulation:
221 b2type = b2_kinematicBody;
222 b2active = true;
223 break;
224 case CanvasItem::DynamicSimulation: default:
225 b2type = b2_dynamicBody;
226 b2active = true;
227 break;
228 }
229 m_body->SetType(b2type);
230 m_body->SetActive(b2active);
231 }
232 }
233
velocity() const234 QPointF CanvasItem::velocity() const
235 {
236 b2Vec2 v = m_body->GetLinearVelocity();
237 return QPointF(v.x, v.y);
238 }
239
setVelocity(const QPointF & newVelocity)240 void CanvasItem::setVelocity(const QPointF& newVelocity)
241 {
242 const QPointF currentVelocity = this->velocity();
243 if (newVelocity != currentVelocity)
244 {
245 const qreal mass = m_body->GetMass();
246 //WARNING: Velocities are NOT scaled. The timestep is scaled, instead.
247 //See where b2World::Step() gets called for more info.
248 if (mass == 0 || m_simulationType != CanvasItem::DynamicSimulation)
249 {
250 m_body->SetLinearVelocity(b2Vec2(newVelocity.x(), newVelocity.y()));
251 }
252 else
253 {
254 const QPointF impulse = (newVelocity - currentVelocity) * mass;
255 m_body->ApplyLinearImpulse(b2Vec2(impulse.x(), impulse.y()), m_body->GetPosition());
256 }
257 }
258 }
259
startSimulation()260 void CanvasItem::startSimulation()
261 {
262 const QPointF position = getPosition() * Kolf::Box2DScaleFactor;
263 m_body->SetTransform(b2Vec2(position.x(), position.y()), 0);
264 }
265
endSimulation()266 void CanvasItem::endSimulation()
267 {
268 //read position
269 b2Vec2 v = m_body->GetPosition();
270 QPointF position = QPointF(v.x, v.y) / Kolf::Box2DScaleFactor;
271 if (position != getPosition())
272 //HACK: The above condition can be removed later, but for now we need to
273 //prevent moveBy() from being called with (0, 0) arguments because such
274 //have a non-standard behavior with some classes (e.g. Ball), i.e. these
275 //arguments trigger some black magic
276 setPosition(position);
277 }
278
overlay(bool createIfNecessary)279 Kolf::Overlay* CanvasItem::overlay(bool createIfNecessary)
280 {
281 //the overlay is created once it is requested
282 if (!m_overlay && createIfNecessary)
283 {
284 m_overlay = createOverlay();
285 if (m_overlay)
286 {
287 //should be above object representation
288 m_overlay->setZValue(m_overlay->qitem()->zValue() + 100);
289 //initialize the overlay's parameters
290 m_overlay->update();
291 }
292 }
293 return m_overlay;
294 }
295
propagateUpdate()296 void CanvasItem::propagateUpdate()
297 {
298 if (m_overlay)
299 m_overlay->update();
300 }
301
302 //BEGIN EllipticalCanvasItem
303
EllipticalCanvasItem(bool withEllipse,const QString & spriteKey,QGraphicsItem * parent,b2World * world)304 EllipticalCanvasItem::EllipticalCanvasItem(bool withEllipse, const QString& spriteKey, QGraphicsItem* parent, b2World* world)
305 : Tagaro::SpriteObjectItem(Kolf::renderer(), spriteKey, parent)
306 , CanvasItem(world)
307 , m_ellipseItem(nullptr)
308 , m_shape(nullptr)
309 {
310 if (withEllipse)
311 {
312 m_ellipseItem = new QGraphicsEllipseItem(this);
313 m_ellipseItem->setFlag(QGraphicsItem::ItemStacksBehindParent);
314 //won't appear unless pen/brush is configured
315 m_ellipseItem->setPen(Qt::NoPen);
316 m_ellipseItem->setBrush(Qt::NoBrush);
317 }
318 m_shape = new Kolf::EllipseShape(QRectF());
319 addShape(m_shape);
320 }
321
contains(const QPointF & point) const322 bool EllipticalCanvasItem::contains(const QPointF& point) const
323 {
324 const QSizeF halfSize = size() / 2;
325 const qreal xScaled = point.x() / halfSize.width();
326 const qreal yScaled = point.y() / halfSize.height();
327 return xScaled * xScaled + yScaled * yScaled < 1;
328 }
329
shape() const330 QPainterPath EllipticalCanvasItem::shape() const
331 {
332 QPainterPath path;
333 path.addEllipse(rect());
334 return path;
335 }
336
rect() const337 QRectF EllipticalCanvasItem::rect() const
338 {
339 return Tagaro::SpriteObjectItem::boundingRect();
340 }
341
setSize(const QSizeF & size)342 void EllipticalCanvasItem::setSize(const QSizeF& size)
343 {
344 setOffset(QPointF(-0.5 * size.width(), -0.5 * size.height()));
345 Tagaro::SpriteObjectItem::setSize(size);
346 if (m_ellipseItem)
347 m_ellipseItem->setRect(this->rect());
348 m_shape->setRect(this->rect());
349 }
350
moveBy(double dx,double dy)351 void EllipticalCanvasItem::moveBy(double dx, double dy)
352 {
353 Tagaro::SpriteObjectItem::moveBy(dx, dy);
354 CanvasItem::moveBy(dx, dy);
355 }
356
saveSize(KConfigGroup * group)357 void EllipticalCanvasItem::saveSize(KConfigGroup* group)
358 {
359 const QSizeF size = this->size();
360 group->writeEntry("width", size.width());
361 group->writeEntry("height", size.height());
362 }
363
loadSize(KConfigGroup * group)364 void EllipticalCanvasItem::loadSize(KConfigGroup* group)
365 {
366 QSizeF size = this->size();
367 size.rwidth() = group->readEntry("width", size.width());
368 size.rheight() = group->readEntry("height", size.height());
369 setSize(size);
370 }
371
372 //END EllipticalCanvasItem
373 //BEGIN ArrowItem
374
ArrowItem(QGraphicsItem * parent)375 ArrowItem::ArrowItem(QGraphicsItem* parent)
376 : QGraphicsPathItem(parent)
377 , m_angle(0), m_length(20)
378 , m_reversed(false)
379 {
380 updatePath();
381 setPen(QPen(Qt::black));
382 setBrush(Qt::NoBrush);
383 }
384
angle() const385 qreal ArrowItem::angle() const
386 {
387 return m_angle;
388 }
389
setAngle(qreal angle)390 void ArrowItem::setAngle(qreal angle)
391 {
392 if (m_angle != angle)
393 {
394 m_angle = angle;
395 updatePath();
396 }
397 }
398
length() const399 qreal ArrowItem::length() const
400 {
401 return m_length;
402 }
403
setLength(qreal length)404 void ArrowItem::setLength(qreal length)
405 {
406 if (m_length != length)
407 {
408 m_length = qMax<qreal>(length, 0.0);
409 updatePath();
410 }
411 }
412
isReversed() const413 bool ArrowItem::isReversed() const
414 {
415 return m_reversed;
416 }
417
setReversed(bool reversed)418 void ArrowItem::setReversed(bool reversed)
419 {
420 if (m_reversed != reversed)
421 {
422 m_reversed = reversed;
423 updatePath();
424 }
425 }
426
vector() const427 Vector ArrowItem::vector() const
428 {
429 return Vector::fromMagnitudeDirection(m_length, m_angle);
430 }
431
updatePath()432 void ArrowItem::updatePath()
433 {
434 if (m_length == 0)
435 {
436 setPath(QPainterPath());
437 return;
438 }
439 //the following three points define the arrow tip
440 const QPointF extent = Vector::fromMagnitudeDirection(m_length, m_angle);
441 const QPointF startPoint = m_reversed ? extent : QPointF();
442 const QPointF endPoint = m_reversed ? QPointF() : extent;
443 const QPointF point1 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle + M_PI / 12);
444 const QPointF point2 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle - M_PI / 12);
445 QPainterPath path;
446 path.addPolygon(QPolygonF() << startPoint << endPoint);
447 path.addPolygon(QPolygonF() << point1 << endPoint << point2);
448 setPath(path);
449 }
450
451 //END ArrowItem
452