sparrow3D - multi platform game engine


Ziz

Advanced Member
Joined
Jan 15, 2006
Messages
3,583
Good evening, folk.


Some of you may know me from "snowman" or "puzzletube". I am the guy behind these games and in the last months I re-engineered my whole engine and gave it a name:


sparrow3d.


Sparrow3d is an engine similar to Penjin, Fenix, glBasic or the new Pengyne engine.


Why another engine?


First of all I wanted to write a 3D software renderer for the gp2x and other open handhelds. I just made more because of my laziness. It began with snowman, where I needed a basis, which had to be the same for every supported system. After snowman came puzzletube and there the first time I put my code in a own library and since then I optimized and rewrote the whole system. And added lot of other useful stuff.


To be fair I start with the disadvantages of sparrow3D (in comparison e.g. to Penjin):

  • no OOP. pure C. (of course you can use it in OOP, but you have to do it yourself)
  • no GL ES. Everything is rendered in software for compatibility to the gp2x and dingoo. So my renderer is slower, but works everywhere :)
  • only 16 bit graphic (better for my optimizations)


But there are of course PLENTY advantage. ^^

  • pure C (faster)
  • many supported platforms (gp2x, open2x, wiz, dingoo, caanoo, pandora, linux, windows)
  • 3D renderer with textures, which works on every supported platform
  • abstraction of the hardware. E.g. you don't have to know, whether there is a analog stick or a D-Pad
  • blit functions for surface in 2D or 3D
  • rotozoom functions (faster then SDL-gfx)
  • simple primitives like rectangles, ellipses or lines
  • mesh loading (obj files)
  • simple Sprite engine
  • font rendering
  • based totally of SDL. No other dependences.
  • using in an existing application. I have plans to make some 3D visualizations in Gmu :)


To see, what you would get, I made a test application for all supported handhelds:


Cannoo


Dingoo


GP2X


Pandora


Pandora Repo


Open2X


WIZ


Furthermore I created a more detailed description. But be aware! It will be corrected by some person with a (much) better English soon™.

Code:
+-----------+

|+---------+|

||sparrow3D||

|+---------+|

+-----------+


This Document is for the illustration of the cohenceres and logics of

the functions from sparrow3D. I hope it makes sense for you and helps

you to come in sparrow3d.


+------------+

|Introduction|

+------------+


Sparrow3D is a software renderer and a (gaming) engine. It affords the

possibilities for event handling, 2D and 3D graphic functions, plotting

of meshs, a simple sprite engine and drawing font.


It totally bases on SDL (with SDL-image and SDL-Font). No other

dependencies. Thus sparrow3D runs on this systems (yet):

- GP2X

- WIZ

- Dingoo

- Cannoo

- Pandora

- PC (Linux and Windows)


It improves SDL on another abstraction layer. That means, that you

don't need to know the name of buttons or which kind of axis (d-pad or

analog stick e.g.) is avaible. You just develop for a generic device.


Advantages / Features of sparrow3D (crudely):

- fast, because written in C

- abstraction of the hardware

- abstracted functions for blitting of surfaces

- faster rotozoom then SDL-gfx e.g.

- primitives like rectangles or ellipses

- 3D Software Renderer, which runs on GP2X and Dingoo, too

- Sprite Engine

- Loading of Meshs

- Drawing of Fonts from ttf-files (so scaleable)


Next up a small introduction of the parts of sparrow3D. You can, but

may not use all of it. E.g. it is possible very easy just to use the 3D

Software Renderer in an existing SDL application.


+-----------+

|sparrowCore|

+-----------+


Although the name supposes it, you don't have to use this part. If you

just want the sprite or 3D handling, you can ignore it.


The core offers you a window, event handling - accompanied by the

abstraction of the different targets - and loading of optimal surfaces

for the system (faster).


Furthermore you have a feedback function if the screen resizes.


+-----------+

|sparrowMath|

+-----------+


Many parts, but at least _all_ 3D functions, base on fixed point

numbers. For people never using them: Fix point numbers are very much

like normal integers. But sparrow3D's fixed point numbers are divided

by 65536. That means the integer number 65536 is as fixed point number

65536/65536 = 1.0 or 128 is 128/65536 = 0.001953125.


Fixed point numbers are (much) more inaccurate then classic floating

point numbers, but on ARM systems without FPU MUCH faster.


For addition or subtraction nothing changes. Only the multiplication

and division needs extra function, which sparrowMath defines. Cosinus

and Sinus are very slow. So sparrow3D has faster (lool up table based)

functions for them, too.


Furthermore sparrowMath has other simplifications like converting a

HSV-Color to a 16-Bit color.


+-----------------+

|sparrowPrimitives|

+-----------------+


For most of you sparrowPrimitives will be the most importent part of

sparrow3D. It is responsible for the drawing of different primitives in

2D. One importent aspect of sparrow3D is, that it depends on SDL, it

improves and extends. First of all you have to choose a render target.

That can be the screen surface, but every other 16 bit Surface is

possible. It is VERY important (really VERY VERY important) to know,

that every render target MUST be a 16 bit surface! If you want to draw

to a 32 bit surface, you have to draw in a 16 bit dummy surface and this

surface you can blit (with SDL methods) to a 32 bit surface. That was

necessary for simplicity and optimization reasons.


Furthermore you have no "real" alpha blending with sparrow3D. If

acitivated, there is just an alpha test. The Color pink (defined as

SP_ALPHA_COLOR) will not be drawn then.


There is no default target. At begin you have to choose one. After this

every drawing operation will work with it. Moreover a z-Buffer is created

for the surface, which minds the z values of the target pixels. Both the

z test and the setting of the z values are adjustable. Drawing with

z-Buffer is always slower, but for 3D essential.


There are (crudely) these parts, which sparrowPrimitives handles:

- Dawing triangles and quads with or without texture

- Rotozoom Drawing of surfaces or surface parts (Tiling)

- Bliting of surfaces (without rotation and zoom, but faster) or

  surface parts (Tiling)

- Drawing of random colored lines, rectangles, ellipses, rectangle

  borders or ellipse borders


All these function are able to write to the zBuffer. That is the reason,

why you should reset the z-Buffer at start like the whole screen.


Another possibility is culling. The triangle and quad functions (NOT the

rectangle functions!) use culling if you want. That means, the primitive

is NOT drawn, if you define it clockwise. So for correction results you

have to define it COUNTER clockwise! That is very important for closed

meshs, because you don't want the back be drawn.


+-------------+

|sparrowRender|

+-------------+


This very important part it the giver of the engine's name: It is for

the three-dimensionality. Based on sparrowPrimitive there are different

functions. Like OpenGL sparrow3D uses matrices for the orientation. A

short OpenGL should give you enough view to use sparrow3D. But

nevertheless sparrow3D is not compatible to openGL because of speed

reasons.


Like OpenGL sparrow3D uses two matrizes for orientation (model view

matrix) and projection (projection matrix). The easist way to understand

the model view matrix is to imagine it as an "invisible roboter arm",

which you can move and rotate however you want. Objects you draw will be

drawn, where the "roboter arm" points to. Keep in mind, that these "arm"

instructions are relative. That means two times moving to (0,0,-2) sets

the "arm" to (0,0,-4). But the best way to get it, is learning by doing!


You are able the reset, translate, rotate or scale the model view

matrix. Furthermore you can manual edit the matrix or save (and reload)

it like glPush or glPop from OpenGL. I do this in testsparrow.c, too.


With the projection matrix sparrow3D "transforms" the space coordinates

to screen/surface coordinates. This happens obserbing the z value. An

object, which is far away (more negative z value), is smaller.


In addition sparrow3D has some very basic light functionalities with

lights you can place whereever you want in the space.


There are 3 kinds of 3D graphic functions:

- "real" 3D functions, where a triangle or quad (with or without

  texture) is placed random in space. These objects consider light.

  Furthermore functions, where you can draw whole face-bunches (meshs)

  in one step, which is much faster because most points are used be many

  faces.

- "Billboard" 3D functions, which point always to the screen/surface,

  but in the xy plain still are rotated and scaled. Here you have

  rectangles, ellipses and border functions again like in

  sparrowPrimitves.

- "Blit" 3D functions, which use a correct calculated position and

  z value, but the size and rotation is ignored. Very fast. You could

  use it e.g. for particles, where the size is not necessary.


+-----------+

|sparrowMesh|

+-----------+


sparrowMesh is used for loading mesh structures from obj-files and

deleting them.


+-----------+

|sparrowFont|

+-----------+


No game without font. These part takes sparrowFont. It is able to open

ttf-files with the aid of SDL-ttf and caches particular letters (e.g.

the alphabet, numbers and punctuation characters) in SDL-Surfaces, so

that you just have to blit them (which is very fast). It uses a very

fast (and cached) binarytree internal.


At the moment (with default sparrow3D stuff) you are not able to draw

text in the 3D space or to rototate or zoom it.


+-------------+

|sparrowSprite|

+-------------+


For most games sprites are essential. If you have a classic 2D Jump and

Run it is obvious, but even a 3D game can use them e.g. for

Head-up-Displays (HUDs).


A sprite from sparrow3D is basically just a a bunch of SDL surfaces or

parts of it (Tiling). Each of these sub units have a time, which shows

how long this surface will be shown before the next surface (or surface

part) will be seeable. If a sprite is shown complete, it starts from the

start again. With this methode very simple animations are possible


Also sprites can be rotated or scaled. This is slower, but skilful and

particular used it is an easy method to get more dynamic in the game. A

good example is worms. Most parts of the game are 2D and not scaled, but

e.g. the projectile is rotated.


+------+

|Finish|

+------+


sparrow3D is advanced constantly. If you have questions, suggestions or

feature request, feel free to ask!


Tomorrow I will post a (new) video, too. But I have screenshots for you:


sparrow1.png


sparrow2.png


sparrow3.png


sparrow4.png


sparrow5.png


sparrow6.png



Maybe I will use this engine for a game for the Rebirth competition, too. At the moment I am not sure because of to few time. :-\


But we will see.


greetings, Ziz
 
Looks good, I'll download you demo later and give it a whirl
 
This sounds great :D Going to check this out when I have some time :D See if my limited programming skills allow me to use this in C++ with OOP :)


Once made a Scrooge McDuck model:


scrooge_m.png



That's 2408tris and a 256x256 texture :) Would itstill run smoothly, viewing a model like that?
 
Last edited by a moderator:
You mean hardware acceleration for systems with gl es core? I wrote something about this in my VERY long about text (first post or here: https://github.com/t...ob/master/About).


There is no plan for hardware acceleration. First of all I would have to "sync" the texture-memory and the surfaces somehow, because my engine doesn't differ. Furthermore one goal is to have one engine with the same possibilities on every supported system. GL hardware acceleration would make sparrow3d MUCH faster (if I solve the sync problem) on pandora, caanoo and wiz. Most people would not even try to make their applications run capable on dingoo or gp2x.


Last but not least: GL is very mighty. It would be a shame to wedge it to the possibilities I gave my 3D part of my engine.


No, my 3D part is made for 3D games with simple graphic or as supported screen element. E.g. like sacred. The whole world was isometric (like Diablo), but the characters have been 3D models. ;-)
 
@Dragon_Slayer: The texture size doesn't matter (for my engine), the most important thing is the triangle count and the size of the object. Bigger object => higher fill rate => slower.


You could try it with the demo I uploaded. Replace "garfield.png" with your texture (the name is important) and my "bamuv.obj" with your model. If it's an object file (every 3D modeling program is able to export to obj), it should be seeable... If it's to big, export it smaller. ^^ If it doesn't load, my object loader fails. I only tried it with obj files exported from Wings3d. ^^
 
Yes i meant opengl within the ES specification, but specifically using the fixed point api since your engine uses it.


thanks for the answer, i was just curious what you thought about it.


have you looked at the works by Quasist? He also wrote a 3d software render engine and its open sources.


also reminds me of the egoboo software render on the second core, too bad that source was released.


good luck with this
 
I know the Software Renderer from the gp2x used by egoboo. I even tried to use it. But I didn't get it to work. ^^


I don't know Quasist's work. Most of the stuff I do, I do it like I thought it should be the best way, but I am quite sure e.g. my Triangle Code sucks. So... not a bad idea to take a look at Quasists work. ^^
 
Just tried loading my model... It works :D Even runs faster than the included model :eek: The texture/uv-coordinates are messed up though...
 
Quasist did (does?) some nice work. I loved AAAA, though I never got very far in it. It's one of those games I should play more than I actually do

have you looked at the works by Quasist? He also wrote a 3d software render engine and its open sources.
Was there a specific link for the 3D renderer? or just the "quasist-game-sources" archive? (I have the latter)


I do like this engine, works really nicely. I really need to pick one for when I get back on Hot Pursuit - too many to chose from though :blink:


What's the API like for your engine? ie, could you post a few lines of example code for model loading, and sprite roto/zoom?
 
Of course :)


First of all, I would advise to have a look at the test-application:


https://github.com/t...r/testsparrow.c


But for your specific questions (with more comments):


Loading and drawing a mesh (complete application code):



Code:
#include "../sparrow3d/sparrow3d.h"

#include <SDL_image.h>


SDL_Surface* screen;

SDL_Surface* texture;

spModelPointer mesh;

Sint32 rotation = 0;


void draw_function(void)

{

  //Reseting Z Buffer to the maximal negative value

  spResetZBuffer();

  //Cleaning the target (in this case the screen surface) with 0 (black).

  spClearTarget(0);

  //Reseting the ModelViewMatrix to an identity matrix.

  spIdentity();


  //Binding the texture

  spBindTexture(texture);


  //Going to the "position" -8.0f

  spTranslate(0,0,-8<<SP_ACCURACY);

  //rotation aorund every axis

  spRotateX(rotation);

  spRotateY(rotation);

  spRotateZ(rotation);

  //Drawing the mesh and no update of the edgelist 

  spMesh3D(mesh,0);

  //Show it!

  spFlip();

}



int calc_function(Uint32 steps)

{

  //update rotation counter

  rotation+=steps*32;

  //if "Start" (or "Menu" for Wiz or "Home" for Caanoo or "Return" at the P) is

  //pressed: exit

  if (spGetInput()->button[SP_BUTTON_START])

    return 1;

  return 0; 

}


void resize(Uint16 w,Uint16 h)

{

  //Selecting the renderTarget. It could be every surface

  spSelectRenderTarget(spGetWindowSurface());

  //Setup of the new/resized window

  spSetPerspective(50.0,(float)spGetWindowSurface()->w/(float)spGetWindowSurface()->h,0.1,100);

}



int main(int argc, char **argv)

{

  //sparrow3D Init

  spInitCore();


  //Setup

  screen = spCreateWindow();

  resize(screen->w,screen->h);


  //Textures loading

  texture = spLoadSurface("./texture.png");

  //Mesh loading

  mesh = spMeshLoadObj("./mesh.obj",texture,65535);


  //Light on! There is one "default" light at 0,0,0 with color white. We just use it.

  spSetLight(1);


  //All glory the main loop

  //every frame the draw_function is called

  //every frame the calc_function is called with the past time as argument

  //at least 10 ms have to be between to frames (max 100 fps)

  //if the window is resized, the resize feedback-function is called (again)

  spLoop(draw_function,calc_funtion,10,resize);


  //Winter Wrap up, Winter Wrap up

  spMeshDelete(mesh);

  SDL_FreeSurface(texture);

  spQuitCore();

  return 0;

}



And here some sprite example code:



Code:
#include "../sparrow3d/sparrow3d.h"

#include <SDL_image.h>


SDL_Surface* screen;

SDL_Surface* tile_map;

spSpritePointer sprite;

Sint32 rotation = 0;


void draw_function(void)

{

  //Cleaning the target (in this case the screen surface) with 0 (black).

  //In this case I don't clean the zBuffer, because I don't use it. ;-)

  //But you ARE able to use it, if you want!

  spClearTarget(0);


  //Drawing the sprite in the middle of the screen

  spDrawSprite(screen->w/2,screen->h/2,-1,sprite);


  //Show it!

  spFlip();

}



int calc_function(Uint32 steps)

{

  //update rotation counter

  rotation+=steps*32;

  //if "Start" (or "Menu" for Wiz or "Home" for Caanoo or "Return" at the P) is

  //pressed: exit

  if (spGetInput()->button[SP_BUTTON_START])

    return 1;


  //Update the sprite (next picture)

  spUpdateSprite(sprite,steps);

  //rotozoom the sprite (without rotozoom it's faster):

  sprite->zoomX = spSin(rotation*8)+(3<<SP_ACCURACY-1);

  sprite->zoomY = spCos(rotation*6)+(3<<SP_ACCURACY-1);

  sprite->rotation = rotation*4;


  return 0; 

}


void resize(Uint16 w,Uint16 h)

{

  //Selecting the renderTarget. It could be every surface

  spSelectRenderTarget(spGetWindowSurface());

}


int main(int argc, char **argv)

{

  //sparrow3D Init

  spInitCore();


  //Setup

  screen = spCreateWindow();

  resize(screen->w,screen->h);


  //Tile map loading

  tile_map = spLoadSurface("./tile_map.png");


  //Creating an empty sprite

  sprite = spNewSprite();

  //Filling it with it subsprites. I asume I have an 256x32 surface with 8 tiles

  int i;

  for (i = 0;i<8;i++)

    spNewSubSpriteWithTiling(sprite,tile_map,i*32,0,32,32,100);


  //We don't want to use the zBuffer in any way

  spSetZSet(0);

  spSetZTest(0);


  //All glory the main loop

  //every frame the draw_function is called

  //every frame the calc_function is called with the past time as argument

  //at least 10 ms have to be between to frames (max 100 fps)

  //if the window is resized, the resize feedback-function is called (again)

  spLoop(draw_function,calc_funtion,10,resize);


  //Winter Wrap up, Winter Wrap up

  spDeleteSprite(sprite);

  SDL_FreeSurface(tile_map);

  spQuitCore();

  return 0;

}


greetings, Ziz
 
Thanks for that - I didn't see the git link anywhere (probably just wasn't looking).


Looks a nice usable API; Definately something I'll have a play with (when I can find some time).


BTW - On the Sprite bit can you specify a "base point" - or will your sprite "position" always use the centre (or topleft) of the image? Also, what point does it rotate around?
 
Oh, damn it, I totally forgot to post the git link somewhere. >.<


I will add it to the front post. ^^


A sprite is always drawn and rotated around it's center. The same for surfaces. So if you have e.g. a sprite with DIFFERENT sizes of subsprites, keep in mind, that the center will be the same for every subsprite.
 
A sprite is always drawn and rotated around it's center. The same for surfaces.
I'm tempted to request an option to set a "drawing origin". When I've been coding platforms and beat 'em ups (even though they rarely get finished). I usually use the Sprite Y as being the "floor" of the player sprite; but I guess it wouldn't be hard to adjust drawing position based on frame in the render side ^_^

So if you have e.g. a sprite with DIFFERENT sizes of subsprites, keep in mind, that the center will be the same for every subsprite.
When you say "the same", you mean the same "x,y" in relation to the top of the sprite, or always the centre of the sprite regardless of it's size?
 
I meant it relative. If one subsprite is 16x16 and another 32x24, the center will be 8:8 and 16:12.


But a possibility to choose the center position seems a good idea. How about two function "spSetHorizontalCenter" and "spSetVerticalCenter", where you can choose between "SP_LEFT", "SP_RIGHT", "SP_BOTTOM", "SP_TOP" and "SP_CENTER" (of course e.g. SL_LEFT only for horizontal).


For both "SP_CENTER" would be the default (what I use at the moment).
 
Thanks for the clarification.


Personally, I'd called them "spSetHorizontalOrigin" and "spSetVerticalOrigin", otherwise I think you risk confusing the term "centre"
 
Back
Top