Showing posts with label 2d. Show all posts
Showing posts with label 2d. Show all posts

Thursday, September 3, 2015

Marble GP - multiplayer game prototype + source code



Playing with Android, I recently completed a prototype of a simple game involving multiplayer mechanisms.

But before diving into technical details, I suggest you to watch a short demo:


Written in Java using the Android SDK, Marble GP  features:
- physics managed by JBox2D
- a home-made simple 2D Open GL ES 2.0 renderer
- a home-made TCP socket multiplayer system
- a home-made parser for levels importing in SVG format

The Open GL renderer is a direct port of the C++ Open GL ES 2.0 renderer Toto2DEngine I have written for the Raspberry PI. It is primarily a sprite batch renderer working with an atlas texture. It is quite simple to use and you could even build your own high-level graphical 2D API from it.

The client-server system is quite original because the socket server is designed to be run directly by one the devices involved in the game. So the game is self-reliant  in the sense that it doesn't need a central big and expensive distant game server. In other words, the game is local wifi multiplayer.

Finally, I was looking for a very simple way to biuld levels. I didn't want to hardcode my levels by hand, because it is tedious and it rarely gives interesting results. But because it is a prototype, I didn't have the time to code a level builder tool. So I choosed to write a simple SVG parser, generating Box2D primitives from SVG XML files. Always in the way to keep things simple, I have drawn my levels with SVG-edit.


Sources

If you are curious, you can find the sources of my 2D Open GL renderer as well as my core client-server socket classes. They are part of a my new framework Simple-Android.

Friday, May 8, 2015

Toto2DEngine : demos and sources

Toto2DEngine is a convenient C++ API to program 2D GPU accelerated graphics on the Raspberry PI.

You can watch some demos:




The code source is available as alpha release:

Friday, May 1, 2015

2D transformation matrices baking (2) : Benchmarks

This is the next part of my article about 2D transformation matrices baking. After the theory, let's go to practice and do some benchmarks. I will play with Starling 1.6 on the Flash platform.


Starling optimization

If you are a Flash platform developer and you like to use Starling for 2D accelerated graphics, go to the source code of 1.6 version and open the starling.display.DisplayObject class. The interesting part for us is in the getter function tranformationMatrix, at line 634:

mTransformationMatrix.identity();
mTransformationMatrix.scale(mScaleX, mScaleY);
MatrixUtil.skew(mTransformationMatrix, mSkewX, mSkewY);
mTransformationMatrix.rotate(mRotation);
mTransformationMatrix.translate(mX, mY);

if (mPivotX != 0.0 || mPivotY != 0.0)
{
// prepend pivot transformation
mTransformationMatrix.tx = mX - mTransformationMatrix.a * mPivotX - mTransformationMatrix.c * mPivotY;
mTransformationMatrix.ty = mY - mTransformationMatrix.b * mPivotX  - mTransformationMatrix.d * mPivotY;
}

That is a really interesting part of the code, that shows an optimized calculation of a complex 2D transformation matrix as a sequence of many basic 2D transformations.

The sequence could be simplified and summarized as follow:
1. begin from the identity matrix
2. apply translation for pivot
3. apply scale
4. apply skew
5. apply rotation
6. apply translation

So it is a quite complex sequence of transformations. Can it be baked and can we hope a significant performances improvement ? The answer is yes !


Matrices baking

I wrote the whole sequence as a concatenation sequence and I gave it to Wolfram.

From:
translation . rotation . skewing . scaling . pivot

I obtained:


I derived a code sample from that baked matrix. It can be copied-pasted in Starling in place of the previous code sample shown above:

__e = Math.cos(mSkewY);
__f = -Math.sin(mSkewX);
__g = Math.sin(mSkewY);
__h = Math.cos(mSkewX);
__i = Math.cos(mRotation);
__j = Math.sin(mRotation);
__ceigj = mScaleX * (__e * __i - __g * __j);
__dfihj = mScaleY * (__f * __i - __h * __j);
__cekgl = mScaleX * (__e * __j + __g * __i);
__dfkhl = mScaleY * (__f * __j + __h * __i);
mTransformationMatrix.a = __ceigj;
mTransformationMatrix.c = __dfihj;
mTransformationMatrix.tx = - mPivotX * __ceigj - mPivotY * __dfihj + mX;
mTransformationMatrix.b = __cekgl;
mTransformationMatrix.d = __dfkhl;
mTransformationMatrix.ty = - mPivotX * __cekgl - mPivotY * __dfkhl + mY;

That optimized code assumes to add the following properties to the starling.display.DisplayObject class:

private var __e:Number;
private var __f:Number;
private var __g:Number;
private var __h:Number;
private var __i:Number;
private var __j:Number;
private var __ceigj:Number;
private var __dfihj:Number;
private var __cekgl:Number;
private var __dfkhl:Number;


Performances

To measure the performances, I choose to instanciate 1000 Sprite (it inherits from DisplayObject), change their properties and call the transformMatrix getter function all along the execution of an Enter Frame. I measure the time elapsed just before and just after the transformMatrix call in order to have the delta. In pseudo code:

vs = new Vector.<starling.display.Sprite>(1000)

function onEnterFrame(e:Event)
{
w = 17
for (i = 0 ; i<1000 ; i++)
{
s = vs[i]
s.pivotX = w
s.pivotY = w
s.rotation = w
s.scaleX = w
s.scaleY = w
s.skewX = w
s.skewY = w
s.x = w
s.y = w

w += 0.1
if (w >= 100)
w = 17
}

time = getTimer();
for (i = 0 ; i<1000 ; i++)
{
s = vs[i];
m = s.transformationMatrix;
}
time = getTimer() - time;
}

It is quite a realistic situation ; 1000 moving sprites on screen can be easily involved for some games. Also, in Starling rendering, the call of transformationMatrix getter function is really done every frame for every moving Sprite on screen.

I ran the code on a laptop computer and on a Android phone, switching from the original Starling code to my optimized code on both devices. I choose to compute and focus on the average time of the last 60 frames all along the benchmark execution because the values per frame can vary less or more 10%. You can look to my full code for more details.

The results:
Laptop device:
- original code: 3ms
- optimized code: 2ms
Mobile device:
- originale code: 6ms
- optimized code: 5ms

In both situation, we reached an average improvement of 1ms by frame. 1ms could look negligible at a first look, but it is really significant if you think that trying to reach 60FPS, you have a very tight budget of only 16ms every frame for your whole code execution. In that context, 1ms is a great save.


Sunday, March 15, 2015

Preview : TotoEngine2D

TotoEngine2D is a library I develop. It exposes a convenient C++ API to display accelerated 2D graphics through OpenGL ES.

I target the Raspberry PI platform. Why the Raspberry PI ? Because at my knowledge, despite the presence of a GPU, the Rasp still doesn't have any descent API for accelerated 2D graphics. At first glimpse, the problem with the Rasp come from his incapacity to deal with OpenGL. Indeed, his architecture allows only the use of OpenGL ES 2.0, which can be seen as a fragment of an old version of OpenGL (2.0). So that's why popular libraries such SDL (accelerated through OpenGL) cannot give their full potential on the Rasp.

If you are interested by the project, have a look to the features of the engine.

Batching

The rendering process in TotoEngine2D is through batching. It means that on every frame, we begin with an empty list of objects to display, then we pile up any number of objects in it. When we filled our list with everything we want to show, we ask to render the whole in the window.

The important things to understand:
- the objects doesn't have any kind of persistence, the object list is totally cleared out after each render call
- the objects are rendered in the window in the same order we piled up them, from back to front

Here is an example of rendering sequence:

Toto2DEngine toto2d;
// sequence :
toto2d.clear(); // fill the window with the default background color
// ...
// pile up our objects
//...
toto2d.swap(); // swap the buffers to render


Atlas map

For performance purpose, only one single texture can be used when triggering the rendering process. In consequence we must work with texture atlas (or sprite sheet). TotoEngine2D manages 2 values of opacity. One pixel in the texture atlas can be full opaque of full transparent. Because texture atlas can only be made from 24bits colors (8bits by channel RGB), one color is sacrificed to play the role of the transparent color. By default this color is the plain fuchsia #FF00FF.

example of sprite sheet as texture atlas

However, several textures atlas can be loaded into memory and switched between different rendering:

toto2d.uploadAtlas(0, "atlas_1.tga"); // attach first atlas on slot 0
toto2d.uploadAtlas(1, "atlas_2.tga"); // attach second atlas on slot 1
toto2d.activeAtlas(0); // active the atlas attached to slot 0

When adding an object to the render list, sub-textures can be selected by the use of simplified UV mappings as axis-aligned rectangles on the texture. Such rectangles are defined by 4-uplet (x, y, w, h) where (x, y) defines the top-left corner coordinate, w the width and h the height. The coordinate system has (0, 0) coordinate at the top-left corner of the texture atlas, the x-axis goes positively on right, the y-axis goes positively on down and the unit is the pixel.

x = 80, y = 45, w = 40, h = 35


Sprites

The sprite object is the most powerfull object implemented in TotoEngine2D. Any single sprite may have its own sub-texture (UV coordinates) and his own transformation 3x3 matrix. It means that all sprites rendered can be independantly scaled, rotated and translated. Also, each sprite can display a different part of the texture atlas. That last feature guarantees the possibility to implement frame-based animation on top of TotoEngine2D.

Additionnally, each single sprite may be applied various color effects, such that tint or saturation.

Here is a sample of code, showing how to add a sprite:

glm::mat3 transf;
toto2d.getSpriteBatcher().addSprite(80, 45, 40, 35, transf);
toto2d.getSpriteBatcher().applyTint(255, 0, 0, 127);

Where (80, 45, 50, 35) defines the sub-texture on the texture atlas as (x, y, w, h) and transf is the 3x3 matrix that will be applied. Finally, a plain red tint (255, 0, 0) is applied with 50%  of intensity (127 as half of 255). Color effects always apply to the last sprite added.



As a result this sprite will render with the identity matrix transformation, so it will show on the top-left corner of the window. Because neither scale not rotation are involved, pixels from the texture align exactly on the pixels of the window. It is a pixel perfect situation, so no texture filtering will be involved.

TotoEngine2D uses the matrices from GLM library so we can find many ways to configure them on the GLM code sample page, Scales and rotations always occur around the (0, 0) origin of the coordinate system, being by default the top-left corner of the window. Translations unit is the pixel,  the x-axis going right and the y-axis going down along the window.

In order to place efficiently and accurately the sprite anywhere on window, TotoEngine2D gives the opportunity to use the TRST tool. The TRST tool is dedicated to set efficiently a matrix as an ordered sequence of Translation, Rotation, Scale, Translation.

Here is n example of TRST usage to rotate, scale and place a sprite exactly at the window center:

glm::mat3 transf;
float t2x, t2y, rot, sx, sy, t1x, t1y;
// the first translation places the sprite to match his local center with the origin:
t1x = -20.0f;
t1y = -17.5f;
// from that we can scale and rotate safely around the local center of the sprite:
sx = 4.0f; // horizontal scale x4
sy = 4.0f; // vertical scale x4
rot = 0.43f; // rotation in radians, approx. 25°
// finally we place the sprite at the center of the 320x240 window:
t2x = 160.0f; 
t2y = 120.0f;
// configure the matrix with the TRST tool
Utils::mat3TRST(t2x, t2y, rot, sx, sy, t1x, t1y, transf);
// add the sprite in the batch list
toto2d.getSpriteBatcher().addSprite(80, 45, 40, 35, transf);
toto2d.getSpriteBatcher().applyTint(255, 0, 0, 127);

Tiles

The tile object is a similar to the sprite, but it is constrained to reach better performance. First, several tile objects can share the same sub-texture. Additionnaly, the tile supports only the translation transformation, so it can be neither scaled nor rotated. Finally, the tile doesn't support color effect.

Here is an example of code, showing how to display 2 tiles sharing the same sub-texture:

int uvID;
toto2d.getSimpleTileBatcher().addUV(5, 173, 30, 25, uvID);
toto2d.getSimpleTileBatcher().addTile(uvID, 50.0f, 100.0f);
toto2d.getSimpleTileBatcher().addTile(uvID, 90.0f, 100.0f);

Where the addUV method registers a sub-texture as a 4-uplet (x, y, w, h) and link it to an ID. Then that ID can be used several times through the addTile method, specifying the position of the top-left corners of the tiles in the window.



Repeat tiles

The repeat tile object is similar to the tile object, but allowing to draw large rectangular surfaces in the window, with repetition of the sub-texture.

Here is an example of code, showing how to display a large surface with a small sub-texture:

int uvID;
toto2d.getRepeatTileBatcher().addUV(82, 205, 32, 16, uvID);
toto2d.getRepeatTileBatcher().addTiles(uvID, 50.0f, 20.0f, 220.0f, 200.0f, 0.0f, 0.0f);

Where addUV method registers the following sub-texture:



And addTiles method draws large rectangle with top-left corner coordinate (50, 20), width 220 and height 200:


Here is the complete signature of the addTiles method:

addTiles(int uvId, float x, float y, float width, float height, float scrollX, float scrollY)

The two last parameters scrollX and scrollY allow to scroll within the sub-texture, meaning that the starting point in the texture that matches the top-left corner of the drawn area will be moved. This feature allows to animate large background translations very easily and efficiently.


Distortion effects

The repeat tile object supports various distortion effects that will affect how the sub-texture will be mapped into the drawn area.

Starting from that 42x42 sub-texture:

Drawing it in a 320x240 window:



Here is the result of a vertical wave distortion with amplitude 10, period 84 and phase 0;

applyDistoWaveVertical(10.0f, 84.0f, 0.0f)

Same parameters but with a horizontal wave distortion:

applyDistoWaveHorizontal(10.0f, 84.0f, 0.0f)

Both cumulated:



Here is the result of horizontal accordeon dirstortion wit amplitude 20, period 84 and phase 0;

applyDistoAccordeonHorizontal(20.0f, 84.0f, 0.0f)

Same parameters but with a vertical accordeon distortion:

applyDistoAccordeonVertical(20.0f, 84.0f, 0.0f)

Both cumulated:



Camera

TotoEngine2D manages a camera system. The most simple feature is to scroll the content in the window. For example, starting from that scene:



 we can move our camera 50 pixels right and 100 pixels down:

toto2d.setCamera(50.0f, 100.0f); 

Resulting in scrolling all the content 50 pixels left and 100 pixels up, showing large drawn area that was outside the window before:



The camera allows us to do more, by focusing directly on a part of the window with opporunity to add a zoom effect:

toto2d.setCameraLookAt(200.0f, 175.0f, 2.0f);

Resulting in scrolling all the content such that the (200, 175) point becomes center of the window and zooming it by a factor 2 around that new center:





Wednesday, February 25, 2015

2D transformation matrices baking

While playing with OpenGL, I was facing the situation where the common way to use matrices can quickly lead to CPU overload.

If you know the 3 common transformations in 2D (in homogeneous coordinates):

translation

scale

rotation

you also surely know that you can compose with these simple transformations to build more complex transformations. That is simply done with the use of matrix multiplication.

For example, if you have a sprite that you want to rotate and scale before translating it to have its own center exactly at the center of the screen. You would simply build 4 simple matrices T1, S, R, T2 and do the multiplication to obtain the final matrix M:

M = T2 * R * S * T1

with:
T1 the translation matrix moving your sprite to match his local center with the origin
S a scale matrix
R a rotation matrix
T2 a translation matrix moving your sprite at the center of the screen

That is a very common way to build complex transformations, but when you need to deal with thousands of them, you quickly overload your CPU.

Why ? Because generic matrix 3x3 multiplication involves 27 scalar multiplications and 18 scalar additions, as shown in the code below to compute C = AxB:

C[0][0] = A[0][0] * B[0][0] + A[1][0] * B[0][1] + A[2][0] * B[0][2];
C[1][0] = A[0][0] * B[1][0] + A[1][0] * B[1][1] + A[2][0] * B[1][2];
C[2][0] = A[0][0] * B[2][0] + A[1][0] * B[2][1] + A[2][0] * B[2][2];

C[0][1] = A[0][1] * B[0][0] + A[1][1] * B[0][1] + A[2][1] * B[0][2];
C[1][1] = A[0][1] * B[1][0] + A[1][1] * B[1][1] + A[2][1] * B[1][2];
C[2][1] = A[0][1] * B[2][0] + A[1][1] * B[2][1] + A[2][1] * B[2][2];

C[0][2] = A[0][2] * B[0][0] + A[1][2] * B[0][1] + A[2][2] * B[0][2];
C[1][2] = A[0][2] * B[1][0] + A[1][2] * B[1][1] + A[2][2] * B[1][2];
C[2][2] = A[0][2] * B[2][0] + A[1][2] * B[2][1] + A[2][2] * B[2][2];

Building M involved 3 matrix multiplications (T2 * R * S * T1), so the total for one sprite is 81 scalar multiplications and 53 scalar additions. It is quite a lot when you have that amount of operations each frame for thousand of sprites.


The solution ? Bake your matrix !

That could seem really complicated to set directly a single matrix that is the result of many matrices composition. But in fact that is not, because the 3 common transformation matrices (translate, rotation, scale) contain a lot of 0 and 1.

For example, here is the matrix M baked for T2 * R * S * T1:



with:
t1x, t1y : the 1st translation
sx, sy : the scale factors
a : the rotation angle
t2x, t2y : the 2nd translation

This matrix looks complicated, but it involves only 12 scalar multiplications and 4 scalar additions. That is such an improvement if we compare with the 81 multiplications and 53 additions of the previous method. Putting that in your code instead of explicitly computing the result of T2 * R * S * T1 saves a lot of CPU resources.

Here is my code using float as scalars:

void mat3TRST(float& t2x, float &t2y, float &rot, float &sx, float &sy, float &t1x, float &t1y, Matrix3 &matRes)
{
float cRot, sRot;
cRot = cos(rot);
sRot = sin(rot);
matRes[0][0] = sx*cRot;
matRes[1][0] = sy*-sRot;
matRes[2][0] = t1x*sx*cRot + t1y*sy*-sRot + t2x;
matRes[0][1] = sx*sRot;
matRes[1][1] = sy*cRot;
matRes[2][1] = t1x*sx*sRot + t1y*sy*cRot + t2y;
matRes[0][2] = 0.0f;
matRes[1][2] = 0.0f;
matRes[2][2] = 1.0f;
}


Baking is easy
How did I obtain that result ? I didn't bake the M matrix "by hand". Instead I used WolframAlpha for that.

My input was:

{{1,0,i},{0,1,j},{0,0,1}} . {{e,f,0},{g,h,0},{0,0,1}} . {{c,0,0},{0,d,0},{0,0,1}} . {{1,0,a},{0,1,b},{0,0,1}}

It is my T2 * R * S * T1 matrix composition, but using Wolfram syntax. From that I obtained the resulting matrix, still involving the constants a,b,c,d,e,f,g,h,i,j:

{{c e, d f, a c e + b d f + i}, {c g, d h, a c g + b d h + j}, {0, 0, 1}}

My job was only to copy/paste it in my code and replace the letters with the good variable names !

Wednesday, February 11, 2015

Bitmap triangulation extraction demo

Simply draw on the surface below. Then press SPACE to extract the triangulation from your picture and embed it into Box2D and see what happen. Press ENTER to clean and play again.



The algo is fast and accurate for almost all pictures. It naturally manages holes and interlocked shapes.

How does it work ? Just few lines of code with the help of Daedalus Lib. Have a look to the wiki page if you need to be conviced !



Monday, February 9, 2015

Guns !

I just upgraded my previous playable dungeon map generator demo with 6 new weapons ! I hope you will like them. You can go directly at the bottom of the article to play the demo.



Here is the complete list of weapons:

1. Energy gun
Very basic weapon. Given for free, it has infinte ammos.




2.  Shotgun
Very popular since Doom, the shotgun disperses a range of high-damage bullets.




3. Machine gun
It quicktly throws an accurate line of bullets.




4. Plasma gun
Faster and stronger version of the energy gun.




5. Rocket gun
The rockets are slow but accurate and the explosions are devastating. Do not use near the walls !




6. Flame thrower
Short range but pass through flock of enemys.




7. Multi-shots energy gun
Upgrade of the energy gun. It throws a range of 5 balls.




8. Chain gun
High-speed frequency and rotating barrel. Devastating.




9. Megablaster
A large and continuous blast of energy ! But it takes some time to charge...




10. Laser
Continuous and accurate ray of energy.




11. Tinyrocket gun
Hybridization between a chain gun and a rocket gun ! Definitely my favorite.




You can experiment the result below. Generate a map (with custom parameters or randomly) and then click  the "play it" button. You can directly catch the 11 weapons. Additionally, you can run by pushing quickly the UP key 2 times and dodge with LEFT or RIGHT keys quickly 2 times.


Tuesday, January 27, 2015

A playable dungeon map generator

Last days I recycled my dungeon map generator and I plugged it to the engine of my proto 61 Cygni.

You can experiment the result below. Generate a map (with custom parameters or randomly) and then click  the "play it" button.

Just for fun, you can directly catch 4 weapons: the shotgun, the machine gun, the plasmagun and the rocketgun ! You will use these to kill the bots ditributed on the level. Additionally, you can run by pushing quickly the UP key 2 times and dodge with LEFT or RIGHT keys quickly 2 times.