normals calculation fix
[voxconv.git] / vox_mesh.d
blob910525b983c2693294e0088c3d7a0bd0a196e40e
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;
12 import iv.pxclock;
14 import vox_texatlas;
15 import vox_2dbmp;
16 import vox_3dbmp;
17 import vox_data;
20 // ////////////////////////////////////////////////////////////////////////// //
21 public enum kvx_max_optim_level = 4;
22 public __gshared int vox_optimisation = kvx_max_optim_level-1;
23 public __gshared bool vox_allow_normals = true;
24 // this helps a little, so why not
25 public __gshared bool vox_optimiser_use_3dbmp = true;
26 public __gshared bool vox_optimiser_use_cvox = true;
29 // ////////////////////////////////////////////////////////////////////////// //
30 static align(1) struct VoxQuadVertex {
31 align(1):
32 float x, y, z;
33 float dx, dy, dz;
34 ubyte qtype; // Xn_Yn_Zn
37 // quad is always one texel strip
38 static align(1) struct VoxQuad {
39 VoxQuadVertex[4] vx;
40 uint cidx; // in colorpack's `citems`
41 VoxWH16 wh; // width and height
42 VoxQuadVertex normal;
43 int type = -1/*Invalid*/;
44 ubyte cull; // for which face this quad was created?
49 this thing is used to create a quad mesh from a voxel data.
50 it contains several conversion methods, from the most simple one
51 "one quad for each visible voxel face", to the most complex one,
52 that goes through each voxel plane, and joins individual voxel
53 faces into a bigger quads (i.e. quads covering several faces at once).
55 this doesn't directly calculates texture coords, tho: it is using an
56 atlas, and records rect coords for each quad. real texture coords are
57 calculated in GL mesh builder instead.
59 struct VoxelMesh {
60 // quad type
61 enum {
62 Invalid = -1,
63 Point,
64 XLong,
65 YLong,
66 ZLong,
67 Quad,
70 enum {
71 Cull_Right = 0x01, // x axis
72 Cull_Left = 0x02, // x axis
73 Cull_Near = 0x04, // y axis
74 Cull_Far = 0x08, // y axis
75 Cull_Top = 0x10, // z axis
76 Cull_Bottom = 0x20, // z axis
78 Cull_XAxisMask = (Cull_Right|Cull_Left),
79 Cull_YAxisMask = (Cull_Near|Cull_Far),
80 Cull_ZAxisMask = (Cull_Top|Cull_Bottom),
83 enum {
84 DMV_X = 0b100,
85 DMV_Y = 0b010,
86 DMV_Z = 0b001,
89 // bitmasks, `DMV_n` can be used to check for `0` or `1`
90 enum {
91 X0_Y0_Z0,
92 X0_Y0_Z1,
93 X0_Y1_Z0,
94 X0_Y1_Z1,
95 X1_Y0_Z0,
96 X1_Y0_Z1,
97 X1_Y1_Z0,
98 X1_Y1_Z1,
101 VoxQuad[] quads;
102 // voxel center point
103 float cx, cy, cz;
104 // color atlas
105 VoxColorPack catlas;
107 static immutable ubyte[4][6] quadFaces = [
108 // right (&0x01) (right)
110 X1_Y1_Z0,
111 X1_Y0_Z0,
112 X1_Y0_Z1,
113 X1_Y1_Z1,
115 // left (&0x02) (left)
117 X0_Y0_Z0,
118 X0_Y1_Z0,
119 X0_Y1_Z1,
120 X0_Y0_Z1,
122 // top (&0x04) (near)
124 X0_Y0_Z0,
125 X0_Y0_Z1,
126 X1_Y0_Z1,
127 X1_Y0_Z0,
129 // bottom (&0x08) (far)
131 X1_Y1_Z0,
132 X1_Y1_Z1,
133 X0_Y1_Z1,
134 X0_Y1_Z0,
136 // back (&0x10) (top)
138 X0_Y1_Z1,
139 X1_Y1_Z1,
140 X1_Y0_Z1,
141 X0_Y0_Z1,
143 // front (&0x20) (bottom)
145 X0_Y0_Z0,
146 X1_Y0_Z0,
147 X1_Y1_Z0,
148 X0_Y1_Z0,
152 static immutable float[3][6] quadNormals = [
153 // right (&0x01) (right)
154 [ 1.0f, 0.0f, 0.0f],
155 // left (&0x02) (left)
156 [-1.0f, 0.0f, 0.0f],
157 // top (&0x04) (near)
158 [ 0.0f,-1.0f, 0.0f],
159 // bottom (&0x08) (far)
160 [ 0.0f, 1.0f, 0.0f],
161 // back (&0x10) (top)
162 [ 0.0f, 0.0f, 1.0f],
163 // front (&0x20) (bottom)
164 [ 0.0f, 0.0f,-1.0f],
167 private void quadCalcNormal (ref VoxQuad vq) {
168 if (vox_allow_normals) {
169 uint qidx;
170 switch (vq.cull) {
171 case 0x01: qidx = 0; break;
172 case 0x02: qidx = 1; break;
173 case 0x04: qidx = 2; break;
174 case 0x08: qidx = 3; break;
175 case 0x10: qidx = 4; break;
176 case 0x20: qidx = 5; break;
177 default: assert(0);
179 vq.normal.x = vq.normal.dx = quadNormals[qidx][0];
180 vq.normal.y = vq.normal.dy = quadNormals[qidx][1];
181 vq.normal.z = vq.normal.dz = quadNormals[qidx][2];
182 } else {
183 vq.normal.x = vq.normal.dx = 0.0f;
184 vq.normal.y = vq.normal.dy = 0.0f;
185 vq.normal.z = vq.normal.dz = 1.0f;
189 private void setColors (ref VoxQuad vq, const(uint)[] clrs, uint wdt, uint hgt) {
190 if (catlas.findRect(clrs, wdt, hgt, &vq.cidx, &vq.wh)) {
192 conwriteln(" ...reused rect: (", catlas.getTexX(vq.cidx, vq.rc), ",",
193 catlas.getTexY(vq.cidx, vq.rc), ")-(", wdt, "x", hgt, ")");
195 return;
197 vq.cidx = catlas.addNewRect(clrs, wdt, hgt);
198 vq.wh = VoxWH16(wdt, hgt);
201 private static VoxQuadVertex genVertex (ubyte type, const float x, const float y, const float z,
202 const float xlen, const float ylen, const float zlen)
204 VoxQuadVertex vx = void;
205 vx.qtype = type;
206 vx.dx = vx.dy = vx.dz = 0.0f;
207 vx.x = x;
208 vx.y = y;
209 vx.z = z;
210 if (type&DMV_X) { vx.x += xlen; vx.dx = 1.0f; }
211 if (type&DMV_Y) { vx.y += ylen; vx.dy = 1.0f; }
212 if (type&DMV_Z) { vx.z += zlen; vx.dz = 1.0f; }
213 return vx;
216 // dmv: bit 2 means XLong, bit 1 means YLong, bit 0 means ZLong
217 void addSlabFace (ubyte cull, ubyte dmv,
218 float x, float y, float z,
219 int len, const(uint)[] colors)
221 if (len < 1) return;
222 assert(dmv == DMV_X || dmv == DMV_Y || dmv == DMV_Z);
223 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
224 assert(colors.length >= len);
225 colors = colors[0..len];
227 bool allsame = true;
228 foreach (auto cidx; 1..colors.length) {
229 if (colors[cidx] != colors[0]) {
230 allsame = false;
231 break;
234 if (allsame) colors = colors[0..1];
236 const int qtype =
237 colors.length == 1 ? Point :
238 (dmv&DMV_X) ? XLong :
239 (dmv&DMV_Y) ? YLong :
240 ZLong;
241 const float dx = (dmv&DMV_X ? cast(float)len : 1.0f);
242 const float dy = (dmv&DMV_Y ? cast(float)len : 1.0f);
243 const float dz = (dmv&DMV_Z ? cast(float)len : 1.0f);
244 uint qidx;
245 switch (cull) {
246 case 0x01: qidx = 0; break;
247 case 0x02: qidx = 1; break;
248 case 0x04: qidx = 2; break;
249 case 0x08: qidx = 3; break;
250 case 0x10: qidx = 4; break;
251 case 0x20: qidx = 5; break;
252 default: assert(0);
254 VoxQuad vq;
255 foreach (uint vidx; 0..4) {
256 vq.vx[vidx] = genVertex(quadFaces[qidx][vidx], x, y, z, dx, dy, dz);
258 setColors(ref vq, colors[], cast(uint)colors.length, 1);
259 vq.type = qtype;
260 vq.cull = cull;
261 quadCalcNormal(vq);
262 quads ~= vq;
265 void addCube (ubyte cull, float x, float y, float z, uint rgb) {
266 immutable uint[1] carr = [rgb];
267 // generate quads
268 foreach (uint qidx; 0..6) {
269 const ubyte cmask = VoxelData.cullmask(qidx);
270 if (cull&cmask) {
271 addSlabFace(cmask, DMV_X/*doesn't matter*/, x, y, z, 1, carr[]);
276 void addQuad (ubyte cull,
277 float x, float y, float z,
278 int wdt, int hgt, // quad size
279 const(uint)[] colors)
281 assert(wdt > 0 && hgt > 0);
282 assert(cull == 0x01 || cull == 0x02 || cull == 0x04 || cull == 0x08 || cull == 0x10 || cull == 0x20);
283 assert(colors.length >= wdt*hgt);
284 colors = colors[0..wdt*hgt];
286 bool allsame = true;
287 foreach (auto cidx; 1..colors.length) {
288 if (colors[cidx] != colors[0]) {
289 allsame = false;
290 break;
293 if (allsame) colors = colors[0..1];
295 const int qtype = Quad;
296 uint qidx;
297 switch (cull) {
298 case 0x01: qidx = 0; break;
299 case 0x02: qidx = 1; break;
300 case 0x04: qidx = 2; break;
301 case 0x08: qidx = 3; break;
302 case 0x10: qidx = 4; break;
303 case 0x20: qidx = 5; break;
304 default: assert(0);
307 VoxQuad vq;
308 for (uint vidx = 0; vidx < 4; ++vidx) {
309 const ubyte vtype = quadFaces[qidx][vidx];
310 VoxQuadVertex vx = void;
311 vx.qtype = vtype;
312 vx.dx = vx.dy = vx.dz = 0.0f;
313 vx.x = x;
314 vx.y = y;
315 vx.z = z;
316 if (cull&Cull_ZAxisMask) {
317 if (vtype&DMV_X) vx.dx = cast(float)wdt;
318 if (vtype&DMV_Y) vx.dy = cast(float)hgt;
319 if (vtype&DMV_Z) vx.dz = 1.0f;
320 } else if (cull&Cull_XAxisMask) {
321 if (vtype&DMV_X) vx.dx = 1.0f;
322 if (vtype&DMV_Y) vx.dy = cast(float)wdt;
323 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
324 } else if (cull&Cull_YAxisMask) {
325 if (vtype&DMV_X) vx.dx = cast(float)wdt;
326 if (vtype&DMV_Y) vx.dy = 1.0f;
327 if (vtype&DMV_Z) vx.dz = cast(float)hgt;
328 } else {
329 assert(0);
331 vx.x += vx.dx;
332 vx.y += vx.dy;
333 vx.z += vx.dz;
334 vq.vx[vidx] = vx;
337 if (colors.length == 1) {
338 setColors(ref vq, colors[], 1, 1);
339 } else {
340 setColors(ref vq, colors[], wdt, hgt);
343 vq.type = qtype;
344 vq.cull = cull;
345 quadCalcNormal(vq);
346 quads ~= vq;
350 void buildOpt0 (ref VoxelData vox) {
351 const float px = vox.cx;
352 const float py = vox.cy;
353 const float pz = vox.cz;
354 foreach (int y; 0..vox.ysize) {
355 foreach (int x; 0..vox.xsize) {
356 uint dofs = vox.getDOfs(x, y);
357 while (dofs) {
358 addCube(vox.data[dofs].cull, x-px, y-py, vox.data[dofs].z-pz, vox.data[dofs].rgb());
359 dofs = vox.data[dofs].nextz;
365 void buildOpt1 (ref VoxelData vox) {
366 const float px = vox.cx;
367 const float py = vox.cy;
368 const float pz = vox.cz;
370 uint[1024] slab = void;
372 foreach (int y; 0..vox.ysize) {
373 foreach (int x; 0..vox.xsize) {
374 // try slabs in all 6 directions?
375 uint dofs = vox.getDOfs(x, y);
376 if (!dofs) continue;
378 // long top and bottom quads
379 while (dofs) {
380 foreach (uint cidx; 4..6) {
381 const ubyte cmask = VoxelData.cullmask(cidx);
382 if ((vox.data[dofs].cull&cmask) == 0) continue;
383 const int z = cast(int)vox.data[dofs].z;
384 slab[0] = vox.data[dofs].rgb();
385 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, 1, slab[0..1]);
387 dofs = vox.data[dofs].nextz;
390 // build long quads for each side
391 foreach (uint cidx; 0..4) {
392 const ubyte cmask = VoxelData.cullmask(cidx);
393 dofs = vox.getDOfs(x, y);
394 while (dofs) {
395 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
396 if (!dofs) break;
397 const int z = cast(int)vox.data[dofs].z;
398 int count = 0;
399 uint eofs = dofs;
400 while (eofs && (vox.data[eofs].cull&cmask)) {
401 if (cast(int)vox.data[eofs].z != z+count) break;
402 vox.data[eofs].cull ^= cmask;
403 slab[count] = vox.data[eofs].rgb();
404 eofs = vox.data[eofs].nextz;
405 ++count;
406 if (count == cast(int)slab.length) break;
408 assert(count);
409 dofs = eofs;
410 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
417 void buildOpt2 (ref VoxelData vox) {
418 const float px = vox.cx;
419 const float py = vox.cy;
420 const float pz = vox.cz;
422 uint[1024] slab = void;
424 foreach (int y; 0..vox.ysize) {
425 foreach (int x; 0..vox.xsize) {
426 // try slabs in all 6 directions?
427 uint dofs = vox.getDOfs(x, y);
428 if (!dofs) continue;
430 // long top and bottom quads
431 while (dofs) {
432 foreach (uint cidx; 4..6) {
433 const ubyte cmask = VoxelData.cullmask(cidx);
434 if ((vox.data[dofs].cull&cmask) == 0) continue;
435 const int z = cast(int)vox.data[dofs].z;
436 assert(vox.queryCull(x, y, z) == vox.data[dofs].cull);
437 // by x
438 int xcount = 0;
439 while (x+xcount < cast(int)vox.xsize) {
440 const ubyte vcull = vox.queryCull(x+xcount, y, z);
441 if ((vcull&cmask) == 0) break;
442 ++xcount;
444 // by y
445 int ycount = 0;
446 while (y+ycount < cast(int)vox.ysize) {
447 const ubyte vcull = vox.queryCull(x, y+ycount, z);
448 if ((vcull&cmask) == 0) break;
449 ++ycount;
451 assert(xcount && ycount);
452 // now use the longest one
453 if (xcount >= ycount) {
454 xcount = 0;
455 while (x+xcount < cast(int)vox.xsize) {
456 const uint vrgb = vox.query(x+xcount, y, z);
457 if (((vrgb>>24)&cmask) == 0) break;
458 slab[xcount] = vrgb|0xff000000U;
459 vox.setVoxelCull(x+xcount, y, z, (vrgb>>24)^cmask);
460 ++xcount;
462 assert(xcount);
463 addSlabFace(cmask, DMV_X, x-px, y-py, z-pz, xcount, slab[0..xcount]);
464 } else {
465 ycount = 0;
466 while (y+ycount < cast(int)vox.ysize) {
467 const uint vrgb = vox.query(x, y+ycount, z);
468 if (((vrgb>>24)&cmask) == 0) break;
469 slab[ycount] = vrgb|0xff000000U;
470 vox.setVoxelCull(x, y+ycount, z, (vrgb>>24)^cmask);
471 ++ycount;
473 assert(ycount);
474 addSlabFace(cmask, DMV_Y, x-px, y-py, z-pz, ycount, slab[0..ycount]);
477 dofs = vox.data[dofs].nextz;
480 // build long quads for each side
481 foreach (uint cidx; 0..4) {
482 const ubyte cmask = VoxelData.cullmask(cidx);
483 dofs = vox.getDOfs(x, y);
484 while (dofs) {
485 while (dofs && (vox.data[dofs].cull&cmask) == 0) dofs = vox.data[dofs].nextz;
486 if (!dofs) break;
487 const int z = cast(int)vox.data[dofs].z;
488 int count = 0;
489 uint eofs = dofs;
490 while (eofs && (vox.data[eofs].cull&cmask)) {
491 if (cast(int)vox.data[eofs].z != z+count) break;
492 vox.data[eofs].cull ^= cmask;
493 slab[count] = vox.data[eofs].rgb();
494 eofs = vox.data[eofs].nextz;
495 ++count;
496 if (count == cast(int)slab.length) break;
498 assert(count);
499 dofs = eofs;
500 addSlabFace(cmask, DMV_Z, x-px, y-py, z-pz, count, slab[0..count]);
507 void buildOpt3 (ref VoxelData vox) {
508 const float px = vox.cx;
509 const float py = vox.cy;
510 const float pz = vox.cz;
512 // try slabs in all 6 directions?
513 uint[1024] slab = void;
515 immutable ubyte[2][3] dmove = [
516 [DMV_Y, DMV_Z], // left, right
517 [DMV_X, DMV_Z], // near, far
518 [DMV_X, DMV_Y], // top, bottom
521 static int getDX (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_X); }
522 static int getDY (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Y); }
523 static int getDZ (in ubyte dmv) pure nothrow @safe @nogc { pragma(inline, true); return !!(dmv&DMV_Z); }
525 static void incXYZ (in ubyte dmv, ref int sx, ref int sy, ref int sz) nothrow @safe @nogc {
526 pragma(inline, true);
527 sx += getDX(dmv);
528 sy += getDY(dmv);
529 sz += getDZ(dmv);
532 foreach (int y; 0..vox.ysize) {
533 foreach (int x; 0..vox.xsize) {
534 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
535 while (vox.data[dofs].cull) {
536 uint count = 0;
537 ubyte clrdmv = 0;
538 ubyte clrmask = 0;
539 const int z = cast(int)vox.data[dofs].z;
540 // check all faces
541 foreach (uint cidx; 0..6) {
542 const ubyte cmask = VoxelData.cullmask(cidx);
543 if ((vox.data[dofs].cull&cmask) == 0) continue;
544 // try two dirs
545 foreach (uint ndir; 0..2) {
546 const ubyte dmv = dmove[cidx>>1][ndir];
547 int cnt = 1;
548 int sx = x, sy = y, sz = z;
549 incXYZ(dmv, ref sx, ref sy, ref sz);
550 for (;;) {
551 const ubyte vxc = vox.queryCull(sx, sy, sz);
552 if ((vxc&cmask) == 0) break;
553 ++cnt;
554 incXYZ(dmv, ref sx, ref sy, ref sz);
556 if (cnt > count) {
557 count = cnt;
558 clrdmv = dmv;
559 clrmask = cmask;
563 if (clrmask) {
564 assert(count);
565 assert(clrdmv == DMV_X || clrdmv == DMV_Y || clrdmv == DMV_Z);
566 int sx = x, sy = y, sz = z;
567 for (uint f = 0; f < count; ++f) {
568 VoxPix *vp = vox.queryVP(sx, sy, sz);
569 slab[f] = vp.rgb();
570 assert(vp.cull&clrmask);
571 vp.cull ^= clrmask;
572 incXYZ(clrdmv, ref sx, ref sy, ref sz);
574 addSlabFace(clrmask, clrdmv, x-px, y-py, z-pz, count, slab[0..count]);
583 // 128 chars should be enough for everyone
584 static char[] milliToBuffer (uint msecs, char[] dest) nothrow @trusted @nogc {
585 import core.stdc.stdio : snprintf;
586 char[128] buf = void;
587 //immutable uint micro = 0; msecs /= 1000;
588 immutable uint milli = cast(uint)(msecs%1000);
589 msecs /= 1000;
590 immutable uint seconds = cast(uint)(msecs%60);
591 msecs /= 60;
592 immutable uint minutes = cast(uint)(msecs%60);
593 msecs /= 60;
594 immutable uint hours = cast(uint)msecs;
595 uint len;
596 if (hours) len = cast(uint)snprintf(buf.ptr, buf.length, "%u:%02u:%02u.%03u", hours, minutes, seconds, milli);
597 else if (minutes) len = cast(uint)snprintf(buf.ptr, buf.length, "%u:%02u.%03u", minutes, seconds, milli);
598 else if (seconds) len = cast(uint)snprintf(buf.ptr, buf.length, "%u.%03u", seconds, milli);
599 else if (milli >= 10) len = cast(uint)snprintf(buf.ptr, buf.length, "%ums", milli);
600 //else if (micro != 0) len = cast(uint)snprintf(buf.ptr, buf.length, "%ums:%umcs", milli, micro);
601 else len = cast(uint)snprintf(buf.ptr, buf.length, "%ums", milli);
602 if (len > dest.length) len = cast(uint)dest.length;
603 dest.ptr[0..len] = buf.ptr[0..len];
604 return dest.ptr[0..len];
607 // this tries to create big quads
608 void buildOpt4 (ref VoxelData vox) {
609 const float px = vox.cx;
610 const float py = vox.cy;
611 const float pz = vox.cz;
613 // try slabs in all 6 directions?
614 uint[] slab = null;
615 scope(exit) { delete slab; slab = null; }
617 // for faster scans
618 Vox3DBitmap bmp3d;
619 scope(exit) bmp3d.clear();
620 if (vox_optimiser_use_3dbmp) vox.create3DBitmap(ref bmp3d);
622 VoxelDataSmall vxopt;
623 scope(exit) vxopt.clear();
624 if (vox_optimiser_use_cvox) vxopt.createFrom(ref vox);
626 const ulong tstart = clockMilli();
627 ulong tlastreport = 0;
628 auto tm = iv.timer.Timer(true);
630 char[129] tbuf = void;
631 char[129] tbuf2 = void;
632 bool wasPrint = false;
634 Vox2DBitmap bmp2d;
635 for (uint cidx = 0; cidx < 6; ++cidx) {
636 const ubyte cmask = VoxelData.cullmask(cidx);
638 uint vwdt, vhgt, vlen;
639 if (cmask&Cull_ZAxisMask) {
640 vwdt = vox.xsize;
641 vhgt = vox.ysize;
642 vlen = vox.zsize;
643 } else if (cmask&Cull_XAxisMask) {
644 vwdt = vox.ysize;
645 vhgt = vox.zsize;
646 vlen = vox.xsize;
647 } else {
648 vwdt = vox.xsize;
649 vhgt = vox.zsize;
650 vlen = vox.ysize;
652 bmp2d.setSize(vwdt, vhgt);
654 for (uint vcrd = 0; vcrd < vlen; ++vcrd) {
655 //bmp2d.clearBmp(); // no need to, it is guaranteed
656 assert(bmp2d.dotCount == 0);
657 for (uint vdy = 0; vdy < vhgt; ++vdy) {
658 for (uint vdx = 0; vdx < vwdt; ++vdx) {
659 uint vx, vy, vz;
660 if (cmask&Cull_ZAxisMask) { vx = vdx; vy = vdy; vz = vcrd; }
661 else if (cmask&Cull_XAxisMask) { vx = vcrd; vy = vdx; vz = vdy; }
662 else { vx = vdx; vy = vcrd; vz = vdy; }
663 //conwriteln("*vcrd=", vcrd, "; vdx=", vdx, "; vdy=", vdy);
664 if (vox_optimiser_use_3dbmp && !bmp3d.getPixel(vx, vy, vz)) continue;
665 if (vox_optimiser_use_cvox) {
666 const uint vd = vxopt.queryVox(vx, vy, vz);
667 if (((vd>>24)&cmask) == 0) continue;
668 bmp2d.setPixel(vdx, vdy, vd|0xff000000U);
669 } else {
670 auto vp = vox.queryVP(vx, vy, vz);
671 if (!vp || ((*vp).cull&cmask) == 0) continue;
672 bmp2d.setPixel(vdx, vdy, (*vp).rgb());
673 if (((*vp).cull ^= cmask) == 0) {
674 vox.removeVoxel(vx, vy, vz);
675 if (vox_optimiser_use_3dbmp) bmp3d.resetPixel(vx, vy, vz);
680 //conwriteln(":: cidx=", cidx, "; vcrd=", vcrd, "; dotCount=", bmp2d.dotCount);
681 if (bmp2d.dotCount == 0) continue;
682 // ok, we have some dots, go create quads
683 int x0, y0, x1, y1;
684 while (bmp2d.doOne(&x0, &y0, &x1, &y1)) {
685 const uint cwdt = (x1-x0)+1;
686 const uint chgt = (y1-y0)+1;
687 if (slab.length < cwdt*chgt) slab.length = ((cwdt*chgt)|0xff)+1;
688 // get colors
689 uint *dp = slab.ptr;
690 for (int dy = y0; dy <= y1; ++dy) {
691 for (int dx = x0; dx <= x1; ++dx) {
692 *dp++ = bmp2d.resetPixel(dx, dy);
695 float fx, fy, fz;
696 if (cmask&Cull_ZAxisMask) { fx = x0; fy = y0; fz = vcrd; }
697 else if (cmask&Cull_XAxisMask) { fx = vcrd; fy = x0; fz = y0; }
698 else { fx = x0; fy = vcrd; fz = y0; }
699 addQuad(cmask, fx-px, fy-py, fz-pz, cwdt, chgt, slab[0..cwdt*chgt]);
702 const ulong ctt = clockMilli();
703 if (ctt-tlastreport >= 1000) {
704 tlastreport = ctt;
705 const uint curr = vlen*cidx+vcrd+1U;
706 const uint total = vlen*6U;
707 const uint stt = cast(uint)(ctt-tstart);
708 const uint ett = cast(uint)(cast(ulong)stt*total/curr);
709 tbuf[] = 0;
710 tbuf2[] = 0;
711 char[] bf = milliToBuffer(stt, tbuf[0..$-1]);
712 char[] bf2 = milliToBuffer(ett, tbuf2[0..$-1]);
713 import core.stdc.stdio : fprintf, stdout, fflush;
714 fprintf(stdout, " %3u%% %s (ETA: %s)\x1b[K\r", 100U*curr/total, bf.ptr, bf2.ptr);
715 fflush(stdout);
716 wasPrint = true;
721 tm.stop();
722 if (wasPrint) {
723 import core.stdc.stdio : fprintf, stdout, fflush;
724 fprintf(stdout, "\r\x1b[K"); fflush(stdout);
726 conwriteln("*** ultra conversion done in ", tm.toString());
730 void createFrom (ref VoxelData vox) {
731 assert(vox.xsize && vox.ysize && vox.zsize);
732 // now build cubes
733 conwriteln("building slabs...");
734 auto tm = iv.timer.Timer(true);
735 switch (vox_optimisation) {
736 case 0: buildOpt0(vox); break;
737 case 1: buildOpt1(vox); break;
738 case 2: buildOpt2(vox); break;
739 case 3: buildOpt3(vox); break;
740 case 4: default: buildOpt4(vox); break;
742 tm.stop();
743 conwriteln("basic conversion: ", quads.length, " quads (", quads.length*2, " tris).");
744 conwriteln("converted in ", tm.toString(), "; optlevel is ", vox_optimisation, "/", kvx_max_optim_level);
745 cx = vox.cx;
746 cy = vox.cy;
747 cz = vox.cz;
750 void clear () {
751 delete quads; quads = null;
752 catlas.clear();
753 cx = cy = cz = 0.0f;
756 bool isEmpty () const pure { pragma(inline, true); return (quads.length == 0); }