1 /* coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
2 * Understanding is not required. Only obedience.
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, version 3 of the License ONLY.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 // texture atlases; used for sprites
24 // ////////////////////////////////////////////////////////////////////////// //
25 __gshared
uint atlasW
= 1024, atlasH
= 1024;
26 __gshared
bool dbgAtlasRect
= false;
28 shared static this () {
29 conRegVar
!atlasW(256, 4096, "r_atlas_width", "texture atlas width");
30 conRegVar
!atlasH(256, 4096, "r_atlas_height", "texture atlas height");
31 conRegVar
!dbgAtlasRect("dbg_atlas_rect", "draw bounding rectangle on each texture in atlas");
35 public void atlasSealVars () {
36 conSealVar("r_atlas_width");
37 conSealVar("r_atlas_height");
38 conwriteln("atlas size: ", atlasW
, "x", atlasH
);
42 // ////////////////////////////////////////////////////////////////////////// //
43 public final class TexAtlas
{
49 pure nothrow @safe @nogc:
50 @property bool valid () const pure { pragma(inline
, true); return (x
>= 0 && y
>= 0 && w
> 0 && h
> 0); }
51 @property int area () const pure { pragma(inline
, true); return w
*h
; }
52 @property int x1 () const pure { pragma(inline
, true); return x
+w
; }
53 @property int y1 () const pure { pragma(inline
, true); return y
+h
; }
54 static @property Rect
Invalid () pure { Rect res
; return res
; }
57 // texture coords in atlas
63 enum BadRect
= uint.max
;
69 uint ogltex
; // OpenGL texture
72 this (int awdt
, int ahgt
) nothrow @trusted {
73 assert(awdt
> 0 && ahgt
> 0);
74 img
= new uint[](awdt
*ahgt
);
76 rects
~= Rect(0, 0, awdt
, ahgt
); // one big rect
81 @property const pure nothrow @safe @nogc {
82 int width () { pragma(inline
, true); return wdt
; }
83 int height () { pragma(inline
, true); return hgt
; }
84 bool hasTexture () { pragma(inline
, true); return (ogltex
!= 0); }
85 uint tex () { pragma(inline
, true); return ogltex
; }
88 // return opengl texture id or 0
89 uint createTexture () nothrow @trusted {
92 enum wrapOpt
= GL_REPEAT
;
93 enum filterOpt
= GL_NEAREST
; //GL_LINEAR;
94 enum ttype
= GL_UNSIGNED_BYTE
;
96 glGenTextures(1, &ogltex
);
98 conwriteln("can't create atlas texture");
103 glGetIntegerv(GL_TEXTURE_BINDING_2D
, &gltextbinding
);
104 scope(exit
) glBindTexture(GL_TEXTURE_2D
, gltextbinding
);
106 glBindTexture(GL_TEXTURE_2D
, ogltex
);
107 glTexParameterf(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_S
, wrapOpt
);
108 glTexParameterf(GL_TEXTURE_2D
, GL_TEXTURE_WRAP_T
, wrapOpt
);
109 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MIN_FILTER
, filterOpt
);
110 glTexParameteri(GL_TEXTURE_2D
, GL_TEXTURE_MAG_FILTER
, filterOpt
);
111 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
112 //glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
114 GLfloat
[4] bclr
= 0.0;
115 glTexParameterfv(GL_TEXTURE_2D
, GL_TEXTURE_BORDER_COLOR
, bclr
.ptr
);
116 glTexImage2D(GL_TEXTURE_2D
, 0, GL_RGBA
, wdt
, hgt
, 0, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.ptr
);
121 // release (delete) OpenGL texture
122 void releaseTexture () nothrow @trusted @nogc {
124 glDeleteTextures(1, &ogltex
);
129 void updateTexture () nothrow @trusted {
131 foreach (const ref rc
; rects
) {
132 imgRect(rc
.x
, rc
.y
, rc
.w
, rc
.h
, 0xff00ffu
);
138 //glTextureSubImage2D(ogltex, 0, 0/*x*/, 0/*y*/, wdt, hgt, GL_RGBA, GL_UNSIGNED_BYTE, img.ptr);
139 glTexSubImage2D(GL_TEXTURE_2D
, 0, 0/*x*/, 0/*y*/, wdt
, hgt
, GL_RGBA
, GL_UNSIGNED_BYTE
, img
.ptr
); // this updates texture
143 FRect
texCoords (in Rect rc
) nothrow @trusted @nogc {
145 res
.x0
= cast(float)rc
.x
/cast(float)(width
);
146 res
.y0
= cast(float)rc
.y
/cast(float)(height
);
147 res
.x1
= cast(float)(rc
.x
+rc
.w
)/cast(float)(width
);
148 res
.y1
= cast(float)(rc
.y
+rc
.h
)/cast(float)(height
);
152 // node id or BadRect
153 private uint findBestFit (int w
, int h
) nothrow @trusted @nogc {
154 uint fitW
= BadRect
, fitH
= BadRect
, biggest
= BadRect
;
155 foreach (immutable idx
, const ref r
; rects
) {
156 if (r
.w
< w || r
.h
< h
) continue; // absolutely can't fit
157 if (r
.w
== w
&& r
.h
== h
) return cast(uint)idx
; // perfect fit
160 if (fitW
== BadRect || rects
.ptr
[fitW
].h
< r
.h
) fitW
= cast(uint)idx
;
161 } else if (r
.h
== h
) {
163 if (fitH
== BadRect || rects
.ptr
[fitH
].w
< r
.w
) fitH
= cast(uint)idx
;
166 if (biggest
== BadRect || rects
.ptr
[biggest
].area
> r
.area
) biggest
= cast(uint)idx
;
170 if (fitW
!= BadRect
&& fitH
!= BadRect
) return (rects
.ptr
[fitW
].area
> rects
.ptr
[fitH
].area ? fitW
: fitH
);
171 if (fitW
!= BadRect
) return fitW
;
172 if (fitH
!= BadRect
) return fitH
;
176 private void imgPutPixel (int x
, int y
, uint c
) nothrow @trusted @nogc {
177 if (x
>= 0 && y
>= 0 && x
< wdt
&& y
< hgt
) img
.ptr
[y
*wdt
+x
] = c|
0x80000000u
;
180 private void imgRect (int x
, int y
, int w
, int h
, uint c
) nothrow @trusted @nogc {
181 foreach (int d
; 0..w
) { imgPutPixel(x
+d
, y
, c
); imgPutPixel(x
+d
, y
+h
-1, c
); }
182 foreach (int d
; 1..h
-1) { imgPutPixel(x
, y
+d
, c
); imgPutPixel(x
+w
-1, y
+d
, c
); }
185 private void imgRect4 (int x
, int y
, int w
, int h
, uint ct
, uint cb
, uint cl
, uint cr
) nothrow @trusted @nogc {
186 foreach (int d
; 0..w
) { imgPutPixel(x
+d
, y
, ct
); imgPutPixel(x
+d
, y
+h
-1, cb
); }
187 foreach (int d
; 1..h
-1) { imgPutPixel(x
, y
+d
, cl
); imgPutPixel(x
+w
-1, y
+d
, cr
); }
190 // returns invalid rect if there is no room
191 Rect
insert (const(uint)[] ximg
, int xwidth
, int xheight
) @trusted {
192 assert(xwidth
> 0 && xheight
> 0);
193 if (xwidth
> wdt || xheight
> hgt
) return Rect
.Invalid
;
194 auto ri
= findBestFit(xwidth
, xheight
);
195 if (ri
== BadRect
) return Rect
.Invalid
;
196 auto rc
= rects
.ptr
[ri
];
197 auto res
= Rect(rc
.x
, rc
.y
, xwidth
, xheight
);
199 if (rc
.w
== res
.w
&& rc
.h
== res
.h
) {
200 // best fit, simply remove this rect
201 foreach (immutable cidx
; ri
+1..rects
.length
) rects
.ptr
[cidx
-1] = rects
.ptr
[cidx
];
203 rects
.assumeSafeAppend
; // for future; we probably won't have alot of best-fitting nodes initially
209 } else if (rc
.h
== res
.h
) {
210 // split horizontally
215 // split in both directions (by longer edge)
216 if (rc
.w
-res
.w
> rc
.h
-res
.h
) {
217 // cut the right part
220 // cut the bottom part
225 // cut the bottom part
228 // cut the right part
238 auto dpl
= img
.ptr
+res
.y
*wdt
+res
.x
;
239 foreach (immutable dy
; 0..xheight
) {
242 foreach (immutable dx
; 0..xwidth
) {
243 if (sp
>= ximg
.length
) break;
244 *dp
++ = ximg
.ptr
[sp
++];
249 imgRect4(res
.x
, res
.y
, res
.w
, res
.h
, 0x0000ffu
, 0x00ffffu
, 0xff0000u
, 0x00ff00u
);
256 // ////////////////////////////////////////////////////////////////////////// //
257 enum MaxTexNameLength
= 128;
259 public struct AtlasItem
{
261 uint aidx
; // atlas index in alist
265 char[MaxTexNameLength
] namebuf
= 0; // texture name
268 Color
getPixel (int x
, int y
) const nothrow @trusted @nogc {
269 if (x
>= 0 && y
>= 0 && x
< rc
.w
&& y
< rc
.h
) {
270 immutable uint cu
= ta
.img
[(y
+rc
.y
)*ta
.wdt
+(x
+rc
.x
)];
271 return Color(cu
&0xff, (cu
>>8)&0xff, (cu
>>16)&0xff, (cu
>>24)&0xff);
273 return Color
.transparent
;
277 void setPixel (int x
, int y
, in Color c
) nothrow @trusted @nogc {
278 if (x
>= 0 && y
>= 0 && x
< rc
.w
&& y
< rc
.h
) ta
.img
[(y
+rc
.y
)*ta
.wdt
+(x
+rc
.x
)] = c
.asUint
;
281 void updateTexture () nothrow @trusted {
286 __gshared TexAtlas
[] alist
;
287 __gshared AtlasItem
[string
] aatex
;
290 void atadd (const(char)[] name
, uint aidx
, TexAtlas
.Rect rc
) {
293 ai
.ta
= alist
.ptr
[aidx
];
295 ai
.frc
= alist
.ptr
[aidx
].texCoords(rc
);
296 ai
.namebuf
[0..name
.length
] = name
[];
297 ai
.name
= ai
.namebuf
[0..name
.length
];
298 aatex
[name
.idup
] = ai
;
299 //conwriteln("[", name, "]: (", rc.x, ",", rc.y, ":", rc.w, ",", rc.h, ")-(", ai.frc.x0, ",", ai.frc.y0, ")-(", ai.frc.x1, ",", ai.frc.y1, ")");
303 // number of texture atlases
304 public uint atlasCount () {
305 return cast(uint)alist
.length
;
309 // total number of textures atlases
310 public uint atlasTexCount () {
311 return cast(uint)aatex
.length
;
315 // remove all texture atlases
316 public void atlasClear(bool clear
=true) () {
318 foreach (TexAtlas ta
; alist
) {
319 ta
.atlas
.releaseTexture();
328 // check if texture with this name already created
329 public bool atlasHas (const(char)[] name
) {
330 if (name
.length
== 0) return false; // unnamed texture, get out of here
331 if (name
.length
> MaxTexNameLength
) name
= name
[0..MaxTexNameLength
];
332 return ((name
in aatex
) !is null);
336 public bool atlasHas (const(char)[] prefix
, uint idx
) {
337 import core
.stdc
.stdio
: snprintf
;
338 if (prefix
.length
> 1024) throw new Exception("prefix too long");
340 nbuf
[0..prefix
.length
] = prefix
[];
341 auto len
= snprintf(nbuf
.ptr
+prefix
.length
, nbuf
.length
-prefix
.length
, "%u".ptr
, idx
);
342 return atlasHas(nbuf
[0..prefix
.length
+len
]);
346 // remove all texture atlases
347 public void atlasAdd (const(char)[] name
, const(uint)[] ximg
, int xwidth
, int xheight
) {
348 if (name
.length
== 0) return; // unnamed texture, get out of here
349 if (name
.length
> MaxTexNameLength
) name
= name
[0..MaxTexNameLength
];
350 if (xwidth
< 1 || xheight
< 1 || xwidth
> 4096 || xheight
> 4096) throw new Exception("invalid texture size");
351 auto iname
= name
.idup
;
352 // find atlas to put into
354 foreach (immutable tidx
, TexAtlas ta
; alist
) {
355 auto rc
= ta
.insert(ximg
, xwidth
, xheight
);
357 atadd(name
, cast(uint)tidx
, rc
);
361 import std
.algorithm
: max
;
363 auto ta
= new TexAtlas(max(xwidth
, atlasW
), max(xheight
, atlasH
));
365 auto rc
= ta
.insert(ximg
, xwidth
, xheight
);
366 if (!rc
.valid
) assert(0, "wtf?!");
367 atadd(name
, cast(uint)alist
.length
-1, rc
);
370 auto ta
= new TexAtlas(xwidth
, xheight
);
372 auto rc
= ta
.insert(ximg
, xwidth
, xheight
);
373 if (!rc
.valid
) assert(0, "wtf?!");
374 atadd(name
, cast(uint)alist
.length
-1, rc
);
379 // remove all texture atlases
380 public void atlasAdd (const(char)[] prefix
, uint idx
, const(uint)[] ximg
, int xwidth
, int xheight
) {
381 import core
.stdc
.stdio
: snprintf
;
382 if (prefix
.length
> 1024) throw new Exception("prefix too long");
384 nbuf
[0..prefix
.length
] = prefix
[];
385 auto len
= snprintf(nbuf
.ptr
+prefix
.length
, nbuf
.length
-prefix
.length
, "%u".ptr
, idx
);
386 atlasAdd(nbuf
[0..prefix
.length
+len
], ximg
, xwidth
, xheight
);
390 // load all atlases into GPU
391 public void atlasLoadGL () {
392 foreach (TexAtlas ta
; alist
) {
393 if (ta
.createTexture() == 0) throw new Exception("can't load atlas texture to GPU");
396 foreach (ref AtlasItem ai
; aatex
.byValue
) {
397 if (ai
.aidx
>= alist
.length
) assert(0, "wtf?!");
398 ai
.tex
= alist
[ai
.aidx
].tex
;
403 // bind OpenGL texture
404 public AtlasItem
* atlasBindGL (const(char)[] name
) {
405 if (name
.length
== 0) {
406 // unnamed texture, get out of here
407 glBindTexture(GL_TEXTURE_2D
, 0);
410 if (name
.length
> MaxTexNameLength
) name
= name
[0..MaxTexNameLength
];
411 if (auto aip
= name
in aatex
) {
412 glBindTexture(GL_TEXTURE_2D
, aip
.tex
);
415 glBindTexture(GL_TEXTURE_2D
, 0);
421 public AtlasItem
* atlasGet (const(char)[] name
) {
422 if (name
.length
== 0) return null; // unnamed texture, get out of here
423 if (name
.length
> MaxTexNameLength
) name
= name
[0..MaxTexNameLength
];
424 if (auto aip
= name
in aatex
) return aip
;
429 public AtlasItem
* atlasGet (const(char)[] prefix
, uint idx
) {
430 import core
.stdc
.stdio
: snprintf
;
431 if (prefix
.length
> 1024) throw new Exception("prefix too long");
433 nbuf
[0..prefix
.length
] = prefix
[];
434 auto len
= snprintf(nbuf
.ptr
+prefix
.length
, nbuf
.length
-prefix
.length
, "%u".ptr
, idx
);
435 return atlasGet(nbuf
[0..prefix
.length
+len
]);
443 public void atlasSavePngs () {
444 import std
.format
: format
;
446 foreach (immutable tidx
, TexAtlas ti
; alist
) {
447 auto fname
= "z_%02d.png".format(tidx
);
448 conwriteln("writing '", fname
, "'");
449 if (img
is null || img
.width
!= ti
.width || img
.height
!= ti
.height
) img
= new TrueColorImage(ti
.width
, ti
.height
);
450 foreach (immutable dy
; 0..ti
.height
) {
451 foreach (immutable dx
; 0..ti
.width
) {
452 auto tc
= ti
.img
[dy
*ti
.width
+dx
];
455 img
.setPixel(dx
, dy
, c
);
458 writePng(fname
, img
);
463 shared static this () {
464 conRegFunc
!atlasSavePngs("dbg_save_atlases", "save atlases to PNG images");