Plasma Engine  2.0
Loading...
Searching...
No Matches
ogt_voxel_meshify.h
1/*
2 opengametools voxel meshifier - v0.9 - MIT license - Justin Paver, April 2020
3
4 This is a single-header-file library that provides easy-to-use
5 support for converting paletted voxel grid data into an indexed triangle mesh.
6
7 Please see the MIT license information at the end of this file.
8
9 Also, please consider sharing any improvements you make.
10
11 For more information and more tools, visit:
12 https://github.com/jpaver/opengametools
13
14 USAGE
15
16 1. load your voxel grid data and palette data (see ogt_vox_model inside ogt_vox_loader.h for example)
17 and obtain the x, y and z dimensions of the grid.
18
19 2. convert into a mesh
20
21 ogt_mesh* mesh = ogt_mesh_from_paletted_voxels_simple( voxel_data, size_x, size_y, size_z, voxel_palette );
22
23 3. use the indexed triangle list in the mesh to construct renderable geometry, collision geometry.
24
25 // This is old sceen OpenGL immediate mode rendering for demonstration purposes only.
26 // Ideally you'd use more modern practices for rendering, including converting ogt_mesh data to
27 // your own engine's layout.
28
29 glBegin(GL_TRIANGLES);
30 for (uint32_t i = 0; i < mesh->index_count; i+=3)
31 {
32 uint32_t i0 = mesh->indices[i + 0];
33 uint32_t i1 = mesh->indices[i + 1];
34 uint32_t i2 = mesh->indices[i + 2];
35 const ogt_mesh_vertex* v0 = &mesh->vertices[i0];
36 const ogt_mesh_vertex* v1 = &mesh->vertices[i1];
37 const ogt_mesh_vertex* v2 = &mesh->vertices[i2];
38 glColor4ubv(&v0->color);
39 glNormal3fv(&v0->normal);
40 glVertex3fv(&v0->pos);
41 glColor4ubv(&v1->color);
42 glNormal3fv(&v1->normal);
43 glVertex3fv(&v1->pos);
44 glColor4ubv(&v2->color);
45 glNormal3fv(&v2->normal);
46 glVertex3fv(&v2->pos);
47 }
48 glEnd();
49
50 EXPLANATION
51
52 We currently only support paletted voxel data as input to the meshing algorithms here.
53
54 Paletted voxel mesh data assumes each voxel within the grid is a single byte
55 that represents a color index into a 256 color palette.
56
57 If the color index is 0, the voxel is assumed to be empty, otherwise it is solid.
58 For this reason, palette[0] will never be used.
59
60 Voxel data is laid out in x, then y, then z order. In other words, given
61 a coordinate (x,y,z) within your grid, you can compute where it is in your voxel
62 array using the following logic:
63
64 voxel_index = x + (y * size_x) + (z * size_x * size_y);
65
66 We support the following algorithms for meshing the voxel data for now:
67
68 * ogt_mesh_from_paletted_voxels_simple: creates 2 triangles for every visible voxel face.
69 * ogt_mesh_from_paletted_voxels_greedy: creates 2 triangles for every rectangular region of voxel faces with the same color
70 * ogt_mesh_from_paletted_voxels_polygon: determines the polygon contour of every connected voxel face with the same color and then triangulates that.
71*/
72#ifndef OGT_VOXEL_MESHIFY_H__
73# define OGT_VOXEL_MESHIFY_H__
74
75
76# if _MSC_VER == 1400
77// VS2005 doesn't have inttypes or stdint so we just define what we need here.
78typedef unsigned char uint8_t;
79typedef signed int int32_t;
80typedef unsigned int uint32_t;
81typedef unsigned short uint16_t;
82# ifndef UINT32_MAX
83# define UINT32_MAX 0xFFFFFFFF
84# endif
85# elif defined(_MSC_VER)
86// general VS*
87# include <inttypes.h>
88# elif __APPLE__
89// general Apple compiler
90# elif defined(__GNUC__)
91// any GCC*
92# include <inttypes.h>
93# include <stdlib.h> // for size_t
94# else
95# error some fixup needed for this platform?
96# endif
97
98
99// a 3 dimensional quantity
101{
102 float x, y, z;
103};
104
105// a color
107{
108 uint8_t r, g, b, a;
109};
110
111// represents a vertex
113{
114 ogt_mesh_vec3 pos;
115 ogt_mesh_vec3 normal;
116 ogt_mesh_rgba color;
117};
118
119// a mesh that contains an indexed triangle list of vertices
121{
122 uint32_t vertex_count; // number of vertices
123 uint32_t index_count; // number of indices
124 ogt_mesh_vertex* vertices; // array of vertices
125 uint32_t* indices; // array of indices
126};
127
128// allocate memory function interface. pass in size, and get a pointer to memory with at least that size available.
129typedef void* (*ogt_voxel_meshify_alloc_func)(size_t size, void* user_data);
130
131// free memory function interface. pass in a pointer previously allocated and it will be released back to the system managing memory.
132typedef void (*ogt_voxel_meshify_free_func)(void* ptr, void* user_data);
133
134// stream function can receive a batch of triangles for each voxel processed by ogt_stream_from_paletted_voxels_simple. (i,j,k)
135typedef void (*ogt_voxel_simple_stream_func)(uint32_t x, uint32_t y, uint32_t z, const ogt_mesh_vertex* vertices, uint32_t vertex_count, const uint32_t* indices, uint32_t index_count, void* user_data);
136
137// a context that allows you to override various internal operations of the below api functions.
139{
140 ogt_voxel_meshify_alloc_func alloc_func; // override allocation function
141 ogt_voxel_meshify_free_func free_func; // override free function
142 void* alloc_free_user_data; // alloc/free user-data (passed to alloc_func / free_func )
143};
144
145// returns the number of quad faces that would be generated by tessellating the specified voxel field using the simple algorithm. Useful for preallocating memory.
146// number of vertices needed would 4x this value, and number of indices needed would be 6x this value.
147uint32_t ogt_face_count_from_paletted_voxels_simple(const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z);
148
149// The simple meshifier returns the most naieve mesh possible, which will be tessellated at voxel granularity.
150ogt_mesh* ogt_mesh_from_paletted_voxels_simple(const ogt_voxel_meshify_context* pCtx, const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette);
151
152// The greedy meshifier will use a greedy box-expansion pass to replace the polygons of adjacent voxels of the same color with a larger polygon that covers the box.
153// It will generally produce t-junctions which can make rasterization not water-tight based on your camera/project/distances.
154ogt_mesh* ogt_mesh_from_paletted_voxels_greedy(const ogt_voxel_meshify_context* pCtx, const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette);
155
156// The polygon meshifier will polygonize and triangulate connected voxels that are of the same color. The boundary of the polygon
157// will be tessellated only to the degree that is necessary to there are tessellations at color discontinuities.
158// This will mostly be water-tight, except for a very small number of cases.
159ogt_mesh* ogt_mesh_from_paletted_voxels_polygon(const ogt_voxel_meshify_context* pCtx, const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette);
160
161// ogt_mesh_remove_duplicate_vertices will in-place remove identical vertices and remap indices to produce an identical mesh.
162// Use this after a call to ogt_mesh_from_paletted_voxels_* functions to remove duplicate vertices with the same attributes.
163void ogt_mesh_remove_duplicate_vertices(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh);
164
165// Removes faceted normals on the mesh and averages vertex normals based on the faces that are adjacent.
166// It is recommended only to call this on ogt_mesh_from_paletted_voxels_simple.
167void ogt_mesh_smooth_normals(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh);
168
169// destroys the mesh returned by ogt_mesh_from_paletted_voxels* functions.
170void ogt_mesh_destroy(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh);
171
172// The simple stream function will stream geometry for the specified voxel field, to the specified stream function, which will be invoked on each voxel that requires geometry.
173void ogt_stream_from_paletted_voxels_simple(const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette, ogt_voxel_simple_stream_func stream_func, void* pStream_func_data);
174
175
176#endif // OGT_VOXEL_MESHIFY_H__
177
178//-----------------------------------------------------------------------------------------------------------------
179//
180// If you're only interested in using this library, everything you need is above this point.
181// If you're interested in how this library works, everything you need is below this point.
182//
183//-----------------------------------------------------------------------------------------------------------------
184#ifdef OGT_VOXEL_MESHIFY_IMPLEMENTATION
185# include <assert.h>
186# include <math.h>
187# include <memory.h>
188# include <stdlib.h>
189
190// a set of up to 65536 bits
191struct ogt_mesh_bitset_64k
192{
193 uint8_t bits[8192];
194 void clear(uint32_t max_bits)
195 {
196 if (max_bits > (8192 * 8))
197 max_bits = (8192 * 8);
198 memset(bits, 0, (max_bits + 7) / 8);
199 }
200 uint8_t is_set(uint32_t index) { return bits[index / 8] & (1 << (index % 8)); }
201 void set(uint32_t index) { bits[index / 8] |= (1 << (index % 8)); }
202 void unset(uint32_t index) { bits[index / 8] &= ~(1 << (index % 8)); }
203};
204
205static void* _voxel_meshify_malloc(const ogt_voxel_meshify_context* pCtx, size_t uiSize)
206{
207 return uiSize ? (pCtx->alloc_func ? pCtx->alloc_func(uiSize, pCtx->alloc_free_user_data) : malloc(uiSize)) : NULL;
208}
209
210static void _voxel_meshify_free(const ogt_voxel_meshify_context* pCtx, void* pOld_ptr)
211{
212 if (pOld_ptr)
213 {
214 if (pCtx->free_func)
215 pCtx->free_func(pOld_ptr, pCtx->alloc_free_user_data);
216 else
217 free(pOld_ptr);
218 }
219}
220
221// column-major 4x4 matrix
222struct ogt_mesh_transform
223{
224 float m00, m01, m02, m03; // column 0 of 4x4 matrix, 1st three elements = x axis vector, last element always 0.0
225 float m10, m11, m12, m13; // column 1 of 4x4 matrix, 1st three elements = y axis vector, last element always 0.0
226 float m20, m21, m22, m23; // column 2 of 4x4 matrix, 1st three elements = z axis vector, last element always 0.0
227 float m30, m31, m32, m33; // column 3 of 4x4 matrix. 1st three elements = translation vector, last element always 1.0
228};
229
230// replaces transforms the point computes: transform * (vec.x, vec.y, vec.z, 1.0)
231inline ogt_mesh_vec3 _transform_point(const ogt_mesh_transform& transform, const ogt_mesh_vec3& vec)
232{
233 ogt_mesh_vec3 ret;
234 ret.x = transform.m30 + (transform.m00 * vec.x) + (transform.m10 * vec.y) + (transform.m20 * vec.z);
235 ret.y = transform.m31 + (transform.m01 * vec.x) + (transform.m11 * vec.y) + (transform.m21 * vec.z);
236 ret.z = transform.m32 + (transform.m02 * vec.x) + (transform.m12 * vec.y) + (transform.m22 * vec.z);
237 return ret;
238}
239
240// replaces transforms the point computes: transform * (vec.x, vec.y, vec.z, 0.0)
241inline ogt_mesh_vec3 _transform_vector(const ogt_mesh_transform& transform, const ogt_mesh_vec3& vec)
242{
243 ogt_mesh_vec3 ret;
244 ret.x = (transform.m00 * vec.x) + (transform.m10 * vec.y) + (transform.m20 * vec.z);
245 ret.y = (transform.m01 * vec.x) + (transform.m11 * vec.y) + (transform.m21 * vec.z);
246 ret.z = (transform.m02 * vec.x) + (transform.m12 * vec.y) + (transform.m22 * vec.z);
247 return ret;
248}
249
250inline ogt_mesh_transform _make_transform(
251 float f00, float f01, float f02, float f03,
252 float f10, float f11, float f12, float f13,
253 float f20, float f21, float f22, float f23,
254 float f30, float f31, float f32, float f33)
255{
256 ogt_mesh_transform ret;
257 ret.m00 = f00;
258 ret.m01 = f01;
259 ret.m02 = f02;
260 ret.m03 = f03;
261 ret.m10 = f10;
262 ret.m11 = f11;
263 ret.m12 = f12;
264 ret.m13 = f13;
265 ret.m20 = f20;
266 ret.m21 = f21;
267 ret.m22 = f22;
268 ret.m23 = f23;
269 ret.m30 = f30;
270 ret.m31 = f31;
271 ret.m32 = f32;
272 ret.m33 = f33;
273 return ret;
274}
275
276inline ogt_mesh_vec3 _make_vec3(float x, float y, float z)
277{
278 ogt_mesh_vec3 ret;
279 ret.x = x;
280 ret.y = y;
281 ret.z = z;
282 return ret;
283}
284
285static inline const ogt_mesh_vec3* _make_vec3_ptr(const float* pXyz_elements)
286{
287 return (ogt_mesh_vec3*)pXyz_elements;
288}
289
290static inline float _dot3(const ogt_mesh_vec3& a, const ogt_mesh_vec3& b)
291{
292 return (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
293}
294
295static inline ogt_mesh_vec3 _cross3(const ogt_mesh_vec3& a, const ogt_mesh_vec3& b)
296{
297 ogt_mesh_vec3 ret;
298 ret.x = (a.y * b.z) - (a.z * b.y);
299 ret.y = (a.z * b.x) - (a.x * b.z);
300 ret.z = (a.x * b.y) - (a.y * b.x);
301 return ret;
302}
303
304static inline ogt_mesh_vec3 _sub3(const ogt_mesh_vec3& a, const ogt_mesh_vec3& b)
305{
306 ogt_mesh_vec3 ret;
307 ret.x = a.x - b.x;
308 ret.y = a.y - b.y;
309 ret.z = a.z - b.z;
310 return ret;
311}
312static inline ogt_mesh_vec3 _add3(const ogt_mesh_vec3& a, const ogt_mesh_vec3& b)
313{
314 ogt_mesh_vec3 ret;
315 ret.x = a.x + b.x;
316 ret.y = a.y + b.y;
317 ret.z = a.z + b.z;
318 return ret;
319}
320static inline ogt_mesh_vec3 _normalize3(const ogt_mesh_vec3& a)
321{
322 float len = sqrtf(a.x * a.x + a.y * a.y + a.z * a.z);
323 assert(len > 0.0f);
324 float len_inv = 1.0f / len;
325 ogt_mesh_vec3 ret;
326 ret.x = a.x * len_inv;
327 ret.y = a.y * len_inv;
328 ret.z = a.z * len_inv;
329 return ret;
330}
331
332static inline ogt_mesh_vertex _mesh_make_vertex(const ogt_mesh_vec3& pos, const ogt_mesh_vec3& normal, const ogt_mesh_rgba color)
333{
334 ogt_mesh_vertex ret;
335 ret.pos = pos;
336 ret.normal = normal;
337 ret.color = color;
338 return ret;
339}
340
341static inline ogt_mesh_vertex _mesh_make_vertex(float fPos_x, float fPos_y, float fPos_z, float fNormal_x, float fNormal_y, float fNormal_z, ogt_mesh_rgba color)
342{
343 return _mesh_make_vertex(_make_vec3(fPos_x, fPos_y, fPos_z), _make_vec3(fNormal_x, fNormal_y, fNormal_z), color);
344}
345
346// counts the number of voxel sized faces that are needed for this voxel grid.
347static uint32_t _count_voxel_sized_faces(const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z)
348{
349 const int32_t k_stride_x = 1;
350 const int32_t k_stride_y = size_x;
351 const int32_t k_stride_z = size_x * size_y;
352 const int32_t k_max_x = size_x - 1;
353 const int32_t k_max_y = size_y - 1;
354 const int32_t k_max_z = size_z - 1;
355
356 uint32_t face_count = 0;
357
358 const uint8_t* current_voxel = pVoxels;
359 for (uint32_t k = 0; k < size_z; k++)
360 {
361 for (uint32_t j = 0; j < size_y; j++)
362 {
363 for (uint32_t i = 0; i < size_x; i++, current_voxel++)
364 {
365 if (current_voxel[0] != 0) // voxel is not empty.
366 {
367 // check each of the -X,+X,-Y,+Y,-Z,+Z directions to see if a face is needed in that direction.
368 face_count += ((i == 0) || (current_voxel[-k_stride_x] == 0)) ? 1 : 0; // if on min x boundary of voxel grid, or neighbor to -1 on x is empty
369 face_count += ((i == k_max_x) || (current_voxel[k_stride_x] == 0)) ? 1 : 0; // if on max x boundary of voxel grid, or neighbor to +1 on x is empty
370 face_count += ((j == 0) || (current_voxel[-k_stride_y] == 0)) ? 1 : 0; // if on min y boundary of voxel grid, or neighbor to -1 on y is empty
371 face_count += ((j == k_max_y) || (current_voxel[k_stride_y] == 0)) ? 1 : 0; // if on max y boundary of voxel grid, or neighbor to +1 on y is empty
372 face_count += ((k == 0) || (current_voxel[-k_stride_z] == 0)) ? 1 : 0; // if on min z boundary of voxel grid, or neighbor to -1 on z is empty
373 face_count += ((k == k_max_z) || (current_voxel[k_stride_z] == 0)) ? 1 : 0; // if on max z boundary of voxel grid, or neighbor to +1 on z is empty
374 }
375 }
376 }
377 }
378 return face_count;
379}
380
381
382// murmur_hash2 - this variant deals with only 4 bytes at a time
383static uint32_t murmur_hash2_size4(uint32_t h, const uint32_t* pData, uint32_t data_len)
384{
385 assert(data_len % 4 == 0);
386 const uint32_t m = 0x5bd1e995;
387 while (data_len >= 4)
388 {
389 uint32_t k = pData[0];
390 k *= m;
391 k ^= k >> (signed)24;
392 k *= m;
393 h *= m;
394 h ^= k;
395 pData++;
396 data_len -= 4;
397 }
398
399 return h;
400}
401
402// quadratic probing in the hash table.
403static uint32_t* hash_table_find_vertex(uint32_t* pTable, uint32_t table_index_mask, const uint8_t* pVertex_data, uint32_t vertex_size, uint32_t vertex_index)
404{
405 uint32_t* this_vertex = (uint32_t*)&pVertex_data[vertex_index * vertex_size];
406 uint32_t bucket_index = murmur_hash2_size4(0, this_vertex, vertex_size) & table_index_mask;
407
408 for (uint32_t probe_count = 0; probe_count <= table_index_mask; probe_count++)
409 {
410 uint32_t* existing_index = &pTable[bucket_index];
411 // if there is an uninitialized value at this bucket, the vertex is definitely not already in the hash table.
412 if (*existing_index == UINT32_MAX)
413 return existing_index;
414 // this item is potentially in the table, we compare to see if the existing vertex matches the one we're trying to find.
415 uint32_t* existing_vertex = (uint32_t*)&pVertex_data[*existing_index * vertex_size];
416 if (memcmp(this_vertex, existing_vertex, vertex_size) == 0)
417 {
418 assert(*existing_index < vertex_index);
419 return existing_index;
420 }
421 // use quadratic probing to find the next bucket in the case of a collision.
422 bucket_index = (bucket_index + probe_count + 1) & table_index_mask;
423 }
424 // hash table is full. We should technically never get here because we always allocate more buckets in the table than vertices we search for.
425 assert(false);
426 return NULL;
427}
428
429
430// quadratic probing in the hash table
431static uint32_t* hash_table_find_vertex_position(uint32_t* pTable, uint32_t table_index_mask, const ogt_mesh_vertex* pVertex_data, uint32_t vertex_index)
432{
433 const ogt_mesh_vertex* this_vertex = &pVertex_data[vertex_index];
434 uint32_t bucket_index = murmur_hash2_size4(0, (uint32_t*)&this_vertex->pos, sizeof(this_vertex->pos)) & table_index_mask;
435
436 for (uint32_t probe_count = 0; probe_count <= table_index_mask; probe_count++)
437 {
438 uint32_t* existing_index = &pTable[bucket_index];
439 // if there is an uninitialized value at this bucket, the vertex is definitely not already in the hash table.
440 if (*existing_index == UINT32_MAX)
441 return existing_index;
442 // this item is potentially in the table, we compare to see if the existing vertex matches the one we're trying to find.
443 const ogt_mesh_vertex* existing_vertex = &pVertex_data[*existing_index];
444 if (memcmp(&this_vertex->pos, &existing_vertex->pos, sizeof(this_vertex->pos)) == 0)
445 {
446 assert(*existing_index < vertex_index);
447 return existing_index;
448 }
449 // use quadratic probing to find the next bucket in the case of a collision.
450 bucket_index = (bucket_index + probe_count + 1) & table_index_mask;
451 }
452 // hash table is full. We should technically never get here because we always allocate more buckets in the table than vertices we search for.
453 assert(false);
454 return NULL;
455}
456
457// removes duplicate vertices in-place from the specified mesh.
458void ogt_mesh_remove_duplicate_vertices(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh)
459{
460
461 uint32_t* indices = pMesh->indices;
462 uint32_t index_count = pMesh->index_count;
463 uint8_t* vertices = (uint8_t*)pMesh->vertices;
464 uint32_t vertex_count = pMesh->vertex_count;
465 uint32_t vertex_size = sizeof(ogt_mesh_vertex);
466 assert(indices && index_count && vertices && vertex_count && vertex_size);
467
468 // allocate a hash table that is sized at the next power of 2 above the vertex count
469 uint32_t hash_table_size = 1;
470 while (hash_table_size < vertex_count)
471 hash_table_size *= 2;
472 uint32_t hash_table_mask = hash_table_size - 1;
473 uint32_t* hash_table = (uint32_t*)_voxel_meshify_malloc(pCtx, sizeof(uint32_t) * hash_table_size);
474 memset(hash_table, -1, hash_table_size * sizeof(uint32_t));
475
476 // generate an remap table for vertex indices
477 uint32_t* remap_indices = (uint32_t*)_voxel_meshify_malloc(pCtx, sizeof(uint32_t) * vertex_count);
478 memset(remap_indices, -1, vertex_count * sizeof(uint32_t));
479 uint32_t num_unique_vertices = 0;
480 for (uint32_t vertex_index = 0; vertex_index < vertex_count; vertex_index++)
481 {
482 uint32_t* hash_table_entry = hash_table_find_vertex(hash_table, hash_table_mask, vertices, vertex_size, vertex_index);
483 if (*hash_table_entry == UINT32_MAX)
484 {
485 // vertex is not already in the hash table. allocate a unique index for it.
486 *hash_table_entry = vertex_index;
487 ;
488 remap_indices[vertex_index] = num_unique_vertices++;
489 }
490 else
491 {
492 // vertex is already in the hash table. Point this to the index that is already existing!
493 assert(remap_indices[*hash_table_entry] != UINT32_MAX);
494 remap_indices[vertex_index] = remap_indices[*hash_table_entry];
495 }
496 }
497
498 // compact all vertices using the remap_indices map.
499 for (uint32_t i = 0; i < vertex_count; i++)
500 {
501 uint32_t dst_index = remap_indices[i];
502 uint32_t src_index = i;
503 assert(dst_index <= src_index);
504 memcpy(&vertices[dst_index * vertex_size], &vertices[src_index * vertex_size], vertex_size);
505 }
506 // remap all indices now
507 for (uint32_t i = 0; i < index_count; i++)
508 {
509 indices[i] = remap_indices[indices[i]];
510 }
511
512 _voxel_meshify_free(pCtx, hash_table);
513 _voxel_meshify_free(pCtx, remap_indices);
514
515 assert(num_unique_vertices <= pMesh->vertex_count);
516 pMesh->vertex_count = num_unique_vertices;
517}
518
519// resets normals for the mesh so they are based on triangle connectivity, while preserving triangle colors.
520void ogt_mesh_smooth_normals(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh)
521{
522 // generate an remap table for vertex indices based on the vertex positions.
523 uint32_t* remap_indices = (uint32_t*)_voxel_meshify_malloc(pCtx, sizeof(uint32_t) * pMesh->vertex_count);
524 memset(remap_indices, -1, pMesh->vertex_count * sizeof(uint32_t));
525 {
526 // allocate a hash table that is sized at the next power of 2 above the vertex count
527 uint32_t hash_table_size = 1;
528 while (hash_table_size < pMesh->vertex_count)
529 hash_table_size *= 2;
530 uint32_t hash_table_mask = hash_table_size - 1;
531 uint32_t* hash_table = (uint32_t*)_voxel_meshify_malloc(pCtx, sizeof(uint32_t) * hash_table_size);
532 memset(hash_table, -1, hash_table_size * sizeof(uint32_t));
533
534 // create a unique mapping for each vertex based purely on its position
535 uint32_t num_unique_vertices = 0;
536 for (uint32_t vertex_index = 0; vertex_index < pMesh->vertex_count; vertex_index++)
537 {
538 uint32_t* hash_table_entry = hash_table_find_vertex_position(hash_table, hash_table_mask, pMesh->vertices, vertex_index);
539 if (*hash_table_entry == UINT32_MAX)
540 {
541 // vertex is not already in the hash table. allocate a unique index for it.
542 *hash_table_entry = vertex_index;
543 remap_indices[vertex_index] = num_unique_vertices++;
544 }
545 else
546 {
547 // vertex is already in the hash table. Point this to the index that is already existing!
548 assert(remap_indices[*hash_table_entry] != UINT32_MAX);
549 remap_indices[vertex_index] = remap_indices[*hash_table_entry];
550 }
551 }
552 // now that we have remap_indices, we no longer need the hash table.
553 _voxel_meshify_free(pCtx, hash_table);
554 }
555
556 // for each triangle face, add the normal of the face to the unique normal for the vertex
557 ogt_mesh_vec3* remap_normals = (ogt_mesh_vec3*)_voxel_meshify_malloc(pCtx, sizeof(ogt_mesh_vec3) * pMesh->vertex_count);
558 memset(remap_normals, 0, sizeof(ogt_mesh_vec3) * pMesh->vertex_count);
559
560 for (uint32_t i = 0; i < pMesh->index_count; i += 3)
561 {
562 uint32_t i0 = pMesh->indices[i + 0];
563 uint32_t i1 = pMesh->indices[i + 1];
564 uint32_t i2 = pMesh->indices[i + 2];
565 ogt_mesh_vertex v0 = pMesh->vertices[i0];
566 ogt_mesh_vertex v1 = pMesh->vertices[i1];
567 ogt_mesh_vertex v2 = pMesh->vertices[i2];
568 ogt_mesh_vec3 normal = _cross3(_sub3(v1.pos, v0.pos), _sub3(v2.pos, v0.pos));
569
570 uint32_t ri0 = remap_indices[i0];
571 uint32_t ri1 = remap_indices[i1];
572 uint32_t ri2 = remap_indices[i2];
573 remap_normals[ri0] = _add3(remap_normals[ri0], normal);
574 remap_normals[ri1] = _add3(remap_normals[ri1], normal);
575 remap_normals[ri2] = _add3(remap_normals[ri2], normal);
576 }
577
578 // for each vertex, copy over remap normal if it's non-zero.
579 for (uint32_t vertex_index = 0; vertex_index < pMesh->vertex_count; vertex_index++)
580 {
581 ogt_mesh_vec3 accumulated_normal = remap_normals[remap_indices[vertex_index]];
582 if (_dot3(accumulated_normal, accumulated_normal) > 0.001f)
583 pMesh->vertices[vertex_index].normal = _normalize3(accumulated_normal);
584 }
585
586 _voxel_meshify_free(pCtx, remap_normals);
587 _voxel_meshify_free(pCtx, remap_indices);
588}
589
590static void _streaming_add_to_mesh(uint32_t x, uint32_t y, uint32_t z, const ogt_mesh_vertex* pVertices, uint32_t vertex_count, const uint32_t* pIndices, uint32_t index_count, void* pStream_func_data)
591{
592 // these params are unused for now.
593 (void)x;
594 (void)y;
595 (void)z;
596 // copy the specified vertices and indices into the mesh
597 ogt_mesh* mesh = (ogt_mesh*)pStream_func_data;
598 memcpy(&mesh->vertices[mesh->vertex_count], pVertices, vertex_count * sizeof(ogt_mesh_vertex));
599 memcpy(&mesh->indices[mesh->index_count], pIndices, index_count * sizeof(uint32_t));
600 mesh->vertex_count += vertex_count;
601 mesh->index_count += index_count;
602}
603
604// returns the number of quad faces that would be generated by tessellating the specified voxel field using the simple algorithm.
605uint32_t ogt_face_count_from_paletted_voxels_simple(const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z)
606{
607 return _count_voxel_sized_faces(pVoxels, size_x, size_y, size_z);
608}
609
610// constructs and returns a mesh from the specified voxel grid with no optimization to the geometry.
611ogt_mesh* ogt_mesh_from_paletted_voxels_simple(
612 const ogt_voxel_meshify_context* pCtx,
613 const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette)
614{
615 uint32_t max_face_count = _count_voxel_sized_faces(pVoxels, size_x, size_y, size_z);
616 uint32_t max_vertex_count = max_face_count * 4;
617 uint32_t max_index_count = max_face_count * 6;
618
619 uint32_t mesh_size = sizeof(ogt_mesh) + (max_vertex_count * sizeof(ogt_mesh_vertex)) + (max_index_count * sizeof(uint32_t));
620 ogt_mesh* mesh = (ogt_mesh*)_voxel_meshify_malloc(pCtx, mesh_size);
621 if (!mesh)
622 return NULL;
623
624 mesh->vertices = (ogt_mesh_vertex*)&mesh[1];
625 mesh->indices = (uint32_t*)&mesh->vertices[max_vertex_count];
626 mesh->vertex_count = 0;
627 mesh->index_count = 0;
628
629 ogt_stream_from_paletted_voxels_simple(pVoxels, size_x, size_y, size_z, pPalette, _streaming_add_to_mesh, mesh);
630
631 assert(mesh->vertex_count == max_vertex_count);
632 assert(mesh->index_count == max_index_count);
633 return mesh;
634}
635
636// streams geometry for each voxel at a time to a specified user function.
637void ogt_stream_from_paletted_voxels_simple(
638 const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette,
639 ogt_voxel_simple_stream_func stream_func, void* pStream_func_data)
640{
641 assert(stream_func);
642 const int32_t k_stride_x = 1;
643 const int32_t k_stride_y = size_x;
644 const int32_t k_stride_z = size_x * size_y;
645 const int32_t k_max_x = size_x - 1;
646 const int32_t k_max_y = size_y - 1;
647 const int32_t k_max_z = size_z - 1;
648
649 const uint8_t* current_voxel = pVoxels;
650
651 uint32_t total_vertex_count = 0;
652 uint32_t total_index_count = 0;
653 for (uint32_t k = 0; k < size_z; k++)
654 {
655 const float min_z = (float)k;
656 const float max_z = min_z + 1.0f;
657 for (uint32_t j = 0; j < size_y; j++)
658 {
659 const float min_y = (float)j;
660 const float max_y = min_y + 1.0f;
661 for (uint32_t i = 0; i < size_x; i++, current_voxel++)
662 {
663 // current voxel slot is empty? skip it.
664 if (current_voxel[0] == 0)
665 continue;
666
667 ogt_mesh_rgba color = pPalette[current_voxel[0]];
668
669 // determine the min/max coords of the voxel for each dimension.
670 const float min_x = (float)i;
671 const float max_x = min_x + 1.0f;
672
673 // determine which faces we need to generate
674 uint32_t neg_x = ((i == 0) || (current_voxel[-k_stride_x] == 0));
675 uint32_t pos_x = ((i == k_max_x) || (current_voxel[k_stride_x] == 0));
676 uint32_t neg_y = ((j == 0) || (current_voxel[-k_stride_y] == 0));
677 uint32_t pos_y = ((j == k_max_y) || (current_voxel[k_stride_y] == 0));
678 uint32_t neg_z = ((k == 0) || (current_voxel[-k_stride_z] == 0));
679 uint32_t pos_z = ((k == k_max_z) || (current_voxel[k_stride_z] == 0));
680
681 // count the number of faces. skip if zero.
682 const uint32_t face_count_needed = (neg_x + pos_x + neg_y + pos_y + neg_z + pos_z);
683 if (!face_count_needed)
684 continue;
685
686 // generate geometry for this voxel to a local buffer first.
687 ogt_mesh_vertex local_vertex[24];
688 uint32_t local_index[36];
689 ogt_mesh_vertex* current_vertex = local_vertex;
690 uint32_t* current_index = local_index;
691
692 // -X direction face
693 if (neg_x)
694 {
695 current_vertex[0] = _mesh_make_vertex(min_x, min_y, min_z, -1.0f, 0.0f, 0.0f, color);
696 current_vertex[1] = _mesh_make_vertex(min_x, max_y, min_z, -1.0f, 0.0f, 0.0f, color);
697 current_vertex[2] = _mesh_make_vertex(min_x, max_y, max_z, -1.0f, 0.0f, 0.0f, color);
698 current_vertex[3] = _mesh_make_vertex(min_x, min_y, max_z, -1.0f, 0.0f, 0.0f, color);
699 current_index[0] = total_vertex_count + 2;
700 current_index[1] = total_vertex_count + 1;
701 current_index[2] = total_vertex_count + 0;
702 current_index[3] = total_vertex_count + 0;
703 current_index[4] = total_vertex_count + 3;
704 current_index[5] = total_vertex_count + 2;
705 total_vertex_count += 4;
706 total_index_count += 6;
707 current_vertex += 4;
708 current_index += 6;
709 }
710
711 // +X direction face
712 if (pos_x)
713 {
714 current_vertex[0] = _mesh_make_vertex(max_x, min_y, min_z, 1.0f, 0.0f, 0.0f, color);
715 current_vertex[1] = _mesh_make_vertex(max_x, max_y, min_z, 1.0f, 0.0f, 0.0f, color);
716 current_vertex[2] = _mesh_make_vertex(max_x, max_y, max_z, 1.0f, 0.0f, 0.0f, color);
717 current_vertex[3] = _mesh_make_vertex(max_x, min_y, max_z, 1.0f, 0.0f, 0.0f, color);
718 current_index[0] = total_vertex_count + 0;
719 current_index[1] = total_vertex_count + 1;
720 current_index[2] = total_vertex_count + 2;
721 current_index[3] = total_vertex_count + 2;
722 current_index[4] = total_vertex_count + 3;
723 current_index[5] = total_vertex_count + 0;
724 total_vertex_count += 4;
725 total_index_count += 6;
726 current_vertex += 4;
727 current_index += 6;
728 }
729
730 // -Y direction face
731 if (neg_y)
732 {
733 current_vertex[0] = _mesh_make_vertex(min_x, min_y, min_z, 0.0f, -1.0f, 0.0f, color);
734 current_vertex[1] = _mesh_make_vertex(max_x, min_y, min_z, 0.0f, -1.0f, 0.0f, color);
735 current_vertex[2] = _mesh_make_vertex(max_x, min_y, max_z, 0.0f, -1.0f, 0.0f, color);
736 current_vertex[3] = _mesh_make_vertex(min_x, min_y, max_z, 0.0f, -1.0f, 0.0f, color);
737 current_index[0] = total_vertex_count + 0;
738 current_index[1] = total_vertex_count + 1;
739 current_index[2] = total_vertex_count + 2;
740 current_index[3] = total_vertex_count + 2;
741 current_index[4] = total_vertex_count + 3;
742 current_index[5] = total_vertex_count + 0;
743 total_vertex_count += 4;
744 total_index_count += 6;
745 current_vertex += 4;
746 current_index += 6;
747 }
748 // +Y direction face
749 if (pos_y)
750 {
751 current_vertex[0] = _mesh_make_vertex(min_x, max_y, min_z, 0.0f, 1.0f, 0.0f, color);
752 current_vertex[1] = _mesh_make_vertex(max_x, max_y, min_z, 0.0f, 1.0f, 0.0f, color);
753 current_vertex[2] = _mesh_make_vertex(max_x, max_y, max_z, 0.0f, 1.0f, 0.0f, color);
754 current_vertex[3] = _mesh_make_vertex(min_x, max_y, max_z, 0.0f, 1.0f, 0.0f, color);
755 current_index[0] = total_vertex_count + 2;
756 current_index[1] = total_vertex_count + 1;
757 current_index[2] = total_vertex_count + 0;
758 current_index[3] = total_vertex_count + 0;
759 current_index[4] = total_vertex_count + 3;
760 current_index[5] = total_vertex_count + 2;
761 total_vertex_count += 4;
762 total_index_count += 6;
763 current_vertex += 4;
764 current_index += 6;
765 }
766 // -Z direction face
767 if (neg_z)
768 {
769 current_vertex[0] = _mesh_make_vertex(min_x, min_y, min_z, 0.0f, 0.0f, -1.0f, color);
770 current_vertex[1] = _mesh_make_vertex(max_x, min_y, min_z, 0.0f, 0.0f, -1.0f, color);
771 current_vertex[2] = _mesh_make_vertex(max_x, max_y, min_z, 0.0f, 0.0f, -1.0f, color);
772 current_vertex[3] = _mesh_make_vertex(min_x, max_y, min_z, 0.0f, 0.0f, -1.0f, color);
773 current_index[0] = total_vertex_count + 2;
774 current_index[1] = total_vertex_count + 1;
775 current_index[2] = total_vertex_count + 0;
776 current_index[3] = total_vertex_count + 0;
777 current_index[4] = total_vertex_count + 3;
778 current_index[5] = total_vertex_count + 2;
779 total_vertex_count += 4;
780 total_index_count += 6;
781 current_vertex += 4;
782 current_index += 6;
783 }
784 // +Z direction face
785 if (pos_z)
786 {
787 current_vertex[0] = _mesh_make_vertex(min_x, min_y, max_z, 0.0f, 0.0f, 1.0f, color);
788 current_vertex[1] = _mesh_make_vertex(max_x, min_y, max_z, 0.0f, 0.0f, 1.0f, color);
789 current_vertex[2] = _mesh_make_vertex(max_x, max_y, max_z, 0.0f, 0.0f, 1.0f, color);
790 current_vertex[3] = _mesh_make_vertex(min_x, max_y, max_z, 0.0f, 0.0f, 1.0f, color);
791 current_index[0] = total_vertex_count + 0;
792 current_index[1] = total_vertex_count + 1;
793 current_index[2] = total_vertex_count + 2;
794 current_index[3] = total_vertex_count + 2;
795 current_index[4] = total_vertex_count + 3;
796 current_index[5] = total_vertex_count + 0;
797 total_vertex_count += 4;
798 total_index_count += 6;
799 current_vertex += 4;
800 current_index += 6;
801 }
802
803 // geometry for this voxel is provided to a caller-specified stream function/callback
804 stream_func(i, j, k, local_vertex, face_count_needed * 4, local_index, face_count_needed * 6, pStream_func_data);
805 }
806 }
807 }
808}
809
810
811// The base algorithm that is used here, is as follows:
812// On a per slice basis, we find a voxel that has not yet been polygonized. We then try to
813// grow a rectangle from that voxel within the slice that can be represented by a polygon.
814// We create the quad polygon to represent the voxel, mark the voxels in the slice that are
815// covered by the rectangle as having been polygonized, and continue on the search through
816// the rest of the slice.
817void _greedy_meshify_voxels_in_face_direction(
818 const uint8_t* pVoxels,
819 const ogt_mesh_rgba* pPalette,
820 int32_t size_x, int32_t size_y, int32_t size_z, // how many voxels in each of X,Y,Z dimensions
821 int32_t k_stride_x, int32_t k_stride_y, int32_t k_stride_z, // the memory stride for each of those X,Y,Z dimensions within the voxel data.
822 const ogt_mesh_transform& transform, // transform to convert from X,Y,Z to "objectSpace"
823 ogt_mesh* out_pMesh)
824{
825
826 // enable aggressive voxel optimization for now.
827 uint32_t max_voxels_per_slice = size_x * size_y;
828
829 // allocate a structure that is used for tracking which voxels in a slice have already been included in output mesh.
830 assert(max_voxels_per_slice <= 65536); //
831 ogt_mesh_bitset_64k voxel_polygonized;
832
833 ogt_mesh_vec3 normal = _transform_vector(transform, _make_vec3(0.0f, 0.0f, 1.0f));
834
835# define VOXELDATA_INDEX(_x, _y, _z) ((_x)*k_stride_x) + ((_y)*k_stride_y) + ((_z)*k_stride_z)
836# define LOCALDATA_INDEX(_x, _y) ((_x) + ((_y)*size_x))
837
838 // use this to remap parity where necessary.
839 uint32_t base_index_start = out_pMesh->index_count;
840
841 uint32_t* index_data = &out_pMesh->indices[out_pMesh->index_count];
842 ogt_mesh_vertex* vertex_data = &out_pMesh->vertices[out_pMesh->vertex_count];
843
844 // determine if the transform parity has flipped in a way that winding would have been switched.
845 const ogt_mesh_vec3* side = _make_vec3_ptr(&transform.m00);
846 const ogt_mesh_vec3* up = _make_vec3_ptr(&transform.m10);
847 const ogt_mesh_vec3* fwd = _make_vec3_ptr(&transform.m20);
848 bool is_parity_flipped = _dot3(*fwd, _cross3(*side, *up)) < 0.0f;
849
850 for (int32_t k0 = 0; k0 < size_z; k0++)
851 {
852 // k0 = current slice, k1 = next slice
853 int32_t k1 = k0 + 1;
854
855 // reset the per-voxel X/Y status for this slice.
856 voxel_polygonized.clear(max_voxels_per_slice);
857
858 // is this the last slice? If yes, we don't check
859 bool is_last_k_slice = (k1 == size_z);
860
861 // here, we search for the first unprocessed voxel
862 for (int32_t j0 = 0; j0 < size_y; j0++)
863 {
864 for (int32_t i0 = 0; i0 < size_x; i0++)
865 {
866 // determine the polygon color index
867 uint8_t color_index = pVoxels[VOXELDATA_INDEX(i0, j0, k0)];
868
869 // this voxel doesn't need to be polygonized if...
870 if ((color_index == 0) || // (1) voxel is empty
871 voxel_polygonized.is_set(LOCALDATA_INDEX(i0, j0)) || // (2) voxel is already part of a polygon for the zslice.
872 (!is_last_k_slice && pVoxels[VOXELDATA_INDEX(i0, j0, k1)] != 0)) // (3) voxel in the next slice (+z direction) is solid
873 {
874 continue;
875 }
876
877 // compute i1. This is the coord bounding the longest span of identical voxels in the +i direction.
878 int32_t i1 = i0 + 1;
879 for (i1 = i0 + 1; i1 < size_x; i1++)
880 {
881 // stop extending i1 if...
882 if ((pVoxels[VOXELDATA_INDEX(i1, j0, k0)] != color_index) || // (1) this voxel doesn't match the match color
883 (voxel_polygonized.is_set(LOCALDATA_INDEX(i1, j0))) || // (2) voxel is already part of a polygon for the zslice
884 (!is_last_k_slice && pVoxels[VOXELDATA_INDEX(i1, j0, k1)] != 0)) // (3) voxel in the next slice (+z direction) is solid
885 {
886 break;
887 }
888 }
889
890
891 // compute j1. The is the coord bounding the longest span of identical voxels [i0..i1] in the +j direction
892 int32_t j1 = j0 + 1;
893 for (j1 = j0 + 1; j1 < size_y; j1++)
894 {
895 bool got_j1 = false;
896 for (int32_t a = i0; a < i1; a++)
897 {
898 // stop extending i1 if...
899 if ((pVoxels[VOXELDATA_INDEX(a, j1, k0)] != color_index) || // (1) this voxel doesn't match the match color
900 (voxel_polygonized.is_set(LOCALDATA_INDEX(a, j1))) || // (2) voxel is already part of a polygon for the zslice
901 (!is_last_k_slice && pVoxels[VOXELDATA_INDEX(a, j1, k1)] != 0)) // (3) voxel in the next slice (+z direction) is solid
902 {
903 got_j1 = true;
904 break;
905 }
906 }
907 if (got_j1)
908 break;
909 }
910
911
912 // now j1 and i1 are the upper bound (exclusive) of the rectangle starting from i0,j0.
913 // mark all of this slice voxels in that rectangle as processed.
914 for (int32_t b = j0; b < j1; b++)
915 for (int32_t a = i0; a < i1; a++)
916 voxel_polygonized.set(LOCALDATA_INDEX(a, b));
917
918 // determine the min/max coords of the polygon for each dimension.
919 float min_x = (float)i0;
920 float max_x = (float)i1;
921 float min_y = (float)j0;
922 float max_y = (float)j1;
923 float max_z = (float)k1;
924
925 // cache the color
926 ogt_mesh_rgba color = pPalette[color_index];
927
928 // write the verts for this face
929 vertex_data[0] = _mesh_make_vertex(_transform_point(transform, _make_vec3(min_x, min_y, max_z)), normal, color);
930 vertex_data[1] = _mesh_make_vertex(_transform_point(transform, _make_vec3(max_x, min_y, max_z)), normal, color);
931 vertex_data[2] = _mesh_make_vertex(_transform_point(transform, _make_vec3(max_x, max_y, max_z)), normal, color);
932 vertex_data[3] = _mesh_make_vertex(_transform_point(transform, _make_vec3(min_x, max_y, max_z)), normal, color);
933
934 // reserve the index order to ensure parity/winding is still correct.
935 if (is_parity_flipped)
936 {
937 index_data[0] = out_pMesh->vertex_count + 0;
938 index_data[1] = out_pMesh->vertex_count + 3;
939 index_data[2] = out_pMesh->vertex_count + 2;
940 index_data[3] = out_pMesh->vertex_count + 2;
941 index_data[4] = out_pMesh->vertex_count + 1;
942 index_data[5] = out_pMesh->vertex_count + 0;
943 }
944 else
945 {
946 index_data[0] = out_pMesh->vertex_count + 0;
947 index_data[1] = out_pMesh->vertex_count + 1;
948 index_data[2] = out_pMesh->vertex_count + 2;
949 index_data[3] = out_pMesh->vertex_count + 2;
950 index_data[4] = out_pMesh->vertex_count + 3;
951 index_data[5] = out_pMesh->vertex_count + 0;
952 }
953
954 vertex_data += 4;
955 index_data += 6;
956
957 out_pMesh->vertex_count += 4;
958 out_pMesh->index_count += 6;
959 }
960 }
961 }
962
963# undef VOXELDATA_INDEX
964# undef LOCALDATA_INDEX
965}
966
967ogt_mesh* ogt_mesh_from_paletted_voxels_greedy(
968 const ogt_voxel_meshify_context* pCtx,
969 const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette)
970{
971 uint32_t max_face_count = _count_voxel_sized_faces(pVoxels, size_x, size_y, size_z);
972 uint32_t max_vertex_count = max_face_count * 4;
973 uint32_t max_index_count = max_face_count * 6;
974
975 uint32_t mesh_size = sizeof(ogt_mesh) + (max_vertex_count * sizeof(ogt_mesh_vertex)) + (max_index_count * sizeof(uint32_t));
976 ogt_mesh* mesh = (ogt_mesh*)_voxel_meshify_malloc(pCtx, mesh_size);
977 if (!mesh)
978 return NULL;
979
980 mesh->vertices = (ogt_mesh_vertex*)&mesh[1];
981 mesh->indices = (uint32_t*)&mesh->vertices[max_vertex_count];
982 mesh->vertex_count = 0;
983 mesh->index_count = 0;
984
985 const int32_t k_stride_x = 1;
986 const int32_t k_stride_y = size_x;
987 const int32_t k_stride_z = size_x * size_y;
988
989 // do the +y PASS
990 {
991 ogt_mesh_transform transform_pos_y = _make_transform(
992 0.0f, 0.0f, 1.0f, 0.0f,
993 1.0f, 0.0f, 0.0f, 0.0f,
994 0.0f, 1.0f, 0.0f, 0.0f,
995 0.0f, 0.0f, 0.0f, 0.0f);
996
997 _greedy_meshify_voxels_in_face_direction(
998 pVoxels, pPalette,
999 size_z, size_x, size_y,
1000 k_stride_z, k_stride_x, k_stride_y,
1001 transform_pos_y,
1002 mesh);
1003 }
1004 // do the -y PASS
1005 {
1006 ogt_mesh_transform transform_neg_y = _make_transform(
1007 0.0f, 0.0f, 1.0f, 0.0f,
1008 1.0f, 0.0f, 0.0f, 0.0f,
1009 0.0f, -1.0f, 0.0f, 0.0f,
1010 0.0f, (float)(size_y), 0.0f, 0.0f);
1011
1012 _greedy_meshify_voxels_in_face_direction(
1013 pVoxels + (size_y - 1) * k_stride_y,
1014 pPalette,
1015 size_z, size_x, size_y,
1016 k_stride_z, k_stride_x, -k_stride_y,
1017 transform_neg_y,
1018 mesh);
1019 }
1020 // do the +X pass
1021 {
1022 ogt_mesh_transform transform_pos_x = _make_transform(
1023 0.0f, 1.0f, 0.0f, 0.0f,
1024 0.0f, 0.0f, 1.0f, 0.0f,
1025 1.0f, 0.0f, 0.0f, 0.0f,
1026 0.0f, 0.0f, 0.0f, 0.0f);
1027
1028 _greedy_meshify_voxels_in_face_direction(
1029 pVoxels, pPalette,
1030 size_y, size_z, size_x,
1031 k_stride_y, k_stride_z, k_stride_x,
1032 transform_pos_x,
1033 mesh);
1034 }
1035 // do the -X pass
1036 {
1037 ogt_mesh_transform transform_neg_x = _make_transform(
1038 0.0f, 1.0f, 0.0f, 0.0f,
1039 0.0f, 0.0f, 1.0f, 0.0f,
1040 -1.0f, 0.0f, 0.0f, 0.0f,
1041 (float)size_x, 0.0f, 0.0f, 0.0f);
1042
1043 _greedy_meshify_voxels_in_face_direction(
1044 pVoxels + (size_x - 1) * k_stride_x,
1045 pPalette,
1046 size_y, size_z, size_x,
1047 k_stride_y, k_stride_z, -k_stride_x,
1048 transform_neg_x,
1049 mesh);
1050 }
1051 // do the +Z pass
1052 {
1053 ogt_mesh_transform transform_pos_z = _make_transform(
1054 1.0f, 0.0f, 0.0f, 0.0f,
1055 0.0f, 1.0f, 0.0f, 0.0f,
1056 0.0f, 0.0f, 1.0f, 0.0f,
1057 0.0f, 0.0f, 0.0f, 0.0f);
1058
1059 _greedy_meshify_voxels_in_face_direction(
1060 pVoxels, pPalette,
1061 size_x, size_y, size_z,
1062 k_stride_x, k_stride_y, k_stride_z,
1063 transform_pos_z,
1064 mesh);
1065 }
1066 // do the -Z pass
1067 {
1068 ogt_mesh_transform transform_neg_z = _make_transform(
1069 1.0f, 0.0f, 0.0f, 0.0f,
1070 0.0f, 1.0f, 0.0f, 0.0f,
1071 0.0f, 0.0f, -1.0f, 0.0f,
1072 0.0f, 0.0f, (float)size_z, 0.0f);
1073
1074 _greedy_meshify_voxels_in_face_direction(
1075 pVoxels + (size_z - 1) * k_stride_z,
1076 pPalette,
1077 size_x, size_y, size_z,
1078 k_stride_x, k_stride_y, -k_stride_z,
1079 transform_neg_z,
1080 mesh);
1081 }
1082
1083 assert(mesh->vertex_count <= max_vertex_count);
1084 assert(mesh->index_count <= max_index_count);
1085 return mesh;
1086}
1087
1088struct ogt_mesh_vec2i
1089{
1090 int32_t x, y;
1091};
1092
1093inline ogt_mesh_vec2i make_vec2i(int32_t x, int32_t y)
1094{
1095 ogt_mesh_vec2i ret;
1096 ret.x = x;
1097 ret.y = y;
1098 return ret;
1099}
1100
1101inline ogt_mesh_vec2i operator+(const ogt_mesh_vec2i& lhs, const ogt_mesh_vec2i& rhs)
1102{
1103 ogt_mesh_vec2i ret;
1104 ret.x = lhs.x + rhs.x;
1105 ret.y = lhs.y + rhs.y;
1106 return ret;
1107}
1108
1109inline ogt_mesh_vec2i operator-(const ogt_mesh_vec2i& lhs, const ogt_mesh_vec2i& rhs)
1110{
1111 ogt_mesh_vec2i ret;
1112 ret.x = lhs.x - rhs.x;
1113 ret.y = lhs.y - rhs.y;
1114 return ret;
1115}
1116
1117inline ogt_mesh_vec2i operator*(const ogt_mesh_vec2i& lhs, const int32_t& rhs)
1118{
1119 ogt_mesh_vec2i ret;
1120 ret.x = lhs.x * rhs;
1121 ret.y = lhs.y * rhs;
1122 return ret;
1123}
1124
1125inline bool is_vec2i_equal(const ogt_mesh_vec2i& lhs, const ogt_mesh_vec2i& rhs)
1126{
1127 return lhs.x == rhs.x && lhs.y == rhs.y;
1128}
1129
1130ogt_mesh_vec2i get_cardinal_unit_vector(const ogt_mesh_vec2i& vec)
1131{
1132 assert((vec.x == 0 && vec.y != 0) || (vec.y == 0 && vec.x != 0)); // assumes this is a cardinal vector
1133 if (vec.x <= -1)
1134 return make_vec2i(-1, 0);
1135 if (vec.x >= 1)
1136 return make_vec2i(1, 0);
1137 if (vec.y <= -1)
1138 return make_vec2i(0, -1);
1139 if (vec.y >= 1)
1140 return make_vec2i(0, 1);
1141 assert(0); // unreachable unless the above assert already failed!
1142 return make_vec2i(0, 0);
1143}
1144
1145int32_t get_cardinal_vector_length(const ogt_mesh_vec2i& vec)
1146{
1147 assert((vec.x == 0 && vec.y != 0) || (vec.y == 0 && vec.x != 0));
1148 return vec.x == 0 ? abs(vec.y) : vec.y == 0 ? abs(vec.x) : 0;
1149}
1150
1151// gets the signed area of the triangle
1152inline int32_t get_triangle_signed_area(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2)
1153{
1154 return ((v0.x - v1.x) * (v2.y - v1.y)) - ((v0.y - v1.y) * (v2.x - v1.x));
1155}
1156
1157// determines whether the triangle is convex or not
1158inline bool is_triangle_convex(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2)
1159{
1160 return get_triangle_signed_area(v0, v1, v2) > 0;
1161}
1162
1163// determines whether the point p is inside the triangle v0,v1,v2
1164bool is_point_in_triangle(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2, const ogt_mesh_vec2i& p)
1165{
1166 bool convex_v0v1 = get_triangle_signed_area(v0, v1, p) >= 0;
1167 bool convex_v1v2 = get_triangle_signed_area(v1, v2, p) >= 0;
1168 bool convex_v2v0 = get_triangle_signed_area(v2, v0, p) >= 0;
1169 return (convex_v0v1 == convex_v1v2) && (convex_v0v1 == convex_v2v0);
1170}
1171
1172uint32_t _tessellate_polygon(uint32_t* pIndices, const ogt_mesh_vec2i* pVerts, uint32_t vert_count)
1173{
1174 static const uint32_t k_max_polygon_size = 16384; // 32KB of stack!
1175 uint16_t ring_indices[k_max_polygon_size];
1176 assert(vert_count >= 3 && vert_count <= k_max_polygon_size);
1177
1178 for (uint16_t i = 0; i < vert_count; i++)
1179 ring_indices[i] = i;
1180 uint32_t ring_count = vert_count;
1181
1182 uint32_t index_count = 0;
1183
1184 // naieve "ear clipping" algorithm. Start with a ring of polygon corners.
1185 // Find 3 sequential corners on the polygon and make sure no other points of the polygon are contained within it.
1186 // If so, remove the inner point from the ring. Rinse and repeat.
1187 uint32_t no_progress_counter = 0;
1188 uint32_t i0 = 0;
1189 while (ring_count > 3)
1190 {
1191 i0 = (i0 + 0) % ring_count;
1192 uint32_t i1 = (i0 + 1) % ring_count;
1193 uint32_t i2 = (i0 + 2) % ring_count;
1194
1195 ogt_mesh_vec2i v0 = pVerts[ring_indices[i0]];
1196 ogt_mesh_vec2i v1 = pVerts[ring_indices[i1]];
1197 ogt_mesh_vec2i v2 = pVerts[ring_indices[i2]];
1198
1199 // check whether we can carve off this ear.
1200 bool can_triangulate = is_triangle_convex(v0, v1, v2);
1201
1202 if (can_triangulate)
1203 {
1204 // make sure that no other points are inside this triangle. We do allow points to be coincident with corners of the triangle though.
1205 for (uint32_t i = 0; i < ring_count; i++)
1206 {
1207 if (i == i0 || i == i1 || i == i2)
1208 continue;
1209 const ogt_mesh_vec2i& p = pVerts[ring_indices[i]];
1210 bool point_on_corner = is_vec2i_equal(v0, p) || is_vec2i_equal(v1, p) || is_vec2i_equal(v2, p);
1211 if (!point_on_corner && is_point_in_triangle(v0, v1, v2, pVerts[ring_indices[i]]))
1212 {
1213 can_triangulate = false;
1214 break;
1215 }
1216 }
1217 }
1218
1219 if (can_triangulate)
1220 {
1221 pIndices[index_count++] = (uint32_t)ring_indices[i2];
1222 pIndices[index_count++] = (uint32_t)ring_indices[i1];
1223 pIndices[index_count++] = (uint32_t)ring_indices[i0];
1224 // compact verts down in the ring indices
1225 ring_count--;
1226 for (uint32_t i = i1; i < ring_count; i++)
1227 ring_indices[i] = ring_indices[i + 1];
1228 // reset no progress counter because we just made progress!
1229 no_progress_counter = 0;
1230 }
1231 else
1232 {
1233 no_progress_counter++;
1234 i0++;
1235 }
1236 // we haven't made progress in a full trip around the ring -- the geometry is probably malformed and cannot be tessellated
1237 if (no_progress_counter == ring_count)
1238 {
1239 return index_count;
1240 }
1241 }
1242 // trailing case, just have one triangle left -- emit it.
1243 pIndices[index_count++] = (uint32_t)ring_indices[2];
1244 pIndices[index_count++] = (uint32_t)ring_indices[1];
1245 pIndices[index_count++] = (uint32_t)ring_indices[0];
1246
1247 return index_count;
1248}
1249
1250// where do we sample for the specified edge
1251ogt_mesh_vec2i get_edge_bias(const ogt_mesh_vec2i& edge_vert0, const ogt_mesh_vec2i& edge_vert1)
1252{
1253 if (edge_vert0.x < edge_vert1.x)
1254 {
1255 assert(edge_vert0.y == edge_vert1.y);
1256 return make_vec2i(0, 0);
1257 }
1258 else if (edge_vert0.x > edge_vert1.x)
1259 {
1260 assert(edge_vert0.y == edge_vert1.y);
1261 return make_vec2i(-1, -1);
1262 }
1263 else if (edge_vert0.y < edge_vert1.y)
1264 {
1265 assert(edge_vert0.x == edge_vert1.x);
1266 return make_vec2i(-1, 0);
1267 }
1268 else if (edge_vert0.y > edge_vert1.y)
1269 {
1270 assert(edge_vert0.x == edge_vert1.x);
1271 return make_vec2i(0, -1);
1272 }
1273 else
1274 {
1275 assert(0);
1276 return make_vec2i(0, 0);
1277 }
1278}
1279
1280uint32_t _tessellate_edge(ogt_mesh_vec2i* pTess, uint32_t max_tess, const ogt_mesh_vec2i& edge_vert0, const ogt_mesh_vec2i& edge_vert1,
1281 const uint8_t* pSlice_colors, int32_t size_x, int32_t size_y)
1282{
1283
1284 uint32_t num_tess = 0;
1285 int32_t edge_len = get_cardinal_vector_length(edge_vert1 - edge_vert0);
1286 ogt_mesh_vec2i edge_bias = get_edge_bias(edge_vert0, edge_vert1);
1287 ogt_mesh_vec2i edge_step = get_cardinal_unit_vector(edge_vert1 - edge_vert0);
1288 ogt_mesh_vec2i curr_pos = edge_vert0 + edge_bias;
1289
1290 // do early-out logic if the entire edge is out-of-bounds
1291 if (edge_vert0.x < edge_vert1.x)
1292 {
1293 // handle left-to-right edges
1294 assert(curr_pos.y <= size_y);
1295 if (curr_pos.y == size_y)
1296 return 0;
1297 }
1298 else if (edge_vert0.x > edge_vert1.x)
1299 {
1300 // handle right-to-left edges
1301 assert(curr_pos.y >= -1);
1302 if (curr_pos.y == -1)
1303 return 0;
1304 }
1305 else if (edge_vert0.y < edge_vert1.y)
1306 {
1307 // handle bottom-to-top edges
1308 assert(curr_pos.x >= -1);
1309 if (curr_pos.x == -1)
1310 return 0;
1311 }
1312 else if (edge_vert0.y > edge_vert1.y)
1313 {
1314 // handle top-to-bottom
1315 assert(curr_pos.x <= size_x);
1316 if (curr_pos.x == size_x)
1317 return 0;
1318 }
1319 else
1320 {
1321 assert(0);
1322 return 0;
1323 }
1324
1325 uint8_t last_color_index = pSlice_colors[curr_pos.x + (curr_pos.y * size_x)];
1326 curr_pos = curr_pos + edge_step;
1327
1328 for (int32_t i = 1; i < edge_len; i++)
1329 {
1330 uint8_t curr_color_index = pSlice_colors[curr_pos.x + (curr_pos.y * size_x)];
1331 if (curr_color_index != last_color_index)
1332 {
1333 assert(num_tess < max_tess);
1334 pTess[num_tess++] = curr_pos - edge_bias;
1335 last_color_index = curr_color_index;
1336 }
1337 curr_pos = curr_pos + edge_step;
1338 }
1339 return num_tess;
1340}
1341
1342// My algorithm for flood-filling the polygon.
1343//
1344// We start with a simple set of verts that represents a polygon ring
1345// surrounding a single voxel.
1346//
1347// v1 +---+ v2
1348// | C |
1349// v0 +---+ v3
1350//
1351// This initial state has 4 verts on the polygon boundary which
1352// represents 4 edges on the polygon boundary, and the color of
1353// the polygon is the voxel color.
1354//
1355// The algorithm from here is fairly simple:
1356// 1. select the next edge from the polygon ring
1357// 2. try extrude the edge as far as possible along its outward
1358// facing normal one voxel at a time.
1359// - stop pushing the edge outward if we'd consume an already polygonized voxel, or if the edge
1360// would push a different color to the inside of the polygon ring.
1361// 3. if the edge could be extruded, insert new points for the edge and each of its left/right
1362// neighbor edges such that color discontinuities on the outside of the edge have a vertex between them.
1363// 4. if the edge could not be extruded and we have gone around the entire ring with no progress,
1364// terminate, otherwise goto 1.
1365// 5. we now have a polygon ring that can be tessellated.
1366//
1367// Example
1368// step(1):
1369//
1370// (e1)
1371// v1 +-------+ v2
1372//(e0) | X | X | (e2) X is the color inside the polygon
1373// v0 + + v3
1374//
1375// edge1 corresponds to the next_edge_index edge within the current polygon ring.
1376//
1377// step(2)
1378// We now try to push edge1 out as far as we can by holding v0 & v3 fixed
1379// and moving v1/v2 in the direction of the edge normal:
1380//
1381// ^
1382// ^
1383// v1 +-------+ v2
1384// | X | X |
1385// | | |
1386// | X | X |
1387// | | |
1388// | X | X |
1389// v0 + + v3
1390//
1391// We can only extend the edge as long as by doing so the polygon remains
1392// filled with it's current color and if the polygon does not cover a
1393// voxel that was already polygonized by a prior pass.
1394//
1395// step(3)
1396// Once we've finished pushing v1/v2 forward, we now check whether we have
1397// to tessellate any of the edges e0, e1, e2. This is now mostly a function
1398// of which colors are on the outside of the polygon boundary.
1399//
1400// X C
1401// +---*---+
1402// B | X | X | D A,B,C,D are colors along the outside of the edges.
1403// * | | Here * are new points of the edges because of an
1404// A | X | X | D exterior change of color along the edge.
1405// | | |
1406// A | X | X | D
1407// + +
1408//
1409// We then insert the new edges into the polygon ring and select a new
1410// edge to start extruding.
1411// If we tessellated e1, then we set next_edge_index to the first child-edge
1412// on that edge again:
1413//
1414// v2
1415// v1 +---*---+ v3
1416// | X X
1417// v0 *
1418//
1419// When we can no longer extrude any of the polygon ring edges, we
1420// terminate, as that'll mean we've flood filled the space.
1421//
1422int32_t _construct_polygon_for_slice(ogt_mesh_vec2i* pVerts, uint32_t max_verts, int32_t i, int32_t j, int32_t size_x, int32_t size_y, const uint8_t* pSlice_colors, ogt_mesh_bitset_64k& ref_voxel_polygonized)
1423{
1424 assert(max_verts > 4);
1425 // start with just a single 4 vertex closed polygon
1426 pVerts[0] = make_vec2i(i, j);
1427 pVerts[1] = make_vec2i(i, j + 1);
1428 pVerts[2] = make_vec2i(i + 1, j + 1);
1429 pVerts[3] = make_vec2i(i + 1, j);
1430 int32_t vert_count = 4;
1431
1432 uint8_t polygon_color_index = pSlice_colors[i + (j * size_x)];
1433
1434 // mark this voxel as polygonized so it can't be further considered.
1435 ref_voxel_polygonized.set(i + (j * size_x));
1436
1437 int32_t next_edge_index = 0;
1438 int32_t no_progress_counter = 0;
1439 while (no_progress_counter < vert_count)
1440 {
1441 // this is just figuring out the vertices for 3 successive edges.
1442 // edge0 from v0...v1, edge1 from v1...v2, edge2 from v2...v3
1443 int32_t v0 = next_edge_index < 1 ? vert_count - 1 : next_edge_index - 1;
1444 int32_t v1 = next_edge_index;
1445 int32_t v2 = (v1 < (vert_count - 1)) ? v1 + 1 : 0;
1446 int32_t v3 = (v2 < (vert_count - 1)) ? v2 + 1 : 0;
1447
1448 // compute the direction of edge1.
1449 ogt_mesh_vec2i edge0_unitvec = get_cardinal_unit_vector(pVerts[v1] - pVerts[v0]);
1450 ogt_mesh_vec2i edge1_unitvec = get_cardinal_unit_vector(pVerts[v2] - pVerts[v1]);
1451 ogt_mesh_vec2i edge2_unitvec = get_cardinal_unit_vector(pVerts[v3] - pVerts[v2]);
1452 ogt_mesh_vec2i edge1_normal = make_vec2i(-edge1_unitvec.y, edge1_unitvec.x);
1453
1454 bool can_extrude_edge1 = !is_vec2i_equal(edge1_normal, (edge0_unitvec * -1)) && !is_vec2i_equal(edge1_normal, edge2_unitvec);
1455
1456 int32_t edge1_pushed_distance = 0;
1457 if (can_extrude_edge1)
1458 {
1459 int32_t edge1_len = get_cardinal_vector_length(pVerts[v2] - pVerts[v1]);
1460 // start 1 unit pushed forward along the edge normal
1461 ogt_mesh_vec2i edge1_origin = pVerts[v1] + get_edge_bias(pVerts[v1], pVerts[v2]);
1462
1463 while (edge1_origin.x >= 0 && edge1_origin.y >= 0 && edge1_origin.x < size_x && edge1_origin.y < size_y)
1464 {
1465 // scan along the entire edge to see if the following criteria are met by pushing the edge forward to this position:
1466 // 1. all of the cells it occupies match the existing polygon color
1467 // 2. none of the cells are already voxelized.
1468 ogt_mesh_vec2i edge1_cursor = edge1_origin;
1469 bool can_push_edge = true;
1470 for (int32_t idx = 0; idx < edge1_len; idx++)
1471 {
1472 assert(edge1_cursor.x >= 0 && edge1_cursor.x < size_x && edge1_cursor.y >= 0 && edge1_cursor.y < size_y);
1473 int32_t slice_index = edge1_cursor.x + (edge1_cursor.y * size_x);
1474 if (pSlice_colors[slice_index] != polygon_color_index || ref_voxel_polygonized.is_set(slice_index))
1475 {
1476 can_push_edge = false;
1477 break;
1478 }
1479 edge1_cursor = edge1_cursor + edge1_unitvec;
1480 }
1481 // we can't push the edge to this location, we've gone as far as we can go with this edge, so jump out immediately.
1482 if (!can_push_edge)
1483 break;
1484 // mark these cells as voxelized as they'll now be part of the polygon.
1485 {
1486 ogt_mesh_vec2i edge1_cursor2 = edge1_origin;
1487 for (int32_t idx = 0; idx < edge1_len; idx++)
1488 {
1489 int32_t slice_index = edge1_cursor2.x + (edge1_cursor2.y * size_x);
1490 ref_voxel_polygonized.set(slice_index);
1491 edge1_cursor2 = edge1_cursor2 + edge1_unitvec;
1492 }
1493 }
1494
1495 // we can push the edge, bump up the distance we've pushed it.
1496 edge1_pushed_distance++;
1497 edge1_origin = edge1_origin + edge1_normal; // step to the next candidate origin.
1498 }
1499 }
1500
1501 if (!edge1_pushed_distance)
1502 {
1503 // step to the next edge
1504 next_edge_index = (next_edge_index + 1) % vert_count;
1505 // bump the exit counter
1506 no_progress_counter++;
1507 }
1508 else
1509 {
1510 // if edge1 was pushed out, we now replace
1511 // v0, v1, v2, v3 in the poly ringbuffer with t0, t1, t2, t3 where t* represents multiple points.
1512
1513 // (0) cache verts v0,v3 and update verts v1, v2
1514 ogt_mesh_vec2i cached_v0 = pVerts[v0];
1515 ogt_mesh_vec2i cached_v1 = pVerts[v1];
1516 ogt_mesh_vec2i cached_v2 = pVerts[v2];
1517 ogt_mesh_vec2i cached_v3 = pVerts[v3];
1518 ogt_mesh_vec2i extruded_v1 = pVerts[v1] + (edge1_normal * edge1_pushed_distance);
1519 ogt_mesh_vec2i extruded_v2 = pVerts[v2] + (edge1_normal * edge1_pushed_distance);
1520
1521 // determine if edge1 is an extrude from edge0 and if edge1 is an extrude from edge2.
1522 // If it is not an extrude, it is an extension.
1523 bool is_e0e1_extrude = is_vec2i_equal(edge0_unitvec, edge1_unitvec);
1524 bool is_e1e2_extrude = is_vec2i_equal(edge1_unitvec, edge2_unitvec);
1525
1526 // (1) try tessellate edge0, edge1, edge2.
1527 const uint32_t k_max_tessellations = 512;
1528 assert(edge1_pushed_distance < k_max_tessellations);
1529 ogt_mesh_vec2i tess_buffer[k_max_tessellations];
1530 uint32_t tess_offset = 0;
1531
1532 // allocate tess_e0
1533 uint32_t e0_offset = tess_offset;
1534 if (is_e0e1_extrude)
1535 {
1536 tess_buffer[tess_offset++] = cached_v0;
1537 tess_buffer[tess_offset++] = cached_v1;
1538 }
1539 else
1540 {
1541 tess_buffer[tess_offset++] = cached_v0;
1542 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, cached_v0, extruded_v1, pSlice_colors, size_x, size_y);
1543 }
1544 uint32_t tess_count_e0 = tess_offset - e0_offset;
1545 // allocate tess_e1
1546 uint32_t e1_offset = tess_offset;
1547 if (is_e0e1_extrude)
1548 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, cached_v1, extruded_v1, pSlice_colors, size_x, size_y);
1549 tess_buffer[tess_offset++] = extruded_v1;
1550 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v1, extruded_v2, pSlice_colors, size_x, size_y);
1551 tess_buffer[tess_offset++] = extruded_v2;
1552 if (is_e1e2_extrude)
1553 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v2, cached_v2, pSlice_colors, size_x, size_y);
1554 uint32_t tess_count_e1 = tess_offset - e1_offset;
1555 // allocate tess_e2
1556 uint32_t e2_offset = tess_offset;
1557 if (is_e1e2_extrude)
1558 tess_buffer[tess_offset++] = cached_v2;
1559 else
1560 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v2, cached_v3, pSlice_colors, size_x, size_y);
1561 uint32_t tess_count_e2 = tess_offset - e2_offset;
1562 // allocate tess_e3
1563 uint32_t e3_offset = tess_offset;
1564 tess_buffer[tess_offset++] = cached_v3;
1565 uint32_t tess_count_e3 = tess_offset - e3_offset;
1566
1567 const ogt_mesh_vec2i* new_tess_e0 = &tess_buffer[e0_offset];
1568 const ogt_mesh_vec2i* new_tess_e1 = &tess_buffer[e1_offset];
1569 const ogt_mesh_vec2i* new_tess_e2 = &tess_buffer[e2_offset];
1570 const ogt_mesh_vec2i* new_tess_e3 = &tess_buffer[e3_offset];
1571
1572 // (2) insert the tessellations into the polygon ring.
1573 // This bit is tricky as v0,v1,v2,v3 can straddle the end of the polygon ring in 4 different combinations which we handle here:
1574 int32_t other1_count = vert_count - 4;
1575 int32_t other2_count = 0;
1576 int32_t old_other1 = (v3 + 1) % vert_count;
1577 int32_t old_other2 = 0;
1578 int32_t new_other2 = -1;
1579 int32_t new_v0, new_v1, new_v2, new_v3, new_other1;
1580
1581 if (v1 < v0)
1582 {
1583 // [v1][v2][v3] (.... other1 ....) [v0]
1584 assert(v1 == 0 && v0 == (vert_count - 1));
1585 new_v1 = 0;
1586 new_v2 = new_v1 + tess_count_e1;
1587 new_v3 = new_v2 + tess_count_e2;
1588 new_other1 = new_v3 + tess_count_e3;
1589 new_v0 = new_other1 + other1_count;
1590 }
1591 else if (v2 < v0)
1592 {
1593 // [v2][v3] (.... other1 ....) [v0][v1]
1594 assert(v2 == 0 && v1 == (vert_count - 1));
1595 new_v2 = 0;
1596 new_v3 = new_v2 + tess_count_e2;
1597 new_other1 = new_v3 + tess_count_e3;
1598 new_v0 = new_other1 + other1_count;
1599 new_v1 = new_v0 + tess_count_e0;
1600 }
1601 else if (v3 < v0)
1602 {
1603 // [v3] (.... other1 ....) [v0][v1][v2]
1604 assert(v3 == 0 && v2 == (vert_count - 1));
1605 new_v3 = 0;
1606 new_other1 = new_v3 + tess_count_e3;
1607 new_v0 = new_other1 + other1_count;
1608 new_v1 = new_v0 + tess_count_e0;
1609 new_v2 = new_v1 + tess_count_e1;
1610 }
1611 else
1612 {
1613 // (.... other1 ....) [v0][v1][v2][v3] (...other2...)
1614 other2_count = vert_count - v3 - 1;
1615 old_other2 = v3 + 1;
1616 other1_count = vert_count - 4 - other2_count;
1617 old_other1 = 0;
1618 new_other1 = 0;
1619 new_v0 = new_other1 + other1_count;
1620 new_v1 = new_v0 + tess_count_e0;
1621 new_v2 = new_v1 + tess_count_e1;
1622 new_v3 = new_v2 + tess_count_e2;
1623 new_other2 = new_v3 + tess_count_e3;
1624 }
1625 uint32_t new_vert_count = vert_count - 4 + tess_count_e0 + tess_count_e1 + tess_count_e2 + tess_count_e3;
1626 // make sure we wouldn't exceed our polygon ring memory size by inserting these tessellated points
1627 assert(new_vert_count <= max_verts);
1628
1629 if (old_other2 != new_other2 && other2_count)
1630 memmove(&pVerts[new_other2], &pVerts[old_other2], other2_count * sizeof(ogt_mesh_vec2i));
1631 if (old_other1 != new_other1 && other1_count)
1632 memmove(&pVerts[new_other1], &pVerts[old_other1], other1_count * sizeof(ogt_mesh_vec2i));
1633 if (tess_count_e0)
1634 memcpy(&pVerts[new_v0], new_tess_e0, tess_count_e0 * sizeof(ogt_mesh_vec2i));
1635 if (tess_count_e1)
1636 memcpy(&pVerts[new_v1], new_tess_e1, tess_count_e1 * sizeof(ogt_mesh_vec2i));
1637 if (tess_count_e2)
1638 memcpy(&pVerts[new_v2], new_tess_e2, tess_count_e2 * sizeof(ogt_mesh_vec2i));
1639 if (tess_count_e3)
1640 memcpy(&pVerts[new_v3], new_tess_e3, tess_count_e3 * sizeof(ogt_mesh_vec2i));
1641 // grow the polygon ring size
1642 vert_count = new_vert_count;
1643
1644 // (3) set next edge to first tessellated edge of edge1.
1645 next_edge_index = new_v1;
1646
1647 // (4) we've progressed, clear the no-progress counter
1648 no_progress_counter = 0;
1649 }
1650 }
1651 return vert_count;
1652}
1653
1654void _polygon_meshify_voxels_in_face_direction(
1655 const uint8_t* pVoxels,
1656 const ogt_mesh_rgba* pPalette,
1657 int32_t size_x, int32_t size_y, int32_t size_z, // how many voxels in each of X,Y,Z dimensions
1658 int32_t k_stride_x, int32_t k_stride_y, int32_t k_stride_z, // the memory stride for each of those X,Y,Z dimensions within the voxel data.
1659 const ogt_mesh_transform& transform, // transform to convert from X,Y,Z to "objectSpace"
1660 ogt_mesh* pMesh)
1661{
1662 // enable aggressive voxel optimization for now.
1663 uint32_t max_voxels_per_slice = size_x * size_y;
1664 assert(max_voxels_per_slice <= 65536); // voxel_polygonized and slice_colors requires this.
1665 ogt_mesh_bitset_64k voxel_polygonized;
1666 uint8_t slice_colors[65536];
1667
1668 // determine if the transform parity has flipped in a way that winding would have been switched.
1669 const ogt_mesh_vec3* side = _make_vec3_ptr(&transform.m00);
1670 const ogt_mesh_vec3* up = _make_vec3_ptr(&transform.m10);
1671 const ogt_mesh_vec3* fwd = _make_vec3_ptr(&transform.m20);
1672 bool is_parity_flipped = _dot3(*fwd, _cross3(*side, *up)) < 0.0f;
1673
1674 ogt_mesh_vec3 normal = _transform_vector(transform, _make_vec3(0.0f, 0.0f, 1.0f));
1675
1676 for (int32_t k = 0; k < (int32_t)size_z; k++)
1677 {
1678 bool is_last_slice = (k == (size_z - 1)) ? true : false;
1679
1680 // clear this slice
1681 voxel_polygonized.clear(max_voxels_per_slice);
1682
1683 // first, fill this slice with all colors for the voxel grid but set to zero where the
1684 // slice has a non-empty voxel in the corresponding location in the k+1 slice.
1685 uint32_t num_non_empty_cells = 0;
1686 for (int32_t j = 0; j < size_y; j++)
1687 {
1688 for (int32_t i = 0; i < size_x; i++)
1689 {
1690 int32_t index_in_slice = i + (j * size_x);
1691 uint8_t cell_color = pVoxels[i * k_stride_x + j * k_stride_y + (k + 0) * k_stride_z];
1692
1693 // if the this cell on this slice is occluded by the corresponding cell on the next slice, we
1694 // mark this polygon as voxelized already so it doesn't get included in any polygons for the current slice.
1695 // we also inherit the next slice's color to ensure the polygon flood fill inserts
1696 // discontinuities where necessary in order to generate a water-tight tessellation
1697 // to the next slice.
1698 uint8_t next_cell_color = !is_last_slice ? pVoxels[i * k_stride_x + j * k_stride_y + (k + 1) * k_stride_z] : 0;
1699 if (next_cell_color != 0)
1700 {
1701 cell_color = next_cell_color;
1702 voxel_polygonized.set(index_in_slice);
1703 }
1704 slice_colors[index_in_slice] = cell_color;
1705
1706 num_non_empty_cells += (cell_color != 0) ? 1 : 0;
1707 }
1708 }
1709 // skip to the next slice if the entire slice is empty.
1710 if (!num_non_empty_cells)
1711 continue;
1712
1713 // polygonize all voxels for this slice.
1714 for (int32_t j = 0; j < (int32_t)size_y; j++)
1715 {
1716 for (int32_t i = 0; i < (int32_t)size_x; i++)
1717 {
1718 int32_t index_in_slice = i + j * size_x;
1719 // this voxel does not need to be polygonized on this slice?
1720 // early out: empty-cell, we don't consider it.
1721 uint8_t color_index = slice_colors[index_in_slice];
1722 if (color_index == 0)
1723 continue;
1724
1725 // this voxel is already polygonized, skip it.
1726 if (voxel_polygonized.is_set(index_in_slice))
1727 continue;
1728
1729 // we always start polygon rasterization with any lower-left corner in (i,j)
1730 // space and fill outward from there. So skip any coords that don't match this
1731 // criteria.
1732 //if ((i > 0 && slice_colors[index_in_slice-1] == color_index) ||
1733 // (j > 0 && slice_colors[index_in_slice-size_x] == color_index))
1734 // continue;
1735
1736 const uint32_t MAX_VERTS = 4096;
1737 ogt_mesh_vec2i verts[MAX_VERTS];
1738 uint32_t vert_count = _construct_polygon_for_slice(verts, MAX_VERTS, i, j, size_x, size_y, slice_colors, voxel_polygonized);
1739
1740 const ogt_mesh_rgba& color = pPalette[color_index];
1741
1742 // generate the verts in the output mesh
1743 uint32_t base_vertex_index = pMesh->vertex_count;
1744 for (uint32_t idx = 0; idx < vert_count; idx++)
1745 {
1746 pMesh->vertices[pMesh->vertex_count++] = _mesh_make_vertex(_transform_point(transform, _make_vec3((float)verts[idx].x, (float)verts[idx].y, (float)(k + 1))), normal, color);
1747 }
1748
1749 // generate the indices in the output mesh.
1750 uint32_t tessellated_index_count = _tessellate_polygon(&pMesh->indices[pMesh->index_count], verts, vert_count);
1751
1752 // flip the winding of tessellated triangles to account for an inversion in the transform.
1753 if (is_parity_flipped)
1754 {
1755 for (uint32_t idx = 0; idx < tessellated_index_count; idx += 3)
1756 {
1757 uint32_t i0 = pMesh->indices[pMesh->index_count + idx + 0];
1758 uint32_t i1 = pMesh->indices[pMesh->index_count + idx + 1];
1759 uint32_t i2 = pMesh->indices[pMesh->index_count + idx + 2];
1760 pMesh->indices[pMesh->index_count + idx + 0] = base_vertex_index + i2;
1761 pMesh->indices[pMesh->index_count + idx + 1] = base_vertex_index + i1;
1762 pMesh->indices[pMesh->index_count + idx + 2] = base_vertex_index + i0;
1763 }
1764 }
1765 else
1766 {
1767 for (uint32_t idx = 0; idx < tessellated_index_count; idx += 3)
1768 {
1769 uint32_t i0 = pMesh->indices[pMesh->index_count + idx + 0];
1770 uint32_t i1 = pMesh->indices[pMesh->index_count + idx + 1];
1771 uint32_t i2 = pMesh->indices[pMesh->index_count + idx + 2];
1772 pMesh->indices[pMesh->index_count + idx + 0] = base_vertex_index + i0;
1773 pMesh->indices[pMesh->index_count + idx + 1] = base_vertex_index + i1;
1774 pMesh->indices[pMesh->index_count + idx + 2] = base_vertex_index + i2;
1775 }
1776 }
1777
1778 pMesh->index_count += tessellated_index_count;
1779 }
1780 }
1781 }
1782
1783# undef SLICE_INDEX
1784}
1785
1786// for each slice
1787// for each voxel cell
1788// if not already polygonized
1789// initialize polygon to a single cell
1790// while (can expand polygon)
1791// choose an edge and expand it out as far as possible, tessellating surrounding edges if neccessary, marking newly expanded cells as polygonized
1792// triangulate the output polygon.
1793ogt_mesh* ogt_mesh_from_paletted_voxels_polygon(
1794 const ogt_voxel_meshify_context* pCtx,
1795 const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette)
1796{
1797 uint32_t max_face_count = _count_voxel_sized_faces(pVoxels, size_x, size_y, size_z);
1798 uint32_t max_vertex_count = max_face_count * 4;
1799 uint32_t max_index_count = max_face_count * 6;
1800
1801 uint32_t mesh_size = sizeof(ogt_mesh) + (max_vertex_count * sizeof(ogt_mesh_vertex)) + (max_index_count * sizeof(uint32_t));
1802 ogt_mesh* mesh = (ogt_mesh*)_voxel_meshify_malloc(pCtx, mesh_size);
1803 if (!mesh)
1804 return NULL;
1805
1806 mesh->vertices = (ogt_mesh_vertex*)&mesh[1];
1807 mesh->indices = (uint32_t*)&mesh->vertices[max_vertex_count];
1808 mesh->vertex_count = 0;
1809 mesh->index_count = 0;
1810
1811 const int32_t k_stride_x = 1;
1812 const int32_t k_stride_y = size_x;
1813 const int32_t k_stride_z = size_x * size_y;
1814
1815 // do the +y PASS
1816 {
1817 ogt_mesh_transform transform_pos_y = _make_transform(
1818 0.0f, 0.0f, 1.0f, 0.0f,
1819 1.0f, 0.0f, 0.0f, 0.0f,
1820 0.0f, 1.0f, 0.0f, 0.0f,
1821 0.0f, 0.0f, 0.0f, 0.0f);
1822
1823 _polygon_meshify_voxels_in_face_direction(
1824 pVoxels, pPalette,
1825 size_z, size_x, size_y,
1826 k_stride_z, k_stride_x, k_stride_y,
1827 transform_pos_y,
1828 mesh);
1829 }
1830
1831 // do the -y PASS
1832 {
1833 ogt_mesh_transform transform_neg_y = _make_transform(
1834 0.0f, 0.0f, 1.0f, 0.0f,
1835 1.0f, 0.0f, 0.0f, 0.0f,
1836 0.0f, -1.0f, 0.0f, 0.0f,
1837 0.0f, (float)(size_y), 0.0f, 0.0f);
1838
1839 _polygon_meshify_voxels_in_face_direction(
1840 pVoxels + (size_y - 1) * k_stride_y,
1841 pPalette,
1842 size_z, size_x, size_y,
1843 k_stride_z, k_stride_x, -k_stride_y,
1844 transform_neg_y,
1845 mesh);
1846 }
1847 // do the +X pass
1848 {
1849 ogt_mesh_transform transform_pos_x = _make_transform(
1850 0.0f, 1.0f, 0.0f, 0.0f,
1851 0.0f, 0.0f, 1.0f, 0.0f,
1852 1.0f, 0.0f, 0.0f, 0.0f,
1853 0.0f, 0.0f, 0.0f, 0.0f);
1854
1855 _polygon_meshify_voxels_in_face_direction(
1856 pVoxels, pPalette,
1857 size_y, size_z, size_x,
1858 k_stride_y, k_stride_z, k_stride_x,
1859 transform_pos_x,
1860 mesh);
1861 }
1862 // do the -X pass
1863 {
1864 ogt_mesh_transform transform_neg_x = _make_transform(
1865 0.0f, 1.0f, 0.0f, 0.0f,
1866 0.0f, 0.0f, 1.0f, 0.0f,
1867 -1.0f, 0.0f, 0.0f, 0.0f,
1868 (float)size_x, 0.0f, 0.0f, 0.0f);
1869
1870 _polygon_meshify_voxels_in_face_direction(
1871 pVoxels + (size_x - 1) * k_stride_x,
1872 pPalette,
1873 size_y, size_z, size_x,
1874 k_stride_y, k_stride_z, -k_stride_x,
1875 transform_neg_x,
1876 mesh);
1877 }
1878 // do the +Z pass
1879 {
1880 ogt_mesh_transform transform_pos_z = _make_transform(
1881 1.0f, 0.0f, 0.0f, 0.0f,
1882 0.0f, 1.0f, 0.0f, 0.0f,
1883 0.0f, 0.0f, 1.0f, 0.0f,
1884 0.0f, 0.0f, 0.0f, 0.0f);
1885
1886 _polygon_meshify_voxels_in_face_direction(
1887 pVoxels, pPalette,
1888 size_x, size_y, size_z,
1889 k_stride_x, k_stride_y, k_stride_z,
1890 transform_pos_z,
1891 mesh);
1892 }
1893 // do the -Z pass
1894 {
1895 ogt_mesh_transform transform_neg_z = _make_transform(
1896 1.0f, 0.0f, 0.0f, 0.0f,
1897 0.0f, 1.0f, 0.0f, 0.0f,
1898 0.0f, 0.0f, -1.0f, 0.0f,
1899 0.0f, 0.0f, (float)size_z, 0.0f);
1900
1901 _polygon_meshify_voxels_in_face_direction(
1902 pVoxels + (size_z - 1) * k_stride_z,
1903 pPalette,
1904 size_x, size_y, size_z,
1905 k_stride_x, k_stride_y, -k_stride_z,
1906 transform_neg_z,
1907 mesh);
1908 }
1909
1910 assert(mesh->vertex_count <= max_vertex_count);
1911 assert(mesh->index_count <= max_index_count);
1912
1913 return mesh;
1914}
1915
1916
1917void ogt_mesh_destroy(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh)
1918{
1919 _voxel_meshify_free(pCtx, pMesh);
1920}
1921
1922#endif // #ifdef OGT_VOXEL_MESHIFY_IMPLEMENTATION
1923
1924/* -------------------------------------------------------------------------------------------------------------------------------------------------
1925
1926 MIT License
1927
1928 Copyright (c) 2020 Justin Paver
1929
1930 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
1931 to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
1932 and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
1933
1934 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1935
1936 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1937 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1938 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
1939 IN THE SOFTWARE.
1940
1941------------------------------------------------------------------------------------------------------------------------------------------------- */
Definition ogt_voxel_meshify.h:107
Definition ogt_voxel_meshify.h:101
Definition ogt_voxel_meshify.h:113
Definition ogt_voxel_meshify.h:121
Definition ogt_voxel_meshify.h:139