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 // this fills everything outside of the voxel, and
370 // then resets culling bits for all invisible faces
371 // i don't care about memory yet
372 uint hollowFill () @trusted {
374 scope(exit
) { bmp
.clear(); }
375 bmp
.setSize(xsize
+2, ysize
+2, zsize
+2);
378 scope(exit
) delete stack
;
381 stack
.length
= 32768;
382 stack
.assumeSafeAppend
;
384 assert(xsize
<= cast(uint)stack
.length
);
386 // this is definitely empty
388 xyz
.x
= xyz
.y
= xyz
.z
= 0;
389 bmp
.setPixel(cast(int)xyz
.x
, cast(int)xyz
.y
, cast(int)xyz
.z
);
390 stack
[stackpos
++] = xyz
;
392 immutable int[3][6] deltas
= [
401 auto tm
= iv
.timer
.Timer(true);
403 xyz
= stack
[--stackpos
];
404 for (uint dd = 0; dd < 6; ++dd) {
405 const int nx
= cast(int)xyz
.x
+deltas
[dd][0];
406 const int ny
= cast(int)xyz
.y
+deltas
[dd][1];
407 const int nz
= cast(int)xyz
.z
+deltas
[dd][2];
408 if (bmp
.setPixel(nx
, ny
, nz
)) continue;
409 if (queryCull(nx
-1, ny
-1, nz
-1)) continue;
410 if (stackpos
== cast(uint)stack
.length
) {
411 stack
.length
+= 32768;
412 stack
.assumeSafeAppend
;
414 stack
[stackpos
++] = VoxXYZ16(cast(ushort)nx
, cast(ushort)ny
, cast(ushort)nz
);
418 version(voxdata_debug
) conwriteln("*** flooded in ", tm
.toString(), " ", stack
.length
, " stack items used.");
420 // unmark contour voxels
421 // this is required for proper face removing
422 foreach (int y
; 0..ysize
) {
423 foreach (int x
; 0..xsize
) {
424 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
425 if (!data
[dofs
].cull
) continue;
426 const int z
= cast(int)data
[dofs
].z
;
427 bmp
.resetPixel(x
+1, y
+1, z
+1);
434 foreach (int y
; 0..ysize
) {
435 foreach (int x
; 0..xsize
) {
436 for (uint dofs
= getDOfs(x
, y
); dofs
; dofs
= data
[dofs
].nextz
) {
437 immutable ubyte omask
= data
[dofs
].cull
;
438 if (!omask
) continue;
439 data
[dofs
].cull
= 0x3f;
441 const int z
= cast(int)data
[dofs
].z
;
442 foreach (uint cidx
; 0..6) {
443 immutable ubyte cmask
= cullmask(cidx
);
444 if (!(data
[dofs
].cull
&cmask
)) continue;
445 const int nx
= x
+cullofs
[cidx
][0];
446 const int ny
= y
+cullofs
[cidx
][1];
447 const int nz
= z
+cullofs
[cidx
][2];
448 if (bmp
.getPixel(nx
+1, ny
+1, nz
+1)) continue;
449 // reset this cull bit
450 data
[dofs
].cull ^
= cmask
;
452 changed
+= (omask
!= data
[dofs
].cull
);
459 void optimise (bool doHollowFill
) @trusted {
460 version(vox_check_invariants
) checkInvariants();
461 version(voxdata_debug
) conwriteln("optimising mesh with ", voxpixtotal
, " individual voxels...");
463 version(voxdata_debug
) conwriteln("optimising voxel culling...");
464 uint count
= hollowFill();
465 if (count
) conwriteln("fixed ", count
, " voxel", (count
!= 1 ?
"s" : ""));
466 //count = fixFaceVisibility();
467 //if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
470 version(voxdata_debug
) conwriteln("optimising voxel culling...");
471 uint count
= fixFaceVisibility();
472 if (count
) conwriteln("fixed ", count
, " voxel", (count
!= 1 ?
"s" : ""));
475 version(vox_check_invariants
) checkInvariants();
476 version(voxdata_debug
) conwriteln("final optimised mesh contains ", voxpixtotal
, " individual voxels...");