switched to GPLv3 ONLY, because i don't trust FSF anymore
[knightmare.git] / tatlas.d
bloba97d1c813e71e6c91148626cf6f97b78cf886cc4
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
17 module tatlas;
18 private:
20 import iv.cmdcon;
21 import iv.glbinds;
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 {
44 public:
45 static struct Rect {
46 int x, y, w, h;
47 alias x0 = x;
48 alias y0 = y;
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
58 static struct FRect {
59 float x0, y0, x1, y1;
62 private:
63 enum BadRect = uint.max;
65 public:
66 uint[] img;
67 Rect[] rects;
68 uint wdt, hgt;
69 uint ogltex; // OpenGL texture
71 public:
72 this (int awdt, int ahgt) nothrow @trusted {
73 assert(awdt > 0 && ahgt > 0);
74 img = new uint[](awdt*ahgt);
75 img[] = 0xff000000u;
76 rects ~= Rect(0, 0, awdt, ahgt); // one big rect
77 wdt = awdt;
78 hgt = ahgt;
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 {
90 releaseTexture();
92 enum wrapOpt = GL_REPEAT;
93 enum filterOpt = GL_NEAREST; //GL_LINEAR;
94 enum ttype = GL_UNSIGNED_BYTE;
96 glGenTextures(1, &ogltex);
97 if (ogltex == 0) {
98 conwriteln("can't create atlas texture");
99 return 0;
102 GLint gltextbinding;
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);
118 return ogltex;
121 // release (delete) OpenGL texture
122 void releaseTexture () nothrow @trusted @nogc {
123 if (ogltex != 0) {
124 glDeleteTextures(1, &ogltex);
125 ogltex = 0;
129 void updateTexture () nothrow @trusted {
130 if (dbgAtlasRect) {
131 foreach (const ref rc; rects) {
132 imgRect(rc.x, rc.y, rc.w, rc.h, 0xff00ffu);
135 if (ogltex == 0) {
136 createTexture();
137 } else {
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 {
144 FRect res;
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);
149 return res;
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
158 if (r.w == w) {
159 // width fit
160 if (fitW == BadRect || rects.ptr[fitW].h < r.h) fitW = cast(uint)idx;
161 } else if (r.h == h) {
162 // height fit
163 if (fitH == BadRect || rects.ptr[fitH].w < r.w) fitH = cast(uint)idx;
164 } else {
165 // get biggest rect
166 if (biggest == BadRect || rects.ptr[biggest].area > r.area) biggest = cast(uint)idx;
169 // both?
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;
173 return biggest;
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);
198 // split this rect
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];
202 rects.length -= 1;
203 rects.assumeSafeAppend; // for future; we probably won't have alot of best-fitting nodes initially
204 } else {
205 if (rc.w == res.w) {
206 // split vertically
207 rc.y += res.h;
208 rc.h -= res.h;
209 } else if (rc.h == res.h) {
210 // split horizontally
211 rc.x += res.w;
212 rc.w -= res.w;
213 } else {
214 Rect nr = rc;
215 // split in both directions (by longer edge)
216 if (rc.w-res.w > rc.h-res.h) {
217 // cut the right part
218 nr.x += res.w;
219 nr.w -= res.w;
220 // cut the bottom part
221 rc.y += res.h;
222 rc.h -= res.h;
223 rc.w = res.w;
224 } else {
225 // cut the bottom part
226 nr.y += res.h;
227 nr.h -= res.h;
228 // cut the right part
229 rc.x += res.w;
230 rc.w -= res.w;
231 rc.h = res.h;
233 rects ~= nr;
235 rects.ptr[ri] = rc;
237 // copy image data
238 auto dpl = img.ptr+res.y*wdt+res.x;
239 foreach (immutable dy; 0..xheight) {
240 auto dp = dpl;
241 auto sp = dy*xwidth;
242 foreach (immutable dx; 0..xwidth) {
243 if (sp >= ximg.length) break;
244 *dp++ = ximg.ptr[sp++];
246 dpl += wdt;
248 if (dbgAtlasRect) {
249 imgRect4(res.x, res.y, res.w, res.h, 0x0000ffu, 0x00ffffu, 0xff0000u, 0x00ff00u);
251 return res;
256 // ////////////////////////////////////////////////////////////////////////// //
257 enum MaxTexNameLength = 128;
259 public struct AtlasItem {
260 uint tex;
261 uint aidx; // atlas index in alist
262 TexAtlas ta;
263 TexAtlas.Rect rc;
264 TexAtlas.FRect frc;
265 char[MaxTexNameLength] namebuf = 0; // texture name
266 const(char)[] 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);
272 } else {
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 {
282 ta.updateTexture();
286 __gshared TexAtlas[] alist;
287 __gshared AtlasItem[string] aatex;
290 void atadd (const(char)[] name, uint aidx, TexAtlas.Rect rc) {
291 AtlasItem ai;
292 ai.aidx = aidx;
293 ai.ta = alist.ptr[aidx];
294 ai.rc = rc;
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) () {
317 static if (clear) {
318 foreach (TexAtlas ta; alist) {
319 ta.atlas.releaseTexture();
320 delete ta;
323 delete alist;
324 aatex.clear;
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");
339 char[1100] nbuf;
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
353 version(all) {
354 foreach (immutable tidx, TexAtlas ta; alist) {
355 auto rc = ta.insert(ximg, xwidth, xheight);
356 if (rc.valid) {
357 atadd(name, cast(uint)tidx, rc);
358 return;
361 import std.algorithm : max;
362 // create new atlas
363 auto ta = new TexAtlas(max(xwidth, atlasW), max(xheight, atlasH));
364 alist ~= ta;
365 auto rc = ta.insert(ximg, xwidth, xheight);
366 if (!rc.valid) assert(0, "wtf?!");
367 atadd(name, cast(uint)alist.length-1, rc);
368 } else {
369 // create new atlas
370 auto ta = new TexAtlas(xwidth, xheight);
371 alist ~= ta;
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");
383 char[1100] nbuf;
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");
395 // fix texids
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);
408 return null;
410 if (name.length > MaxTexNameLength) name = name[0..MaxTexNameLength];
411 if (auto aip = name in aatex) {
412 glBindTexture(GL_TEXTURE_2D, aip.tex);
413 return aip;
414 } else {
415 glBindTexture(GL_TEXTURE_2D, 0);
416 return null;
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;
425 return null;
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");
432 char[1100] nbuf;
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]);
439 import iv.bclamp;
440 import arsd.color;
441 import arsd.png;
443 public void atlasSavePngs () {
444 import std.format : format;
445 TrueColorImage img;
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];
453 Color c;
454 c.asUint = tc;
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");