more code cleanups
[voxconv.git] / vox_mesh.d
blob7fd6d8d9775e139c78467551b95b26f7282a29e7
1 module vox_mesh;
3 import iv.bclamp;
4 import iv.glbinds.utils;
5 import iv.cmdcongl;
6 import iv.strex;
7 import iv.vfs;
8 import iv.vfs.io;
9 import iv.vmath;
11 static import iv.timer;
13 import vox_texatlas;
14 import vox_2dbmp;
15 import vox_data;
18 // ////////////////////////////////////////////////////////////////////////// //
19 public enum kvx_max_optim_level = 4;
20 public __gshared int vox_optimisation = kvx_max_optim_level-1;
21 public __gshared bool vox_allow_normals = true;
24 // ////////////////////////////////////////////////////////////////////////// //
26 this thing is used to create a quad mesh from a voxel data.
27 it contains several conversion methods, from the most simple one
28 "one quad for each visible voxel face", to the most complex one,
29 that goes through each voxel plane, and joins individual voxel
30 faces into a bigger quads (i.e. quads covering several faces at once).
32 this doesn't directly calculates texture coords, tho: it is using an
33 atlas, and records rect coords for each quad. real texture coords are
34 calculated in GL mesh builder instead.
36 final class VoxelMesh {
37 // quad type
38 enum {
39 Point,
40 XLong,
41 YLong,
42 ZLong,
43 Quad,
44 Invalid,
47 enum {
48 Cull_Right = 0x01, // x axis
49 Cull_Left = 0x02, // x axis
50 Cull_Near = 0x04, // y axis
51 Cull_Far = 0x08, // y axis
52 Cull_Top = 0x10, // z axis
53 Cull_Bottom = 0x20, // z axis
55 Cull_XAxisMask = (Cull_Right|Cull_Left),
56 Cull_YAxisMask = (Cull_Near|Cull_Far),
57 Cull_ZAxisMask = (Cull_Top|Cull_Bottom),
60 enum {
61 DMV_X = 0b100,
62 DMV_Y = 0b010,
63 DMV_Z = 0b001,
66 // bitmasks, `DMV_n` can be used to check for `0` or `1`
67 enum {
68 X0_Y0_Z0,
69 X0_Y0_Z1,
70 X0_Y1_Z0,
71 X0_Y1_Z1,
72 X1_Y0_Z0,
73 X1_Y0_Z1,
74 X1_Y1_Z0,
75 X1_Y1_Z1,
78 static align(1) struct Vertex {
79 align(1):
80 float x, y, z;
81 float dx, dy, dz;
82 ubyte qtype; // Xn_Yn_Zn
85 // quad is always one texel strip
86 static align(1) struct VoxQuad {
87 Vertex[4] vx;
88 uint cidx; // in colorpack's `citems`
89 VoxWH16 wh; // width and height
90 Vertex normal;
91 int type = Invalid;
92 ubyte cull; // for which face this quad was created?
94 void calcNormal () {
95 if (vox_allow_normals) {
96 immutable vec3 v1 = vec3(vx[0].x, vx[0].y, vx[0].z);
97 immutable vec3 v2 = vec3(vx[1].x, vx[1].y, vx[1].z);
98 immutable vec3 v3 = vec3(vx[2].x, vx[2].y, vx[2].z);
99 immutable vec3 d1 = v2-v3;
100 immutable vec3 d2 = v1-v3;
101 vec3 PlaneNormal = d1.cross(d2);
102 if (PlaneNormal.lengthSquared() == 0.0f) {
103 PlaneNormal = vec3(0.0f, 0.0f, 1.0f);
104 } else {
105 PlaneNormal = PlaneNormal.normalized();
107 normal.x = normal.dx = PlaneNormal.x;
108 normal.y = normal.dy = PlaneNormal.y;
109 normal.z = normal.dz = PlaneNormal.z;
110 } else {
111 normal.x = normal.dx = 0.0f;
112 normal.y = normal.dy = 0.0f;
113 normal.z = normal.dz = 0.0f;
118 VoxQuad[] quads;
119 // voxel center point
120 float cx, cy, cz;
121 // color atlas
122 VoxColorPack catlas;
124 static immutable ubyte[4][6] quadFaces = [
125 // right (&0x01) (right)
127 X1_Y1_Z0,
128 X1_Y0_Z0,
129 X1_Y0_Z1,
130 X1_Y1_Z1,
132 // left (&0x02) (left)
134 X0_Y0_Z0,
135 X0_Y1_Z0,
136 X0_Y1_Z1,
137 X0_Y0_Z1,
139 // top (&0x04) (near)
141 X0_Y0_Z0,
142 X0_Y0_Z1,
143 X1_Y0_Z1,
144 X1_Y0_Z0,
146 // bottom (&0x08) (far)
148 X1_Y1_Z0,
149 X1_Y1_Z1,
150 X0_Y1_Z1,
151 X0_Y1_Z0,
153 // back (&0x10) (top)
155 X0_Y1_Z1,
156 X1_Y1_Z1,
157 X1_Y0_Z1,
158 X0_Y0_Z1,
160 // front (&0x20) (bottom)
162 X0_Y0_Z0,
163 X1_Y0_Z0,
164 X1_Y1_Z0,
165 X0_Y1_Z0,
169 private void setColors (ref VoxQuad vq, const(uint)[] clrs, uint wdt, uint hgt) {
170 if (catlas.findRect(clrs, wdt, hgt, &vq.cidx, &vq.wh)) {
172 conwriteln(" ...reused rect: (", catlas.getTexX(vq.cidx, vq.rc), ",",
173 catlas.getTexY(vq.cidx, vq.rc), ")-(", wdt, "x", hgt, ")");
175 return;
177 vq.cidx = catlas.addNewRect(clrs, wdt, hgt);
178 vq.wh = VoxWH16(wdt, hgt);
181 private static Vertex genVertex (ubyte type, const float x, const float y, const float z,
182 const float xlen, const float ylen, const float zlen)
184 Vertex vx = void;
185 vx.qtype = type;
186 vx.dx = vx.dy = vx.dz = 0.0f;
187 vx.x = x;
188 vx.y = y;
189 vx.z = z;
190 if (type&DMV_X) { vx.x += xlen; vx.dx = 1.0f; }
191 if (type&DMV_Y) { vx.y += ylen; vx.dy = 1.0f; }
192 if (type&DMV_Z) { vx.z += zlen; vx.dz = 1.0f; }
193 return vx;
196 // dmv: bit 2 means XLong, bit 1 means YLong, bit 0 means ZLong
197 void addSlabFace (ubyte cull, ubyte dmv,
198 float x, float y, float z,
199 int len, const(uint)[] colors)
201 if (len < 1) return;
202 assert(dmv == DMV_X || dmv == DMV_Y || dmv == DMV_Z);
203 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
204 assert(colors.length >= len);
205 colors = colors[0..len];
207 bool allsame = true;
208 foreach (auto cidx; 1..colors.length) {
209 if (colors[cidx] != colors[0]) {
210 allsame = false;
211 break;
214 if (allsame) colors = colors[0..1];
216 const int qtype =
217 colors.length == 1 ? Point :
218 (dmv&DMV_X) ? XLong :
219 (dmv&DMV_Y) ? YLong :
220 ZLong;
221 float dx = (dmv&DMV_X ? cast(float)len : 1.0f);
222 float dy = (dmv&DMV_Y ? cast(float)len : 1.0f);
223 float dz = (dmv&DMV_Z ? cast(float)len : 1.0f);
224 uint qidx;
225 switch (cull) {
226 case 0x01: qidx = 0; break;
227 case 0x02: qidx = 1; break;
228 case 0x04: qidx = 2; break;
229 case 0x08: qidx = 3; break;
230 case 0x10: qidx = 4; break;
231 case 0x20: qidx = 5; break;
232 default: assert(0);
234 VoxQuad vq;
235 foreach (uint vidx; 0..4) {
236 vq.vx[vidx] = genVertex(quadFaces[qidx][vidx], x, y, z, dx, dy, dz);
238 setColors(ref vq, colors[], cast(uint)colors.length, 1);
239 vq.type = qtype;
240 vq.cull = cull;
241 quads ~= vq;
244 void addCube (ubyte cull, float x, float y, float z, uint rgb) {
245 immutable uint[1] carr = [rgb];
246 // generate quads
247 foreach (uint qidx; 0..6) {
248 const ubyte cmask = VoxelData.cullmask(qidx);
249 if (cull&cmask) {
250 addSlabFace(cmask, DMV_X/*doesn't matter*/, x, y, z, 1, carr[]);
255 void addQuad (ubyte cull,
256 float x, float y, float z,
257 int wdt, int hgt, // quad size
258 const(uint)[] colors)
260 assert(wdt > 0 && hgt > 0);
261 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
262 assert(colors.length >= wdt*hgt);
263 colors = colors[0..wdt*hgt];
265 bool allsame = true;
266 foreach (auto cidx; 1..colors.length) {
267 if (colors[cidx] != colors[0]) {
268 allsame = false;
269 break;
272 if (allsame) colors = colors[0..1];
274 const int qtype = Quad;
275 uint qidx;
276 switch (cull) {
277 case 0x01: qidx = 0; break;
278 case 0x02: qidx = 1; break;
279 case 0x04: qidx = 2; break;
280 case 0x08: qidx = 3; break;
281 case 0x10: qidx = 4; break;
282 case 0x20: qidx = 5; break;
283 default: assert(0);
286 VoxQuad vq;
287 for (uint vidx = 0; vidx < 4; ++vidx) {
288 const ubyte vtype = quadFaces[qidx][vidx];
289 Vertex vx = void;
290 vx.qtype = vtype;
291 vx.dx = vx.dy = vx.dz = 0.0f;
292 vx.x = x;
293 vx.y = y;
294 vx.z = z;
295 if (cull&Cull_ZAxisMask) {
296 if (vtype&DMV_X) vx.dx = cast(float)wdt;
297 if (vtype&DMV_Y) vx.dy = cast(float)hgt;
298 if (vtype&DMV_Z) vx.dz = 1.0f;
299 } else if (cull&Cull_XAxisMask) {
300 if (vtype&DMV_X) vx.dx = 1.0f;
301 if (vtype&DMV_Y) vx.dy = cast(float)wdt;
302 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
303 } else if (cull&Cull_YAxisMask) {
304 if (vtype&DMV_X) vx.dx = cast(float)wdt;
305 if (vtype&DMV_Y) vx.dy = 1.0f;
306 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
307 } else {
308 assert(0);
310 vx.x += vx.dx;
311 vx.y += vx.dy;
312 vx.z += vx.dz;
313 vq.vx[vidx] = vx;
316 if (colors.length == 1) {
317 setColors(ref vq, colors[], 1, 1);
318 } else {
319 setColors(ref vq, colors[], wdt, hgt);
321 vq.type = qtype;
322 vq.cull = cull;
324 quads ~= vq;
328 void buildOpt0 (ref VoxelData vox) {
329 const float px = vox.cx;
330 const float py = vox.cy;
331 const float pz = vox.cz;
332 foreach (int y; 0..vox.ysize) {
333 foreach (int x; 0..vox.xsize) {
334 uint dofs = vox.getDOfs(x, y);
335 while (dofs) {
336 addCube(vox.data[dofs].cull, x-px, y-py, vox.data[dofs].z-pz, vox.data[dofs].rgb());
337 dofs = vox.data[dofs].nextz;
343 void buildOpt1 (ref VoxelData vox) {
344 const float px = vox.cx;
345 const float py = vox.cy;
346 const float pz = vox.cz;
348 uint[1024] slab = void;
350 foreach (int y; 0..vox.ysize) {
351 foreach (int x; 0..vox.xsize) {
352 // try slabs in all 6 directions?
353 uint dofs = vox.getDOfs(x, y);
354 if (!dofs) continue;
356 // long top and bottom quads
357 while (dofs) {
358 foreach (uint cidx; 4..6) {
359 const ubyte cmask = VoxelData.cullmask(cidx);
360 if ((vox.data[dofs].cull&cmask) == 0) continue;
361 const int z = cast(int)vox.data[dofs].z;
362 slab[0] = vox.data[dofs].rgb();
363 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, 1, slab[0..1]);
365 dofs = vox.data[dofs].nextz;
368 // build long quads for each side
369 foreach (uint cidx; 0..4) {
370 const ubyte cmask = VoxelData.cullmask(cidx);
371 dofs = vox.getDOfs(x, y);
372 while (dofs) {
373 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
374 if (!dofs) break;
375 const int z = cast(int)vox.data[dofs].z;
376 int count = 0;
377 uint eofs = dofs;
378 while (eofs && (vox.data[eofs].cull&cmask)) {
379 if (cast(int)vox.data[eofs].z != z+count) break;
380 vox.data[eofs].cull ^= cmask;
381 slab[count] = vox.data[eofs].rgb();
382 eofs = vox.data[eofs].nextz;
383 ++count;
384 if (count == cast(int)slab.length) break;
386 assert(count);
387 dofs = eofs;
388 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
395 void buildOpt2 (ref VoxelData vox) {
396 const float px = vox.cx;
397 const float py = vox.cy;
398 const float pz = vox.cz;
400 uint[1024] slab = void;
402 foreach (int y; 0..vox.ysize) {
403 foreach (int x; 0..vox.xsize) {
404 // try slabs in all 6 directions?
405 uint dofs = vox.getDOfs(x, y);
406 if (!dofs) continue;
408 // long top and bottom quads
409 while (dofs) {
410 foreach (uint cidx; 4..6) {
411 const ubyte cmask = VoxelData.cullmask(cidx);
412 if ((vox.data[dofs].cull&cmask) == 0) continue;
413 const int z = cast(int)vox.data[dofs].z;
414 assert(vox.queryCull(x, y, z) == vox.data[dofs].cull);
415 // by x
416 int xcount = 0;
417 while (x+xcount < cast(int)vox.xsize) {
418 const ubyte vcull = vox.queryCull(x+xcount, y, z);
419 if ((vcull&cmask) == 0) break;
420 ++xcount;
422 // by y
423 int ycount = 0;
424 while (y+ycount < cast(int)vox.ysize) {
425 const ubyte vcull = vox.queryCull(x, y+ycount, z);
426 if ((vcull&cmask) == 0) break;
427 ++ycount;
429 assert(xcount && ycount);
430 // now use the longest one
431 if (xcount >= ycount) {
432 xcount = 0;
433 while (x+xcount < cast(int)vox.xsize) {
434 const uint vrgb = vox.query(x+xcount, y, z);
435 if (((vrgb>>24)&cmask) == 0) break;
436 slab[xcount] = vrgb|0xff000000U;
437 vox.setVoxelCull(x+xcount, y, z, (vrgb>>24)^cmask);
438 ++xcount;
440 assert(xcount);
441 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, xcount, slab[0..xcount]);
442 } else {
443 ycount = 0;
444 while (y+ycount < cast(int)vox.ysize) {
445 const uint vrgb = vox.query(x, y+ycount, z);
446 if (((vrgb>>24)&cmask) == 0) break;
447 slab[ycount] = vrgb|0xff000000U;
448 vox.setVoxelCull(x, y+ycount, z, (vrgb>>24)^cmask);
449 ++ycount;
451 assert(ycount);
452 addSlabFace(cmask, DMV_Y, x-px, y-py, z-pz, ycount, slab[0..ycount]);
455 dofs = vox.data[dofs].nextz;
458 // build long quads for each side
459 foreach (uint cidx; 0..4) {
460 const ubyte cmask = VoxelData.cullmask(cidx);
461 dofs = vox.getDOfs(x, y);
462 while (dofs) {
463 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
464 if (!dofs) break;
465 const int z = cast(int)vox.data[dofs].z;
466 int count = 0;
467 uint eofs = dofs;
468 while (eofs && (vox.data[eofs].cull&cmask)) {
469 if (cast(int)vox.data[eofs].z != z+count) break;
470 vox.data[eofs].cull ^= cmask;
471 slab[count] = vox.data[eofs].rgb();
472 eofs = vox.data[eofs].nextz;
473 ++count;
474 if (count == cast(int)slab.length) break;
476 assert(count);
477 dofs = eofs;
478 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
485 void buildOpt3 (ref VoxelData vox) {
486 const float px = vox.cx;
487 const float py = vox.cy;
488 const float pz = vox.cz;
490 // try slabs in all 6 directions?
491 uint[1024] slab = void;
493 immutable ubyte[2][3] dmove = [
494 [DMV_Y, DMV_Z], // left, right
495 [DMV_X, DMV_Z], // near, far
496 [DMV_X, DMV_Y], // top, bottom
499 static int getDX (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_X); }
500 static int getDY (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Y); }
501 static int getDZ (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Z); }
503 static void incXYZ (in ubyte dmv, ref int sx, ref int sy, ref int sz) nothrow @safe @nogc {
504 pragma(inline, true);
505 sx += getDX(dmv);
506 sy += getDY(dmv);
507 sz += getDZ(dmv);
510 foreach (int y; 0..vox.ysize) {
511 foreach (int x; 0..vox.xsize) {
512 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
513 while (vox.data[dofs].cull) {
514 uint count = 0;
515 ubyte clrdmv = 0;
516 ubyte clrmask = 0;
517 const int z = cast(int)vox.data[dofs].z;
518 // check all faces
519 foreach (uint cidx; 0..6) {
520 const ubyte cmask = VoxelData.cullmask(cidx);
521 if ((vox.data[dofs].cull&cmask) == 0) continue;
522 // try two dirs
523 foreach (uint ndir; 0..2) {
524 const ubyte dmv = dmove[cidx>>1][ndir];
525 int cnt = 1;
526 int sx = x, sy = y, sz = z;
527 incXYZ(dmv, ref sx, ref sy, ref sz);
528 for (;;) {
529 const ubyte vxc = vox.queryCull(sx, sy, sz);
530 if ((vxc&cmask) == 0) break;
531 ++cnt;
532 incXYZ(dmv, ref sx, ref sy, ref sz);
534 if (cnt > count) {
535 count = cnt;
536 clrdmv = dmv;
537 clrmask = cmask;
541 if (clrmask) {
542 assert(count);
543 assert(clrdmv == DMV_X || clrdmv == DMV_Y || clrdmv == DMV_Z);
544 int sx = x, sy = y, sz = z;
545 for (uint f = 0; f < count; ++f) {
546 VoxPix *vp = vox.queryVP(sx, sy, sz);
547 slab[f] = vp.rgb();
548 assert(vp.cull&clrmask);
549 vp.cull ^= clrmask;
550 incXYZ(clrdmv, ref sx, ref sy, ref sz);
552 addSlabFace(clrmask, clrdmv, x-px, y-py, z-pz, count, slab[0..count]);
561 // this tries to create big quads
562 void buildOpt4 (ref VoxelData vox) {
563 const float px = vox.cx;
564 const float py = vox.cy;
565 const float pz = vox.cz;
567 // try slabs in all 6 directions?
568 uint[] slab = null;
569 scope(exit) { delete slab; slab = null; }
571 Vox2DBitmap bmp2d;
572 for (uint cidx = 0; cidx < 6; ++cidx) {
573 const ubyte cmask = VoxelData.cullmask(cidx);
575 uint vwdt, vhgt, vlen;
576 if (cmask&Cull_ZAxisMask) {
577 vwdt = vox.xsize;
578 vhgt = vox.ysize;
579 vlen = vox.zsize;
580 } else if (cmask&Cull_XAxisMask) {
581 vwdt = vox.ysize;
582 vhgt = vox.zsize;
583 vlen = vox.xsize;
584 } else {
585 vwdt = vox.xsize;
586 vhgt = vox.zsize;
587 vlen = vox.ysize;
589 bmp2d.setSize(vwdt, vhgt);
591 for (uint vcrd = 0; vcrd < vlen; ++vcrd) {
592 bmp2d.clearBmp();
593 for (uint vdy = 0; vdy < vhgt; ++vdy) {
594 for (uint vdx = 0; vdx < vwdt; ++vdx) {
595 uint vx, vy, vz;
596 if (cmask&Cull_ZAxisMask) { vx = vdx; vy = vdy; vz = vcrd; }
597 else if (cmask&Cull_XAxisMask) { vx = vcrd; vy = vdx; vz = vdy; }
598 else { vx = vdx; vy = vcrd; vz = vdy; }
599 //conwriteln("*vcrd=", vcrd, "; vdx=", vdx, "; vdy=", vdy);
600 auto vp = vox.queryVP(vx, vy, vz);
601 if (!vp || ((*vp).cull&cmask) == 0) continue;
602 bmp2d.setPixel(vdx, vdy, (*vp).rgb());
605 //conwriteln(":: cidx=", cidx, "; vcrd=", vcrd, "; dotCount=", bmp2d.dotCount);
606 if (bmp2d.dotCount == 0) continue;
607 // ok, we have some dots, go create quads
608 int x0, y0, x1, y1;
609 while (bmp2d.doOne(&x0, &y0, &x1, &y1)) {
610 const uint cwdt = (x1-x0)+1;
611 const uint chgt = (y1-y0)+1;
612 if (slab.length < cwdt*chgt) slab.length = ((cwdt*chgt)|0xff)+1;
613 // get colors
614 uint *dp = slab.ptr;
615 for (int dy = y0; dy <= y1; ++dy) {
616 for (int dx = x0; dx <= x1; ++dx) {
617 *dp++ = bmp2d.resetPixel(dx, dy);
620 float fx, fy, fz;
621 if (cmask&Cull_ZAxisMask) { fx = x0; fy = y0; fz = vcrd; }
622 else if (cmask&Cull_XAxisMask) { fx = vcrd; fy = x0; fz = y0; }
623 else { fx = x0; fy = vcrd; fz = y0; }
624 addQuad(cmask, fx-px, fy-py, fz-pz, cwdt, chgt, slab[0..cwdt*chgt]);
631 this (ref VoxelData vox) {
632 assert(vox.xsize && vox.ysize && vox.zsize);
633 // now build cubes
634 conwriteln("building slabs...");
635 auto tm = iv.timer.Timer(true);
636 switch (vox_optimisation) {
637 case 0: buildOpt0(vox); break;
638 case 1: buildOpt1(vox); break;
639 case 2: buildOpt2(vox); break;
640 case 3: buildOpt3(vox); break;
641 case 4: default: buildOpt4(vox); break;
643 conwriteln("basic conversion: ", quads.length, " quads (", quads.length*2, " tris).");
644 tm.stop();
645 conwriteln("converted in ", tm.toString(), "; optlevel is ", vox_optimisation, "/", kvx_max_optim_level);
646 cx = vox.cx;
647 cy = vox.cy;
648 cz = vox.cz;
651 void clear () {
652 delete quads; quads = null;
653 catlas.clear();
654 cx = cy = cz = 0.0f;