normals calculation fix
[voxconv.git] / vox_data.d
blobb731bb6d990361b1b8de4367d8ca4e40155d7285
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 auto tm = iv.timer.Timer(true);
499 while (stackpos) {
500 xyz = stack[--stackpos];
501 for (uint dd = 0; dd < 6; ++dd) {
502 const int nx = cast(int)xyz.x+cullofs[dd][0];
503 const int ny = cast(int)xyz.y+cullofs[dd][1];
504 const int nz = cast(int)xyz.z+cullofs[dd][2];
505 if (bmp.setPixel(nx, ny, nz)) continue;
506 if (queryCull(nx-1, ny-1, nz-1)) continue;
507 if (stackpos == cast(uint)stack.length) {
508 stack.length += 32768;
509 stack.assumeSafeAppend;
511 stack[stackpos++] = VoxXYZ16(cast(ushort)nx, cast(ushort)ny, cast(ushort)nz);
514 tm.stop();
515 version(voxdata_debug) conwriteln("*** flooded in ", tm.toString(), " ", stack.length, " stack items used.");
517 // unmark contour voxels
518 // this is required for proper face removing
519 foreach (int y; 0..ysize) {
520 foreach (int x; 0..xsize) {
521 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
522 if (!data[dofs].cull) continue;
523 const int z = cast(int)data[dofs].z;
524 bmp.resetPixel(x+1, y+1, z+1);
529 // now check it
530 uint changed = 0;
531 foreach (int y; 0..ysize) {
532 foreach (int x; 0..xsize) {
533 for (uint dofs = getDOfs(x, y); dofs; dofs = data[dofs].nextz) {
534 immutable ubyte omask = data[dofs].cull;
535 if (!omask) continue;
536 data[dofs].cull = 0x3f;
537 // check
538 const int z = cast(int)data[dofs].z;
539 foreach (uint cidx; 0..6) {
540 immutable ubyte cmask = cullmask(cidx);
541 if (!(data[dofs].cull&cmask)) continue;
542 const int nx = x+cullofs[cidx][0];
543 const int ny = y+cullofs[cidx][1];
544 const int nz = z+cullofs[cidx][2];
545 if (bmp.getPixel(nx+1, ny+1, nz+1)) continue;
546 // reset this cull bit
547 data[dofs].cull ^= cmask;
549 changed += (omask != data[dofs].cull);
553 return changed;
556 void optimise (bool doHollowFill) @trusted {
557 version(vox_check_invariants) checkInvariants();
558 version(voxdata_debug) conwriteln("optimising mesh with ", voxpixtotal, " individual voxels...");
559 if (doHollowFill) {
560 version(voxdata_debug) conwriteln("optimising voxel culling...");
561 uint count = hollowFill();
562 if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
563 //count = fixFaceVisibility();
564 //if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
565 } else {
566 removeInsideFaces();
567 version(voxdata_debug) conwriteln("optimising voxel culling...");
568 uint count = fixFaceVisibility();
569 if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
571 removeEmptyVoxels();
572 version(vox_check_invariants) checkInvariants();
573 version(voxdata_debug) conwriteln("final optimised mesh contains ", voxpixtotal, " individual voxels...");
578 // ////////////////////////////////////////////////////////////////////////// //
580 voxel data optimised for queries
582 each slab is kept in this format:
583 ushort zlo, zhi
584 ushort runcount
585 then run index follows:
586 ushort z0
587 ushort z1+1
588 ushort ofs (from this z)
589 ushort reserved
590 index always ends with zhi+1, zhi+1
591 then (b,g,r,cull) array
593 struct VoxelDataSmall {
594 public:
595 uint xsize = 0, ysize = 0, zsize = 0;
596 float cx = 0.0f, cy = 0.0f, cz = 0.0f;
598 ubyte[] data;
599 // xsize*ysize array, offsets in `data`; 0 means "no data here"
600 // slabs are sorted from bottom to top, and never intersects
601 uint[] xyofs;
603 public:
604 void save (VFile fl) {
605 fl.rawWriteExact("K8VOXDC0");
606 fl.writeNum!ushort(cast(ushort)xsize);
607 fl.writeNum!ushort(cast(ushort)ysize);
608 fl.writeNum!ushort(cast(ushort)zsize);
609 fl.writeNum(cx);
610 fl.writeNum(cy);
611 fl.writeNum(cz);
612 fl.rawWriteExact(xyofs[]);
613 fl.writeNum!uint(cast(uint)data.length);
614 fl.rawWriteExact(data[]);
618 void load (VFile fl) {
619 char[8] sign = void;
620 fl.rawReadExact(sign[]);
621 if (sign[] != "K8VOXDC0") throw new Exception("invalid compressed voxel data");
622 xsize = fl.readNum!ushort;
623 ysize = fl.readNum!ushort;
624 zsize = fl.readNum!ushort;
625 cx = fl.readNum!float;
626 cy = fl.readNum!float;
627 cz = fl.readNum!float;
628 delete xyofs;
629 xyofs = new uint[xsize*ysize];
630 fl.rawReadExact(xyofs[]);
631 const uint dsz = fl.readNum!uint;
632 delete data;
633 data = new ubyte[dsz];
634 fl.rawReadExact(data[]);
637 private:
638 void appendByte (ubyte v) @trusted {
639 pragma(inline, true);
640 data.assumeSafeAppend;
641 data ~= v;
644 void appendShort (ushort v) @trusted {
645 pragma(inline, true);
646 data.assumeSafeAppend;
647 data ~= cast(ubyte)v;
648 data.assumeSafeAppend;
649 data ~= cast(ubyte)(v>>8);
652 private:
653 private uint createSlab (ref VoxelData vox, uint dofs0) {
654 while (dofs0 && !vox.data[dofs0].cull) dofs0 = vox.data[dofs0].nextz;
655 if (!dofs0) return 0;
656 // calculate zlo and zhi, and count runs
657 ushort runcount = 0;
658 ushort z0 = vox.data[dofs0].z;
659 ushort z1 = z0;
660 ushort nxz = cast(ushort)(z0-1);
661 for (uint dofs = dofs0; dofs; dofs = vox.data[dofs].nextz) {
662 if (!vox.data[dofs].cull) continue;
663 z1 = vox.data[dofs].z;
664 if (z1 != nxz) ++runcount;
665 nxz = cast(ushort)(z1+1);
667 assert(runcount);
668 if (data.length == 0) appendByte(0); // unused
669 const uint startofs = cast(uint)data.length;
670 // zlo
671 appendShort(z0);
672 // zhi
673 appendShort(z1);
674 // runcount
675 appendShort(runcount);
676 // run index (will be filled later)
677 uint idxofs = cast(uint)data.length;
678 for (uint f = 0; f < runcount; ++f) {
679 appendShort(0); // z0
680 appendShort(0); // z1
681 appendShort(0); // offset
682 appendShort(0); // reserved
684 // last index item
685 appendShort(cast(ushort)(z1+1));
686 appendShort(cast(ushort)(z1+1));
687 appendShort(0); // offset
688 appendShort(0); // reserved
689 nxz = cast(ushort)(z0-1);
690 ushort lastz = ushort.max;
691 // put runs
692 for (uint dofs = dofs0; dofs; dofs = vox.data[dofs].nextz) {
693 if (!vox.data[dofs].cull) continue;
694 z1 = vox.data[dofs].z;
695 if (z1 != nxz) {
696 // new run
697 // fix prev run?
698 if (lastz != ushort.max) {
699 data[idxofs-6] = cast(ubyte)lastz;
700 data[idxofs-5] = cast(ubyte)(lastz>>8);
702 // set run info
703 // calculate offset
704 const uint rofs = cast(uint)data.length-idxofs;
705 assert(rofs <= 0xffffU);
706 // z0
707 data[idxofs++] = cast(ubyte)z1;
708 data[idxofs++] = cast(ubyte)(z1>>8);
709 // skip z1
710 idxofs += 2;
711 // offset
712 data[idxofs++] = cast(ubyte)rofs;
713 data[idxofs++] = cast(ubyte)(rofs>>8);
714 // skip reserved
715 idxofs += 2;
717 lastz = nxz = cast(ushort)(z1+1);
718 // b, g, r, cull
719 appendByte(vox.data[dofs].b);
720 appendByte(vox.data[dofs].g);
721 appendByte(vox.data[dofs].r);
722 appendByte(vox.data[dofs].cull);
724 // fix prev run?
725 assert(lastz != ushort.max);
726 data[idxofs-6] = cast(ubyte)lastz;
727 data[idxofs-5] = cast(ubyte)(lastz>>8);
728 return startofs;
732 void checkValidity (ref VoxelData vox) {
733 version(none) {
734 foreach (uint y; 0..ysize) {
735 foreach (uint x; 0..xsize) {
736 for (uint dofs = vox.getDOfs(x, y); dofs; dofs = vox.data[dofs].nextz) {
737 if (!vox.data[dofs].cull) continue;
738 const uint vd = queryVox(x, y, vox.data[dofs].z);
739 if (vd != vox.data[dofs].rgbcull()) assert(0);
743 } else {
744 foreach (uint y; 0..ysize) {
745 foreach (uint x; 0..xsize) {
746 foreach (uint z; 0..zsize) {
747 const uint vd = vox.query(x, y, z);
748 if (vd != queryVox(x, y, z)) assert(0);
755 public:
756 void checkAccessTimes (ref VoxelData vox) {
757 uint vvn = 0;
758 auto tm = iv.timer.Timer(true);
759 foreach (uint count; 0..3) {
760 foreach (uint y; 0..ysize) {
761 foreach (uint x; 0..xsize) {
762 foreach (uint z; 0..zsize) {
763 vvn += vox.query(x, y, z);
768 tm.stop();
769 conwriteln("linked list data query time: ", tm.toString(), " (", vvn, ")");
771 vvn = 0;
772 tm.restart();
773 foreach (uint count; 0..3) {
774 foreach (uint y; 0..ysize) {
775 foreach (uint x; 0..xsize) {
776 foreach (uint z; 0..zsize) {
777 vvn += queryVox(x, y, z);
782 tm.stop();
783 conwriteln("sorted array data query time: ", tm.toString(), " (", vvn, ")");
786 public:
787 void clear () {
788 delete data; data = null;
789 delete xyofs; xyofs = null;
790 xsize = ysize = zsize = 0;
791 cx = cy = cz = 0.0f;
795 void createFrom (ref VoxelData vox) {
796 clear();
797 //vox.removeEmptyVoxels(); // just in case
798 xsize = vox.xsize;
799 ysize = vox.ysize;
800 zsize = vox.zsize;
801 xyofs.length = xsize*ysize;
802 cx = vox.cx;
803 cy = vox.cy;
804 cz = vox.cz;
805 foreach (uint y; 0..ysize) {
806 foreach (uint x; 0..xsize) {
807 const uint dofs = createSlab(vox, vox.getDOfs(x, y));
808 xyofs[cast(uint)y*xsize+cast(uint)x] = dofs;
811 conwriteln("*** created compressed voxel data; ", data.length, " bytes for ",
812 xsize, "x", ysize, "x", zsize);
813 checkValidity(vox);
817 uint queryVox (int x, int y, int z) nothrow @trusted @nogc {
818 //pragma(inline, true);
819 if (x < 0 || y < 0 || z < 0) return 0;
820 if (cast(uint)x >= xsize || cast(uint)y >= ysize) return 0;
821 uint dofs = xyofs.ptr[cast(uint)y*xsize+cast(uint)x];
822 if (!dofs) return 0;
823 const(ushort) *dptr = cast(const(ushort)*)(data.ptr+dofs);
824 //conwriteln("z=", z, "; zlo=", dptr[0], "; zhi=", dptr[1], "; runcount=", dptr[2]);
825 if (cast(ushort)z < *dptr++) return 0;
826 if (cast(ushort)z > *dptr++) return 0;
827 uint runcount = *dptr++;
828 if (runcount <= 4) {
829 // there is no reason to perform binary search here
830 while (cast(ushort)z > *dptr) dptr += 4;
831 //conwriteln(" rz0=", dptr[0], "; rz1=", dptr[1], "; rofs=", dptr[2]);
832 if (z == *dptr) {
833 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dptr)+dptr[2]);
834 return *dv;
835 } else {
836 dptr -= 4;
837 const ushort cz = *dptr;
838 //conwriteln(" cz=", cz, "; cz1=", dptr[1], "; rofs=", dptr[2]);
839 assert(cz < z);
840 if (cast(ushort)z >= dptr[1]) return 0; // no such voxel
841 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dptr)+dptr[2]);
842 return *(dv+z-cz);
844 } else {
845 //{ import core.stdc.stdio : printf; printf("runcount=%u\n", cast(uint)runcount); }
846 //assert(0);
847 // perform binary search
848 uint lo = 0, hi = runcount-1;
849 for (;;) {
850 uint mid = (lo+hi)>>1;
851 const(ushort) *dp = dptr+(mid<<2);
852 if (cast(ushort)z >= dp[0] && cast(ushort)z < dp[1]) {
853 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dp)+dp[2]);
854 return *(dv+z-*dp);
856 if (cast(ushort)z < dp[0]) {
857 if (mid == lo) break;
858 hi = mid-1;
859 } else {
860 if (mid == hi) { lo = hi; break; }
861 lo = mid+1;
864 const(ushort) *dp = dptr+(lo<<2);
865 //{ 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]); }
866 while (cast(ushort)z >= dp[1]) dp += 4;
867 //{ 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]); }
868 if (cast(ushort)z < dp[0]) return 0;
869 const(uint) *dv = cast(const(uint)*)((cast(const(ubyte)*)dp)+dp[2]);
870 return *(dv+z-*dp);