Codesnippets > 3D-snippets

X_SPRITE replacement - (faster) 3D sprite with rotation (billboards)

(1/8) > >>

kanonet:
Introduction:
If you ever wanted to be able to rotate X_SPRITEs or you want them to be faster (so you can create plenty of them, e.g. for a particle engine), than here is a solution for you:
My TSprite can get scaled, rotated and positioned anywhere in the 3D-scene, it will always turn to the camera (like X_SPRITE does), even if you use X_CAMERAUP (for a 6DOF camera). Its all encapsulated into one Type, for easy transfer to your projects. I tested this lib on Windows, Linux and Android, its all working perfectly and I know no reason why it should not work on any other major platform.

You can find newest libQMATH here: http://www.kanonet.de/downloads/libqmath
You always find the latest version on my website: http://www.kanonet.de/downloads/libsprite


Comparison with X_SPRITE:
differences:
* its midhandled (centered)
* need to get textured manually
* scale=1 has alwasy the same size (1 unit in 3D environment), no matter what size the texture gotadvantages:
* rotation is possible
* can draw two different types of billboards:
* draw()   -> spherical billboard, same like x_sprite, but with rotation
* draw2() -> cylindrical billboard, use it for trees etc., can rotate too
* faster at least +5-400% on my machine (without texture ordering)
* gives you the ability for texture ordering (+700-3200% more speed on my machine!  :O)
* you can set texture coordinates, so you can draw all sprites from one single texture atlas!disadvantages:
* you need to call more than one function
* my libQMATH is needed in your project

How to use it:
At game start, define the type:

--- Code: GLBasic ---LOCAL sprite AS TSpriteIn your gameloop render all your GLB 3D stuff, than call sprite.Start(), draw your sprites and texture them, try to use as few X_SETTEXTURE as possible if some sprites use the same texture, just make only one X_SETTEXTURE and draw all sprites that use it (texture ordering). You can call SetTextureCoords() to use a sprite Atlas (and avoid calls for X_SETTEXTURE)! End your sprite drawing with sprite.Stop(). Just use one Start() and Stop() pair each loop and draw all sprites between them. Do not use any GLB OpenGL calls between Start() and Stop()!

--- Code: GLBasic ---sprite.Start()
        X_SETTEXTURE 0
        sprite.Draw()
        sprite.Draw()
        sprite.Draw2()
        sprite.SetTextureCoords()
        sprite.Draw()
        sprite.Draw2()
        X_SETTEXTURE 1
        sprite.Draw()
        X_SETTEXTURE 2
        sprite.Draw()
        sprite.Draw2()
sprite.Stop()

Code:
--- Code: GLBasic ---// ------------------------------------------ //
// Project: TSprite
// Start: Sunday, July 24, 2011
// Autor: Kanonet
// Last Update: August 22, 2015
// For a newer Version go to: http://www.kanonet.de/downloads/libsprite
// ------------------------------------------ //

TYPE TSprite
        INLINE
                namespace libSPRITE {
                        typedef unsigned int    GLenum;
                        typedef void            GLvoid;
                        typedef int             GLint;
                        typedef int             GLsizei;
                        typedef float           GLfloat;
                        #ifdef __WIN32__
                                #ifndef APIENTRY
                                        #define APIENTRY __stdcall
                                #endif
                        #else
                                #ifndef APIENTRY
                                        #define APIENTRY
                                #endif
                        #endif
                        const GLint GL_TRIANGLE_STRIP                           = 0x0005;
                        const GLint GL_FLOAT                                            = 0x1406;
                        const GLint GL_VERTEX_ARRAY                                     = 0x8074;
                        const GLint GL_TEXTURE_COORD_ARRAY                      = 0x8078;
                        extern "C" {
                                void APIENTRY glNormal3f( GLfloat nx , GLfloat ny , GLfloat nz );
                                void APIENTRY glColor4f( GLfloat red , GLfloat green , GLfloat blue , GLfloat alpha );
                                void APIENTRY glEnableClientState( GLenum cap );                /* 1.1 */
                                void APIENTRY glDisableClientState( GLenum cap );               /* 1.1 */
                                void APIENTRY glVertexPointer( GLint size , GLenum typ , GLsizei stride , const GLvoid *ptr );
                                void APIENTRY glTexCoordPointer( GLint size , GLenum typ , GLsizei stride , const GLvoid *ptr );
                                void APIENTRY glDrawArrays( GLenum  mode,  GLint  first,  GLsizei  count );
                        }
                        struct TSpriteContainer {
                                float texcoord[8];
                                float vertices[12];
                                float m0, m1, m2;
                                float m4, m5;
                                float m8, m9, m10;
                                float normalx, normaly, normalz;
                                int lastnum;
                        } static TSpr = { {0.0f,1.0f,1.0f,1.0f,0.0f,0.0f,1.0f,0.0f} };
                }
        ENDINLINE

        // start drawing of sprites, call it one time  each loop, before you draw your sprites and call Stop() when you did draw all your sprites
        // do not use any normal GLB OpenGL between Start() and Stop()!
        FUNCTION Start%:
                STATIC mat#[]
                X_GETCAMERAMATRIX mat[]
                INLINE
                        using libSPRITE::TSpr;
                        TSpr.lastnum = 0;
                        TSpr.normalx = mat(2);
                        TSpr.normaly = mat(6);
                        TSpr.normalz = mat(10);
                        TSpr.m0  =  -mat(0);
                        TSpr.m4  =  -mat(4);
                        TSpr.m8  =  -mat(8);
                        TSpr.m1  =  mat(1);
                        TSpr.m5  =  mat(5);
                        TSpr.m9  =  mat(9);
                        TSpr.m10 =  qInvSQR(TSpr.normalx*TSpr.normalx + TSpr.normalz*TSpr.normalz);
                        TSpr.m2  =  TSpr.normalx*TSpr.m10;
                        TSpr.m10 *= TSpr.normalz;
                        libSPRITE::glEnableClientState( libSPRITE::GL_VERTEX_ARRAY );
                        libSPRITE::glEnableClientState( libSPRITE::GL_TEXTURE_COORD_ARRAY );
                        libSPRITE::glTexCoordPointer( 2, libSPRITE::GL_FLOAT, 0, &TSpr.texcoord );
                        libSPRITE::glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
                ENDINLINE
        ENDFUNCTION

        // draw a spherical sprite (same like X_SPRITE)
        // needs to be after Start() [and before Stop()]
        // sprite will be midhandled (unlike X_SPRITE)
        FUNCTION Draw%: x AS float, y AS float, z AS float, scale#=1.0, rotation#=0.0
                INLINE
                        using libSPRITE::TSpr;
                        scale *= 0.5;
                        float rc = qSIN( rotation + 90 ) * scale;
                        float rs = qSIN( rotation      ) * scale;

                        float rot = TSpr.m1 * rs + TSpr.m0 * rc;
                        float xp  = TSpr.m1 * rc - TSpr.m0 * rs;
                        float xn  = rot-xp;  xp += rot;
                        TSpr.vertices[0] = x + xp;  TSpr.vertices[3] = x - xn;  TSpr.vertices[6] = x + xn;  TSpr.vertices[9] = x - xp;

                        rot = TSpr.m5 * rs + TSpr.m4 * rc;
                        xp  = TSpr.m5 * rc - TSpr.m4 * rs;
                        xn  = rot-xp;  xp += rot;
                        TSpr.vertices[1] = y + xp;  TSpr.vertices[4] = y - xn;  TSpr.vertices[7] = y + xn;  TSpr.vertices[10]= y - xp;

                        rot = TSpr.m9 * rs + TSpr.m8 * rc;
                        xp  = TSpr.m9 * rc - TSpr.m8 * rs;
                        xn  = rot-xp;  xp += rot;
                        TSpr.vertices[2] = z + xp;  TSpr.vertices[5] = z - xn;  TSpr.vertices[8] = z + xn;  TSpr.vertices[11]= z - xp;

                        if (TSpr.lastnum != 1) {
                                libSPRITE::glNormal3f( TSpr.normalx, TSpr.normaly, TSpr.normalz );
                                TSpr.lastnum = 1;
                        };
                        libSPRITE::glVertexPointer( 3, libSPRITE::GL_FLOAT, 0, TSpr.vertices );
                        libSPRITE::glDrawArrays( libSPRITE::GL_TRIANGLE_STRIP, 0, 4 );
                ENDINLINE
        ENDFUNCTION

        // draw a cylindrical sprite (same like X_SPRITE)
        // needs to be after Start() [and before Stop()]
        // sprite will be midhandled (unlike X_SPRITE)
        FUNCTION Draw2%: x AS float, y AS float, z AS float, scale#=1.0, rotation#=0.0
                INLINE
                        using libSPRITE::TSpr;
                        scale *= 0.5;
                        float rc = qSIN( rotation + 90 ) * scale;
                        float rs = qSIN( rotation      ) * scale;

                        float rot = TSpr.m10 * rc;
                        float xn  = TSpr.m10 * rs;
                        float xp  = rot-xn;  xn += rot;
                        TSpr.vertices[0] = x - xp;  TSpr.vertices[3] = x + xn;  TSpr.vertices[6] = x - xn;  TSpr.vertices[9] = x + xp;

                        TSpr.vertices[1] = y + rs + rc;  TSpr.vertices[4] = y - rs + rc;  TSpr.vertices[7] = y + rs - rc;  TSpr.vertices[10]= y - rs - rc;

                        rot = TSpr.m2 * rc;
                        xn  = TSpr.m2 * rs;
                        xp  = rot-xn;  xn += rot;
                        TSpr.vertices[2] = z + xp;  TSpr.vertices[5] = z - xn;  TSpr.vertices[8] = z + xn;  TSpr.vertices[11]= z - xp;

                        if (TSpr.lastnum != 2) {
                                libSPRITE::glNormal3f( TSpr.m2, 0.0f, TSpr.m10 );
                                TSpr.lastnum = 2;
                        };
                        libSPRITE::glVertexPointer( 3, libSPRITE::GL_FLOAT, 0, TSpr.vertices );
                        libSPRITE::glDrawArrays( libSPRITE::GL_TRIANGLE_STRIP, 0, 4 );
                ENDINLINE
        ENDFUNCTION

        // use this if you want to change texture coords
        // its way faster to change texture coords instead of changing texture with X_SETTEXTURE
        // it will affect all following draw()/draw2() calls, in all following loops!
        // default is (0,1, 1,1, 0,0, 1,0)
        FUNCTION SetTextureCoords%: t0x AS float, t0y AS float, t1x AS float, t1y AS float, t2x AS float, t2y AS float, t3x AS float, t3y AS float
                INLINE
                        using libSPRITE::TSpr;
                        TSpr.texcoord[0] = t0x; TSpr.texcoord[1] = t0y;
                        TSpr.texcoord[2] = t1x; TSpr.texcoord[3] = t1y;
                        TSpr.texcoord[4] = t2x; TSpr.texcoord[5] = t2y;
                        TSpr.texcoord[6] = t3x; TSpr.texcoord[7] = t3y;
                ENDINLINE
        ENDFUNCTION

        // call it after you did draw all your sprites
        // its not totally necessary to use this function, but strongly recommended
        FUNCTION Stop%:
                INLINE
                        libSPRITE::glDisableClientState( libSPRITE::GL_TEXTURE_COORD_ARRAY );
                        libSPRITE::glDisableClientState( libSPRITE::GL_VERTEX_ARRAY );
                ENDINLINE
        ENDFUNCTION

ENDTYPE

Test Program:
--- Code: GLBasic ---// --------------------------------- //
// Project: TSprite
// Start: Sunday, July 24, 2011
// Autor: Kanonet
// Last Update: September 06, 2013

SETSCREEN 800,600,0
SYSTEMPOINTER TRUE
ALLOWESCAPE FALSE
AUTOPAUSE FALSE
LIMITFPS -1

// number of sprites to draw:
LOCAL count%=1000

LOCAL root%=SQR(count)/2, line%, row%, fps, timer, mx%, my%=15, mz=-4, selected%=1, rot%

// texture
CREATESCREEN 0, 0, 16,16
USESCREEN 0
DRAWRECT 0,0, 16, 16, RGB(0,0,255)
DRAWRECT 12,0, 4,8, RGB(255,127,0)
DRAWRECT 0,12, 4,4, RGB(255,0,63)
DRAWLINE 0,0, 16,16, 0xffffff
DRAWLINE 0,16, 16,0, 0xffffff
USESCREEN -1
CREATESCREEN 0,-1,0,0

// sprite
LOCAL sprite AS TSprite

// mainloop
REPEAT

        line=-root; row=-root; INC rot

        // mouse for camera control:
        mx = mx + MOUSEAXIS(0)
        my = my + MOUSEAXIS(1)
        mz = mz + MOUSEAXIS(2)
        IF KEY(27) // + more sprites
                INC count
                root=SQR(count)/2
        ENDIF
        IF KEY(53) AND count>1 // - less sprites
                DEC count
                root=SQR(count)/2
        ENDIF
        IF KEY(2) THEN selected=1
        IF KEY(3) THEN selected=2
        IF KEY(4) THEN selected=3

        X_MAKE3D 0.1, 1000, 45
        X_CAMERA my*COS(mx)*root*0.2,mz*2,my*SIN(mx)*root*0.2, 0,0,0

        X_DRAWAXES 0,0,0
        X_CULLMODE 1

        LOCAL cx=my*COS(mx)*root*0.2, cy=mz*2, cz=my*SIN(mx)*root*0.2

        IF selected=1
                FOR i=1 TO count

                        X_SPRITE 0, 0, line, row, 0.06

                        INC row
                        IF row>root
                                row=-root
                                INC line
                        ENDIF
                NEXT
        ELSEIF selected=2
                sprite.Start()
                FOR i=1 TO count

                        X_SETTEXTURE 0, -1
                        sprite.Draw(0, line, row, 0.5)

                        INC row
                        IF row>root
                                row=-root
                                INC line
                        ENDIF
                NEXT
                sprite.Stop()
        ELSE
                sprite.Start()
                X_SETTEXTURE 0, -1
                FOR i=1 TO count

                        sprite.Draw(0, line, row, 0.5)

                        INC row
                        IF row>root
                                row=-root
                                INC line
                        ENDIF
                NEXT
                sprite.Stop()
        ENDIF

        X_MAKE2D
        ALPHAMODE 0.9
        DRAWRECT 0,0,800,60,RGB(70,70,70)
        PRINT "CAMERA CONTROL: MOVE MOUSE / MOUSE WHEEL    |    FPS: "+INTEGER(1000/(timer-fps)), 0,0
        PRINT "SPRITES CURRENTLY: "+count+"    |    PRESS (+) FOR MORE SPRITES AND (-) FOR LESS", 0,15
        PRINT "SELECT: (1) X_SPRITE    |    (2) TSPRITE    |    (3) TSPRITE WITH TEXTURE ORDERING", 0, 30
        PRINT "SELECTED: "+selected, 0, 45

        LOCAL b1%, b2%
        MOUSESTATE mx, my, b1, b2
        PRINT "v",mx,my
        DRAWRECT 80,80,20,20,RGB(255,127,127)
        PRINT "0", 90,90
        IF mx>80 AND mx<100 AND my>80 AND my<100 AND b1 THEN selected=1
        DRAWRECT 110,80,20,20,RGB(255,127,127)
        PRINT "1", 120,90
        IF mx>110 AND mx<130 AND my>80 AND my<100 AND b1 THEN selected=2
        DRAWRECT 140,80,20,20,RGB(255,127,127)
        PRINT "2", 150,90
        IF mx>140 AND mx<160 AND my>80 AND my<100 AND b1 THEN selected=3

        SHOWSCREEN
        fps=timer
        timer=GETTIMERALL()
UNTIL KEY(1)
END

Finally some test results (FPS), done on my T410s (see signature), which have two graphiccards (i can select one to use):
Intel HD Graphics - 1000 Sprites: 46 / 48 / 393
Intel HD Graphics - 2000 Sprites: 24 / 25 / 253
Intel HD Graphics - 3000 Sprites: 16 / 17 / 196
Intel HD Graphics - 4000 Sprites: 12 / 13 / 152
Intel HD Graphics - 5000 Sprites: 10 / 10 / 125
Intel HD Graphics - 7500 Sprites: 7 / 7 / 88
Intel HD Graphics - 10000 Sprites: 5 / 6 / 68
NVidia NVS 3100M - 1000 Sprites: 85 / 459 / 500 (cant do more)
NVidia NVS 3100M - 2000 Sprites: 50 / 256 / 500 (cant do more)
NVidia NVS 3100M - 3000 Sprites: 35 / 175 / 500 (cant do more)
NVidia NVS 3100M - 4000 Sprites: 27 / 134 / 500 (cant do more)
NVidia NVS 3100M - 5000 Sprites: 22 / 110 / 500 (cant do more)
NVidia NVS 3100M - 7500 Sprites: 15 / 74 / 480
NVidia NVS 3100M - 10000 Sprites: 11 / 56 / 370

Slydog:
Wow, pretty cool stuff!    :nw:
And using Vertex Arrays too!
Great to see how to work with the OpenGL directly, will be referencing this soon.

By looking at the code, it appears to only support full textured sprites, correct?
ie: You can't have your sprites in a spritesheet / texture map?

It is probably pretty easy I suppose to add this support.
You use

--- Code: GLBasic ---DIMDATA self.tex[], 0,1, 1,1, 0,0, 1,0to define your texture coordinates, and you specify to use the entire texture.

You would just need a way to predefine a sprite's texture locations.
And move the 'glTexCoordPointer( 2, GL_FLOAT, 0, self.tex[] )' command to the .Draw() routine.


--- Code: GLBasic ---TYPE TSpriteTexture
  tex[]

  // Set uv location by absolute values (between 0 and 1)
  FUNCTION Set: x1, y1, x2, y2 // y goes from bottom to top
    DIMDATA self.tex[], x1,y1,  x2,y1,  x1,y2,  x2,y2
  ENDFUNCTION

  // Set uv location by pixel and texture size (between 0 and texture size)
  FUNCTION SetByPixel: x1, y1, x2, y2, texture_size_x, texture_size_y
    self.Set(x1/texture_size_x, y1/texture_size_y, x2/texture_size_x, y2/texture_size_y)
  ENDFUNCTION

ENDTYPE
 
And then pass this to a modified .Draw() function:

--- Code: GLBasic ---FUNCTION Draw%: x#,y#,z#, scale#=1, rotation#=0, uv AS TSpriteTexture
  glTexCoordPointer( 2, GL_FLOAT, 0, uv.tex[] )
... Remaining function code as is
ENDFUNCTION
Sample code:

--- Code: GLBasic ---GLOBAL st_full AS TSpriteTexture  // Use when sprite is the entire texture
GLOBAL st_sprite1 AS TSpriteTexture // Location of sprite 1 in sprite sheet
GLOBAL st_sprite2 AS TSpriteTexture // Location of sprite 2 in sprite sheet
st_full.Set(0,0, 1,1)
st_sprite1.SetByPixel(10,11,  20,21,   256,256)  // Sprite1 is located from pixels 10,11 to 20,21 of a 256x256 sprite sheet
st_sprite2.SetByPixel(40,41,  50,51,   256,256)  // Sprite2 is located from pixels 40,41 to 50,51 of a 256x256 sprite sheet
sprite.Start()
        X_SETTEXTURE 0
        sprite.Draw(..., st_sprite1)
        sprite.Draw(..., st_sprite2) // Sprite1 and Sprite2 share the same sprite sheet
        X_SETTEXTURE 1
        sprite.Draw(..., st_full)
sprite.Stop()
 
And ya, please somebody use this to create a particle generator!!!!!!   :good:


[Edit]  The above changes would slow down this somewhat because of the extra 'glTexCoordPointer' call per SPRITE as opposed to per FRAME.  So maybe for a particle generator you woudn't want these changes as speed would be very important, but you'd be limited to using particle textures that are full sprites.  Maybe two versions!  :D

Cool!  Forum Post #666 !!!  :nw::rant:   he he

kanonet:
Yeah Slydog your right, it only supports full textures (like X_SPRITE does too), but its easy to add it, like you mentioned. Just a correction at possible speed drop: you would not need to set new texture coordinates each sprite, you just need to push new texturecoordinates, when you define a new texture (just put glTexCoordPointer behind X_SETTEXTURE), or when you change animationframe in this sprite sheet (just call it manually in this case). So you would not have to call it too often and so you would not lose to much speed.

In fact glTexCoordPointer is way faster, than X_SETTEXTURE so it would be fastest, if you pack all sprite textures in one spritesheet and just change the texturecoordinates. Would be good for the particle engine too, of cause.

BTW did you try the test program? What are your results like? Cuz i just tested it on only one pc, so would be interesting to see if its useful on other machines too.

kanonet:
LOL i just realized, that i forgot to include the test program, its a bit pointless, asking for other peoples results, if you dont give them the code.^^
So its in the first post now. :'(

mentalthink:
HI Kanoket, thanks a  lot about this program.... very very Nice... no doubt I try in my mobile Devices... perhaps can be awesome including in my space Ships...

Thanks a lot...

Navigation

[0] Message Index

[#] Next page

Go to full version