Texture Atlas - DarkFunction importer

Previous topic - Next topic

bigtunacan

Is anyone using an app to create Texture Atlas with GLBasic?  If so; just wondering what is a good product for this?

And along those same lines; is anyone doing any type of scripted sprite management?  Creating some type of sprite/entity TYPE then storing the data for each sprite in files and the scripting the load process indirectly through these files?

Slydog

#1
A few weeks ago I found a great sprite atlas manager called 'darkFunction', here:
http://darkfunction.com/

They only have a 'free' version, although they mention a 'pro' version is coming soon.

It outputs an XML file that looks like this:
http://darkfunction.com/index.php?id=documentation

I love how it lets you define hierarchies for the sprites so you can categorize them in code such as 'gui/button', 'gui/label', 'enemy/ufo', etc!

I wrote a TYPE and function to read this file. (I originally was going to use the XML reader I found in the forums, but hard coded it instead!).  I included my other TYPES and supporting functions if you want to see how I manage everything.

Here's my TSprite and TSpriteSheet TYPEs: (TSpriteSheet.Load() is the code to load the XML file)
Code (glbasic) Select
CONSTANT POLYMODE_STRIP = 2

TYPE TSprite
name$
uv AS TXyXy
colour%

FUNCTION Draw: x%, y%, w%=-1, h%=-1, colour%=BLANK, stretch%=TRUE, scale#=1.0
LOCAL ix%, iy%
LOCAL xy AS TXyXy
LOCAL uv_size AS TSize
xy.x1 = x
xy.y1 = y
IF w < 0 THEN w = (self.uv.x2 - self.uv.x1)
IF h < 0 THEN h = (self.uv.y2 - self.uv.y1)
w = w * scale
h = h * scale
xy.x2 = x + w
xy.y2 = y + h
IF colour = BLANK THEN colour = self.colour

IF stretch
Poly_Draw(xy, self.uv, colour)
// or else tile the image to fit boundary
ELSE
uv_size.Set(self.uv.x2 - self.uv.x1, self.uv.y2 - self.uv.y1)
FOR ix = 0 TO (w / uv_size.w)
xy.x1 = ix * uv_size.w + x
xy.x2 = xy.x1 + uv_size.w
FOR iy = 0 TO (h / uv_size.h)
xy.y1 = iy * uv_size.h + y
xy.y2 = xy.y1 + uv_size.h
Poly_Draw(xy, self.uv, colour)
NEXT
NEXT
ENDIF
ENDFUNCTION

ENDTYPE


TYPE TSpriteSheet
fn_image$ // Filename of image
id_sprite% // Sprite ID of image
size AS TSize // Size of image
sprites[] AS TSprite

FUNCTION Load%: path$, fn$
LOCAL ix%
LOCAL fh% // File Handle
LOCAL line$
LOCAL tags$[]
LOCAL key$
LOCAL value$
LOCAL name$
LOCAL sprite AS TSprite

self.fn_image$ = ""
self.id_sprite = 0
self.size.Set(0,0)
DIM self.sprites[0]
path$ = TRIM$(path$)

fh = GENFILE()
IF OPENFILE(fh, path$ + fn$, 1) = FALSE // Open SpriteSheet file
DEBUG ">TSpriteSheet.Load(): [" + fn$ + "] -> Error opening file\n"
RETURN FALSE // Exit and return 'FALSE' if can't open
ENDIF

WHILE ENDOFFILE(fh) = FALSE
READLINE fh, line$ // Read next line of data for the next shortacter
line$ = TRIM$(line$)
line$ = MID$(line$, 1) // Strip off first '<' character
line$ = LEFT$(line$, LEN(line$) - 1) // Strip off last '>' character
IF RIGHT$(line$, 1) = "/" THEN line$ = LEFT$(line$, LEN(line$) - 1) // Strip off last '/' character if present
SPLITSTR(line$, tags$[], " ", TRUE)
IF LEN(tags$[]) < 1 THEN CONTINUE // Ignore elements with no tags
tags$[0] = TRIM$(UCASE$(tags$[0]))
SELECT tags$[0]

CASE "DIR"
// Image FileName
String_KeyValue(tags$[1], key$, value$)
value$ = UCASE$(TRIM$(value$))
IF value$ <> "/" THEN name$ = name$ + value$
name$ = name$ + "/"

CASE "/DIR"
ix = REVINSTR(name$, "/", LEN(name$)-2)
IF ix >= 0
name$ = LEFT$(name$, ix+1)
ENDIF

CASE "IMG"
// Image FileName
String_KeyValue(tags$[1], key$, value$)
self.fn_image$ = path$ + value$
// Image Width
String_KeyValue(tags$[2], key$, value$)
self.size.w = INTEGER(value$)
// Image Height
String_KeyValue(tags$[3], key$, value$)
self.size.h = INTEGER(value$)
DEBUG "  >IMG: fn:[" + self.fn_image$ + "] w:[" + self.size.w + "] h:[" + self.size.h + "]\n"

CASE "SPR"
// Sprite Name
String_KeyValue(tags$[1], key$, value$)
sprite.name$ = name$ + UCASE$(value$)
// Sprite X
String_KeyValue(tags$[2], key$, value$)
sprite.uv.x1 = INTEGER(value$)
// Sprite Y
String_KeyValue(tags$[3], key$, value$)
sprite.uv.y1 = INTEGER(value$)
// Sprite W
String_KeyValue(tags$[4], key$, value$)
sprite.uv.x2 = sprite.uv.x1 + INTEGER(value$)
// Sprite H
String_KeyValue(tags$[5], key$, value$)
sprite.uv.y2 = sprite.uv.y1 + INTEGER(value$)
sprite.colour = RGB(255,255,255)
DIMPUSH self.sprites[], sprite
                                //[EDIT] removed following line as the .Dump$ function isn't included as it uses other formatting functions not included
//DEBUG "  >SPR: name:[" + sprite.name$ + "] xywh:" + sprite.uv.Dump$() + "\n"

ENDSELECT
WEND
CLOSEFILE fh // Close '.fnt' file

self.id_sprite = Sprite_Load(self.fn_image$, FALSE)
ENDFUNCTION

FUNCTION PolyStart:
STARTPOLY self.id_sprite, POLYMODE_STRIP
ENDFUNCTION

FUNCTION PolyEnd:
ENDPOLY
ENDFUNCTION

FUNCTION Find%: name$
LOCAL sx%
name$ = UCASE$(TRIM$(name$))
FOR sx = 0 TO LEN(self.sprites[]) - 1
IF INSTR(self.sprites[sx].name$, name$) >= 0
DEBUG "Sprite.Find(): [" + name$ + "], sx:[" + sx + "]\n"
RETURN sx
ENDIF
NEXT
RETURN 0
ENDFUNCTION

FUNCTION Draw%: id%, x%, y%, w%=-1, h%=-1, colour%=BLANK, stretch%=TRUE, scale#=1.0
IF id < 0 THEN RETURN
IF id > LEN(self.sprites[])-1 THEN RETURN
self.sprites[id].Draw(x, y, w, h, colour, stretch, scale)
ENDFUNCTION

// Use this function to return the UV values to use for a 3D mesh quad, not used for 2D.
// 'is_top_left' = TRUE :: returns the top/left UV point, or else it returns the bottom/right UV point.
FUNCTION CalcUv AS TUv: id_sprite%, is_top_left%=TRUE
LOCAL uv AS TUv
LOCAL sw#, sh#
sw = self.size.w
sh = self.size.h
IF is_top_left
uv.Set(self.sprites[id_sprite].uv.x1 / sw, self.sprites[id_sprite].uv.y1 / sh)
ELSE
uv.Set(self.sprites[id_sprite].uv.x2 / sw, self.sprites[id_sprite].uv.y2 / sh)
ENDIF
RETURN uv
ENDFUNCTION

ENDTYPE


Supporting TYPEs:
Code (glbasic) Select
TYPE TSize
w%
h%

FUNCTION Set%: w%, h%
self.w = w
self.h = h
ENDFUNCTION
ENDTYPE

TYPE TUv
u#
v#

FUNCTION Set: u#, v#
self.u = u
self.v = v
ENDFUNCTION
ENDTYPE

TYPE TXyXy
x1%
y1%
x2%
y2%

FUNCTION Set: x1%, y1%, x2%, y2%
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
ENDFUNCTION

FUNCTION Clear:
self.x1 = 0
self.x2 = 0
self.y1 = 0
self.y2 = 0
ENDFUNCTION

FUNCTION Move: point AS TPoint
INC self.x1, point.x
INC self.x2, point.x
INC self.y1, point.y
INC self.y2, point.y
ENDFUNCTION

ENDTYPE


Supporting Functions:
Code (glbasic) Select
CONSTANT BLANK% = -32760

FUNCTION Sprite_Load%: fn$, is_bump%=FALSE
LOCAL id%
IF DOESFILEEXIST(fn$)
id = Sprite_GetNextId()
DEBUG ">GFX:{Sprite_Load     } fn:[" + fn$ + "] | id:[" + id + "]\n"
IF is_bump
LOADBUMPTEXTURE fn$, id
ELSE
LOADSPRITE fn$, id
ENDIF
RETURN id
ELSE
DEBUG ">GFX:{Sprite_Load     } *** File NOT FOUND!: [" + fn$ + "]\n"
RETURN 1
ENDIF
ENDFUNCTION

// Find free sprite slot
FUNCTION Sprite_GetNextId%:
LOCAL id% = 1
LOCAL sx%, sy%
GETSPRITESIZE id, sx, sy
WHILE sx>0 OR sy>0
INC id
IF id > 10000 THEN RETURN 1
GETSPRITESIZE id, sx, sy
WEND
RETURN id
ENDFUNCTION


FUNCTION Poly_Draw: xy AS TXyXy, uv AS TXyXy, colour%, offset_x%=0, offset_y%=0
POLYNEWSTRIP
POLYVECTOR xy.x1 + offset_x, xy.y1 + offset_y, uv.x1, uv.y1, colour // TL
POLYVECTOR xy.x1 + offset_x, xy.y2 + offset_y, uv.x1, uv.y2, colour // BL
POLYVECTOR xy.x2 + offset_x, xy.y1 + offset_y, uv.x2, uv.y1, colour // TR
POLYVECTOR xy.x2 + offset_x, xy.y2 + offset_y, uv.x2, uv.y2, colour // BR
ENDFUNCTION



FUNCTION String_KeyValue%: string$, BYREF keyword$, BYREF value$, strip_quotes%=TRUE
LOCAL ix%
ix = INSTR(string$, "=")
IF ix < 0
keyword$ = ""
value$   = ""
ELSE
keyword$ = UCASE$(TRIM$(LEFT$(string$, ix)))  // Fixed bug pointed out by bigtunacan
value$   =        TRIM$( MID$(string$, ix+1))
ENDIF
IF strip_quotes
IF LEFT$(value$, 1) = CHR$(34) THEN value$ = MID$(value$, 1)
IF RIGHT$(value$, 1) = CHR$(34) THEN value$ = LEFT$(value$, LEN(value$)-1)
ENDIF
ENDFUNCTION


Sample code usage:
Code (glbasic) Select

GLOBAL sprites AS TSpriteSheet
GLOBAL sp_gui_button%

// Load and initialize the global Sprite Sheet
sprites.Load("Graphics/", "sprites.sprites")

// Locate and define the 'button' gui element
sp_gui_button = sprites.Find("Gui/Button")

// In main loop . . .

// Start new OpenGL Material / Texture
sprites.PolyStart()

// Draw all your sprites from this sprite sheet here . . .
sprites.Draw(sp_gui_button, 10, 10, -1, -1, RGB(0,0,250))
sprites.Draw(sp_gui_button, 10, 90, -1, -1, RGB(250,0,0))

// Close Texture
sprites.PolyEnd()


I think I included all required code and functions, but I may be missing something.

[Edit]
I should add that 'darkFunction' also has a decent animation manager too, and outputs a separate animation XML file.
I haven't created an associated TYPE yet for this as I currently have no need, but it seems like a trivial task. 
It would use the TSprite and TSpriteSheet TYPES for managing the sprite portion, and it would just handle loading the XML data and handling frame state (and which sprite ids from the Sprite Sheet), etc.

[Edit2] Fixed bug pointed out by bigtunacan
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

Albert

I'll give it a shot! Nice find!

bigtunacan

Thanks for the reply and double thanks for the loader code!


bigtunacan

@Slydog,

There is a bug in your String_KeyValue% function; I have it listed as line 6 below. You should instead set keyword$ as

Code (glbasic) Select

keyword$ = UCASE$(TRIM$(LEFT$(string$, ix)))


Currently it has an off by one where the last letter is cut off of the keyword; this is because both INSTR() and MID() are using a zero based index; but LEFT is just a strict number of characters.

Code (glbasic) Select

1:: ix = INSTR(string$, "=")
2:: IF ix < 0
3:: keyword$ = ""
4:: value$   = ""
5:: ELSE
6:: keyword$ = UCASE$(TRIM$(LEFT$(string$, ix-1)))
7:: value$   =        TRIM$( MID$(string$, ix+1))
8:: ENDIF



Slydog

Oops! 
Thanks for pointing that out.
I'll update my code.
My current project (WIP) :: TwistedMaze <<  [Updated: 2015-11-25]

AndreasLoew

#6
Hi,

I have a tool called TexturePacker (http://www.texturepacker.com). It's a sprite atlas creator and image optimizer with a big load of features.
I would be happy to add an exporter for GLBasic.
If anybody is interested in helping me (since I am no GLBasic expert) I would give a free license as reward.

Please contact me at support <mail-symbol> code-and-web.de

Cheers
Andreas

bigtunacan

Andreas,

I just wrote an importer for your tool yesterday that I was planning to make public here anyway once I get some more features implemented.  I have sent you an email regarding this.

Thanks

AndreasLoew

Nice ;-)

Which exporter did you use?

bigtunacan

CEGUI/OGRE.  I'm using the essential version of Texture Packer currently; so I have only tested with standard block layout of texture atlas; may need some tweaks to get it working with rotated/better packed texture atlas, but I don't currently have access to the full version of your tool.


bigtunacan


Wampus

Very handy. Having to manually edit a texture atlas in a photo editor is horribly time consuming.

bigsofty

Almost missed this, nice post and tool!  :good:
Cheers,

Ian.

"It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration."
(E. W. Dijkstra)

Albert

Nice I'm using it from now! (Dark Function importer)