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/libqmathYou always find the latest version on my website:
http://www.kanonet.de/downloads/libspriteComparison 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 got
advantages:- 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!
) - 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:
LOCAL sprite AS TSprite
In 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()!
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:// ------------------------------------------ //
// 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:// --------------------------------- //
// 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