4 import iv
.glbinds
.utils
;
11 static import iv
.timer
;
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
{
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
),
69 // bitmasks, `DMV_n` can be used to check for `0` or `1`
81 static align(1) struct Vertex
{
85 ubyte qtype
; // Xn_Yn_Zn
88 // quad is always one texel strip
89 static align(1) struct VoxQuad
{
91 uint cidx
; // in colorpack's `citems`
92 VoxWH16 wh
; // width and height
95 ubyte cull
; // for which face this quad was created?
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);
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
;
114 normal
.x
= normal
.dx
= 0.0f;
115 normal
.y
= normal
.dy
= 0.0f;
116 normal
.z
= normal
.dz
= 0.0f;
122 // voxel center point
127 static immutable ubyte[4][6] quadFaces
= [
128 // right (&0x01) (right)
135 // left (&0x02) (left)
142 // top (&0x04) (near)
149 // bottom (&0x08) (far)
156 // back (&0x10) (top)
163 // front (&0x20) (bottom)
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, ")");
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
)
189 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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; }
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
)
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
];
211 foreach (auto cidx
; 1..colors
.length
) {
212 if (colors
[cidx
] != colors
[0]) {
217 if (allsame
) colors
= colors
[0..1];
220 colors
.length
== 1 ? Point
:
221 (dmv
&DMV_X
) ? XLong
:
222 (dmv
&DMV_Y
) ? YLong
:
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);
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;
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);
247 void addCube (ubyte cull
, float x
, float y
, float z
, uint rgb
) {
248 immutable uint[1] carr
= [rgb
];
250 foreach (uint qidx
; 0..6) {
251 const ubyte cmask
= VoxelData
.cullmask(qidx
);
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
];
269 foreach (auto cidx
; 1..colors
.length
) {
270 if (colors
[cidx
] != colors
[0]) {
275 if (allsame
) colors
= colors
[0..1];
277 const int qtype
= Quad
;
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;
290 for (uint vidx
= 0; vidx
< 4; ++vidx
) {
291 const ubyte vtype
= quadFaces
[qidx
][vidx
];
294 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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
;
319 if (colors
.length
== 1) {
320 setColors(ref vq
, colors
[], 1, 1);
322 setColors(ref vq
, colors
[], wdt
, hgt
);
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
);
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
);
359 // long top and bottom quads
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
);
376 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
378 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
387 if (count
== cast(int)slab
.length
) break;
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
);
411 // long top and bottom quads
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
);
420 while (x
+xcount
< cast(int)vox
.xsize
) {
421 const ubyte vcull
= vox
.queryCull(x
+xcount
, y
, z
);
422 if ((vcull
&cmask
) == 0) break;
427 while (y
+ycount
< cast(int)vox
.ysize
) {
428 const ubyte vcull
= vox
.queryCull(x
, y
+ycount
, z
);
429 if ((vcull
&cmask
) == 0) break;
432 assert(xcount
&& ycount
);
433 // now use the longest one
434 if (xcount
>= ycount
) {
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
);
444 addSlabFace(cmask
, DMV_X
, x
-px
, y
-py
, z
-pz
, xcount
, slab
[0..xcount
]);
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
);
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
);
466 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
468 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
477 if (count
== cast(int)slab
.length
) break;
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);
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
) {
520 const int z
= cast(int)vox
.data
[dofs
].z
;
522 foreach (uint cidx
; 0..6) {
523 const ubyte cmask
= VoxelData
.cullmask(cidx
);
524 if ((vox
.data
[dofs
].cull
&cmask
) == 0) continue;
526 foreach (uint ndir
; 0..2) {
527 const ubyte dmv
= dmove
[cidx
>>1][ndir
];
529 int sx
= x
, sy
= y
, sz
= z
;
530 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
532 const ubyte vxc
= vox
.queryCull(sx
, sy
, sz
);
533 if ((vxc
&cmask
) == 0) break;
535 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
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
);
551 assert(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?
572 scope(exit
) { delete slab
; slab
= null; }
576 scope(exit
) bmp3d
.clear();
577 if (vox_optimiser_use_3dbmp
) vox
.create3DBitmap(ref bmp3d
);
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
) {
588 } else if (cmask
&Cull_XAxisMask
) {
597 bmp2d
.setSize(vwdt
, vhgt
);
599 for (uint vcrd
= 0; vcrd
< vlen
; ++vcrd
) {
601 for (uint vdy
= 0; vdy
< vhgt
; ++vdy
) {
602 for (uint vdx
= 0; vdx
< vwdt
; ++vdx
) {
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
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;
628 for (int dy
= y0
; dy
<= y1
; ++dy
) {
629 for (int dx
= x0
; dx
<= x1
; ++dx
) {
630 *dp
++ = bmp2d
.resetPixel(dx
, dy
);
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
);
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).");
658 conwriteln("converted in ", tm
.toString(), "; optlevel is ", vox_optimisation
, "/", kvx_max_optim_level
);
665 delete quads
; quads
= null;