Design considerations of Space Blok Qt
This page describes the design and implementation choices made when designing Space Blok.
For information about the porting of Space Blok Qt from Space Blok XNA, see the porting story page.
Contents
- Design considerations of Space Blok Qt
Integration of Qt3D
The application uses Qt3D to implement the drawing of 3D objects. The QML bindings for Qt3D exists in QtQuick3D, but a decision was made not to use them as this would have required the implementation of QML bindings of the Bullet physics library as well.
At the moment, Qt3D is not provided by the QtSDK and must be compiled manually. See the section 'Setting up the Qt3D' for more information.
Qt3D is included in the project by adding these lines to the project file:
QT += opengl declarative CONFIG += qt3d
The main widget class in our application is GameView, derived from QGLView. QGLView extends QtOpenGL's QGLWidget by adding 3D viewing support, such as abstraction for camera, default mouse and keyboard events to move the camera, and stereo viewing.
All of the representations of the 3D models are stored in the QGLSceneNode object tree. A single QGLSceneNode manages the vertexes, materials, effects, and transformations of a single 3D object. By adding some other QGLSceneNode as a child of another node, the 3D objects can be 'glued' together.
In this application, QGLSceneNode is a base class of the GameObject class, defining the graphical representation of the game object. Whenever Bullet moves the game object, the member variables of QGLSceneNode are updated - moving the graphical object.
Qt3D provides higher level classes such as QGLMaterial and QGLMaterialCollection to define the materials (including textures) for the 3D objects. The same material objects can be assigned for multiple QGLSceneNode objects, giving them the appearance of the specified material when they are drawn.
Qt3D's QGLShaderProgramEffect allows us to write effects with GLSL for 3D rendering. In Space Blok, a custom shader is implemented to create lighting effects. See fragment and vertex shader codes for more details.
The documentation of Qt3D can be found at: http://doc.qt.nokia.com/qt3d-snapshot/index.html
Example: add sphere to scene
Adding a sphere to a 3D scene can be done with Qt3D as follows:
QGLBuilder builder; builder << QGLSphere(radius * 2.0f, 3); m_SceneNode = builder.finalizedSceneNode(); m_SceneNode->setEffect(QGL::LitMaterial); m_SceneNode->setPalette(materialCollection); m_SceneNode->setMaterialIndex(materialIndex);
Qt3D provides the QGLBuilder class to easily build 3D shapes, either by manually defining the geometry of each object, or by using simple primitives such as a sphere. Here we use the sphere, defining its diameter and level-of-detail = 3 (to make the sphere build from 256 triangles). After we are done defining the object for QGLBuilder, the object can be created by calling the finalizedSceneNode of the builder.
By setting the effect, palette, and material, the node is set to draw the faces of the object using the given material and the given effect.
Integration of Bullet
Bullet provides great interfaces for integrating the physics world simulation and the physics objects to the application.
The Bullet sources are included in projects by using a separate bullet.pri including all required headers and sources. Not all Bullet sources are included: for example, we don't need to implement soft body simulation in our application.
The rigid body simulation is added to the project by using the btDiscreteDynamicWorld class. In addition, a few default implementations must be given to btDiscreteDynamicWorld, such as btSequentialImpulseConstraintSolver, btCollisionDispatcher, btDefaultCollisionConfiguration, and btBroadphaseInterface. It is possible to customise the default behavior of the Bullet simulation logic, but in this application the default implementations are more than sufficient in the accuracy and the performance of the simulation.
The btDiscreetDynamicWorld class will simulate the position and rotation of each physical body in the world. Bullet uses btCollisionShape-derived objects to define the collision object of the body. In this application we use box, sphere, and plane type collision shapes to simulate the collision of the ball, blackhole, and blok objects. The level compound is constructed from several box-like collision shapes glued as one body, making the blok compound rotate as a single object.
As Bullet simulates only non-graphical objects, the simulation objects must be integrated with Qt3D QGLSceneNodes. A really nice interface is available to get the position and rotation data, stored in the btTransform object, from Bullet by deriving our own class from btMotionState. Each btMotionState object is attached to a Bullet body object. When Bullet updates the body, btMotionState::setWorldTransform is called with the btTransform parameter, specifying where the body is currently. The given btTransform object must be interpreted and the data must be placed into the Qt3D QGLSceneNode object in order to move the graphical representation of the body.
See the tutorial about using btMotionStates at Bullet's web page: http://bulletphysics.org/mediawiki-1.5.8/index.php/MotionStates. Also, see how it is implemented in Space Blok to set the transform for QGLSceneNode in the file trunk/src/src/gameobject.cpp.
The discreet world must be simulated on each tick of the application by calling btDiscreetDynamicWorld::stepSimulation and giving the the realtime time from last call to the function.
More help on using Bullet can be found in their wiki page: http://bulletphysics.org/mediawiki-1.5.8/index.php/Main_Page
Also, the Doxygen help can be found on the web: http://www.continuousphysics.com/Bullet/BulletFull/index.html
Example to add sphere-like shape to simulation world
For example when creating a ball object, the Bullet body and collision shape are created like this:
btCollisionShape *shape = new btSphereShape(radius);
btScalar mass = 5;
btVector3 inertia(0, 0, 0);
shape->calculateLocalInertia(mass, inertia);
btRigidBody::btRigidBodyConstructionInfo bodyCI(mass, this, shape, inertia);
bodyCI.m_angularSleepingThreshold = 0.0f;
bodyCI.m_linearSleepingThreshold = 0.0f;
bodyCI.m_restitution = 0.9f;
if (still) {
bodyCI.m_linearDamping = 1.0f;
}
m_Body = new btRigidBody(bodyCI);
m_Body->setUserPointer(this);
world->addRigidBody(m_Body, COL_BALL, COL_BALL | COL_BLACK_HOLE | COL_BLOK | COL_PLATFORM);
First the btSphereShape collision shape is created for the given radius. This defines the shape and dimension for the collision calculations. The mass and inertia are defined, inertia is also calculated for the shape.
Bullet uses structs to set the initial values for objects, reducing the parameter list in the class constructor.
To define all of the parameters when btRigidBody is created, btRigidBodyConstructionInfo is used. In this case we give the struct the mass, the pointer to the btMotionState object (in Space Blok, GameObject is derived from it), the collision shape, and the inertia of the shape. Angular and linear sleeping thresholds are set to 0, disabling the ability to make the bodies sleep. Sleeping means that once a body is set to sleep, it's physics calculations are skipped, reducing the load on physics calculations. The linear damping disables the movement of the body. In Space Blok, when the ball is created, it first floats on top of the platform until the user swipes it off the platform.
The btRigidBodyConstructionInfo struct is used to create btRigidBody. The pointer of this object is stored inside the body, and will be used in the custom collision handling later, when we want to detect the collision of Ball and Blok objects.
Finally, the body is added as part of the world simulation by calling the addRigiBody of btDiscreteDynamicWorld to make Bullet simulate this body. The given parameters define which bodies' collisions are handled.
Detecting collisions between bodies
Bullet provides information about the collisions between bodies via manifolds. From each manifold the contacting bodies can be retrieved, the base class of btRigidBody is btCollisionObject, which contactManifold gives in the following example. Via the bodies, the stored user pointers can be accessed. Also, contactManifold gives the contacting point in world coordinates, allowing us to write specific behavior of a collision.
int manifoldsCount = m_Dispatcher->getNumManifolds();
for (int i=0; i<manifoldsCount; i++) {
btPersistentManifold *contactManifold = m_Dispatcher->getManifoldByIndexInternal(i);
int contactsCount = contactManifold->getNumContacts();
for (int j=0; j<contactsCount; j++) {
btManifoldPoint &pt = contactManifold->getContactPoint(j);
btCollisionObject *objA = static_cast<btCollisionObject*>(contactManifold->getBody0());
btCollisionObject *objB = static_cast<btCollisionObject*>(contactManifold->getBody1());
// Retrieve our own object from user pointer
GameObject *gameObjectA = static_cast<GameObject*>(objA->getUserPointer());
GameObject *gameObjectB = static_cast<GameObject*>(objB->getUserPointer());
if ((gameObjectA->gameObjectType() == GameObject::BALL && gameObjectB->gameObjectType() == GameObject::BLOK) ||
(gameObjectA->gameObjectType() == GameObject::BLOK && gameObjectB->gameObjectType() == GameObject::BALL))
{
// Handle collision between BALL and BLOK
}
}
}
Multitouch Support
Space Blok detects multiple touches on Harmattan and Symbian, enabling the swiping of multiple balls at the same time.
To make the GameView to handle touch events, first a specific attribute is set for the GameView in the constructor:
setAttribute(Qt::WA_AcceptTouchEvents);
All events are handled in the virtual method event(QEvent *event) to differentiate the events, the touch events are given for each Platform object to be further handled.
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd: {
foreach (Platform *platform, m_Platforms) {
platform->handleEvent(event);
}
return true;
}
In the class Platform::handleEvent, the events are further interpreted:
switch (event->type()) {
case QEvent::TouchBegin:
case QEvent::TouchUpdate:
case QEvent::TouchEnd: {
QList<QTouchEvent::TouchPoint> touchPoints = static_cast<QTouchEvent*>(event)->touchPoints();
foreach (const QTouchEvent::TouchPoint &touchPoint, touchPoints) {
switch (touchPoint.state()) {
case Qt::TouchPointPressed: {
QVector3D pos = m_GameView->map2DPointToZLevelPoint(touchPoint.pos().toPoint(), m_BallInitialPos.z());
handlePressInput(pos, touchPoint.id());
break;
}
case Qt::TouchPointReleased: {
QVector3D pos = m_GameView->map2DPointToZLevelPoint(touchPoint.pos().toPoint(), m_BallInitialPos.z());
handleReleaseInput(pos, touchPoint.id());
break;
}
default: {
break;
}
}
}
break;
}
default:
return false;
}
return true;
The TouchBegin, TouchUpdate, and TouchEnd events' TouchPoint list is iterated. In the TouchPoint state pressed, the position of the touch point is converted to a 3D coordinate and given with the ID of the point for the function handlePressInput. The touch ID is very important as it identifies which touch point of all simultaneous touch points we are dealing with. In the TouchPoint state released, again, the point is converted to a 3D coordinate and handled with the touch ID on a handleReleaseInput function. If the touch ID was the same on release as when it was pressed, the ball can be swiped.
See the whole code in Platform.cpp.
Integration of Qt GameEnabler Audio engine
Qt GameEnabler's audio engine is integrated by adding qtgameenableraudio.pri to the project file, including all required sources.
The use of audio effects is implemented to the AudioManager class, resulting in only a few lines of code.
For more information about the Qt GameEnabler, see the project page at: https://projects.developer.nokia.com/qtgameenabler
Integration of QML menus
The main idea of integrating the QML menus to a 3D scene is to render the QML scene to the texture and show the texture in the face of a 3D model. The painting and delivering of mouse and keyboard events to the QML scene must be handled manually.
Menu loading and handling is implemented in the MenuManager class. The QGraphicsScene-derived Qt3D class QGraphicsEmbedScene is used to hold the QML scene. The class also provides the delivering of events for the QML and the rendering of the scene to the texture. The rendering does have some issues in the current implementation of Qt3D and similar implementation is done manually in the MenuManager class.
Optimization tricks
There are few major optimizations implemented on the Space Blok Qt to increase the performance of the application.
The Qt3D is not released yet and there are some performance issues relating to the standard effects, especially on the Symbian target. That's why the use of QGLMaterials are somewhat bypassed. The material parameters are passed manually for the custom implemented shader by using uniforms, for example see the code at Level::draw and bloks.fsh how this is implemented.
The use of alpha blending is kept as low as possible because it seems to be really costly operation.
The whirl effect of the black hole is implemented by using custom shader that renders the three layers of black hole as one. Earlier this was implemented by having three separate QGLSceneNodes as different layers, this reduced the FPS by 8 on mobiles so the shader really improves the performance.
Custom implementation of QGLRenderOrderComparator is implemented to skip the render ordering of the QGlSceneNodes. The QGLSceneNodes are rendered in the order they are processed in the Space Blok Qt code giving us more performance.
The drawing of the QML menus are implemented by rendering the graphics scene into a QGLFrameBufferObject, this reduces the transferring of the graphics between server and client side of the OpenGl.
Setting up Qt3D
Qt3D must be compiled manually in order to compile applications against it.
On the Qt3D documentation page you can find instructions on how to build Qt3D from the sources:
http://doc.qt.nokia.com/qt3d-snapshot/qt3d-building.html
Setting up Qt3D for Symbian^3
Qt3D can be compiled with the latest Qt SDK.
- Download the Qt SDK.
Get the Qt3D TP2 from https://qt.gitorious.org/qt/quick3d by running these commands with git:
$ git clone git://gitorious.org/qt/quick3d.git quick3d $ cd quick3d $ git checkout tp2
Then start the Qt SDK and open the project quick3d.pro in the quick3d folder.
Select Symbian^3 target and build the project. After successful build your Qt SDK is ready to compile Qt3D applications.
To install the Qt3D library to the Symbian^3 device, you must create the sis packages. This can be done by opening the project quick3d/devices/symbian/symbian.pro. By building this project you will get the required .sis files which can be installed to the device.
Setting up Qt3D for Harmattan
In order to provide work-around for the current problems on Qt3D on Harmattan target, the Space Blok Qt project contains Qt3D that compiles statically to the Space Blok Qt binary. Qt3D library is not required to be installed.
In the spaceblok.pro file comment the following line, like this:
#CONFIG += qt3d
and uncomment the following line, like this:
include(qt3d/qt3d.pri)
Run qmake and you are ready to compile. The build will take some time as the Qt3D is built inside the application.
Setting up Qt3D for Windows Desktop
Install the latest Qt SDK.
Get the latest sources of Qt3D from: https://qt.gitorious.org/qt/quick3d by running these command on git:
$ git clone git://gitorious.org/qt/quick3d.git quick3d $ cd quick3d $ git checkout tp2
Open the quick3d/quick3d.pro project file in the Qt SDK and compile the project to the Windows target. After successful build the Qt3D is installed to your Qt SDK and it is ready to build Qt3D applications.
Class diagram of using GameObject
The following image represents the use of GameObject in the Space Blok application. GameObject combines Bullet's physical body and Qt3D's graphics representation, making GameObject a very essential piece of code in Space Blok.
Release downloads
| ID | File | Description | Size | Uploaded | Dls | Uploader | Component | Version | Platform | Type |
|---|---|---|---|---|---|---|---|---|---|---|
|
Harmattan (static Qt3D) binary
|
||||||||||
|
Symbian Anna binary
|
||||||||||
|
Qt3D for Symbian Anna
|
||||||||||
|
Project source with binaries
|
||||||||||
|
Windows binary
|
||||||||||
|
Symbian binary
|
||||||||||
|
Qt3D for Symbian^3
|
||||||||||
|
Windows binary
|
||||||||||
|
Symbian binary
|
||||||||||
|
Harmattan binary
|
||||||||||
|
Project source with binaries
|
||||||||||
|
Project source with binaries
|
||||||||||
|
Harmattan binary
|
||||||||||
|
Windows binary
|
Attachments
-
game.jpg
(82.1 KB) -
added by kratsan 7 months ago.
-
in_game_menu.jpg
(64.2 KB) -
added by kratsan 7 months ago.
-
main_menu.jpg
(81.2 KB) -
added by kratsan 7 months ago.
-
results_view.jpg
(60.8 KB) -
added by kratsan 7 months ago.
-
info_view.jpg
(96.8 KB) -
added by kratsan 7 months ago.
-
GameObjectHierarchy.png
(6.8 KB) -
added by kratsan 7 months ago.


