added progress indicator for long conversions
[voxconv.git] / vox_data.d
blob1771b3aa83ef42175b02cb56bc5cb57d2d881ca0
1 module vox_data;
3 import iv.bclamp;
4 import iv.glbinds.utils;
5 import iv.cmdcongl;
6 import iv.strex;
7 import iv.vfs;
8 import iv.vfs.io;
9 import iv.vmath;
11 static import iv.timer;
13 import vox_3dbmp;
15 version = voxdata_debug;
16 //version = vox_check_invariants;
19 // ////////////////////////////////////////////////////////////////////////// //
20 static align(1) struct VoxXYZ16 {
21 align(1):
22 ushort x = void, y = void, z = void;
26 // ////////////////////////////////////////////////////////////////////////// //
28 this is a struct that keeps info about an individual voxel.
29 it keeps voxel color, and face visibility info.
31 align(1) struct VoxPix {
32 align(1):
33 ubyte b, g, r;
34 ubyte cull;
35 uint nextz; // voxel with the next z; 0 means "no more"
36 ushort z; // z of the current voxel
38 uint rgb () const pure nothrow @safe @nogc {
39 pragma(inline, true);
40 return 0xff000000U|b|(cast(uint)g<<8)|(cast(uint)r<<16);
43 uint rgbcull () const pure nothrow @safe @nogc {
44 pragma(inline, true);
45 return b|(cast(uint)g<<8)|(cast(uint)r<<16)|(cast(uint)cull<<24);
50 // ////////////////////////////////////////////////////////////////////////// //
52 this keeps voxel "voxmap" (it's like a pixmap, but with voxels instead
53 of pixels). the representation is slightly optimised, tho: it keeps only
54 actually used voxels, in vertical "slabs". as most voxel models have only
55 contour voxels stored, this greatly reduces memory consumption. quering
56 the individial voxel data is slightly slower, tho, but for our case it is
57 not really important.
59 there are some methods to "optimise" the voxmap. for example, to fix voxel
60 face visibility info, and to remove "internal" voxels (it is called "hollow fill").
62 also note that some optimisation methods may leave empty voxels in the voxmap.
63 they have `cull` field equal to zero.
65 to build a voxmap you simply call `addVoxel()` method, optionally providing
66 the face visibility info. but you can use `0x3f` as a visibility, and then ask
67 `VoxelData` object to calculate the proprer "cull" values for you. you can also
68 add "internal" voxels, and then ask the object to fix the vixmap for you, removing
69 all the unnecessary data.
71 struct VoxelData {
72 public:
73 uint xsize = 0, ysize = 0, zsize = 0;
74 float cx = 0.0f, cy = 0.0f, cz = 0.0f;
76 VoxPix[] data; // [0] is never used
77 // xsize*ysize array, offsets in `data`; 0 means "no data here"
78 // slabs are sorted from bottom to top, and never intersects
79 uint[] xyofs;
80 uint freelist = 0;
81 uint voxpixtotal = 0;
83 public:
84 void save (VFile fl) {
85 fl.rawWriteExact("K8VOXDT0");
86 fl.writeNum!ushort(cast(ushort)xsize);
87 fl.writeNum!ushort(cast(ushort)ysize);
88 fl.writeNum!ushort(cast(ushort)zsize);
89 fl.writeNum(cx);
90 fl.writeNum(cy);
91 fl.writeNum(cz);
93 uint voxelCount = 0;
94 foreach (int y; 0..ysize) {
95 foreach (int x; 0..xsize) {
96 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
97 if (data[dofs].cull) ++voxelCount;
101 fl.writeNum(voxelCount);
103 foreach (int y; 0..ysize) {
104 foreach (int x; 0..xsize) {
105 uint count = 0;
106 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
107 if (data[dofs].cull) ++count;
109 fl.writeNum!ushort(cast(ushort)count);
110 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
111 if (data[dofs].cull) {
112 fl.writeNum(data[dofs].b);
113 fl.writeNum(data[dofs].g);
114 fl.writeNum(data[dofs].r);
115 fl.writeNum(data[dofs].cull);
116 fl.writeNum(data[dofs].z);
123 void load (VFile fl) {
124 char[8] sign = void;
125 fl.rawReadExact(sign[]);
126 if (sign[] != "K8VOXDT0") throw new Exception("invalid voxel data");
128 xsize = fl.readNum!ushort;
129 ysize = fl.readNum!ushort;
130 zsize = fl.readNum!ushort;
131 cx = fl.readNum!float;
132 cy = fl.readNum!float;
133 cz = fl.readNum!float;
135 const uint voxelCount = fl.readNum!uint;
136 delete xyofs;
137 xyofs = new uint[xsize*ysize];
138 delete data;
139 data = new VoxPix[voxelCount+1]; // [0] is unused
141 freelist = 0;
142 voxpixtotal = voxelCount;
144 uint cpos = 1;
145 foreach (int y; 0..ysize) {
146 foreach (int x; 0..xsize) {
147 uint count = fl.readNum!ushort;
148 if (count == 0) {
149 xyofs[cast(uint)y*xsize+cast(uint)x] = 0;
150 continue;
152 xyofs[cast(uint)y*xsize+cast(uint)x] = cpos;
153 int prevz = -1;
154 while (count--) {
155 VoxPix *vx = &data[cpos];
156 vx.b = fl.readNum!ubyte;
157 vx.g = fl.readNum!ubyte;
158 vx.r = fl.readNum!ubyte;
159 vx.cull = fl.readNum!ubyte;
160 vx.nextz = -1;
161 if (!vx.cull) throw new Exception("invalid voxel data (cull)");
162 vx.z = fl.readNum!ushort;
163 if (prevz >= 0) {
164 if (data[prevz].z >= vx.z) throw new Exception("invalid voxel data (z)");
165 data[prevz].nextz = cast(int)cpos;
167 prevz = cast(int)cpos;
168 ++cpos;
172 assert(cpos == data.length);
175 private:
176 uint allocVox () nothrow @safe {
177 assert(data.length);
178 ++voxpixtotal;
179 if (!freelist) {
180 if (data.length >= 0x3fffffffU) assert(0, "too many voxels");
181 const uint lastel = cast(uint)data.length;
182 data.length += 1024;
183 freelist = cast(uint)data.length-1;
184 while (freelist >= lastel) {
185 data[freelist].nextz = freelist+1;
186 --freelist;
188 freelist = lastel;
189 data[$-1].nextz = 0;
191 const uint res = freelist;
192 freelist = data[res].nextz;
193 return res;
196 public:
197 void clear () {
198 delete data;
199 data = null;
200 delete xyofs;
201 xyofs = null;
202 xsize = ysize = zsize = 0;
203 cx = cy = cz = 0.0f;
204 freelist = 0;
205 voxpixtotal = 0;
208 void setSize (uint xs, uint ys, uint zs) {
209 clear();
210 if (!xs || !ys || !zs) return;
211 xsize = xs;
212 ysize = ys;
213 zsize = zs;
214 xyofs = new uint[xsize*ysize];
215 xyofs[] = 0;
216 data.length = 1; // data[0] is never used
219 uint getDOfs (int x, int y) const nothrow @trusted @nogc {
220 pragma(inline, true);
221 if (x < 0 || y < 0) return 0;
222 if (cast(uint)x >= xsize || cast(uint)y >= ysize) return 0;
223 return xyofs.ptr[cast(uint)y*xsize+cast(uint)x];
226 // high byte is cull info
227 // returns 0 if there is no such voxel
228 uint voxofs (int x, int y, int z) const nothrow @safe @nogc {
229 uint dofs = getDOfs(x, y);
230 while (dofs) {
231 if (data[dofs].z == cast(ushort)z) return dofs;
232 if (data[dofs].z > cast(ushort)z) return 0;
233 dofs = data[dofs].nextz;
235 return 0;
238 // high byte is cull info
239 // returns 0 if there is no such voxel
240 uint query (int x, int y, int z) const nothrow @safe @nogc {
241 immutable uint dofs = voxofs(x, y, z);
242 if (!dofs) return 0;
243 if (!data[dofs].cull) return 0;
244 return data[dofs].rgbcull();
247 VoxPix *queryVP (int x, int y, int z) nothrow @trusted @nogc {
248 pragma(inline, true);
249 immutable uint dofs = voxofs(x, y, z);
250 return (dofs ? &data[dofs] : null);
253 // high byte is cull info
254 // returns 0 if there is no such voxel
255 ubyte queryCull (int x, int y, int z) const nothrow @safe @nogc {
256 immutable uint dofs = voxofs(x, y, z);
257 return (dofs ? data[dofs].cull : 0);
260 void removeVoxel (int x, int y, int z) nothrow @safe @nogc {
261 uint dofs = getDOfs(x, y);
262 uint prevdofs = 0;
263 while (dofs) {
264 if (data[dofs].z == cast(ushort)z) {
265 // remove this voxel
266 if (prevdofs) {
267 data[prevdofs].nextz = data[dofs].nextz;
268 } else {
269 xyofs[cast(uint)y*xsize+cast(uint)x] = data[dofs].nextz;
271 data[dofs].nextz = freelist;
272 freelist = dofs;
273 --voxpixtotal;
274 return;
276 if (data[dofs].z > cast(ushort)z) return;
277 prevdofs = dofs;
278 dofs = data[dofs].nextz;
282 void addVoxel (int x, int y, int z, uint rgb, ubyte cull) nothrow @safe {
283 cull &= 0x3f;
284 if (!cull) { removeVoxel(x, y, z); return; }
285 if (x < 0 || y < 0 || z < 0) return;
286 if (cast(uint)x >= xsize || cast(uint)y >= ysize || cast(uint)z >= zsize) return;
287 uint dofs = getDOfs(x, y);
288 uint prevdofs = 0;
289 while (dofs) {
290 if (data[dofs].z == cast(ushort)z) {
291 // replace this voxel
292 data[dofs].b = rgb&0xff;
293 data[dofs].g = (rgb>>8)&0xff;
294 data[dofs].r = (rgb>>16)&0xff;
295 data[dofs].cull = cull;
296 return;
298 if (data[dofs].z > cast(ushort)z) break;
299 prevdofs = dofs;
300 dofs = data[dofs].nextz;
302 // insert before dofs
303 immutable uint vidx = allocVox();
304 data[vidx].b = rgb&0xff;
305 data[vidx].g = (rgb>>8)&0xff;
306 data[vidx].r = (rgb>>16)&0xff;
307 data[vidx].cull = cull;
308 data[vidx].z = cast(ushort)z;
309 data[vidx].nextz = dofs;
310 if (prevdofs) {
311 assert(data[prevdofs].nextz == dofs);
312 data[prevdofs].nextz = vidx;
313 } else {
314 xyofs[cast(uint)y*xsize+cast(uint)x] = vidx;
318 // only for existing voxels; won't remove empty voxels
319 void setVoxelCull (int x, int y, int z, ubyte cull) nothrow @safe @nogc {
320 VoxPix *vp = queryVP(x, y, z);
321 if (vp) vp.cull = cast(ubyte)(cull&0x3f);
324 version(vox_check_invariants)
325 void checkInvariants () const nothrow /*@safe*/ @trusted /*@nogc*/ {
326 version(voxdata_debug) conwriteln("checking invariants...");
327 uint voxcount = 0;
328 foreach (uint y; 0..ysize) {
329 foreach (uint x; 0..xsize) {
330 uint dofs = getDOfs(x, y);
331 if (!dofs) continue;
332 ++voxcount;
333 ushort prevz = data[dofs].z;
334 dofs = data[dofs].nextz;
335 while (dofs) {
336 ++voxcount;
337 assert(prevz < data[dofs].z, "broken voxel data Z invariant");
338 prevz = data[dofs].z;
339 dofs = data[dofs].nextz;
343 assert(voxcount == voxpixtotal, "invalid number of voxels");
346 void removeEmptyVoxels () nothrow /*@safe*/ @trusted /*@nogc*/ {
347 version(voxdata_debug) conwriteln("removing empty voxels...");
348 uint count = 0;
349 foreach (uint y; 0..ysize) {
350 foreach (uint x; 0..xsize) {
351 uint dofs = getDOfs(x, y);
352 if (!dofs) continue;
353 uint prevdofs = 0;
354 while (dofs) {
355 if (!data[dofs].cull) {
356 // remove it
357 const uint ndofs = data[dofs].nextz;
358 if (prevdofs) {
359 data[prevdofs].nextz = ndofs;
360 } else {
361 xyofs[cast(uint)y*xsize+cast(uint)x] = ndofs;
363 data[dofs].nextz = freelist;
364 freelist = dofs;
365 --voxpixtotal;
366 dofs = ndofs;
367 ++count;
368 } else {
369 prevdofs = dofs;
370 dofs = data[dofs].nextz;
375 if (count) conwriteln("removed ", count, " empty voxel", (count != 1 ? "s" : ""));
378 static immutable int[3][6] cullofs = [
379 [ 1, 0, 0], // left
380 [-1, 0, 0], // right
381 [ 0,-1, 0], // near
382 [ 0, 1, 0], // far
383 [ 0, 0, 1], // top
384 [ 0, 0,-1], // bottom
387 static ubyte cullmask (uint cidx) pure nothrow @safe @nogc {
388 pragma(inline, true);
389 return cast(ubyte)(1U<<cidx);
392 // opposite mask
393 static ubyte cullopmask (uint cidx) pure nothrow @safe @nogc {
394 pragma(inline, true);
395 return cast(ubyte)(1U<<(cidx^1));
398 // remove inside voxels, leaving only contour
399 void removeInsideFaces () nothrow /*@safe*/ @trusted {
400 version(voxdata_debug) conwriteln("removing inside voxels...");
401 foreach (int y; 0..ysize) {
402 foreach (int x; 0..xsize) {
403 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
404 if (!data[dofs].cull) continue;
405 // check
406 const int z = cast(int)data[dofs].z;
407 foreach (uint cidx; 0..6) {
408 // go in this dir, removing the corresponding voxel side
409 immutable ubyte cmask = cullmask(cidx);
410 immutable ubyte opmask = cullopmask(cidx);
411 immutable ubyte checkmask = cmask|opmask;
412 immutable int dx = cullofs[cidx][0];
413 immutable int dy = cullofs[cidx][1];
414 immutable int dz = cullofs[cidx][2];
415 int vx = x, vy = y, vz = z;
416 uint myofs = dofs;
417 while (myofs && (data[myofs].cull&cmask)) {
418 immutable int sx = vx+dx;
419 immutable int sy = vy+dy;
420 immutable int sz = vz+dz;
421 immutable uint sofs = voxofs(sx, sy, sz);
422 if (!sofs) break;
423 if (!(data[sofs].cull&checkmask)) break;
424 // fix culls
425 data[myofs].cull ^= cmask;
426 data[sofs].cull &= cast(ubyte)(~cast(uint)opmask);
427 vx = sx;
428 vy = sy;
429 vz = sz;
430 myofs = sofs;
438 // if we have ANY voxel at the corresponding side, don't render that face
439 // return number of fixed voxels
440 uint fixFaceVisibility () nothrow @safe @nogc {
441 uint count = 0;
442 foreach (int y; 0..ysize) {
443 foreach (int x; 0..xsize) {
444 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
445 const ubyte ocull = data[dofs].cull;
446 if (!ocull) continue;
447 const int z = cast(int)data[dofs].z;
448 // if we have ANY voxel at the corresponding side, don't render that face
449 foreach (uint cidx; 0..6) {
450 const ubyte cmask = cullmask(cidx);
451 if (data[dofs].cull&cmask) {
452 if (queryCull(x+cullofs[cidx][0], y+cullofs[cidx][1], z+cullofs[cidx][2])) {
453 data[dofs].cull ^= cmask; // reset bit
457 count += (data[dofs].cull != ocull);
461 return count;
464 void create3DBitmap (ref Vox3DBitmap bmp) {
465 bmp.setSize(xsize, ysize, zsize);
466 foreach (int y; 0..ysize) {
467 foreach (int x; 0..xsize) {
468 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
469 if (data[dofs].cull) bmp.setPixel(x, y, cast(int)data[dofs].z);
475 // this fills everything outside of the voxel, and
476 // then resets culling bits for all invisible faces
477 // i don't care about memory yet
478 uint hollowFill () @trusted {
479 Vox3DBitmap bmp;
480 scope(exit) { bmp.clear(); }
481 bmp.setSize(xsize+2, ysize+2, zsize+2);
483 VoxXYZ16[] stack;
484 scope(exit) delete stack;
485 uint stackpos;
487 stack.length = 32768;
488 stack.assumeSafeAppend;
489 stackpos = 0;
490 assert(xsize <= cast(uint)stack.length);
492 // this is definitely empty
493 VoxXYZ16 xyz;
494 xyz.x = xyz.y = xyz.z = 0;
495 bmp.setPixel(cast(int)xyz.x, cast(int)xyz.y, cast(int)xyz.z);
496 stack[stackpos++] = xyz;
498 immutable int[3][6] deltas = [
499 [-1, 0, 0],
500 [ 1, 0, 0],
501 [ 0,-1, 0],
502 [ 0, 1, 0],
503 [ 0, 0,-1],
504 [ 0, 0, 1],
507 auto tm = iv.timer.Timer(true);
508 while (stackpos) {
509 xyz = stack[--stackpos];
510 for (uint dd = 0; dd < 6; ++dd) {
511 const int nx = cast(int)xyz.x+deltas[dd][0];
512 const int ny = cast(int)xyz.y+deltas[dd][1];
513 const int nz = cast(int)xyz.z+deltas[dd][2];
514 if (bmp.setPixel(nx, ny, nz)) continue;
515 if (queryCull(nx-1, ny-1, nz-1)) continue;
516 if (stackpos == cast(uint)stack.length) {
517 stack.length += 32768;
518 stack.assumeSafeAppend;
520 stack[stackpos++] = VoxXYZ16(cast(ushort)nx, cast(ushort)ny, cast(ushort)nz);
523 tm.stop();
524 version(voxdata_debug) conwriteln("*** flooded in ", tm.toString(), " ", stack.length, " stack items used.");
526 // unmark contour voxels
527 // this is required for proper face removing
528 foreach (int y; 0..ysize) {
529 foreach (int x; 0..xsize) {
530 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
531 if (!data[dofs].cull) continue;
532 const int z = cast(int)data[dofs].z;
533 bmp.resetPixel(x+1, y+1, z+1);
538 // now check it
539 uint changed = 0;
540 foreach (int y; 0..ysize) {
541 foreach (int x; 0..xsize) {
542 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
543 immutable ubyte omask = data[dofs].cull;
544 if (!omask) continue;
545 data[dofs].cull = 0x3f;
546 // check
547 const int z = cast(int)data[dofs].z;
548 foreach (uint cidx; 0..6) {
549 immutable ubyte cmask = cullmask(cidx);
550 if (!(data[dofs].cull&cmask)) continue;
551 const int nx = x+cullofs[cidx][0];
552 const int ny = y+cullofs[cidx][1];
553 const int nz = z+cullofs[cidx][2];
554 if (bmp.getPixel(nx+1, ny+1, nz+1)) continue;
555 // reset this cull bit
556 data[dofs].cull ^= cmask;
558 changed += (omask != data[dofs].cull);
562 return changed;
565 void optimise (bool doHollowFill) @trusted {
566 version(vox_check_invariants) checkInvariants();
567 version(voxdata_debug) conwriteln("optimising mesh with ", voxpixtotal, " individual voxels...");
568 if (doHollowFill) {
569 version(voxdata_debug) conwriteln("optimising voxel culling...");
570 uint count = hollowFill();
571 if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
572 //count = fixFaceVisibility();
573 //if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
574 } else {
575 removeInsideFaces();
576 version(voxdata_debug) conwriteln("optimising voxel culling...");
577 uint count = fixFaceVisibility();
578 if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
580 removeEmptyVoxels();
581 version(vox_check_invariants) checkInvariants();
582 version(voxdata_debug) conwriteln("final optimised mesh contains ", voxpixtotal, " individual voxels...");
587 // ////////////////////////////////////////////////////////////////////////// //
589 voxel data optimised for queries
591 each slab is kept in this format:
592 ushort zlo, zhi
593 ushort runcount
594 then run index follows:
595 ushort z0
596 ushort z1+1
597 ushort ofs (from this z)
598 ushort reserved
599 index always ends with zhi+1, zhi+1
600 then (b,g,r,cull) array
602 struct VoxelDataSmall {
603 public:
604 uint xsize = 0, ysize = 0, zsize = 0;
605 float cx = 0.0f, cy = 0.0f, cz = 0.0f;
607 ubyte[] data;
608 // xsize*ysize array, offsets in `data`; 0 means "no data here"
609 // slabs are sorted from bottom to top, and never intersects
610 uint[] xyofs;
612 public:
613 void save (VFile fl) {
614 fl.rawWriteExact("K8VOXDC0");
615 fl.writeNum!ushort(cast(ushort)xsize);
616 fl.writeNum!ushort(cast(ushort)ysize);
617 fl.writeNum!ushort(cast(ushort)zsize);
618 fl.writeNum(cx);
619 fl.writeNum(cy);
620 fl.writeNum(cz);
621 fl.rawWriteExact(xyofs[]);
622 fl.writeNum!uint(cast(uint)data.length);
623 fl.rawWriteExact(data[]);
627 void load (VFile fl) {
628 char[8] sign = void;
629 fl.rawReadExact(sign[]);
630 if (sign[] != "K8VOXDC0") throw new Exception("invalid compressed voxel data");
631 xsize = fl.readNum!ushort;
632 ysize = fl.readNum!ushort;
633 zsize = fl.readNum!ushort;
634 cx = fl.readNum!float;
635 cy = fl.readNum!float;
636 cz = fl.readNum!float;
637 delete xyofs;
638 xyofs = new uint[xsize*ysize];
639 fl.rawReadExact(xyofs[]);
640 const uint dsz = fl.readNum!uint;
641 delete data;
642 data = new ubyte[dsz];
643 fl.rawReadExact(data[]);
646 private:
647 void appendByte (ubyte v) @trusted {
648 pragma(inline, true);
649 data.assumeSafeAppend;
650 data ~= v;
653 void appendShort (ushort v) @trusted {
654 pragma(inline, true);
655 data.assumeSafeAppend;
656 data ~= cast(ubyte)v;
657 data.assumeSafeAppend;
658 data ~= cast(ubyte)(v>>8);
661 private:
662 private uint createSlab (ref VoxelData vox, uint dofs0) {
663 while (dofs0 && !vox.data[dofs0].cull) dofs0 = vox.data[dofs0].nextz;
664 if (!dofs0) return 0;
665 // calculate zlo and zhi, and count runs
666 ushort runcount = 0;
667 ushort z0 = vox.data[dofs0].z;
668 ushort z1 = z0;
669 ushort nxz = cast(ushort)(z0-1);
670 for (uint dofs = dofs0; dofs; dofs = vox.data[dofs].nextz) {
671 if (!vox.data[dofs].cull) continue;
672 z1 = vox.data[dofs].z;
673 if (z1 != nxz) ++runcount;
674 nxz = cast(ushort)(z1+1);
676 assert(runcount);
677 if (data.length == 0) appendByte(0); // unused
678 const uint startofs = cast(uint)data.length;
679 // zlo
680 appendShort(z0);
681 // zhi
682 appendShort(z1);
683 // runcount
684 appendShort(runcount);
685 // run index (will be filled later)
686 uint idxofs = cast(uint)data.length;
687 for (uint f = 0; f < runcount; ++f) {
688 appendShort(0); // z0
689 appendShort(0); // z1
690 appendShort(0); // offset
691 appendShort(0); // reserved
693 // last index item
694 appendShort(cast(ushort)(z1+1));
695 appendShort(cast(ushort)(z1+1));
696 appendShort(0); // offset
697 appendShort(0); // reserved
698 nxz = cast(ushort)(z0-1);
699 ushort lastz = ushort.max;
700 // put runs
701 for (uint dofs = dofs0; dofs; dofs = vox.data[dofs].nextz) {
702 if (!vox.data[dofs].cull) continue;
703 z1 = vox.data[dofs].z;
704 if (z1 != nxz) {
705 // new run
706 // fix prev run?
707 if (lastz != ushort.max) {
708 data[idxofs-6] = cast(ubyte)lastz;
709 data[idxofs-5] = cast(ubyte)(lastz>>8);
711 // set run info
712 // calculate offset
713 const uint rofs = cast(uint)data.length-idxofs;
714 assert(rofs <= 0xffffU);
715 // z0
716 data[idxofs++] = cast(ubyte)z1;
717 data[idxofs++] = cast(ubyte)(z1>>8);
718 // skip z1
719 idxofs += 2;
720 // offset
721 data[idxofs++] = cast(ubyte)rofs;
722 data[idxofs++] = cast(ubyte)(rofs>>8);
723 // skip reserved
724 idxofs += 2;
726 lastz = nxz = cast(ushort)(z1+1);
727 // b, g, r, cull
728 appendByte(vox.data[dofs].b);
729 appendByte(vox.data[dofs].g);
730 appendByte(vox.data[dofs].r);
731 appendByte(vox.data[dofs].cull);
733 // fix prev run?
734 assert(lastz != ushort.max);
735 data[idxofs-6] = cast(ubyte)lastz;
736 data[idxofs-5] = cast(ubyte)(lastz>>8);
737 return startofs;
741 void checkValidity (ref VoxelData vox) {
742 version(none) {
743 foreach (uint y; 0..ysize) {
744 foreach (uint x; 0..xsize) {
745 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
746 if (!vox.data[dofs].cull) continue;
747 const uint vd = queryVox(x, y, vox.data[dofs].z);
748 if (vd != vox.data[dofs].rgbcull()) assert(0);
752 } else {
753 foreach (uint y; 0..ysize) {
754 foreach (uint x; 0..xsize) {
755 foreach (uint z; 0..zsize) {
756 const uint vd = vox.query(x, y, z);
757 if (vd != queryVox(x, y, z)) assert(0);
764 public:
765 void checkAccessTimes (ref VoxelData vox) {
766 uint vvn = 0;
767 auto tm = iv.timer.Timer(true);
768 foreach (uint count; 0..3) {
769 foreach (uint y; 0..ysize) {
770 foreach (uint x; 0..xsize) {
771 foreach (uint z; 0..zsize) {
772 vvn += vox.query(x, y, z);
777 tm.stop();
778 conwriteln("linked list data query time: ", tm.toString(), " (", vvn, ")");
780 vvn = 0;
781 tm.restart();
782 foreach (uint count; 0..3) {
783 foreach (uint y; 0..ysize) {
784 foreach (uint x; 0..xsize) {
785 foreach (uint z; 0..zsize) {
786 vvn += queryVox(x, y, z);
791 tm.stop();
792 conwriteln("sorted array data query time: ", tm.toString(), " (", vvn, ")");
795 public:
796 void clear () {
797 delete data; data = null;
798 delete xyofs; xyofs = null;
799 xsize = ysize = zsize = 0;
800 cx = cy = cz = 0.0f;
804 void createFrom (ref VoxelData vox) {
805 clear();
806 //vox.removeEmptyVoxels(); // just in case
807 xsize = vox.xsize;
808 ysize = vox.ysize;
809 zsize = vox.zsize;
810 xyofs.length = xsize*ysize;
811 cx = vox.cx;
812 cy = vox.cy;
813 cz = vox.cz;
814 foreach (uint y; 0..ysize) {
815 foreach (uint x; 0..xsize) {
816 const uint dofs = createSlab(vox, vox.getDOfs(x, y));
817 xyofs[cast(uint)y*xsize+cast(uint)x] = dofs;
820 conwriteln("*** created compressed voxel data; ", data.length, " bytes for ",
821 xsize, "x", ysize, "x", zsize);
822 checkValidity(vox);
826 uint queryVox (int x, int y, int z) nothrow @trusted @nogc {
827 //pragma(inline, true);
828 if (x < 0 || y < 0 || z < 0) return 0;
829 if (cast(uint)x >= xsize || cast(uint)y >= ysize) return 0;
830 uint dofs = xyofs.ptr[cast(uint)y*xsize+cast(uint)x];
831 if (!dofs) return 0;
832 const(ushort) *dptr = cast(const(ushort)*)(data.ptr+dofs);
833 //conwriteln("z=", z, "; zlo=", dptr[0], "; zhi=", dptr[1], "; runcount=", dptr[2]);
834 if (cast(ushort)z < *dptr++) return 0;
835 if (cast(ushort)z > *dptr++) return 0;
836 uint runcount = *dptr++;
837 if (runcount <= 4) {
838 // there is no reason to perform binary search here
839 while (cast(ushort)z > *dptr) dptr += 4;
840 //conwriteln(" rz0=", dptr[0], "; rz1=", dptr[1], "; rofs=", dptr[2]);
841 if (z == *dptr) {
842 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dptr)+dptr[2]);
843 return *dv;
844 } else {
845 dptr -= 4;
846 const ushort cz = *dptr;
847 //conwriteln(" cz=", cz, "; cz1=", dptr[1], "; rofs=", dptr[2]);
848 assert(cz < z);
849 if (cast(ushort)z >= dptr[1]) return 0; // no such voxel
850 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dptr)+dptr[2]);
851 return *(dv+z-cz);
853 } else {
854 //{ import core.stdc.stdio : printf; printf("runcount=%u\n", cast(uint)runcount); }
855 //assert(0);
856 // perform binary search
857 uint lo = 0, hi = runcount-1;
858 for (;;) {
859 uint mid = (lo+hi)>>1;
860 const(ushort) *dp = dptr+(mid<<2);
861 if (cast(ushort)z >= dp[0] && cast(ushort)z < dp[1]) {
862 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dp)+dp[2]);
863 return *(dv+z-*dp);
865 if (cast(ushort)z < dp[0]) {
866 if (mid == lo) break;
867 hi = mid-1;
868 } else {
869 if (mid == hi) { lo = hi; break; }
870 lo = mid+1;
873 const(ushort) *dp = dptr+(lo<<2);
874 //{ import core.stdc.stdio : printf; printf("000: z=%u; lo=%u; cz0=%u; cz1=%u\n", cast(uint)z, cast(uint)lo, cast(uint)dp[0], cast(uint)dp[1]); }
875 while (cast(ushort)z >= dp[1]) dp += 4;
876 //{ import core.stdc.stdio : printf; printf("001: lo=%u; cz0=%u; cz1=%u\n", cast(uint)z, cast(uint)lo, cast(uint)dp[0], cast(uint)dp[1]); }
877 if (cast(ushort)z < dp[0]) return 0;
878 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dp)+dp[2]);
879 return *(dv+z-*dp);