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
30static const int ZValueStep = 100;
31
32CanvasItem::CanvasItem(b2World* world)
33 : game(0)
34 , m_zBehavior(CanvasItem::FixedZValue)
35 , m_zValue(0)
36 , m_strut(0)
37 , m_staticStrut(0)
38 , m_body(0)
39 , m_overlay(0)
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
48CanvasItem::~CanvasItem()
49{
50 //disconnect struts
51 if (m_strut)
52 m_strut->m_struttedItems.removeAll(this);
53 foreach (CanvasItem* item, m_struttedItems)
54 item->m_strut = 0;
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
63void 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
77void CanvasItem::setStaticStrut(CanvasItem* citem)
78{
79 m_staticStrut = citem;
80}
81
82void 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 = 0;
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 foreach (QGraphicsItem* qitem, self->collidingItems())
108 {
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
134void CanvasItem::moveItemsOnStrut(const QPointF& posDiff)
135{
136 foreach (CanvasItem* citem, m_struttedItems)
137 {
138 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
139 if (!qitem || qitem->data(0) == Rtti_Putter)
140 continue;
141 citem->moveBy(posDiff.x(), posDiff.y());
142 Ball* ball = dynamic_cast<Ball*>(citem);
143 if (ball && game && !game->isEditing() && game->curBall() == ball)
144 game->ballMoved();
145 }
146}
147
148/*static*/ bool CanvasItem::mayCollide(CanvasItem* citem1, CanvasItem* citem2)
149{
150 //which one is the ball?
151 Ball* ball = dynamic_cast<Ball*>(citem1);
152 CanvasItem* citem = citem2;
153 if (!ball)
154 {
155 ball = dynamic_cast<Ball*>(citem2);
156 citem = citem1;
157 }
158 if (!ball)
159 //huh, no ball involved? then don't restrict anything, because
160 //that likely introduces weird bugs later
161 return true;
162 //if both items are graphicsitems, restrict collisions of ball to thos
163 //objects on same strut level or above (i.e. don't collide with
164 //stuff below the current strut)
165 const QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(citem);
166 if (!qitem)
167 return true;
168 const int ballStrutLevel = int(ball->zValue()) / ZValueStep;
169 const int itemStrutLevel = int(qitem->zValue()) / ZValueStep;
170 return ballStrutLevel <= itemStrutLevel;
171}
172
173void CanvasItem::moveBy(double dx, double dy)
174{
175 Q_UNUSED(dx) Q_UNUSED(dy)
176 QGraphicsItem* qitem = dynamic_cast<QGraphicsItem*>(this);
177 if (qitem)
178 updateZ(qitem);
179}
180
181void CanvasItem::save(KConfigGroup *cfgGroup)
182{
183 cfgGroup->writeEntry("dummykey", true);
184}
185
186void CanvasItem::playSound(const QString &file, double vol)
187{
188 if (game)
189 game->playSound(file, vol);
190}
191
192void CanvasItem::editModeChanged(bool editing)
193{
194 Kolf::Overlay* overlay = this->overlay();
195 if (overlay)
196 overlay->setVisible(editing);
197}
198
199b2World* CanvasItem::world() const
200{
201 return m_body->GetWorld();
202}
203
204void CanvasItem::addShape(Kolf::Shape* shape)
205{
206 if (shape->attach(this)) //this will fail if the shape is already attached to some object
207 m_shapes << shape;
208}
209
210void CanvasItem::setSimulationType(CanvasItem::SimulationType type)
211{
212 if (m_simulationType != type)
213 {
214 m_simulationType = type;
215 //write type into b2Body
216 b2BodyType b2type; bool b2active;
217 switch (type)
218 {
219 case CanvasItem::NoSimulation:
220 b2type = b2_staticBody;
221 b2active = false;
222 break;
223 case CanvasItem::CollisionSimulation:
224 b2type = b2_staticBody;
225 b2active = true;
226 break;
227 case CanvasItem::KinematicSimulation:
228 b2type = b2_kinematicBody;
229 b2active = true;
230 break;
231 case CanvasItem::DynamicSimulation: default:
232 b2type = b2_dynamicBody;
233 b2active = true;
234 break;
235 }
236 m_body->SetType(b2type);
237 m_body->SetActive(b2active);
238 }
239}
240
241QPointF CanvasItem::velocity() const
242{
243 b2Vec2 v = m_body->GetLinearVelocity();
244 return QPointF(v.x, v.y);
245}
246
247void CanvasItem::setVelocity(const QPointF& newVelocity)
248{
249 const QPointF currentVelocity = this->velocity();
250 if (newVelocity != currentVelocity)
251 {
252 const qreal mass = m_body->GetMass();
253 //WARNING: Velocities are NOT scaled. The timestep is scaled, instead.
254 //See where b2World::Step() gets called for more info.
255 if (mass == 0 || m_simulationType != CanvasItem::DynamicSimulation)
256 {
257 m_body->SetLinearVelocity(b2Vec2(newVelocity.x(), newVelocity.y()));
258 }
259 else
260 {
261 const QPointF impulse = (newVelocity - currentVelocity) * mass;
262 m_body->ApplyLinearImpulse(b2Vec2(impulse.x(), impulse.y()), m_body->GetPosition());
263 }
264 }
265}
266
267void CanvasItem::startSimulation()
268{
269 const QPointF position = getPosition() * Kolf::Box2DScaleFactor;
270 m_body->SetTransform(b2Vec2(position.x(), position.y()), 0);
271}
272
273void CanvasItem::endSimulation()
274{
275 //read position
276 b2Vec2 v = m_body->GetPosition();
277 QPointF position = QPointF(v.x, v.y) / Kolf::Box2DScaleFactor;
278 if (position != getPosition())
279 //HACK: The above condition can be removed later, but for now we need to
280 //prevent moveBy() from being called with (0, 0) arguments because such
281 //have a non-standard behavior with some classes (e.g. Ball), i.e. these
282 //arguments trigger some black magic
283 setPosition(position);
284}
285
286Kolf::Overlay* CanvasItem::overlay(bool createIfNecessary)
287{
288 //the overlay is created once it is requested
289 if (!m_overlay && createIfNecessary)
290 {
291 m_overlay = createOverlay();
292 if (m_overlay)
293 {
294 //should be above object representation
295 m_overlay->setZValue(m_overlay->qitem()->zValue() + 100);
296 //initialize the overlay's parameters
297 m_overlay->update();
298 }
299 }
300 return m_overlay;
301}
302
303void CanvasItem::propagateUpdate()
304{
305 if (m_overlay)
306 m_overlay->update();
307}
308
309//BEGIN EllipticalCanvasItem
310
311EllipticalCanvasItem::EllipticalCanvasItem(bool withEllipse, const QString& spriteKey, QGraphicsItem* parent, b2World* world)
312 : Tagaro::SpriteObjectItem(Kolf::renderer(), spriteKey, parent)
313 , CanvasItem(world)
314 , m_ellipseItem(0)
315 , m_shape(0)
316{
317 if (withEllipse)
318 {
319 m_ellipseItem = new QGraphicsEllipseItem(this);
320 m_ellipseItem->setFlag(QGraphicsItem::ItemStacksBehindParent);
321 //won't appear unless pen/brush is configured
322 m_ellipseItem->setPen(Qt::NoPen);
323 m_ellipseItem->setBrush(Qt::NoBrush);
324 }
325 m_shape = new Kolf::EllipseShape(QRectF());
326 addShape(m_shape);
327}
328
329bool EllipticalCanvasItem::contains(const QPointF& point) const
330{
331 const QSizeF halfSize = size() / 2;
332 const qreal xScaled = point.x() / halfSize.width();
333 const qreal yScaled = point.y() / halfSize.height();
334 return xScaled * xScaled + yScaled * yScaled < 1;
335}
336
337QPainterPath EllipticalCanvasItem::shape() const
338{
339 QPainterPath path;
340 path.addEllipse(rect());
341 return path;
342}
343
344QRectF EllipticalCanvasItem::rect() const
345{
346 return Tagaro::SpriteObjectItem::boundingRect();
347}
348
349void EllipticalCanvasItem::setSize(const QSizeF& size)
350{
351 setOffset(QPointF(-0.5 * size.width(), -0.5 * size.height()));
352 Tagaro::SpriteObjectItem::setSize(size);
353 if (m_ellipseItem)
354 m_ellipseItem->setRect(this->rect());
355 m_shape->setRect(this->rect());
356}
357
358void EllipticalCanvasItem::moveBy(double dx, double dy)
359{
360 Tagaro::SpriteObjectItem::moveBy(dx, dy);
361 CanvasItem::moveBy(dx, dy);
362}
363
364void EllipticalCanvasItem::saveSize(KConfigGroup* group)
365{
366 const QSizeF size = this->size();
367 group->writeEntry("width", size.width());
368 group->writeEntry("height", size.height());
369}
370
371void EllipticalCanvasItem::loadSize(KConfigGroup* group)
372{
373 QSizeF size = this->size();
374 size.rwidth() = group->readEntry("width", size.width());
375 size.rheight() = group->readEntry("height", size.height());
376 setSize(size);
377}
378
379//END EllipticalCanvasItem
380//BEGIN ArrowItem
381
382ArrowItem::ArrowItem(QGraphicsItem* parent)
383 : QGraphicsPathItem(parent)
384 , m_angle(0), m_length(20)
385 , m_reversed(false)
386{
387 updatePath();
388 setPen(QPen(Qt::black));
389 setBrush(Qt::NoBrush);
390}
391
392qreal ArrowItem::angle() const
393{
394 return m_angle;
395}
396
397void ArrowItem::setAngle(qreal angle)
398{
399 if (m_angle != angle)
400 {
401 m_angle = angle;
402 updatePath();
403 }
404}
405
406qreal ArrowItem::length() const
407{
408 return m_length;
409}
410
411void ArrowItem::setLength(qreal length)
412{
413 if (m_length != length)
414 {
415 m_length = qMax<qreal>(length, 0.0);
416 updatePath();
417 }
418}
419
420bool ArrowItem::isReversed() const
421{
422 return m_reversed;
423}
424
425void ArrowItem::setReversed(bool reversed)
426{
427 if (m_reversed != reversed)
428 {
429 m_reversed = reversed;
430 updatePath();
431 }
432}
433
434Vector ArrowItem::vector() const
435{
436 return Vector::fromMagnitudeDirection(m_length, m_angle);
437}
438
439void ArrowItem::updatePath()
440{
441 if (m_length == 0)
442 {
443 setPath(QPainterPath());
444 return;
445 }
446 //the following three points define the arrow tip
447 const QPointF extent = Vector::fromMagnitudeDirection(m_length, m_angle);
448 const QPointF startPoint = m_reversed ? extent : QPointF();
449 const QPointF endPoint = m_reversed ? QPointF() : extent;
450 const QPointF point1 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle + M_PI / 12);
451 const QPointF point2 = endPoint - Vector::fromMagnitudeDirection(m_length / 2, m_angle - M_PI / 12);
452 QPainterPath path;
453 path.addPolygon(QPolygonF() << startPoint << endPoint);
454 path.addPolygon(QPolygonF() << point1 << endPoint << point2);
455 setPath(path);
456}
457
458//END ArrowItem
459