Codesnippets > 3D-snippets
X_SPRITE replacement - (faster) 3D sprite with rotation (billboards)
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 TSprite
--- End code ---
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()!
--- 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()
--- End code ---
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
--- End code ---
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
--- End code ---
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,0
--- End code ---
to 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
--- End code ---
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
--- End code ---
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()
--- End code ---
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