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