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 VoxRect16 rc
; // inside the given citem
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
.rc
)) {
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
.rc
= VoxRect16
.Zero();
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
)
188 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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; }
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
)
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
];
210 foreach (auto cidx
; 1..colors
.length
) {
211 if (colors
[cidx
] != colors
[0]) {
216 if (allsame
) colors
= colors
[0..1];
219 colors
.length
== 1 ? Point
:
220 (dmv
&DMV_X
) ? XLong
:
221 (dmv
&DMV_Y
) ? YLong
:
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);
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;
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);
246 void addCube (ubyte cull
, float x
, float y
, float z
, uint rgb
) {
247 immutable uint[1] carr
= [rgb
];
249 foreach (uint qidx
; 0..6) {
250 const ubyte cmask
= VoxelData
.cullmask(qidx
);
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
];
268 foreach (auto cidx
; 1..colors
.length
) {
269 if (colors
[cidx
] != colors
[0]) {
274 if (allsame
) colors
= colors
[0..1];
276 const int qtype
= Quad
;
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;
289 for (uint vidx
= 0; vidx
< 4; ++vidx
) {
290 const ubyte vtype
= quadFaces
[qidx
][vidx
];
293 vx
.dx
= vx
.dy
= vx
.dz
= 0.0f;
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
;
318 if (colors
.length
== 1) {
319 setColors(ref vq
, colors
[], 1, 1);
321 setColors(ref vq
, colors
[], wdt
, hgt
);
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
);
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
);
358 // long top and bottom quads
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
);
375 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
377 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
386 if (count
== cast(int)slab
.length
) break;
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
);
410 // long top and bottom quads
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
);
419 while (x
+xcount
< cast(int)vox
.xsize
) {
420 const ubyte vcull
= vox
.queryCull(x
+xcount
, y
, z
);
421 if ((vcull
&cmask
) == 0) break;
426 while (y
+ycount
< cast(int)vox
.ysize
) {
427 const ubyte vcull
= vox
.queryCull(x
, y
+ycount
, z
);
428 if ((vcull
&cmask
) == 0) break;
431 assert(xcount
&& ycount
);
432 // now use the longest one
433 if (xcount
>= ycount
) {
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
);
443 addSlabFace(cmask
, DMV_X
, x
-px
, y
-py
, z
-pz
, xcount
, slab
[0..xcount
]);
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
);
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
);
465 while (dofs
&& (vox
.data
[dofs
].cull
&cmask
) == 0) dofs
= vox
.data
[dofs
].nextz
;
467 const int z
= cast(int)vox
.data
[dofs
].z
;
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
;
476 if (count
== cast(int)slab
.length
) break;
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);
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
) {
519 const int z
= cast(int)vox
.data
[dofs
].z
;
521 foreach (uint cidx
; 0..6) {
522 const ubyte cmask
= VoxelData
.cullmask(cidx
);
523 if ((vox
.data
[dofs
].cull
&cmask
) == 0) continue;
525 foreach (uint ndir
; 0..2) {
526 const ubyte dmv
= dmove
[cidx
>>1][ndir
];
528 int sx
= x
, sy
= y
, sz
= z
;
529 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
531 const ubyte vxc
= vox
.queryCull(sx
, sy
, sz
);
532 if ((vxc
&cmask
) == 0) break;
534 incXYZ(dmv
, ref sx
, ref sy
, ref sz
);
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
);
550 assert(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?
571 scope(exit
) { delete slab
; slab
= null; }
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
) {
582 } else if (cmask
&Cull_XAxisMask
) {
591 bmp2d
.setSize(vwdt
, vhgt
);
593 for (uint vcrd
= 0; vcrd
< vlen
; ++vcrd
) {
595 for (uint vdy
= 0; vdy
< vhgt
; ++vdy
) {
596 for (uint vdx
= 0; vdx
< vwdt
; ++vdx
) {
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
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;
617 for (int dy
= y0
; dy
<= y1
; ++dy
) {
618 for (int dx
= x0
; dx
<= x1
; ++dx
) {
619 *dp
++ = bmp2d
.resetPixel(dx
, dy
);
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
);
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).");
647 conwriteln("converted in ", tm
.toString(), "; optlevel is ", vox_optimisation
, "/", kvx_max_optim_level
);
654 delete quads
; quads
= null;