Add linear impulse example
[qml-box2d:ankrats-qml-box2d.git] / box2dworld.cpp
1 /*
2  * Box2D QML plugin
3  * Copyright (C) 2010 Nokia Corporation
4  *
5  * This file is part of the Box2D QML plugin.
6  *
7  * This library is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation; either version 2.1 of the License, or (at
10  * your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
15  * License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this library;  If not, see <http://www.gnu.org/licenses/>.
19  */
20
21 #include "box2dworld.h"
22
23 #include "box2dbody.h"
24 #include "box2dfixture.h"
25
26 #include <QTimerEvent>
27
28 #include <Box2D.h>
29
30 class ContactEvent
31 {
32 public:
33     enum Type {
34         BeginContact,
35         EndContact
36     };
37
38     Type type;
39     Box2DFixture *fixtureA;
40     Box2DFixture *fixtureB;
41 };
42
43 class ContactListener : public b2ContactListener
44 {
45 public:
46     void BeginContact(b2Contact *contact);
47     void EndContact(b2Contact *contact);
48
49     void clearEvents() { mEvents.clear(); }
50     const QList<ContactEvent> &events() { return mEvents; }
51
52 private:
53     QList<ContactEvent> mEvents;
54 };
55
56 static Box2DFixture *toBox2DFixture(b2Fixture *fixture)
57 {
58     return static_cast<Box2DFixture*>(fixture->GetUserData());
59 }
60
61 void ContactListener::BeginContact(b2Contact *contact)
62 {
63     ContactEvent event;
64     event.type = ContactEvent::BeginContact;
65     event.fixtureA = toBox2DFixture(contact->GetFixtureA());
66     event.fixtureB = toBox2DFixture(contact->GetFixtureB());
67     mEvents.append(event);
68 }
69
70 void ContactListener::EndContact(b2Contact *contact)
71 {
72     ContactEvent event;
73     event.type = ContactEvent::EndContact;
74     event.fixtureA = toBox2DFixture(contact->GetFixtureA());
75     event.fixtureB = toBox2DFixture(contact->GetFixtureB());
76     mEvents.append(event);
77 }
78
79
80 Box2DWorld::Box2DWorld(QDeclarativeItem *parent) :
81     QDeclarativeItem(parent),
82     mWorld(0),
83     mContactListener(new ContactListener),
84     mTimeStep(1.0f / 60.0f),
85     mVelocityIterations(10),
86     mPositionIterations(10),
87     mFrameTime(1000 / 60),
88     mGravity(qreal(0), qreal(-10)),
89     mTimerId(0)
90 {
91 }
92
93 Box2DWorld::~Box2DWorld()
94 {
95     delete mWorld;
96     delete mContactListener;
97 }
98
99 void Box2DWorld::setGravity(const QPointF &gravity)
100 {
101     if (mGravity == gravity)
102         return;
103
104     mGravity = gravity;
105     if (mWorld)
106         mWorld->SetGravity(b2Vec2(gravity.x(), gravity.y()));
107
108     emit gravityChanged();
109 }
110
111 void Box2DWorld::componentComplete()
112 {
113     QDeclarativeItem::componentComplete();
114
115     const b2Vec2 gravity(mGravity.x(), mGravity.y());
116     bool doSleep = true;
117
118     mWorld = new b2World(gravity, doSleep);
119     mWorld->SetContactListener(mContactListener);
120
121     foreach (QGraphicsItem *child, childItems())
122         if (Box2DBody *body = dynamic_cast<Box2DBody*>(child))
123             registerBody(body);
124
125     mTimerId = startTimer(mFrameTime);
126 }
127
128 /**
129  * Registers a Box2D body with this world. When the world component is
130  * complete, it will initialize the body.
131  */
132 void Box2DWorld::registerBody(Box2DBody *body)
133 {
134     mBodies.append(body);
135     body->initialize(mWorld);
136 }
137
138 /**
139  * Unregisters a Box2D body from this world. It will be asked to clean up after
140  * itself.
141  */
142 void Box2DWorld::unregisterBody(Box2DBody *body)
143 {
144     mBodies.removeOne(body);
145     body->cleanup(mWorld);
146 }
147
148 void Box2DWorld::timerEvent(QTimerEvent *event)
149 {
150     if (event->timerId() == mTimerId) {
151         mWorld->Step(mTimeStep, mVelocityIterations, mPositionIterations);
152         foreach (Box2DBody *body, mBodies)
153             body->synchronize();
154
155         // Emit contact signals
156         foreach (const ContactEvent &event, mContactListener->events()) {
157             switch (event.type) {
158             case ContactEvent::BeginContact:
159                 event.fixtureA->emitBeginContact(event.fixtureB);
160                 event.fixtureB->emitBeginContact(event.fixtureA);
161                 break;
162             case ContactEvent::EndContact:
163                 event.fixtureA->emitEndContact(event.fixtureB);
164                 event.fixtureB->emitEndContact(event.fixtureA);
165                 break;
166             }
167         }
168         mContactListener->clearEvents();
169
170         // Emit signals for the current state of the contacts
171         b2Contact *contact = mWorld->GetContactList();
172         while (contact) {
173             Box2DFixture *fixtureA = toBox2DFixture(contact->GetFixtureA());
174             Box2DFixture *fixtureB = toBox2DFixture(contact->GetFixtureB());
175
176             fixtureA->emitContactChanged(fixtureB);
177             fixtureB->emitContactChanged(fixtureA);
178
179             contact = contact->GetNext();
180         }
181
182         emit stepped();
183     }
184     QDeclarativeItem::timerEvent(event);
185 }
186
187 QVariant Box2DWorld::itemChange(GraphicsItemChange change,
188                                 const QVariant &value)
189 {
190     if (isComponentComplete()) {
191         if (change == ItemChildAddedChange) {
192             QGraphicsItem *child = value.value<QGraphicsItem*>();
193             if (Box2DBody *body = dynamic_cast<Box2DBody*>(child))
194                 registerBody(body);
195         } else if (change == ItemChildRemovedChange) {
196             QGraphicsItem *child = value.value<QGraphicsItem*>();
197             if (Box2DBody *body = dynamic_cast<Box2DBody*>(child))
198                 unregisterBody(body);
199         }
200     }
201
202     return QDeclarativeItem::itemChange(change, value);
203 }