1 /*
2  * box2dbody.cpp
3  * Copyright (c) 2010-2011 Thorbjørn Lindeijer <thorbjorn@lindeijer.nl>
4  * Copyright (c) 2011 Daker Fernandes Pinheiro <daker.pinheiro@openbossa.org>
5  * Copyright (c) 2011 Tan Miaoqing <miaoqing.tan@nokia.com>
6  * Copyright (c) 2011 Antonio Aloisio <antonio.aloisio@nokia.com>
7  * Copyright (c) 2011 Alessandro Portale <alessandro.portale@nokia.com>
8  * Copyright (c) 2011 Joonas Erkinheimo <joonas.erkinheimo@nokia.com>
9  * Copyright (c) 2011 Antti Krats <antti.krats@digia.com>
10  *
11  * This file is part of the Box2D QML plugin.
12  *
13  * This software is provided 'as-is', without any express or implied warranty.
14  * In no event will the authors be held liable for any damages arising from
15  * the use of this software.
16  *
17  * Permission is granted to anyone to use this software for any purpose,
18  * including commercial applications, and to alter it and redistribute it
19  * freely, subject to the following restrictions:
20  *
21  * 1. The origin of this software must not be misrepresented; you must not
22  *    claim that you wrote the original software. If you use this software in
23  *    a product, an acknowledgment in the product documentation would be
24  *    appreciated but is not required.
25  *
26  * 2. Altered source versions must be plainly marked as such, and must not be
27  *    misrepresented as being the original software.
28  *
29  * 3. This notice may not be removed or altered from any source distribution.
30  */
31 
32 #include "box2dbody.h"
33 
34 #include "box2dfixture.h"
35 #include "box2dworld.h"
36 #include <qmath.h>
37 
sync(float & value,float newValue)38 static bool sync(float &value, float newValue)
39 {
40     if (qFuzzyCompare(value, newValue))
41         return false;
42 
43     value = newValue;
44     return true;
45 }
46 
sync(b2Vec2 & value,const b2Vec2 & newValue)47 static bool sync(b2Vec2 &value, const b2Vec2 &newValue)
48 {
49     if (qFuzzyCompare(value.x, newValue.x) && qFuzzyCompare(value.y, newValue.y))
50         return false;
51 
52     value = newValue;
53     return true;
54 }
55 
56 
Box2DBody(QObject * parent)57 Box2DBody::Box2DBody(QObject *parent) :
58     QObject(parent),
59     mWorld(0),
60     mTarget(0),
61     mBody(0),
62     mComponentComplete(false),
63     mTransformDirty(false),
64     mCreatePending(false)
65 {
66     mBodyDef.userData = this;
67     setWorld(Box2DWorld::defaultWorld());
68 }
69 
~Box2DBody()70 Box2DBody::~Box2DBody()
71 {
72     if (mBody)
73         mWorld->world().DestroyBody(mBody);
74 }
75 
setLinearDamping(float linearDamping)76 void Box2DBody::setLinearDamping(float linearDamping)
77 {
78     if (mBodyDef.linearDamping == linearDamping)
79         return;
80 
81     mBodyDef.linearDamping = linearDamping;
82     if (mBody)
83         mBody->SetLinearDamping(linearDamping);
84 
85     emit linearDampingChanged();
86 }
87 
setAngularDamping(float angularDamping)88 void Box2DBody::setAngularDamping(float angularDamping)
89 {
90     if (mBodyDef.angularDamping == angularDamping)
91         return;
92 
93     mBodyDef.angularDamping = angularDamping;
94     if (mBody)
95         mBody->SetAngularDamping(angularDamping);
96 
97     emit angularDampingChanged();
98 }
99 
setBodyType(BodyType bodyType)100 void Box2DBody::setBodyType(BodyType bodyType)
101 {
102     if (mBodyDef.type == static_cast<b2BodyType>(bodyType))
103         return;
104 
105     mBodyDef.type = static_cast<b2BodyType>(bodyType);
106     if (mBody)
107         mBody->SetType(mBodyDef.type);
108 
109     emit bodyTypeChanged();
110 }
111 
setBullet(bool bullet)112 void Box2DBody::setBullet(bool bullet)
113 {
114     if (mBodyDef.bullet == bullet)
115         return;
116 
117     mBodyDef.bullet = bullet;
118     if (mBody)
119         mBody->SetBullet(bullet);
120 
121     emit bulletChanged();
122 }
123 
setSleepingAllowed(bool sleepingAllowed)124 void Box2DBody::setSleepingAllowed(bool sleepingAllowed)
125 {
126     if (mBodyDef.allowSleep == sleepingAllowed)
127         return;
128 
129     mBodyDef.allowSleep = sleepingAllowed;
130     if (mBody)
131         mBody->SetSleepingAllowed(sleepingAllowed);
132 
133     emit sleepingAllowedChanged();
134 }
135 
setFixedRotation(bool fixedRotation)136 void Box2DBody::setFixedRotation(bool fixedRotation)
137 {
138     if (mBodyDef.fixedRotation == fixedRotation)
139         return;
140 
141     mBodyDef.fixedRotation = fixedRotation;
142     if (mBody)
143         mBody->SetFixedRotation(fixedRotation);
144 
145     emit fixedRotationChanged();
146 }
147 
setActive(bool active)148 void Box2DBody::setActive(bool active)
149 {
150     if (mBodyDef.active == active)
151         return;
152 
153     mBodyDef.active = active;
154     if (mBody)
155         mBody->SetActive(active);
156 }
157 
isAwake() const158 bool Box2DBody::isAwake() const
159 {
160     return mBody ? mBody->IsAwake() : mBodyDef.awake;
161 }
162 
setAwake(bool awake)163 void Box2DBody::setAwake(bool awake)
164 {
165     mBodyDef.awake = awake;
166     if (mBody)
167         mBody->SetAwake(awake);
168 }
169 
linearVelocity() const170 QPointF Box2DBody::linearVelocity() const
171 {
172     if (mBody)
173         return invertY(mBody->GetLinearVelocity());
174     return invertY(mBodyDef.linearVelocity);
175 }
176 
setLinearVelocity(const QPointF & velocity)177 void Box2DBody::setLinearVelocity(const QPointF &velocity)
178 {
179     if (linearVelocity() == velocity)
180         return;
181 
182     mBodyDef.linearVelocity = invertY(velocity);
183     if (mBody)
184         mBody->SetLinearVelocity(mBodyDef.linearVelocity);
185 
186     emit linearVelocityChanged();
187 }
188 
angularVelocity() const189 float Box2DBody::angularVelocity() const
190 {
191     if (mBody)
192         return toDegrees(mBody->GetAngularVelocity());
193     return toDegrees(mBodyDef.angularVelocity);
194 }
195 
setAngularVelocity(float velocity)196 void Box2DBody::setAngularVelocity(float velocity)
197 {
198     if (angularVelocity() == velocity)
199         return;
200 
201     mBodyDef.angularVelocity = toRadians(velocity);
202     if (mBody)
203         mBody->SetAngularVelocity(mBodyDef.angularVelocity);
204 
205     emit angularVelocityChanged();
206 }
207 
setGravityScale(float gravityScale)208 void Box2DBody::setGravityScale(float gravityScale)
209 {
210     if (mBodyDef.gravityScale == gravityScale)
211         return;
212 
213     mBodyDef.gravityScale = gravityScale;
214     if (mBody)
215         mBody->SetGravityScale(gravityScale);
216 
217     emit gravityScaleChanged();
218 }
219 
fixtures()220 QQmlListProperty<Box2DFixture> Box2DBody::fixtures()
221 {
222     return QQmlListProperty<Box2DFixture>(this, 0,
223                                           &Box2DBody::append_fixture,
224                                           &Box2DBody::count_fixture,
225                                           &Box2DBody::at_fixture,
226                                           0);
227 }
228 
append_fixture(QQmlListProperty<Box2DFixture> * list,Box2DFixture * fixture)229 void Box2DBody::append_fixture(QQmlListProperty<Box2DFixture> *list,
230                                Box2DFixture *fixture)
231 {
232     Box2DBody *body = static_cast<Box2DBody*>(list->object);
233     body->mFixtures.append(fixture);
234 }
235 
count_fixture(QQmlListProperty<Box2DFixture> * list)236 int Box2DBody::count_fixture(QQmlListProperty<Box2DFixture> *list)
237 {
238     Box2DBody *body = static_cast<Box2DBody*>(list->object);
239     return body->mFixtures.length();
240 }
241 
at_fixture(QQmlListProperty<Box2DFixture> * list,int index)242 Box2DFixture *Box2DBody::at_fixture(QQmlListProperty<Box2DFixture> *list, int index)
243 {
244     Box2DBody *body = static_cast<Box2DBody*>(list->object);
245     return body->mFixtures.at(index);
246 }
247 
originOffset() const248 QPointF Box2DBody::originOffset() const
249 {
250     Q_ASSERT(mTarget);
251 
252     QPointF origin = -mTarget->transformOriginPoint();
253     qreal c = qCos(-mBodyDef.angle);
254     qreal s = qSin(-mBodyDef.angle);
255 
256     return QPointF(origin.x() * c - origin.y() * s - origin.x(),
257                    origin.x() * s + origin.y() * c - origin.y());
258 }
259 
addFixture(Box2DFixture * fixture)260 void Box2DBody::addFixture(Box2DFixture *fixture)
261 {
262     mFixtures.append(fixture);
263     if (mBody)
264         fixture->initialize(this);
265 }
266 
createBody()267 void Box2DBody::createBody()
268 {
269     if (!mWorld)
270         return;
271 
272     if (!mComponentComplete) {
273         // When components are created dynamically, they get their parent
274         // assigned before they have been completely initialized. In that case
275         // we need to delay initialization.
276         mCreatePending = true;
277         return;
278     }
279 
280     if (!mTarget)
281         mTarget = qobject_cast<QQuickItem *>(parent());
282 
283     if (mTarget) {
284         mBodyDef.angle = toRadians(mTarget->rotation());
285         mBodyDef.position = mWorld->toMeters(
286                     mTarget->transformOrigin() == QQuickItem::TopLeft ?
287                         mTarget->position() :
288                         mTarget->position() + originOffset());
289     }
290 
291     mBody = mWorld->world().CreateBody(&mBodyDef);
292     mCreatePending = false;
293     mTransformDirty = false;
294     foreach (Box2DFixture *fixture, mFixtures)
295         fixture->initialize(this);
296     emit bodyCreated();
297 }
298 
299 /**
300  * Synchronizes the state of this body with the internal Box2D state.
301  */
synchronize()302 void Box2DBody::synchronize()
303 {
304     Q_ASSERT(mBody);
305 
306     if (sync(mBodyDef.angle, mBody->GetAngle()))
307         if (mTarget)
308             mTarget->setRotation(toDegrees(mBodyDef.angle));
309 
310     if (sync(mBodyDef.position, mBody->GetPosition())) {
311         if (mTarget) {
312             mTarget->setPosition(
313                         mTarget->transformOrigin() == QQuickItem::TopLeft ?
314                             mWorld->toPixels(mBodyDef.position) :
315                             mWorld->toPixels(mBodyDef.position) - originOffset());
316 
317         }
318         emit positionChanged();
319     }
320 }
321 
classBegin()322 void Box2DBody::classBegin()
323 {
324 }
325 
componentComplete()326 void Box2DBody::componentComplete()
327 {
328     mComponentComplete = true;
329 
330     if (mCreatePending)
331         createBody();
332 }
333 
setWorld(Box2DWorld * world)334 void Box2DBody::setWorld(Box2DWorld *world)
335 {
336     if (mWorld == world)
337         return;
338 
339     if (mWorld)
340         disconnect(mWorld, SIGNAL(pixelsPerMeterChanged()), this, SLOT(onWorldPixelsPerMeterChanged()));
341     if (world)
342         connect(world, SIGNAL(pixelsPerMeterChanged()), this, SLOT(onWorldPixelsPerMeterChanged()));
343 
344     // Destroy body when leaving our previous world
345     if (mWorld && mBody) {
346         mWorld->world().DestroyBody(mBody);
347         mBody = 0;
348     }
349 
350     mWorld = world;
351     createBody();
352 }
353 
setTarget(QQuickItem * target)354 void Box2DBody::setTarget(QQuickItem *target)
355 {
356     if (mTarget == target)
357         return;
358 
359     if (mTarget)
360         mTarget->disconnect(this);
361 
362     mTarget = target;
363     mTransformDirty = target != 0;
364 
365     if (target) {
366         connect(target, SIGNAL(xChanged()), this, SLOT(markTransformDirty()));
367         connect(target, SIGNAL(yChanged()), this, SLOT(markTransformDirty()));
368         connect(target, SIGNAL(rotationChanged()), this, SLOT(markTransformDirty()));
369     }
370 
371     emit targetChanged();
372 }
373 
updateTransform()374 void Box2DBody::updateTransform()
375 {
376     Q_ASSERT(mTarget);
377     Q_ASSERT(mBody);
378     Q_ASSERT(mTransformDirty);
379 
380     mBodyDef.angle = toRadians(mTarget->rotation());
381     mBodyDef.position = mWorld->toMeters(
382                 mTarget->transformOrigin() == QQuickItem::TopLeft ?
383                     mTarget->position() :
384                     mTarget->position() + originOffset());
385 
386     mBody->SetTransform(mBodyDef.position, mBodyDef.angle);
387     mTransformDirty = false;
388 }
389 
applyLinearImpulse(const QPointF & impulse,const QPointF & point)390 void Box2DBody::applyLinearImpulse(const QPointF &impulse,
391                                    const QPointF &point)
392 {
393     if (mBody)
394         mBody->ApplyLinearImpulse(invertY(impulse), mWorld->toMeters(point), true);
395 }
396 
applyAngularImpulse(qreal impulse)397 void Box2DBody::applyAngularImpulse(qreal impulse)
398 {
399     if (mBody)
400         mBody->ApplyAngularImpulse(impulse, true);
401 }
402 
applyTorque(qreal torque)403 void Box2DBody::applyTorque(qreal torque)
404 {
405     if (mBody)
406         mBody->ApplyTorque(torque, true);
407 }
408 
getWorldCenter() const409 QPointF Box2DBody::getWorldCenter() const
410 {
411     if (mBody)
412         return mWorld->toPixels(mBody->GetWorldCenter());
413     return QPointF();
414 }
415 
getLocalCenter() const416 QPointF Box2DBody::getLocalCenter() const
417 {
418     if (mBody)
419         return mWorld->toPixels(mBody->GetLocalCenter());
420     return QPointF();
421 }
422 
applyForce(const QPointF & force,const QPointF & point)423 void Box2DBody::applyForce(const QPointF &force, const QPointF &point)
424 {
425     if (mBody)
426         mBody->ApplyForce(invertY(force), mWorld->toMeters(point), true);
427 }
428 
applyForceToCenter(const QPointF & force)429 void Box2DBody::applyForceToCenter(const QPointF &force)
430 {
431     if (mBody)
432         mBody->ApplyForceToCenter(invertY(force), true);
433 }
434 
getMass() const435 float Box2DBody::getMass() const
436 {
437     return mBody ? mBody->GetMass() : 0.0;
438 }
439 
resetMassData()440 void Box2DBody::resetMassData()
441 {
442     if (mBody)
443         mBody->ResetMassData();
444 }
445 
getInertia() const446 float Box2DBody::getInertia() const
447 {
448     return mBody ? mBody->GetInertia() : 0.0;
449 }
450 
toWorldPoint(const QPointF & localPoint) const451 QPointF Box2DBody::toWorldPoint(const QPointF &localPoint) const
452 {
453     if (mBody)
454         return mWorld->toPixels(mBody->GetWorldPoint(mWorld->toMeters(localPoint)));
455     return QPointF();
456 }
457 
toWorldVector(const QPointF & localVector) const458 QPointF Box2DBody::toWorldVector(const QPointF &localVector) const
459 {
460     if (mBody)
461         return mWorld->toPixels(mBody->GetWorldVector(mWorld->toMeters(localVector)));
462     return QPointF();
463 }
464 
toLocalPoint(const QPointF & worldPoint) const465 QPointF Box2DBody::toLocalPoint(const QPointF &worldPoint) const
466 {
467     if (mBody)
468         return mWorld->toPixels(mBody->GetLocalPoint(mWorld->toMeters(worldPoint)));
469     return QPointF();
470 }
471 
toLocalVector(const QPointF & worldVector) const472 QPointF Box2DBody::toLocalVector(const QPointF &worldVector) const
473 {
474     if (mBody)
475         return mWorld->toPixels(mBody->GetLocalVector(mWorld->toMeters(worldVector)));
476     return QPointF();
477 }
478 
getLinearVelocityFromWorldPoint(const QPointF & point) const479 QPointF Box2DBody::getLinearVelocityFromWorldPoint(const QPointF &point) const
480 {
481     if (mBody)
482         return invertY(mBody->GetLinearVelocityFromWorldPoint(mWorld->toMeters(point)));
483     return QPointF();
484 }
485 
getLinearVelocityFromLocalPoint(const QPointF & point) const486 QPointF Box2DBody::getLinearVelocityFromLocalPoint(const QPointF &point) const
487 {
488     if (mBody)
489         return invertY(mBody->GetLinearVelocityFromLocalPoint(mWorld->toMeters(point)));
490     return QPointF();
491 }
492 
markTransformDirty()493 void Box2DBody::markTransformDirty()
494 {
495     mTransformDirty = mTransformDirty || (mWorld && !mWorld->isSynchronizing());
496 }
497 
onWorldPixelsPerMeterChanged()498 void Box2DBody::onWorldPixelsPerMeterChanged()
499 {
500     if (mBody) {
501         foreach (Box2DFixture *fixture, mFixtures)
502             fixture->recreateFixture();
503         markTransformDirty();
504         updateTransform();
505     }
506 }
507