fixed quad texture mapping
[voxconv.git] / vox_data.d
blob39cd59c33232ccd1c2d06229ef7972e65540829d
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;
16 // ////////////////////////////////////////////////////////////////////////// //
17 static align(1) struct VoxXYZ16 {
18 align(1):
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 {
29 align(1):
30 ubyte b, g, r;
31 ubyte cull;
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 {
36 pragma(inline, true);
37 return 0xff000000U|b|(cast(uint)g<<8)|(cast(uint)r<<16);
40 uint rgbcull () const pure nothrow @safe @nogc {
41 pragma(inline, true);
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
54 not really important.
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.
68 struct VoxelData {
69 public:
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
76 uint[] xyofs;
77 uint freelist = 0;
78 uint voxpixtotal = 0;
80 private:
81 uint allocVox () nothrow @safe {
82 assert(data.length);
83 ++voxpixtotal;
84 if (!freelist) {
85 if (data.length >= 0x3fffffffU) assert(0, "too many voxels");
86 const uint lastel = cast(uint)data.length;
87 data.length += 1024;
88 freelist = cast(uint)data.length-1;
89 while (freelist >= lastel) {
90 data[freelist].nextz = freelist+1;
91 --freelist;
93 freelist = lastel;
94 data[$-1].nextz = 0;
96 const uint res = freelist;
97 freelist = data[res].nextz;
98 return res;
101 public:
102 void clear () {
103 delete data;
104 data = null;
105 delete xyofs;
106 xyofs = null;
107 xsize = ysize = zsize = 0;
108 cx = cy = cz = 0.0f;
109 freelist = 0;
110 voxpixtotal = 0;
113 void setSize (uint xs, uint ys, uint zs) {
114 clear();
115 if (!xs || !ys || !zs) return;
116 xsize = xs;
117 ysize = ys;
118 zsize = zs;
119 xyofs = new uint[xsize*ysize];
120 xyofs[] = 0;
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);
135 while (dofs) {
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;
140 return 0;
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);
147 if (!dofs) return 0;
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);
167 uint prevdofs = 0;
168 while (dofs) {
169 if (data[dofs].z == cast(ushort)z) {
170 // remove this voxel
171 if (prevdofs) {
172 data[prevdofs].nextz = data[dofs].nextz;
173 } else {
174 xyofs[cast(uint)y*xsize+cast(uint)x] = data[dofs].nextz;
176 data[dofs].nextz = freelist;
177 freelist = dofs;
178 --voxpixtotal;
179 return;
181 if (data[dofs].z > cast(ushort)z) return;
182 prevdofs = dofs;
183 dofs = data[dofs].nextz;
187 void addVoxel (int x, int y, int z, uint rgb, ubyte cull) nothrow @safe {
188 cull &= 0x3f;
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);
193 uint prevdofs = 0;
194 while (dofs) {
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;
201 return;
203 if (data[dofs].z > cast(ushort)z) break;
204 prevdofs = dofs;
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;
215 if (prevdofs) {
216 assert(data[prevdofs].nextz == dofs);
217 data[prevdofs].nextz = vidx;
218 } else {
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...");
232 uint voxcount = 0;
233 foreach (uint y; 0..ysize) {
234 foreach (uint x; 0..xsize) {
235 uint dofs = getDOfs(x, y);
236 if (!dofs) continue;
237 ++voxcount;
238 ushort prevz = data[dofs].z;
239 dofs = data[dofs].nextz;
240 while (dofs) {
241 ++voxcount;
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...");
253 uint count = 0;
254 foreach (uint y; 0..ysize) {
255 foreach (uint x; 0..xsize) {
256 uint dofs = getDOfs(x, y);
257 if (!dofs) continue;
258 uint prevdofs = 0;
259 while (dofs) {
260 if (!data[dofs].cull) {
261 // remove it
262 const uint ndofs = data[dofs].nextz;
263 if (prevdofs) {
264 data[prevdofs].nextz = ndofs;
265 } else {
266 xyofs[cast(uint)y*xsize+cast(uint)x] = ndofs;
268 data[dofs].nextz = freelist;
269 freelist = dofs;
270 --voxpixtotal;
271 dofs = ndofs;
272 ++count;
273 } else {
274 prevdofs = dofs;
275 dofs = data[dofs].nextz;
280 if (count) conwriteln("removed ", count, " empty voxel", (count != 1 ? "s" : ""));
283 static immutable int[3][6] cullofs = [
284 [ 1, 0, 0], // left
285 [-1, 0, 0], // right
286 [ 0,-1, 0], // near
287 [ 0, 1, 0], // far
288 [ 0, 0, 1], // top
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);
297 // opposite mask
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;
310 // check
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;
321 uint myofs = dofs;
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);
327 if (!sofs) break;
328 if (!(data[sofs].cull&checkmask)) break;
329 // fix culls
330 data[myofs].cull ^= cmask;
331 data[sofs].cull &= cast(ubyte)(~cast(uint)opmask);
332 vx = sx;
333 vy = sy;
334 vz = sz;
335 myofs = sofs;
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 {
346 uint count = 0;
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);
366 return count;
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 {
373 Vox3DBitmap bmp;
374 scope(exit) { bmp.clear(); }
375 bmp.setSize(xsize+2, ysize+2, zsize+2);
377 VoxXYZ16[] stack;
378 scope(exit) delete stack;
379 uint stackpos;
381 stack.length = 32768;
382 stack.assumeSafeAppend;
383 stackpos = 0;
384 assert(xsize <= cast(uint)stack.length);
386 // this is definitely empty
387 VoxXYZ16 xyz;
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 = [
393 [-1, 0, 0],
394 [ 1, 0, 0],
395 [ 0,-1, 0],
396 [ 0, 1, 0],
397 [ 0, 0,-1],
398 [ 0, 0, 1],
401 auto tm = iv.timer.Timer(true);
402 while (stackpos) {
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);
417 tm.stop();
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);
432 // now check it
433 uint changed = 0;
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;
440 // check
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);
456 return changed;
459 void optimise (bool doHollowFill) @trusted {
460 version(vox_check_invariants) checkInvariants();
461 version(voxdata_debug) conwriteln("optimising mesh with ", voxpixtotal, " individual voxels...");
462 if (doHollowFill) {
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" : ""));
468 } else {
469 removeInsideFaces();
470 version(voxdata_debug) conwriteln("optimising voxel culling...");
471 uint count = fixFaceVisibility();
472 if (count) conwriteln("fixed ", count, " voxel", (count != 1 ? "s" : ""));
474 removeEmptyVoxels();
475 version(vox_check_invariants) checkInvariants();
476 version(voxdata_debug) conwriteln("final optimised mesh contains ", voxpixtotal, " individual voxels...");