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 | |
32 | CanvasItem::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 | |
48 | CanvasItem::~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 | |
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 | |
77 | void CanvasItem::setStaticStrut(CanvasItem* citem) |
78 | { |
79 | m_staticStrut = citem; |
80 | } |
81 | |
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 = 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 | |
134 | void 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 | |
173 | void 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 | |
181 | void CanvasItem::save(KConfigGroup *cfgGroup) |
182 | { |
183 | cfgGroup->writeEntry("dummykey" , true); |
184 | } |
185 | |
186 | void CanvasItem::playSound(const QString &file, double vol) |
187 | { |
188 | if (game) |
189 | game->playSound(file, vol); |
190 | } |
191 | |
192 | void CanvasItem::editModeChanged(bool editing) |
193 | { |
194 | Kolf::Overlay* overlay = this->overlay(); |
195 | if (overlay) |
196 | overlay->setVisible(editing); |
197 | } |
198 | |
199 | b2World* CanvasItem::world() const |
200 | { |
201 | return m_body->GetWorld(); |
202 | } |
203 | |
204 | void 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 | |
210 | void 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 | |
241 | QPointF CanvasItem::velocity() const |
242 | { |
243 | b2Vec2 v = m_body->GetLinearVelocity(); |
244 | return QPointF(v.x, v.y); |
245 | } |
246 | |
247 | void 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 | |
267 | void CanvasItem::startSimulation() |
268 | { |
269 | const QPointF position = getPosition() * Kolf::Box2DScaleFactor; |
270 | m_body->SetTransform(b2Vec2(position.x(), position.y()), 0); |
271 | } |
272 | |
273 | void 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 | |
286 | Kolf::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 | |
303 | void CanvasItem::propagateUpdate() |
304 | { |
305 | if (m_overlay) |
306 | m_overlay->update(); |
307 | } |
308 | |
309 | //BEGIN EllipticalCanvasItem |
310 | |
311 | EllipticalCanvasItem::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 | |
329 | bool 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 | |
337 | QPainterPath EllipticalCanvasItem::shape() const |
338 | { |
339 | QPainterPath path; |
340 | path.addEllipse(rect()); |
341 | return path; |
342 | } |
343 | |
344 | QRectF EllipticalCanvasItem::rect() const |
345 | { |
346 | return Tagaro::SpriteObjectItem::boundingRect(); |
347 | } |
348 | |
349 | void 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 | |
358 | void EllipticalCanvasItem::moveBy(double dx, double dy) |
359 | { |
360 | Tagaro::SpriteObjectItem::moveBy(dx, dy); |
361 | CanvasItem::moveBy(dx, dy); |
362 | } |
363 | |
364 | void 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 | |
371 | void 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 | |
382 | ArrowItem::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 | |
392 | qreal ArrowItem::angle() const |
393 | { |
394 | return m_angle; |
395 | } |
396 | |
397 | void ArrowItem::setAngle(qreal angle) |
398 | { |
399 | if (m_angle != angle) |
400 | { |
401 | m_angle = angle; |
402 | updatePath(); |
403 | } |
404 | } |
405 | |
406 | qreal ArrowItem::length() const |
407 | { |
408 | return m_length; |
409 | } |
410 | |
411 | void ArrowItem::setLength(qreal length) |
412 | { |
413 | if (m_length != length) |
414 | { |
415 | m_length = qMax<qreal>(length, 0.0); |
416 | updatePath(); |
417 | } |
418 | } |
419 | |
420 | bool ArrowItem::isReversed() const |
421 | { |
422 | return m_reversed; |
423 | } |
424 | |
425 | void ArrowItem::setReversed(bool reversed) |
426 | { |
427 | if (m_reversed != reversed) |
428 | { |
429 | m_reversed = reversed; |
430 | updatePath(); |
431 | } |
432 | } |
433 | |
434 | Vector ArrowItem::vector() const |
435 | { |
436 | return Vector::fromMagnitudeDirection(m_length, m_angle); |
437 | } |
438 | |
439 | void 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 | |