4 import iv
.glbinds
.utils
;
11 static import iv
.timer
;
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
{
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
),
66 // bitmasks, `DMV_n` can be used to check for `0` or `1`
78 static align(1) struct Vertex
{
82 ubyte qtype
; // Xn_Yn_Zn
85 // quad is always one texel strip
86 static align(1) struct VoxQuad
{
88 uint cidx
; // in colorpack's `citems`
89 VoxWH16 wh
; // width and height
92 ubyte cull
; // for which face this quad was created?
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);
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
;
111 normal
.x
= normal
.dx
= 0.0f;
112 normal
.y
= normal
.dy
= 0.0f;
113 normal
.z
= normal
.dz
= 0.0f;
119 // voxel center point
124 static immutable ubyte[4][6] quadFaces
= [
125 // right (&0x01) (right)
132 // left (&0x02) (left)
139 // top (&0x04) (near)
146 // bottom (&0x08) (far)
153 // back (&0x10) (top)
160 // front (&0x20) (bottom)
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, ")");
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
)
186 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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; }
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
)
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
];
208 foreach (auto cidx
; 1..colors
.length
) {
209 if (colors
[cidx
] != colors
[0]) {
214 if (allsame
) colors
= colors
[0..1];
217 colors
.length
== 1 ? Point
:
218 (dmv
&DMV_X
) ? XLong
:
219 (dmv
&DMV_Y
) ? YLong
:
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);
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;
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);
244 void addCube (ubyte cull
, float x
, float y
, float z
, uint rgb
) {
245 immutable uint[1] carr
= [rgb
];
247 foreach (uint qidx
; 0..6) {
248 const ubyte cmask
= VoxelData
.cullmask(qidx
);
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
];
266 foreach (auto cidx
; 1..colors
.length
) {
267 if (colors
[cidx
] != colors
[0]) {
272 if (allsame
) colors
= colors
[0..1];
274 const int qtype
= Quad
;
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;
287 for (uint vidx
= 0; vidx
< 4; ++vidx
) {
288 const ubyte vtype
= quadFaces
[qidx
][vidx
];
291 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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
;
316 if (colors
.length
== 1) {
317 setColors(ref vq
, colors
[], 1, 1);
319 setColors(ref vq
, colors
[], wdt
, hgt
);
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
);
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
);
356 // long top and bottom quads
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
);
373 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
375 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
384 if (count
== cast(int)slab
.length
) break;
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
);
408 // long top and bottom quads
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
);
417 while (x
+xcount
< cast(int)vox
.xsize
) {
418 const ubyte vcull
= vox
.queryCull(x
+xcount
, y
, z
);
419 if ((vcull
&cmask
) == 0) break;
424 while (y
+ycount
< cast(int)vox
.ysize
) {
425 const ubyte vcull
= vox
.queryCull(x
, y
+ycount
, z
);
426 if ((vcull
&cmask
) == 0) break;
429 assert(xcount
&& ycount
);
430 // now use the longest one
431 if (xcount
>= ycount
) {
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
);
441 addSlabFace(cmask
, DMV_X
, x
-px
, y
-py
, z
-pz
, xcount
, slab
[0..xcount
]);
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
);
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
);
463 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
465 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
474 if (count
== cast(int)slab
.length
) break;
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);
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
) {
517 const int z
= cast(int)vox
.data
[dofs
].z
;
519 foreach (uint cidx
; 0..6) {
520 const ubyte cmask
= VoxelData
.cullmask(cidx
);
521 if ((vox
.data
[dofs
].cull
&cmask
) == 0) continue;
523 foreach (uint ndir
; 0..2) {
524 const ubyte dmv
= dmove
[cidx
>>1][ndir
];
526 int sx
= x
, sy
= y
, sz
= z
;
527 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
529 const ubyte vxc
= vox
.queryCull(sx
, sy
, sz
);
530 if ((vxc
&cmask
) == 0) break;
532 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
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
);
548 assert(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?
569 scope(exit
) { delete slab
; slab
= null; }
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
) {
580 } else if (cmask
&Cull_XAxisMask
) {
589 bmp2d
.setSize(vwdt
, vhgt
);
591 for (uint vcrd
= 0; vcrd
< vlen
; ++vcrd
) {
593 for (uint vdy
= 0; vdy
< vhgt
; ++vdy
) {
594 for (uint vdx
= 0; vdx
< vwdt
; ++vdx
) {
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
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;
615 for (int dy
= y0
; dy
<= y1
; ++dy
) {
616 for (int dx
= x0
; dx
<= x1
; ++dx
) {
617 *dp
++ = bmp2d
.resetPixel(dx
, dy
);
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
);
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).");
645 conwriteln("converted in ", tm
.toString(), "; optlevel is ", vox_optimisation
, "/", kvx_max_optim_level
);
652 delete quads
; quads
= null;