4 import iv
.glbinds
.utils
;
11 static import iv
.timer
;
16 // ////////////////////////////////////////////////////////////////////////// //
17 static align(1) struct VoxXYZ16
{
19 ushort x
= void, y
= void, z
= void;
23 // ////////////////////////////////////////////////////////////////////////// //
25 this is a struct that keeps info about an individual voxel.
26 it keeps voxel color, and face visibility info.
28 align(1) struct VoxPix
{
32 uint nextz
; // voxel with the next z; 0 means "no more"
33 ushort z
; // z of the current voxel
35 uint rgb () const pure nothrow @safe @nogc {
37 return 0xff000000U|b|
(cast(uint)g
<<8)|
(cast(uint)r
<<16);
40 uint rgbcull () const pure nothrow @safe @nogc {
42 return b|
(cast(uint)g
<<8)|
(cast(uint)r
<<16)|
(cast(uint)cull
<<24);
47 // ////////////////////////////////////////////////////////////////////////// //
49 this keeps voxel "voxmap" (it's like a pixmap, but with voxels instead
50 of pixels). the representation is slightly optimised, tho: it keeps only
51 actually used voxels, in vertical "slabs". as most voxel models have only
52 contour voxels stored, this greatly reduces memory consumption. quering
53 the individial voxel data is slightly slower, tho, but for our case it is
56 there are some methods to "optimise" the voxmap. for example, to fix voxel
57 face visibility info, and to remove "internal" voxels (it is called "hollow fill").
59 also note that some optimisation methods may leave empty voxels in the voxmap.
60 they have `cull` field equal to zero.
62 to build a voxmap you simply call `addVoxel()` method, optionally providing
63 the face visibility info. but you can use `0x3f` as a visibility, and then ask
64 `VoxelData` object to calculate the proprer "cull" values for you. you can also
65 add "internal" voxels, and then ask the object to fix the vixmap for you, removing
66 all the unnecessary data.
70 uint xsize
= 0, ysize
= 0, zsize
= 0;
71 float cx
= 0.0f, cy
= 0.0f, cz
= 0.0f;
73 VoxPix
[] data
; // [0] is never used
74 // xsize*ysize array, offsets in `data`; 0 means "no data here"
75 // slabs are sorted from bottom to top, and never intersects
81 uint allocVox () nothrow @safe {
85 if (data
.length
>= 0x3fffffffU
) assert(0, "too many voxels");
86 const uint lastel
= cast(uint)data
.length
;
88 freelist
= cast(uint)data
.length
-1;
89 while (freelist
>= lastel
) {
90 data
[freelist
].nextz
= freelist
+1;
96 const uint res
= freelist
;
97 freelist
= data
[res
].nextz
;
107 xsize
= ysize
= zsize
= 0;
113 void setSize (uint xs
, uint ys
, uint zs
) {
115 if (!xs ||
!ys ||
!zs
) return;
119 xyofs
= new uint[xsize
*ysize
];
121 data
.length
= 1; // data[0] is never used
124 uint getDOfs (int x
, int y
) const nothrow @trusted @nogc {
125 pragma(inline
, true);
126 if (x
< 0 || y
< 0) return 0;
127 if (cast(uint)x
>= xsize ||
cast(uint)y
>= ysize
) return 0;
128 return xyofs
.ptr
[cast(uint)y
*xsize
+cast(uint)x
];
131 // high byte is cull info
132 // returns 0 if there is no such voxel
133 uint voxofs (int x
, int y
, int z
) const nothrow @safe @nogc {
134 uint dofs
= getDOfs(x
, y
);
136 if (data
[dofs
].z
== cast(ushort)z
) return dofs
;
137 if (data
[dofs
].z
> cast(ushort)z
) return 0;
138 dofs
= data
[dofs
].nextz
;
143 // high byte is cull info
144 // returns 0 if there is no such voxel
145 uint query (int x
, int y
, int z
) const nothrow @safe @nogc {
146 immutable uint dofs
= voxofs(x
, y
, z
);
148 if (!data
[dofs
].cull
) return 0;
149 return data
[dofs
].rgbcull();
152 VoxPix
*queryVP (int x
, int y
, int z
) nothrow @trusted @nogc {
153 pragma(inline
, true);
154 immutable uint dofs
= voxofs(x
, y
, z
);
155 return (dofs ?
&data
[dofs
] : null);
158 // high byte is cull info
159 // returns 0 if there is no such voxel
160 ubyte queryCull (int x
, int y
, int z
) const nothrow @safe @nogc {
161 immutable uint dofs
= voxofs(x
, y
, z
);
162 return (dofs ? data
[dofs
].cull
: 0);
165 void removeVoxel (int x
, int y
, int z
) nothrow @safe @nogc {
166 uint dofs
= getDOfs(x
, y
);
169 if (data
[dofs
].z
== cast(ushort)z
) {
172 data
[prevdofs
].nextz
= data
[dofs
].nextz
;
174 xyofs
[cast(uint)y
*xsize
+cast(uint)x
] = data
[dofs
].nextz
;
176 data
[dofs
].nextz
= freelist
;
181 if (data
[dofs
].z
> cast(ushort)z
) return;
183 dofs
= data
[dofs
].nextz
;
187 void addVoxel (int x
, int y
, int z
, uint rgb
, ubyte cull
) nothrow @safe {
189 if (!cull
) { removeVoxel(x
, y
, z
); return; }
190 if (x
< 0 || y
< 0 || z
< 0) return;
191 if (cast(uint)x
>= xsize ||
cast(uint)y
>= ysize ||
cast(uint)z
>= zsize
) return;
192 uint dofs
= getDOfs(x
, y
);
195 if (data
[dofs
].z
== cast(ushort)z
) {
196 // replace this voxel
197 data
[dofs
].b
= rgb
&0xff;
198 data
[dofs
].g
= (rgb
>>8)&0xff;
199 data
[dofs
].r
= (rgb
>>16)&0xff;
200 data
[dofs
].cull
= cull
;
203 if (data
[dofs
].z
> cast(ushort)z
) break;
205 dofs
= data
[dofs
].nextz
;
207 // insert before dofs
208 immutable uint vidx
= allocVox();
209 data
[vidx
].b
= rgb
&0xff;
210 data
[vidx
].g
= (rgb
>>8)&0xff;
211 data
[vidx
].r
= (rgb
>>16)&0xff;
212 data
[vidx
].cull
= cull
;
213 data
[vidx
].z
= cast(ushort)z
;
214 data
[vidx
].nextz
= dofs
;
216 assert(data
[prevdofs
].nextz
== dofs
);
217 data
[prevdofs
].nextz
= vidx
;
219 xyofs
[cast(uint)y
*xsize
+cast(uint)x
] = vidx
;
223 // only for existing voxels; won't remove empty voxels
224 void setVoxelCull (int x
, int y
, int z
, ubyte cull
) nothrow @safe @nogc {
225 VoxPix
*vp
= queryVP(x
, y
, z
);
226 if (vp
) vp
.cull
= cast(ubyte)(cull
&0x3f);
229 version(vox_check_invariants
)
230 void checkInvariants () const nothrow /*@safe*/ @trusted /*@nogc*/ {
231 version(voxdata_debug
) conwriteln("checking invariants...");
233 foreach (uint y
; 0..ysize
) {
234 foreach (uint x
; 0..xsize
) {
235 uint dofs
= getDOfs(x
, y
);
238 ushort prevz
= data
[dofs
].z
;
239 dofs
= data
[dofs
].nextz
;
242 assert(prevz
< data
[dofs
].z
, "broken voxel data Z invariant");
243 prevz
= data
[dofs
].z
;
244 dofs
= data
[dofs
].nextz
;
248 assert(voxcount
== voxpixtotal
, "invalid number of voxels");
251 void removeEmptyVoxels () nothrow /*@safe*/ @trusted /*@nogc*/ {
252 version(voxdata_debug
) conwriteln("removing empty voxels...");
254 foreach (uint y
; 0..ysize
) {
255 foreach (uint x
; 0..xsize
) {
256 uint dofs
= getDOfs(x
, y
);
260 if (!data
[dofs
].cull
) {
262 const uint ndofs
= data
[dofs
].nextz
;
264 data
[prevdofs
].nextz
= ndofs
;
266 xyofs
[cast(uint)y
*xsize
+cast(uint)x
] = ndofs
;
268 data
[dofs
].nextz
= freelist
;
275 dofs
= data
[dofs
].nextz
;
280 if (count
) conwriteln("removed ", count
, " empty voxel", (count
!= 1 ?
"s" : ""));
283 static immutable int[3][6] cullofs
= [
289 [ 0, 0,-1], // bottom
292 static ubyte cullmask (uint cidx
) pure nothrow @safe @nogc {
293 pragma(inline
, true);
294 return cast(ubyte)(1U<<cidx
);
298 static ubyte cullopmask (uint cidx
) pure nothrow @safe @nogc {
299 pragma(inline
, true);
300 return cast(ubyte)(1U<<(cidx^
1));
303 // remove inside voxels, leaving only contour
304 void removeInsideFaces () nothrow /*@safe*/ @trusted {
305 version(voxdata_debug
) conwriteln("removing inside voxels...");
306 foreach (int y
; 0..ysize
) {
307 foreach (int x
; 0..xsize
) {
308 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
309 if (!data
[dofs
].cull
) continue;
311 const int z
= cast(int)data
[dofs
].z
;
312 foreach (uint cidx
; 0..6) {
313 // go in this dir, removing the corresponding voxel side
314 immutable ubyte cmask
= cullmask(cidx
);
315 immutable ubyte opmask
= cullopmask(cidx
);
316 immutable ubyte checkmask
= cmask|opmask
;
317 immutable int dx
= cullofs
[cidx
][0];
318 immutable int dy
= cullofs
[cidx
][1];
319 immutable int dz
= cullofs
[cidx
][2];
320 int vx
= x
, vy
= y
, vz
= z
;
322 while (myofs
&& (data
[myofs
].cull
&cmask
)) {
323 immutable int sx
= vx
+dx
;
324 immutable int sy
= vy
+dy
;
325 immutable int sz
= vz
+dz
;
326 immutable uint sofs
= voxofs(sx
, sy
, sz
);
328 if (!(data
[sofs
].cull
&checkmask
)) break;
330 data
[myofs
].cull ^
= cmask
;
331 data
[sofs
].cull
&= cast(ubyte)(~cast(uint)opmask
);
343 // if we have ANY voxel at the corresponding side, don't render that face
344 // return number of fixed voxels
345 uint fixFaceVisibility () nothrow @safe @nogc {
347 foreach (int y
; 0..ysize
) {
348 foreach (int x
; 0..xsize
) {
349 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
350 const ubyte ocull
= data
[dofs
].cull
;
351 if (!ocull
) continue;
352 const int z
= cast(int)data
[dofs
].z
;
353 // if we have ANY voxel at the corresponding side, don't render that face
354 foreach (uint cidx
; 0..6) {
355 const ubyte cmask
= cullmask(cidx
);
356 if (data
[dofs
].cull
&cmask
) {
357 if (queryCull(x
+cullofs
[cidx
][0], y
+cullofs
[cidx
][1], z
+cullofs
[cidx
][2])) {
358 data
[dofs
].cull ^
= cmask
; // reset bit
362 count
+= (data
[dofs
].cull
!= ocull
);
369 void create3DBitmap (ref Vox3DBitmap bmp
) {
370 bmp
.setSize(xsize
, ysize
, zsize
);
371 foreach (int y
; 0..ysize
) {
372 foreach (int x
; 0..xsize
) {
373 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
374 if (data
[dofs
].cull
) bmp
.setPixel(x
, y
, cast(int)data
[dofs
].z
);
380 // this fills everything outside of the voxel, and
381 // then resets culling bits for all invisible faces
382 // i don't care about memory yet
383 uint hollowFill () @trusted {
385 scope(exit
) { bmp
.clear(); }
386 bmp
.setSize(xsize
+2, ysize
+2, zsize
+2);
389 scope(exit
) delete stack
;
392 stack
.length
= 32768;
393 stack
.assumeSafeAppend
;
395 assert(xsize
<= cast(uint)stack
.length
);
397 // this is definitely empty
399 xyz
.x
= xyz
.y
= xyz
.z
= 0;
400 bmp
.setPixel(cast(int)xyz
.x
, cast(int)xyz
.y
, cast(int)xyz
.z
);
401 stack
[stackpos
++] = xyz
;
403 immutable int[3][6] deltas
= [
412 auto tm
= iv
.timer
.Timer(true);
414 xyz
= stack
[--stackpos
];
415 for (uint dd = 0; dd < 6; ++dd) {
416 const int nx
= cast(int)xyz
.x
+deltas
[dd][0];
417 const int ny
= cast(int)xyz
.y
+deltas
[dd][1];
418 const int nz
= cast(int)xyz
.z
+deltas
[dd][2];
419 if (bmp
.setPixel(nx
, ny
, nz
)) continue;
420 if (queryCull(nx
-1, ny
-1, nz
-1)) continue;
421 if (stackpos
== cast(uint)stack
.length
) {
422 stack
.length
+= 32768;
423 stack
.assumeSafeAppend
;
425 stack
[stackpos
++] = VoxXYZ16(cast(ushort)nx
, cast(ushort)ny
, cast(ushort)nz
);
429 version(voxdata_debug
) conwriteln("*** flooded in ", tm
.toString(), " ", stack
.length
, " stack items used.");
431 // unmark contour voxels
432 // this is required for proper face removing
433 foreach (int y
; 0..ysize
) {
434 foreach (int x
; 0..xsize
) {
435 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
436 if (!data
[dofs
].cull
) continue;
437 const int z
= cast(int)data
[dofs
].z
;
438 bmp
.resetPixel(x
+1, y
+1, z
+1);
445 foreach (int y
; 0..ysize
) {
446 foreach (int x
; 0..xsize
) {
447 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
448 immutable ubyte omask
= data
[dofs
].cull
;
449 if (!omask
) continue;
450 data
[dofs
].cull
= 0x3f;
452 const int z
= cast(int)data
[dofs
].z
;
453 foreach (uint cidx
; 0..6) {
454 immutable ubyte cmask
= cullmask(cidx
);
455 if (!(data
[dofs
].cull
&cmask
)) continue;
456 const int nx
= x
+cullofs
[cidx
][0];
457 const int ny
= y
+cullofs
[cidx
][1];
458 const int nz
= z
+cullofs
[cidx
][2];
459 if (bmp
.getPixel(nx
+1, ny
+1, nz
+1)) continue;
460 // reset this cull bit
461 data
[dofs
].cull ^
= cmask
;
463 changed
+= (omask
!= data
[dofs
].cull
);
470 void optimise (bool doHollowFill
) @trusted {
471 version(vox_check_invariants
) checkInvariants();
472 version(voxdata_debug
) conwriteln("optimising mesh with ", voxpixtotal
, " individual voxels...");
474 version(voxdata_debug
) conwriteln("optimising voxel culling...");
475 uint count
= hollowFill();
476 if (count
) conwriteln("fixed ", count
, " voxel", (count
!= 1 ?
"s" : ""));
477 //count = fixFaceVisibility();
478 //if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
481 version(voxdata_debug
) conwriteln("optimising voxel culling...");
482 uint count
= fixFaceVisibility();
483 if (count
) conwriteln("fixed ", count
, " voxel", (count
!= 1 ?
"s" : ""));
486 version(vox_check_invariants
) checkInvariants();
487 version(voxdata_debug
) conwriteln("final optimised mesh contains ", voxpixtotal
, " individual voxels...");