some more experiments
[voxconv.git] / vox_mesh.d
blobdb5fbab082da4c106949fc623ba665ec356460ef
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_3dbmp;
16 import vox_data;
19 // ////////////////////////////////////////////////////////////////////////// //
20 public enum kvx_max_optim_level = 4;
21 public __gshared int vox_optimisation = kvx_max_optim_level-1;
22 public __gshared bool vox_allow_normals = true;
23 // alas, this doesn't help much
24 public __gshared bool vox_optimiser_use_3dbmp = false;
27 // ////////////////////////////////////////////////////////////////////////// //
29 this thing is used to create a quad mesh from a voxel data.
30 it contains several conversion methods, from the most simple one
31 "one quad for each visible voxel face", to the most complex one,
32 that goes through each voxel plane, and joins individual voxel
33 faces into a bigger quads (i.e. quads covering several faces at once).
35 this doesn't directly calculates texture coords, tho: it is using an
36 atlas, and records rect coords for each quad. real texture coords are
37 calculated in GL mesh builder instead.
39 final class VoxelMesh {
40 // quad type
41 enum {
42 Point,
43 XLong,
44 YLong,
45 ZLong,
46 Quad,
47 Invalid,
50 enum {
51 Cull_Right = 0x01, // x axis
52 Cull_Left = 0x02, // x axis
53 Cull_Near = 0x04, // y axis
54 Cull_Far = 0x08, // y axis
55 Cull_Top = 0x10, // z axis
56 Cull_Bottom = 0x20, // z axis
58 Cull_XAxisMask = (Cull_Right|Cull_Left),
59 Cull_YAxisMask = (Cull_Near|Cull_Far),
60 Cull_ZAxisMask = (Cull_Top|Cull_Bottom),
63 enum {
64 DMV_X = 0b100,
65 DMV_Y = 0b010,
66 DMV_Z = 0b001,
69 // bitmasks, `DMV_n` can be used to check for `0` or `1`
70 enum {
71 X0_Y0_Z0,
72 X0_Y0_Z1,
73 X0_Y1_Z0,
74 X0_Y1_Z1,
75 X1_Y0_Z0,
76 X1_Y0_Z1,
77 X1_Y1_Z0,
78 X1_Y1_Z1,
81 static align(1) struct Vertex {
82 align(1):
83 float x, y, z;
84 float dx, dy, dz;
85 ubyte qtype; // Xn_Yn_Zn
88 // quad is always one texel strip
89 static align(1) struct VoxQuad {
90 Vertex[4] vx;
91 uint cidx; // in colorpack's `citems`
92 VoxWH16 wh; // width and height
93 Vertex normal;
94 int type = Invalid;
95 ubyte cull; // for which face this quad was created?
97 void calcNormal () {
98 if (vox_allow_normals) {
99 immutable vec3 v1 = vec3(vx[0].x, vx[0].y, vx[0].z);
100 immutable vec3 v2 = vec3(vx[1].x, vx[1].y, vx[1].z);
101 immutable vec3 v3 = vec3(vx[2].x, vx[2].y, vx[2].z);
102 immutable vec3 d1 = v2-v3;
103 immutable vec3 d2 = v1-v3;
104 vec3 PlaneNormal = d1.cross(d2);
105 if (PlaneNormal.lengthSquared() == 0.0f) {
106 PlaneNormal = vec3(0.0f, 0.0f, 1.0f);
107 } else {
108 PlaneNormal = PlaneNormal.normalized();
110 normal.x = normal.dx = PlaneNormal.x;
111 normal.y = normal.dy = PlaneNormal.y;
112 normal.z = normal.dz = PlaneNormal.z;
113 } else {
114 normal.x = normal.dx = 0.0f;
115 normal.y = normal.dy = 0.0f;
116 normal.z = normal.dz = 0.0f;
121 VoxQuad[] quads;
122 // voxel center point
123 float cx, cy, cz;
124 // color atlas
125 VoxColorPack catlas;
127 static immutable ubyte[4][6] quadFaces = [
128 // right (&0x01) (right)
130 X1_Y1_Z0,
131 X1_Y0_Z0,
132 X1_Y0_Z1,
133 X1_Y1_Z1,
135 // left (&0x02) (left)
137 X0_Y0_Z0,
138 X0_Y1_Z0,
139 X0_Y1_Z1,
140 X0_Y0_Z1,
142 // top (&0x04) (near)
144 X0_Y0_Z0,
145 X0_Y0_Z1,
146 X1_Y0_Z1,
147 X1_Y0_Z0,
149 // bottom (&0x08) (far)
151 X1_Y1_Z0,
152 X1_Y1_Z1,
153 X0_Y1_Z1,
154 X0_Y1_Z0,
156 // back (&0x10) (top)
158 X0_Y1_Z1,
159 X1_Y1_Z1,
160 X1_Y0_Z1,
161 X0_Y0_Z1,
163 // front (&0x20) (bottom)
165 X0_Y0_Z0,
166 X1_Y0_Z0,
167 X1_Y1_Z0,
168 X0_Y1_Z0,
172 private void setColors (ref VoxQuad vq, const(uint)[] clrs, uint wdt, uint hgt) {
173 if (catlas.findRect(clrs, wdt, hgt, &vq.cidx, &vq.wh)) {
175 conwriteln(" ...reused rect: (", catlas.getTexX(vq.cidx, vq.rc), ",",
176 catlas.getTexY(vq.cidx, vq.rc), ")-(", wdt, "x", hgt, ")");
178 return;
180 vq.cidx = catlas.addNewRect(clrs, wdt, hgt);
181 vq.wh = VoxWH16(wdt, hgt);
184 private static Vertex genVertex (ubyte type, const float x, const float y, const float z,
185 const float xlen, const float ylen, const float zlen)
187 Vertex vx = void;
188 vx.qtype = type;
189 vx.dx = vx.dy = vx.dz = 0.0f;
190 vx.x = x;
191 vx.y = y;
192 vx.z = z;
193 if (type&DMV_X) { vx.x += xlen; vx.dx = 1.0f; }
194 if (type&DMV_Y) { vx.y += ylen; vx.dy = 1.0f; }
195 if (type&DMV_Z) { vx.z += zlen; vx.dz = 1.0f; }
196 return vx;
199 // dmv: bit 2 means XLong, bit 1 means YLong, bit 0 means ZLong
200 void addSlabFace (ubyte cull, ubyte dmv,
201 float x, float y, float z,
202 int len, const(uint)[] colors)
204 if (len < 1) return;
205 assert(dmv == DMV_X || dmv == DMV_Y || dmv == DMV_Z);
206 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
207 assert(colors.length >= len);
208 colors = colors[0..len];
210 bool allsame = true;
211 foreach (auto cidx; 1..colors.length) {
212 if (colors[cidx] != colors[0]) {
213 allsame = false;
214 break;
217 if (allsame) colors = colors[0..1];
219 const int qtype =
220 colors.length == 1 ? Point :
221 (dmv&DMV_X) ? XLong :
222 (dmv&DMV_Y) ? YLong :
223 ZLong;
224 float dx = (dmv&DMV_X ? cast(float)len : 1.0f);
225 float dy = (dmv&DMV_Y ? cast(float)len : 1.0f);
226 float dz = (dmv&DMV_Z ? cast(float)len : 1.0f);
227 uint qidx;
228 switch (cull) {
229 case 0x01: qidx = 0; break;
230 case 0x02: qidx = 1; break;
231 case 0x04: qidx = 2; break;
232 case 0x08: qidx = 3; break;
233 case 0x10: qidx = 4; break;
234 case 0x20: qidx = 5; break;
235 default: assert(0);
237 VoxQuad vq;
238 foreach (uint vidx; 0..4) {
239 vq.vx[vidx] = genVertex(quadFaces[qidx][vidx], x, y, z, dx, dy, dz);
241 setColors(ref vq, colors[], cast(uint)colors.length, 1);
242 vq.type = qtype;
243 vq.cull = cull;
244 quads ~= vq;
247 void addCube (ubyte cull, float x, float y, float z, uint rgb) {
248 immutable uint[1] carr = [rgb];
249 // generate quads
250 foreach (uint qidx; 0..6) {
251 const ubyte cmask = VoxelData.cullmask(qidx);
252 if (cull&cmask) {
253 addSlabFace(cmask, DMV_X/*doesn't matter*/, x, y, z, 1, carr[]);
258 void addQuad (ubyte cull,
259 float x, float y, float z,
260 int wdt, int hgt, // quad size
261 const(uint)[] colors)
263 assert(wdt > 0 && hgt > 0);
264 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
265 assert(colors.length >= wdt*hgt);
266 colors = colors[0..wdt*hgt];
268 bool allsame = true;
269 foreach (auto cidx; 1..colors.length) {
270 if (colors[cidx] != colors[0]) {
271 allsame = false;
272 break;
275 if (allsame) colors = colors[0..1];
277 const int qtype = Quad;
278 uint qidx;
279 switch (cull) {
280 case 0x01: qidx = 0; break;
281 case 0x02: qidx = 1; break;
282 case 0x04: qidx = 2; break;
283 case 0x08: qidx = 3; break;
284 case 0x10: qidx = 4; break;
285 case 0x20: qidx = 5; break;
286 default: assert(0);
289 VoxQuad vq;
290 for (uint vidx = 0; vidx < 4; ++vidx) {
291 const ubyte vtype = quadFaces[qidx][vidx];
292 Vertex vx = void;
293 vx.qtype = vtype;
294 vx.dx = vx.dy = vx.dz = 0.0f;
295 vx.x = x;
296 vx.y = y;
297 vx.z = z;
298 if (cull&Cull_ZAxisMask) {
299 if (vtype&DMV_X) vx.dx = cast(float)wdt;
300 if (vtype&DMV_Y) vx.dy = cast(float)hgt;
301 if (vtype&DMV_Z) vx.dz = 1.0f;
302 } else if (cull&Cull_XAxisMask) {
303 if (vtype&DMV_X) vx.dx = 1.0f;
304 if (vtype&DMV_Y) vx.dy = cast(float)wdt;
305 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
306 } else if (cull&Cull_YAxisMask) {
307 if (vtype&DMV_X) vx.dx = cast(float)wdt;
308 if (vtype&DMV_Y) vx.dy = 1.0f;
309 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
310 } else {
311 assert(0);
313 vx.x += vx.dx;
314 vx.y += vx.dy;
315 vx.z += vx.dz;
316 vq.vx[vidx] = vx;
319 if (colors.length == 1) {
320 setColors(ref vq, colors[], 1, 1);
321 } else {
322 setColors(ref vq, colors[], wdt, hgt);
324 vq.type = qtype;
325 vq.cull = cull;
327 quads ~= vq;
331 void buildOpt0 (ref VoxelData vox) {
332 const float px = vox.cx;
333 const float py = vox.cy;
334 const float pz = vox.cz;
335 foreach (int y; 0..vox.ysize) {
336 foreach (int x; 0..vox.xsize) {
337 uint dofs = vox.getDOfs(x, y);
338 while (dofs) {
339 addCube(vox.data[dofs].cull, x-px, y-py, vox.data[dofs].z-pz, vox.data[dofs].rgb());
340 dofs = vox.data[dofs].nextz;
346 void buildOpt1 (ref VoxelData vox) {
347 const float px = vox.cx;
348 const float py = vox.cy;
349 const float pz = vox.cz;
351 uint[1024] slab = void;
353 foreach (int y; 0..vox.ysize) {
354 foreach (int x; 0..vox.xsize) {
355 // try slabs in all 6 directions?
356 uint dofs = vox.getDOfs(x, y);
357 if (!dofs) continue;
359 // long top and bottom quads
360 while (dofs) {
361 foreach (uint cidx; 4..6) {
362 const ubyte cmask = VoxelData.cullmask(cidx);
363 if ((vox.data[dofs].cull&cmask) == 0) continue;
364 const int z = cast(int)vox.data[dofs].z;
365 slab[0] = vox.data[dofs].rgb();
366 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, 1, slab[0..1]);
368 dofs = vox.data[dofs].nextz;
371 // build long quads for each side
372 foreach (uint cidx; 0..4) {
373 const ubyte cmask = VoxelData.cullmask(cidx);
374 dofs = vox.getDOfs(x, y);
375 while (dofs) {
376 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
377 if (!dofs) break;
378 const int z = cast(int)vox.data[dofs].z;
379 int count = 0;
380 uint eofs = dofs;
381 while (eofs && (vox.data[eofs].cull&cmask)) {
382 if (cast(int)vox.data[eofs].z != z+count) break;
383 vox.data[eofs].cull ^= cmask;
384 slab[count] = vox.data[eofs].rgb();
385 eofs = vox.data[eofs].nextz;
386 ++count;
387 if (count == cast(int)slab.length) break;
389 assert(count);
390 dofs = eofs;
391 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
398 void buildOpt2 (ref VoxelData vox) {
399 const float px = vox.cx;
400 const float py = vox.cy;
401 const float pz = vox.cz;
403 uint[1024] slab = void;
405 foreach (int y; 0..vox.ysize) {
406 foreach (int x; 0..vox.xsize) {
407 // try slabs in all 6 directions?
408 uint dofs = vox.getDOfs(x, y);
409 if (!dofs) continue;
411 // long top and bottom quads
412 while (dofs) {
413 foreach (uint cidx; 4..6) {
414 const ubyte cmask = VoxelData.cullmask(cidx);
415 if ((vox.data[dofs].cull&cmask) == 0) continue;
416 const int z = cast(int)vox.data[dofs].z;
417 assert(vox.queryCull(x, y, z) == vox.data[dofs].cull);
418 // by x
419 int xcount = 0;
420 while (x+xcount < cast(int)vox.xsize) {
421 const ubyte vcull = vox.queryCull(x+xcount, y, z);
422 if ((vcull&cmask) == 0) break;
423 ++xcount;
425 // by y
426 int ycount = 0;
427 while (y+ycount < cast(int)vox.ysize) {
428 const ubyte vcull = vox.queryCull(x, y+ycount, z);
429 if ((vcull&cmask) == 0) break;
430 ++ycount;
432 assert(xcount && ycount);
433 // now use the longest one
434 if (xcount >= ycount) {
435 xcount = 0;
436 while (x+xcount < cast(int)vox.xsize) {
437 const uint vrgb = vox.query(x+xcount, y, z);
438 if (((vrgb>>24)&cmask) == 0) break;
439 slab[xcount] = vrgb|0xff000000U;
440 vox.setVoxelCull(x+xcount, y, z, (vrgb>>24)^cmask);
441 ++xcount;
443 assert(xcount);
444 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, xcount, slab[0..xcount]);
445 } else {
446 ycount = 0;
447 while (y+ycount < cast(int)vox.ysize) {
448 const uint vrgb = vox.query(x, y+ycount, z);
449 if (((vrgb>>24)&cmask) == 0) break;
450 slab[ycount] = vrgb|0xff000000U;
451 vox.setVoxelCull(x, y+ycount, z, (vrgb>>24)^cmask);
452 ++ycount;
454 assert(ycount);
455 addSlabFace(cmask, DMV_Y, x-px, y-py, z-pz, ycount, slab[0..ycount]);
458 dofs = vox.data[dofs].nextz;
461 // build long quads for each side
462 foreach (uint cidx; 0..4) {
463 const ubyte cmask = VoxelData.cullmask(cidx);
464 dofs = vox.getDOfs(x, y);
465 while (dofs) {
466 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
467 if (!dofs) break;
468 const int z = cast(int)vox.data[dofs].z;
469 int count = 0;
470 uint eofs = dofs;
471 while (eofs && (vox.data[eofs].cull&cmask)) {
472 if (cast(int)vox.data[eofs].z != z+count) break;
473 vox.data[eofs].cull ^= cmask;
474 slab[count] = vox.data[eofs].rgb();
475 eofs = vox.data[eofs].nextz;
476 ++count;
477 if (count == cast(int)slab.length) break;
479 assert(count);
480 dofs = eofs;
481 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
488 void buildOpt3 (ref VoxelData vox) {
489 const float px = vox.cx;
490 const float py = vox.cy;
491 const float pz = vox.cz;
493 // try slabs in all 6 directions?
494 uint[1024] slab = void;
496 immutable ubyte[2][3] dmove = [
497 [DMV_Y, DMV_Z], // left, right
498 [DMV_X, DMV_Z], // near, far
499 [DMV_X, DMV_Y], // top, bottom
502 static int getDX (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_X); }
503 static int getDY (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Y); }
504 static int getDZ (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Z); }
506 static void incXYZ (in ubyte dmv, ref int sx, ref int sy, ref int sz) nothrow @safe @nogc {
507 pragma(inline, true);
508 sx += getDX(dmv);
509 sy += getDY(dmv);
510 sz += getDZ(dmv);
513 foreach (int y; 0..vox.ysize) {
514 foreach (int x; 0..vox.xsize) {
515 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
516 while (vox.data[dofs].cull) {
517 uint count = 0;
518 ubyte clrdmv = 0;
519 ubyte clrmask = 0;
520 const int z = cast(int)vox.data[dofs].z;
521 // check all faces
522 foreach (uint cidx; 0..6) {
523 const ubyte cmask = VoxelData.cullmask(cidx);
524 if ((vox.data[dofs].cull&cmask) == 0) continue;
525 // try two dirs
526 foreach (uint ndir; 0..2) {
527 const ubyte dmv = dmove[cidx>>1][ndir];
528 int cnt = 1;
529 int sx = x, sy = y, sz = z;
530 incXYZ(dmv, ref sx, ref sy, ref sz);
531 for (;;) {
532 const ubyte vxc = vox.queryCull(sx, sy, sz);
533 if ((vxc&cmask) == 0) break;
534 ++cnt;
535 incXYZ(dmv, ref sx, ref sy, ref sz);
537 if (cnt > count) {
538 count = cnt;
539 clrdmv = dmv;
540 clrmask = cmask;
544 if (clrmask) {
545 assert(count);
546 assert(clrdmv == DMV_X || clrdmv == DMV_Y || clrdmv == DMV_Z);
547 int sx = x, sy = y, sz = z;
548 for (uint f = 0; f < count; ++f) {
549 VoxPix *vp = vox.queryVP(sx, sy, sz);
550 slab[f] = vp.rgb();
551 assert(vp.cull&clrmask);
552 vp.cull ^= clrmask;
553 incXYZ(clrdmv, ref sx, ref sy, ref sz);
555 addSlabFace(clrmask, clrdmv, x-px, y-py, z-pz, count, slab[0..count]);
564 // this tries to create big quads
565 void buildOpt4 (ref VoxelData vox) {
566 const float px = vox.cx;
567 const float py = vox.cy;
568 const float pz = vox.cz;
570 // try slabs in all 6 directions?
571 uint[] slab = null;
572 scope(exit) { delete slab; slab = null; }
574 // for faster scans
575 Vox3DBitmap bmp3d;
576 scope(exit) bmp3d.clear();
577 if (vox_optimiser_use_3dbmp) vox.create3DBitmap(ref bmp3d);
579 Vox2DBitmap bmp2d;
580 for (uint cidx = 0; cidx < 6; ++cidx) {
581 const ubyte cmask = VoxelData.cullmask(cidx);
583 uint vwdt, vhgt, vlen;
584 if (cmask&Cull_ZAxisMask) {
585 vwdt = vox.xsize;
586 vhgt = vox.ysize;
587 vlen = vox.zsize;
588 } else if (cmask&Cull_XAxisMask) {
589 vwdt = vox.ysize;
590 vhgt = vox.zsize;
591 vlen = vox.xsize;
592 } else {
593 vwdt = vox.xsize;
594 vhgt = vox.zsize;
595 vlen = vox.ysize;
597 bmp2d.setSize(vwdt, vhgt);
599 for (uint vcrd = 0; vcrd < vlen; ++vcrd) {
600 bmp2d.clearBmp();
601 for (uint vdy = 0; vdy < vhgt; ++vdy) {
602 for (uint vdx = 0; vdx < vwdt; ++vdx) {
603 uint vx, vy, vz;
604 if (cmask&Cull_ZAxisMask) { vx = vdx; vy = vdy; vz = vcrd; }
605 else if (cmask&Cull_XAxisMask) { vx = vcrd; vy = vdx; vz = vdy; }
606 else { vx = vdx; vy = vcrd; vz = vdy; }
607 //conwriteln("*vcrd=", vcrd, "; vdx=", vdx, "; vdy=", vdy);
608 if (vox_optimiser_use_3dbmp && !bmp3d.getPixel(vx, vy, vz)) continue;
609 auto vp = vox.queryVP(vx, vy, vz);
610 if (!vp || ((*vp).cull&cmask) == 0) continue;
611 bmp2d.setPixel(vdx, vdy, (*vp).rgb());
612 if (((*vp).cull ^= cmask) == 0) {
613 vox.removeVoxel(vx, vy, vz);
614 if (vox_optimiser_use_3dbmp) bmp3d.resetPixel(vx, vy, vz);
618 //conwriteln(":: cidx=", cidx, "; vcrd=", vcrd, "; dotCount=", bmp2d.dotCount);
619 if (bmp2d.dotCount == 0) continue;
620 // ok, we have some dots, go create quads
621 int x0, y0, x1, y1;
622 while (bmp2d.doOne(&x0, &y0, &x1, &y1)) {
623 const uint cwdt = (x1-x0)+1;
624 const uint chgt = (y1-y0)+1;
625 if (slab.length < cwdt*chgt) slab.length = ((cwdt*chgt)|0xff)+1;
626 // get colors
627 uint *dp = slab.ptr;
628 for (int dy = y0; dy <= y1; ++dy) {
629 for (int dx = x0; dx <= x1; ++dx) {
630 *dp++ = bmp2d.resetPixel(dx, dy);
633 float fx, fy, fz;
634 if (cmask&Cull_ZAxisMask) { fx = x0; fy = y0; fz = vcrd; }
635 else if (cmask&Cull_XAxisMask) { fx = vcrd; fy = x0; fz = y0; }
636 else { fx = x0; fy = vcrd; fz = y0; }
637 addQuad(cmask, fx-px, fy-py, fz-pz, cwdt, chgt, slab[0..cwdt*chgt]);
644 this (ref VoxelData vox) {
645 assert(vox.xsize && vox.ysize && vox.zsize);
646 // now build cubes
647 conwriteln("building slabs...");
648 auto tm = iv.timer.Timer(true);
649 switch (vox_optimisation) {
650 case 0: buildOpt0(vox); break;
651 case 1: buildOpt1(vox); break;
652 case 2: buildOpt2(vox); break;
653 case 3: buildOpt3(vox); break;
654 case 4: default: buildOpt4(vox); break;
656 conwriteln("basic conversion: ", quads.length, " quads (", quads.length*2, " tris).");
657 tm.stop();
658 conwriteln("converted in ", tm.toString(), "; optlevel is ", vox_optimisation, "/", kvx_max_optim_level);
659 cx = vox.cx;
660 cy = vox.cy;
661 cz = vox.cz;
664 void clear () {
665 delete quads; quads = null;
666 catlas.clear();
667 cx = cy = cz = 0.0f;