fixed quad texture mapping
[voxconv.git] / vox_mesh.d
blobc8fe76d17dbd4448895dde39d12c4103fd646ba6
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 VoxRect16 rc; // inside the given citem
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.rc)) {
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.rc = VoxRect16.Zero();
179 vq.rc.setW(wdt);
180 vq.rc.setH(hgt);
183 private static Vertex genVertex (ubyte type, const float x, const float y, const float z,
184 const float xlen, const float ylen, const float zlen)
186 Vertex vx = void;
187 vx.qtype = type;
188 vx.dx = vx.dy = vx.dz = 0.0f;
189 vx.x = x;
190 vx.y = y;
191 vx.z = z;
192 if (type&DMV_X) { vx.x += xlen; vx.dx = 1.0f; }
193 if (type&DMV_Y) { vx.y += ylen; vx.dy = 1.0f; }
194 if (type&DMV_Z) { vx.z += zlen; vx.dz = 1.0f; }
195 return vx;
198 // dmv: bit 2 means XLong, bit 1 means YLong, bit 0 means ZLong
199 void addSlabFace (ubyte cull, ubyte dmv,
200 float x, float y, float z,
201 int len, const(uint)[] colors)
203 if (len < 1) return;
204 assert(dmv == DMV_X || dmv == DMV_Y || dmv == DMV_Z);
205 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
206 assert(colors.length >= len);
207 colors = colors[0..len];
209 bool allsame = true;
210 foreach (auto cidx; 1..colors.length) {
211 if (colors[cidx] != colors[0]) {
212 allsame = false;
213 break;
216 if (allsame) colors = colors[0..1];
218 const int qtype =
219 colors.length == 1 ? Point :
220 (dmv&DMV_X) ? XLong :
221 (dmv&DMV_Y) ? YLong :
222 ZLong;
223 float dx = (dmv&DMV_X ? cast(float)len : 1.0f);
224 float dy = (dmv&DMV_Y ? cast(float)len : 1.0f);
225 float dz = (dmv&DMV_Z ? cast(float)len : 1.0f);
226 uint qidx;
227 switch (cull) {
228 case 0x01: qidx = 0; break;
229 case 0x02: qidx = 1; break;
230 case 0x04: qidx = 2; break;
231 case 0x08: qidx = 3; break;
232 case 0x10: qidx = 4; break;
233 case 0x20: qidx = 5; break;
234 default: assert(0);
236 VoxQuad vq;
237 foreach (uint vidx; 0..4) {
238 vq.vx[vidx] = genVertex(quadFaces[qidx][vidx], x, y, z, dx, dy, dz);
240 setColors(ref vq, colors[], cast(uint)colors.length, 1);
241 vq.type = qtype;
242 vq.cull = cull;
243 quads ~= vq;
246 void addCube (ubyte cull, float x, float y, float z, uint rgb) {
247 immutable uint[1] carr = [rgb];
248 // generate quads
249 foreach (uint qidx; 0..6) {
250 const ubyte cmask = VoxelData.cullmask(qidx);
251 if (cull&cmask) {
252 addSlabFace(cmask, DMV_X/*doesn't matter*/, x, y, z, 1, carr[]);
257 void addQuad (ubyte cull,
258 float x, float y, float z,
259 int wdt, int hgt, // quad size
260 const(uint)[] colors)
262 assert(wdt > 0 && hgt > 0);
263 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
264 assert(colors.length >= wdt*hgt);
265 colors = colors[0..wdt*hgt];
267 bool allsame = true;
268 foreach (auto cidx; 1..colors.length) {
269 if (colors[cidx] != colors[0]) {
270 allsame = false;
271 break;
274 if (allsame) colors = colors[0..1];
276 const int qtype = Quad;
277 uint qidx;
278 switch (cull) {
279 case 0x01: qidx = 0; break;
280 case 0x02: qidx = 1; break;
281 case 0x04: qidx = 2; break;
282 case 0x08: qidx = 3; break;
283 case 0x10: qidx = 4; break;
284 case 0x20: qidx = 5; break;
285 default: assert(0);
288 VoxQuad vq;
289 for (uint vidx = 0; vidx < 4; ++vidx) {
290 const ubyte vtype = quadFaces[qidx][vidx];
291 Vertex vx = void;
292 vx.qtype = vtype;
293 vx.dx = vx.dy = vx.dz = 0.0f;
294 vx.x = x;
295 vx.y = y;
296 vx.z = z;
297 if (cull&Cull_ZAxisMask) {
298 if (vtype&DMV_X) vx.dx = cast(float)wdt;
299 if (vtype&DMV_Y) vx.dy = cast(float)hgt;
300 if (vtype&DMV_Z) vx.dz = 1.0f;
301 } else if (cull&Cull_XAxisMask) {
302 if (vtype&DMV_X) vx.dx = 1.0f;
303 if (vtype&DMV_Y) vx.dy = cast(float)wdt;
304 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
305 } else if (cull&Cull_YAxisMask) {
306 if (vtype&DMV_X) vx.dx = cast(float)wdt;
307 if (vtype&DMV_Y) vx.dy = 1.0f;
308 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
309 } else {
310 assert(0);
312 vx.x += vx.dx;
313 vx.y += vx.dy;
314 vx.z += vx.dz;
315 vq.vx[vidx] = vx;
318 if (colors.length == 1) {
319 setColors(ref vq, colors[], 1, 1);
320 } else {
321 setColors(ref vq, colors[], wdt, hgt);
323 vq.type = qtype;
324 vq.cull = cull;
326 quads ~= vq;
330 void buildOpt0 (ref VoxelData vox) {
331 const float px = vox.cx;
332 const float py = vox.cy;
333 const float pz = vox.cz;
334 foreach (int y; 0..vox.ysize) {
335 foreach (int x; 0..vox.xsize) {
336 uint dofs = vox.getDOfs(x, y);
337 while (dofs) {
338 addCube(vox.data[dofs].cull, x-px, y-py, vox.data[dofs].z-pz, vox.data[dofs].rgb());
339 dofs = vox.data[dofs].nextz;
345 void buildOpt1 (ref VoxelData vox) {
346 const float px = vox.cx;
347 const float py = vox.cy;
348 const float pz = vox.cz;
350 uint[1024] slab = void;
352 foreach (int y; 0..vox.ysize) {
353 foreach (int x; 0..vox.xsize) {
354 // try slabs in all 6 directions?
355 uint dofs = vox.getDOfs(x, y);
356 if (!dofs) continue;
358 // long top and bottom quads
359 while (dofs) {
360 foreach (uint cidx; 4..6) {
361 const ubyte cmask = VoxelData.cullmask(cidx);
362 if ((vox.data[dofs].cull&cmask) == 0) continue;
363 const int z = cast(int)vox.data[dofs].z;
364 slab[0] = vox.data[dofs].rgb();
365 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, 1, slab[0..1]);
367 dofs = vox.data[dofs].nextz;
370 // build long quads for each side
371 foreach (uint cidx; 0..4) {
372 const ubyte cmask = VoxelData.cullmask(cidx);
373 dofs = vox.getDOfs(x, y);
374 while (dofs) {
375 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
376 if (!dofs) break;
377 const int z = cast(int)vox.data[dofs].z;
378 int count = 0;
379 uint eofs = dofs;
380 while (eofs && (vox.data[eofs].cull&cmask)) {
381 if (cast(int)vox.data[eofs].z != z+count) break;
382 vox.data[eofs].cull ^= cmask;
383 slab[count] = vox.data[eofs].rgb();
384 eofs = vox.data[eofs].nextz;
385 ++count;
386 if (count == cast(int)slab.length) break;
388 assert(count);
389 dofs = eofs;
390 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
397 void buildOpt2 (ref VoxelData vox) {
398 const float px = vox.cx;
399 const float py = vox.cy;
400 const float pz = vox.cz;
402 uint[1024] slab = void;
404 foreach (int y; 0..vox.ysize) {
405 foreach (int x; 0..vox.xsize) {
406 // try slabs in all 6 directions?
407 uint dofs = vox.getDOfs(x, y);
408 if (!dofs) continue;
410 // long top and bottom quads
411 while (dofs) {
412 foreach (uint cidx; 4..6) {
413 const ubyte cmask = VoxelData.cullmask(cidx);
414 if ((vox.data[dofs].cull&cmask) == 0) continue;
415 const int z = cast(int)vox.data[dofs].z;
416 assert(vox.queryCull(x, y, z) == vox.data[dofs].cull);
417 // by x
418 int xcount = 0;
419 while (x+xcount < cast(int)vox.xsize) {
420 const ubyte vcull = vox.queryCull(x+xcount, y, z);
421 if ((vcull&cmask) == 0) break;
422 ++xcount;
424 // by y
425 int ycount = 0;
426 while (y+ycount < cast(int)vox.ysize) {
427 const ubyte vcull = vox.queryCull(x, y+ycount, z);
428 if ((vcull&cmask) == 0) break;
429 ++ycount;
431 assert(xcount && ycount);
432 // now use the longest one
433 if (xcount >= ycount) {
434 xcount = 0;
435 while (x+xcount < cast(int)vox.xsize) {
436 const uint vrgb = vox.query(x+xcount, y, z);
437 if (((vrgb>>24)&cmask) == 0) break;
438 slab[xcount] = vrgb|0xff000000U;
439 vox.setVoxelCull(x+xcount, y, z, (vrgb>>24)^cmask);
440 ++xcount;
442 assert(xcount);
443 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, xcount, slab[0..xcount]);
444 } else {
445 ycount = 0;
446 while (y+ycount < cast(int)vox.ysize) {
447 const uint vrgb = vox.query(x, y+ycount, z);
448 if (((vrgb>>24)&cmask) == 0) break;
449 slab[ycount] = vrgb|0xff000000U;
450 vox.setVoxelCull(x, y+ycount, z, (vrgb>>24)^cmask);
451 ++ycount;
453 assert(ycount);
454 addSlabFace(cmask, DMV_Y, x-px, y-py, z-pz, ycount, slab[0..ycount]);
457 dofs = vox.data[dofs].nextz;
460 // build long quads for each side
461 foreach (uint cidx; 0..4) {
462 const ubyte cmask = VoxelData.cullmask(cidx);
463 dofs = vox.getDOfs(x, y);
464 while (dofs) {
465 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
466 if (!dofs) break;
467 const int z = cast(int)vox.data[dofs].z;
468 int count = 0;
469 uint eofs = dofs;
470 while (eofs && (vox.data[eofs].cull&cmask)) {
471 if (cast(int)vox.data[eofs].z != z+count) break;
472 vox.data[eofs].cull ^= cmask;
473 slab[count] = vox.data[eofs].rgb();
474 eofs = vox.data[eofs].nextz;
475 ++count;
476 if (count == cast(int)slab.length) break;
478 assert(count);
479 dofs = eofs;
480 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
487 void buildOpt3 (ref VoxelData vox) {
488 const float px = vox.cx;
489 const float py = vox.cy;
490 const float pz = vox.cz;
492 // try slabs in all 6 directions?
493 uint[1024] slab = void;
495 immutable ubyte[2][3] dmove = [
496 [DMV_Y, DMV_Z], // left, right
497 [DMV_X, DMV_Z], // near, far
498 [DMV_X, DMV_Y], // top, bottom
501 static int getDX (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_X); }
502 static int getDY (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Y); }
503 static int getDZ (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Z); }
505 static void incXYZ (in ubyte dmv, ref int sx, ref int sy, ref int sz) nothrow @safe @nogc {
506 pragma(inline, true);
507 sx += getDX(dmv);
508 sy += getDY(dmv);
509 sz += getDZ(dmv);
512 foreach (int y; 0..vox.ysize) {
513 foreach (int x; 0..vox.xsize) {
514 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
515 while (vox.data[dofs].cull) {
516 uint count = 0;
517 ubyte clrdmv = 0;
518 ubyte clrmask = 0;
519 const int z = cast(int)vox.data[dofs].z;
520 // check all faces
521 foreach (uint cidx; 0..6) {
522 const ubyte cmask = VoxelData.cullmask(cidx);
523 if ((vox.data[dofs].cull&cmask) == 0) continue;
524 // try two dirs
525 foreach (uint ndir; 0..2) {
526 const ubyte dmv = dmove[cidx>>1][ndir];
527 int cnt = 1;
528 int sx = x, sy = y, sz = z;
529 incXYZ(dmv, ref sx, ref sy, ref sz);
530 for (;;) {
531 const ubyte vxc = vox.queryCull(sx, sy, sz);
532 if ((vxc&cmask) == 0) break;
533 ++cnt;
534 incXYZ(dmv, ref sx, ref sy, ref sz);
536 if (cnt > count) {
537 count = cnt;
538 clrdmv = dmv;
539 clrmask = cmask;
543 if (clrmask) {
544 assert(count);
545 assert(clrdmv == DMV_X || clrdmv == DMV_Y || clrdmv == DMV_Z);
546 int sx = x, sy = y, sz = z;
547 for (uint f = 0; f < count; ++f) {
548 VoxPix *vp = vox.queryVP(sx, sy, sz);
549 slab[f] = vp.rgb();
550 assert(vp.cull&clrmask);
551 vp.cull ^= clrmask;
552 incXYZ(clrdmv, ref sx, ref sy, ref sz);
554 addSlabFace(clrmask, clrdmv, x-px, y-py, z-pz, count, slab[0..count]);
563 // this tries to create big quads
564 void buildOpt4 (ref VoxelData vox) {
565 const float px = vox.cx;
566 const float py = vox.cy;
567 const float pz = vox.cz;
569 // try slabs in all 6 directions?
570 uint[] slab = null;
571 scope(exit) { delete slab; slab = null; }
573 Vox2DBitmap bmp2d;
574 for (uint cidx = 0; cidx < 6; ++cidx) {
575 const ubyte cmask = VoxelData.cullmask(cidx);
577 uint vwdt, vhgt, vlen;
578 if (cmask&Cull_ZAxisMask) {
579 vwdt = vox.xsize;
580 vhgt = vox.ysize;
581 vlen = vox.zsize;
582 } else if (cmask&Cull_XAxisMask) {
583 vwdt = vox.ysize;
584 vhgt = vox.zsize;
585 vlen = vox.xsize;
586 } else {
587 vwdt = vox.xsize;
588 vhgt = vox.zsize;
589 vlen = vox.ysize;
591 bmp2d.setSize(vwdt, vhgt);
593 for (uint vcrd = 0; vcrd < vlen; ++vcrd) {
594 bmp2d.clearBmp();
595 for (uint vdy = 0; vdy < vhgt; ++vdy) {
596 for (uint vdx = 0; vdx < vwdt; ++vdx) {
597 uint vx, vy, vz;
598 if (cmask&Cull_ZAxisMask) { vx = vdx; vy = vdy; vz = vcrd; }
599 else if (cmask&Cull_XAxisMask) { vx = vcrd; vy = vdx; vz = vdy; }
600 else { vx = vdx; vy = vcrd; vz = vdy; }
601 //conwriteln("*vcrd=", vcrd, "; vdx=", vdx, "; vdy=", vdy);
602 auto vp = vox.queryVP(vx, vy, vz);
603 if (!vp || ((*vp).cull&cmask) == 0) continue;
604 bmp2d.setPixel(vdx, vdy, (*vp).rgb());
607 //conwriteln(":: cidx=", cidx, "; vcrd=", vcrd, "; dotCount=", bmp2d.dotCount);
608 if (bmp2d.dotCount == 0) continue;
609 // ok, we have some dots, go create quads
610 int x0, y0, x1, y1;
611 while (bmp2d.doOne(&x0, &y0, &x1, &y1)) {
612 const uint cwdt = (x1-x0)+1;
613 const uint chgt = (y1-y0)+1;
614 if (slab.length < cwdt*chgt) slab.length = ((cwdt*chgt)|0xff)+1;
615 // get colors
616 uint *dp = slab.ptr;
617 for (int dy = y0; dy <= y1; ++dy) {
618 for (int dx = x0; dx <= x1; ++dx) {
619 *dp++ = bmp2d.resetPixel(dx, dy);
622 float fx, fy, fz;
623 if (cmask&Cull_ZAxisMask) { fx = x0; fy = y0; fz = vcrd; }
624 else if (cmask&Cull_XAxisMask) { fx = vcrd; fy = x0; fz = y0; }
625 else { fx = x0; fy = vcrd; fz = y0; }
626 addQuad(cmask, fx-px, fy-py, fz-pz, cwdt, chgt, slab[0..cwdt*chgt]);
633 this (ref VoxelData vox) {
634 assert(vox.xsize && vox.ysize && vox.zsize);
635 // now build cubes
636 conwriteln("building slabs...");
637 auto tm = iv.timer.Timer(true);
638 switch (vox_optimisation) {
639 case 0: buildOpt0(vox); break;
640 case 1: buildOpt1(vox); break;
641 case 2: buildOpt2(vox); break;
642 case 3: buildOpt3(vox); break;
643 case 4: default: buildOpt4(vox); break;
645 conwriteln("basic conversion: ", quads.length, " quads (", quads.length*2, " tris).");
646 tm.stop();
647 conwriteln("converted in ", tm.toString(), "; optlevel is ", vox_optimisation, "/", kvx_max_optim_level);
648 cx = vox.cx;
649 cy = vox.cy;
650 cz = vox.cz;
653 void clear () {
654 delete quads; quads = null;
655 catlas.clear();
656 cx = cy = cz = 0.0f;