XII Release 0.1.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) :
1149 0;
1150}
1151
1152// gets the signed area of the triangle
1153inline int32_t get_triangle_signed_area(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2)
1154{
1155 return ((v0.x - v1.x) * (v2.y - v1.y)) - ((v0.y - v1.y) * (v2.x - v1.x));
1156}
1157
1158// determines whether the triangle is convex or not
1159inline bool is_triangle_convex(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2)
1160{
1161 return get_triangle_signed_area(v0, v1, v2) > 0;
1162}
1163
1164// determines whether the point p is inside the triangle v0,v1,v2
1165bool is_point_in_triangle(const ogt_mesh_vec2i& v0, const ogt_mesh_vec2i& v1, const ogt_mesh_vec2i& v2, const ogt_mesh_vec2i& p)
1166{
1167 bool convex_v0v1 = get_triangle_signed_area(v0, v1, p) >= 0;
1168 bool convex_v1v2 = get_triangle_signed_area(v1, v2, p) >= 0;
1169 bool convex_v2v0 = get_triangle_signed_area(v2, v0, p) >= 0;
1170 return (convex_v0v1 == convex_v1v2) && (convex_v0v1 == convex_v2v0);
1171}
1172
1173uint32_t _tessellate_polygon(uint32_t* pIndices, const ogt_mesh_vec2i* pVerts, uint32_t vert_count)
1174{
1175 static const uint32_t k_max_polygon_size = 16384; // 32KB of stack!
1176 uint16_t ring_indices[k_max_polygon_size];
1177 assert(vert_count >= 3 && vert_count <= k_max_polygon_size);
1178
1179 for (uint16_t i = 0; i < vert_count; i++)
1180 ring_indices[i] = i;
1181 uint32_t ring_count = vert_count;
1182
1183 uint32_t index_count = 0;
1184
1185 // naieve "ear clipping" algorithm. Start with a ring of polygon corners.
1186 // Find 3 sequential corners on the polygon and make sure no other points of the polygon are contained within it.
1187 // If so, remove the inner point from the ring. Rinse and repeat.
1188 uint32_t no_progress_counter = 0;
1189 uint32_t i0 = 0;
1190 while (ring_count > 3)
1191 {
1192 i0 = (i0 + 0) % ring_count;
1193 uint32_t i1 = (i0 + 1) % ring_count;
1194 uint32_t i2 = (i0 + 2) % ring_count;
1195
1196 ogt_mesh_vec2i v0 = pVerts[ring_indices[i0]];
1197 ogt_mesh_vec2i v1 = pVerts[ring_indices[i1]];
1198 ogt_mesh_vec2i v2 = pVerts[ring_indices[i2]];
1199
1200 // check whether we can carve off this ear.
1201 bool can_triangulate = is_triangle_convex(v0, v1, v2);
1202
1203 if (can_triangulate)
1204 {
1205 // make sure that no other points are inside this triangle. We do allow points to be coincident with corners of the triangle though.
1206 for (uint32_t i = 0; i < ring_count; i++)
1207 {
1208 if (i == i0 || i == i1 || i == i2)
1209 continue;
1210 const ogt_mesh_vec2i& p = pVerts[ring_indices[i]];
1211 bool point_on_corner = is_vec2i_equal(v0, p) || is_vec2i_equal(v1, p) || is_vec2i_equal(v2, p);
1212 if (!point_on_corner && is_point_in_triangle(v0, v1, v2, pVerts[ring_indices[i]]))
1213 {
1214 can_triangulate = false;
1215 break;
1216 }
1217 }
1218 }
1219
1220 if (can_triangulate)
1221 {
1222 pIndices[index_count++] = (uint32_t)ring_indices[i2];
1223 pIndices[index_count++] = (uint32_t)ring_indices[i1];
1224 pIndices[index_count++] = (uint32_t)ring_indices[i0];
1225 // compact verts down in the ring indices
1226 ring_count--;
1227 for (uint32_t i = i1; i < ring_count; i++)
1228 ring_indices[i] = ring_indices[i + 1];
1229 // reset no progress counter because we just made progress!
1230 no_progress_counter = 0;
1231 }
1232 else
1233 {
1234 no_progress_counter++;
1235 i0++;
1236 }
1237 // we haven't made progress in a full trip around the ring -- the geometry is probably malformed and cannot be tessellated
1238 if (no_progress_counter == ring_count)
1239 {
1240 return index_count;
1241 }
1242 }
1243 // trailing case, just have one triangle left -- emit it.
1244 pIndices[index_count++] = (uint32_t)ring_indices[2];
1245 pIndices[index_count++] = (uint32_t)ring_indices[1];
1246 pIndices[index_count++] = (uint32_t)ring_indices[0];
1247
1248 return index_count;
1249}
1250
1251// where do we sample for the specified edge
1252ogt_mesh_vec2i get_edge_bias(const ogt_mesh_vec2i& edge_vert0, const ogt_mesh_vec2i& edge_vert1)
1253{
1254 if (edge_vert0.x < edge_vert1.x)
1255 {
1256 assert(edge_vert0.y == edge_vert1.y);
1257 return make_vec2i(0, 0);
1258 }
1259 else if (edge_vert0.x > edge_vert1.x)
1260 {
1261 assert(edge_vert0.y == edge_vert1.y);
1262 return make_vec2i(-1, -1);
1263 }
1264 else if (edge_vert0.y < edge_vert1.y)
1265 {
1266 assert(edge_vert0.x == edge_vert1.x);
1267 return make_vec2i(-1, 0);
1268 }
1269 else if (edge_vert0.y > edge_vert1.y)
1270 {
1271 assert(edge_vert0.x == edge_vert1.x);
1272 return make_vec2i(0, -1);
1273 }
1274 else
1275 {
1276 assert(0);
1277 return make_vec2i(0, 0);
1278 }
1279}
1280
1281uint32_t _tessellate_edge(ogt_mesh_vec2i* pTess, uint32_t max_tess, const ogt_mesh_vec2i& edge_vert0, const ogt_mesh_vec2i& edge_vert1,
1282 const uint8_t* pSlice_colors, int32_t size_x, int32_t size_y)
1283{
1284
1285 uint32_t num_tess = 0;
1286 int32_t edge_len = get_cardinal_vector_length(edge_vert1 - edge_vert0);
1287 ogt_mesh_vec2i edge_bias = get_edge_bias(edge_vert0, edge_vert1);
1288 ogt_mesh_vec2i edge_step = get_cardinal_unit_vector(edge_vert1 - edge_vert0);
1289 ogt_mesh_vec2i curr_pos = edge_vert0 + edge_bias;
1290
1291 // do early-out logic if the entire edge is out-of-bounds
1292 if (edge_vert0.x < edge_vert1.x)
1293 {
1294 // handle left-to-right edges
1295 assert(curr_pos.y <= size_y);
1296 if (curr_pos.y == size_y)
1297 return 0;
1298 }
1299 else if (edge_vert0.x > edge_vert1.x)
1300 {
1301 // handle right-to-left edges
1302 assert(curr_pos.y >= -1);
1303 if (curr_pos.y == -1)
1304 return 0;
1305 }
1306 else if (edge_vert0.y < edge_vert1.y)
1307 {
1308 // handle bottom-to-top edges
1309 assert(curr_pos.x >= -1);
1310 if (curr_pos.x == -1)
1311 return 0;
1312 }
1313 else if (edge_vert0.y > edge_vert1.y)
1314 {
1315 // handle top-to-bottom
1316 assert(curr_pos.x <= size_x);
1317 if (curr_pos.x == size_x)
1318 return 0;
1319 }
1320 else
1321 {
1322 assert(0);
1323 return 0;
1324 }
1325
1326 uint8_t last_color_index = pSlice_colors[curr_pos.x + (curr_pos.y * size_x)];
1327 curr_pos = curr_pos + edge_step;
1328
1329 for (int32_t i = 1; i < edge_len; i++)
1330 {
1331 uint8_t curr_color_index = pSlice_colors[curr_pos.x + (curr_pos.y * size_x)];
1332 if (curr_color_index != last_color_index)
1333 {
1334 assert(num_tess < max_tess);
1335 pTess[num_tess++] = curr_pos - edge_bias;
1336 last_color_index = curr_color_index;
1337 }
1338 curr_pos = curr_pos + edge_step;
1339 }
1340 return num_tess;
1341}
1342
1343// My algorithm for flood-filling the polygon.
1344//
1345// We start with a simple set of verts that represents a polygon ring
1346// surrounding a single voxel.
1347//
1348// v1 +---+ v2
1349// | C |
1350// v0 +---+ v3
1351//
1352// This initial state has 4 verts on the polygon boundary which
1353// represents 4 edges on the polygon boundary, and the color of
1354// the polygon is the voxel color.
1355//
1356// The algorithm from here is fairly simple:
1357// 1. select the next edge from the polygon ring
1358// 2. try extrude the edge as far as possible along its outward
1359// facing normal one voxel at a time.
1360// - stop pushing the edge outward if we'd consume an already polygonized voxel, or if the edge
1361// would push a different color to the inside of the polygon ring.
1362// 3. if the edge could be extruded, insert new points for the edge and each of its left/right
1363// neighbor edges such that color discontinuities on the outside of the edge have a vertex between them.
1364// 4. if the edge could not be extruded and we have gone around the entire ring with no progress,
1365// terminate, otherwise goto 1.
1366// 5. we now have a polygon ring that can be tessellated.
1367//
1368// Example
1369// step(1):
1370//
1371// (e1)
1372// v1 +-------+ v2
1373//(e0) | X | X | (e2) X is the color inside the polygon
1374// v0 + + v3
1375//
1376// edge1 corresponds to the next_edge_index edge within the current polygon ring.
1377//
1378// step(2)
1379// We now try to push edge1 out as far as we can by holding v0 & v3 fixed
1380// and moving v1/v2 in the direction of the edge normal:
1381//
1382// ^
1383// ^
1384// v1 +-------+ v2
1385// | X | X |
1386// | | |
1387// | X | X |
1388// | | |
1389// | X | X |
1390// v0 + + v3
1391//
1392// We can only extend the edge as long as by doing so the polygon remains
1393// filled with it's current color and if the polygon does not cover a
1394// voxel that was already polygonized by a prior pass.
1395//
1396// step(3)
1397// Once we've finished pushing v1/v2 forward, we now check whether we have
1398// to tessellate any of the edges e0, e1, e2. This is now mostly a function
1399// of which colors are on the outside of the polygon boundary.
1400//
1401// X C
1402// +---*---+
1403// B | X | X | D A,B,C,D are colors along the outside of the edges.
1404// * | | Here * are new points of the edges because of an
1405// A | X | X | D exterior change of color along the edge.
1406// | | |
1407// A | X | X | D
1408// + +
1409//
1410// We then insert the new edges into the polygon ring and select a new
1411// edge to start extruding.
1412// If we tessellated e1, then we set next_edge_index to the first child-edge
1413// on that edge again:
1414//
1415// v2
1416// v1 +---*---+ v3
1417// | X X
1418// v0 *
1419//
1420// When we can no longer extrude any of the polygon ring edges, we
1421// terminate, as that'll mean we've flood filled the space.
1422//
1423int32_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)
1424{
1425 assert(max_verts > 4);
1426 // start with just a single 4 vertex closed polygon
1427 pVerts[0] = make_vec2i(i, j);
1428 pVerts[1] = make_vec2i(i, j + 1);
1429 pVerts[2] = make_vec2i(i + 1, j + 1);
1430 pVerts[3] = make_vec2i(i + 1, j);
1431 int32_t vert_count = 4;
1432
1433 uint8_t polygon_color_index = pSlice_colors[i + (j * size_x)];
1434
1435 // mark this voxel as polygonized so it can't be further considered.
1436 ref_voxel_polygonized.set(i + (j * size_x));
1437
1438 int32_t next_edge_index = 0;
1439 int32_t no_progress_counter = 0;
1440 while (no_progress_counter < vert_count)
1441 {
1442 // this is just figuring out the vertices for 3 successive edges.
1443 // edge0 from v0...v1, edge1 from v1...v2, edge2 from v2...v3
1444 int32_t v0 = next_edge_index < 1 ? vert_count - 1 : next_edge_index - 1;
1445 int32_t v1 = next_edge_index;
1446 int32_t v2 = (v1 < (vert_count - 1)) ? v1 + 1 : 0;
1447 int32_t v3 = (v2 < (vert_count - 1)) ? v2 + 1 : 0;
1448
1449 // compute the direction of edge1.
1450 ogt_mesh_vec2i edge0_unitvec = get_cardinal_unit_vector(pVerts[v1] - pVerts[v0]);
1451 ogt_mesh_vec2i edge1_unitvec = get_cardinal_unit_vector(pVerts[v2] - pVerts[v1]);
1452 ogt_mesh_vec2i edge2_unitvec = get_cardinal_unit_vector(pVerts[v3] - pVerts[v2]);
1453 ogt_mesh_vec2i edge1_normal = make_vec2i(-edge1_unitvec.y, edge1_unitvec.x);
1454
1455 bool can_extrude_edge1 = !is_vec2i_equal(edge1_normal, (edge0_unitvec * -1)) && !is_vec2i_equal(edge1_normal, edge2_unitvec);
1456
1457 int32_t edge1_pushed_distance = 0;
1458 if (can_extrude_edge1)
1459 {
1460 int32_t edge1_len = get_cardinal_vector_length(pVerts[v2] - pVerts[v1]);
1461 // start 1 unit pushed forward along the edge normal
1462 ogt_mesh_vec2i edge1_origin = pVerts[v1] + get_edge_bias(pVerts[v1], pVerts[v2]);
1463
1464 while (edge1_origin.x >= 0 && edge1_origin.y >= 0 && edge1_origin.x < size_x && edge1_origin.y < size_y)
1465 {
1466 // scan along the entire edge to see if the following criteria are met by pushing the edge forward to this position:
1467 // 1. all of the cells it occupies match the existing polygon color
1468 // 2. none of the cells are already voxelized.
1469 ogt_mesh_vec2i edge1_cursor = edge1_origin;
1470 bool can_push_edge = true;
1471 for (int32_t idx = 0; idx < edge1_len; idx++)
1472 {
1473 assert(edge1_cursor.x >= 0 && edge1_cursor.x < size_x && edge1_cursor.y >= 0 && edge1_cursor.y < size_y);
1474 int32_t slice_index = edge1_cursor.x + (edge1_cursor.y * size_x);
1475 if (pSlice_colors[slice_index] != polygon_color_index || ref_voxel_polygonized.is_set(slice_index))
1476 {
1477 can_push_edge = false;
1478 break;
1479 }
1480 edge1_cursor = edge1_cursor + edge1_unitvec;
1481 }
1482 // 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.
1483 if (!can_push_edge)
1484 break;
1485 // mark these cells as voxelized as they'll now be part of the polygon.
1486 {
1487 ogt_mesh_vec2i edge1_cursor2 = edge1_origin;
1488 for (int32_t idx = 0; idx < edge1_len; idx++)
1489 {
1490 int32_t slice_index = edge1_cursor2.x + (edge1_cursor2.y * size_x);
1491 ref_voxel_polygonized.set(slice_index);
1492 edge1_cursor2 = edge1_cursor2 + edge1_unitvec;
1493 }
1494 }
1495
1496 // we can push the edge, bump up the distance we've pushed it.
1497 edge1_pushed_distance++;
1498 edge1_origin = edge1_origin + edge1_normal; // step to the next candidate origin.
1499 }
1500 }
1501
1502 if (!edge1_pushed_distance)
1503 {
1504 // step to the next edge
1505 next_edge_index = (next_edge_index + 1) % vert_count;
1506 // bump the exit counter
1507 no_progress_counter++;
1508 }
1509 else
1510 {
1511 // if edge1 was pushed out, we now replace
1512 // v0, v1, v2, v3 in the poly ringbuffer with t0, t1, t2, t3 where t* represents multiple points.
1513
1514 // (0) cache verts v0,v3 and update verts v1, v2
1515 ogt_mesh_vec2i cached_v0 = pVerts[v0];
1516 ogt_mesh_vec2i cached_v1 = pVerts[v1];
1517 ogt_mesh_vec2i cached_v2 = pVerts[v2];
1518 ogt_mesh_vec2i cached_v3 = pVerts[v3];
1519 ogt_mesh_vec2i extruded_v1 = pVerts[v1] + (edge1_normal * edge1_pushed_distance);
1520 ogt_mesh_vec2i extruded_v2 = pVerts[v2] + (edge1_normal * edge1_pushed_distance);
1521
1522 // determine if edge1 is an extrude from edge0 and if edge1 is an extrude from edge2.
1523 // If it is not an extrude, it is an extension.
1524 bool is_e0e1_extrude = is_vec2i_equal(edge0_unitvec, edge1_unitvec);
1525 bool is_e1e2_extrude = is_vec2i_equal(edge1_unitvec, edge2_unitvec);
1526
1527 // (1) try tessellate edge0, edge1, edge2.
1528 const uint32_t k_max_tessellations = 512;
1529 assert(edge1_pushed_distance < k_max_tessellations);
1530 ogt_mesh_vec2i tess_buffer[k_max_tessellations];
1531 uint32_t tess_offset = 0;
1532
1533 // allocate tess_e0
1534 uint32_t e0_offset = tess_offset;
1535 if (is_e0e1_extrude)
1536 {
1537 tess_buffer[tess_offset++] = cached_v0;
1538 tess_buffer[tess_offset++] = cached_v1;
1539 }
1540 else
1541 {
1542 tess_buffer[tess_offset++] = cached_v0;
1543 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, cached_v0, extruded_v1, pSlice_colors, size_x, size_y);
1544 }
1545 uint32_t tess_count_e0 = tess_offset - e0_offset;
1546 // allocate tess_e1
1547 uint32_t e1_offset = tess_offset;
1548 if (is_e0e1_extrude)
1549 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, cached_v1, extruded_v1, pSlice_colors, size_x, size_y);
1550 tess_buffer[tess_offset++] = extruded_v1;
1551 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v1, extruded_v2, pSlice_colors, size_x, size_y);
1552 tess_buffer[tess_offset++] = extruded_v2;
1553 if (is_e1e2_extrude)
1554 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v2, cached_v2, pSlice_colors, size_x, size_y);
1555 uint32_t tess_count_e1 = tess_offset - e1_offset;
1556 // allocate tess_e2
1557 uint32_t e2_offset = tess_offset;
1558 if (is_e1e2_extrude)
1559 tess_buffer[tess_offset++] = cached_v2;
1560 else
1561 tess_offset += _tessellate_edge(&tess_buffer[tess_offset], k_max_tessellations - tess_offset, extruded_v2, cached_v3, pSlice_colors, size_x, size_y);
1562 uint32_t tess_count_e2 = tess_offset - e2_offset;
1563 // allocate tess_e3
1564 uint32_t e3_offset = tess_offset;
1565 tess_buffer[tess_offset++] = cached_v3;
1566 uint32_t tess_count_e3 = tess_offset - e3_offset;
1567
1568 const ogt_mesh_vec2i* new_tess_e0 = &tess_buffer[e0_offset];
1569 const ogt_mesh_vec2i* new_tess_e1 = &tess_buffer[e1_offset];
1570 const ogt_mesh_vec2i* new_tess_e2 = &tess_buffer[e2_offset];
1571 const ogt_mesh_vec2i* new_tess_e3 = &tess_buffer[e3_offset];
1572
1573 // (2) insert the tessellations into the polygon ring.
1574 // 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:
1575 int32_t other1_count = vert_count - 4;
1576 int32_t other2_count = 0;
1577 int32_t old_other1 = (v3 + 1) % vert_count;
1578 int32_t old_other2 = 0;
1579 int32_t new_other2 = -1;
1580 int32_t new_v0, new_v1, new_v2, new_v3, new_other1;
1581
1582 if (v1 < v0)
1583 {
1584 // [v1][v2][v3] (.... other1 ....) [v0]
1585 assert(v1 == 0 && v0 == (vert_count - 1));
1586 new_v1 = 0;
1587 new_v2 = new_v1 + tess_count_e1;
1588 new_v3 = new_v2 + tess_count_e2;
1589 new_other1 = new_v3 + tess_count_e3;
1590 new_v0 = new_other1 + other1_count;
1591 }
1592 else if (v2 < v0)
1593 {
1594 // [v2][v3] (.... other1 ....) [v0][v1]
1595 assert(v2 == 0 && v1 == (vert_count - 1));
1596 new_v2 = 0;
1597 new_v3 = new_v2 + tess_count_e2;
1598 new_other1 = new_v3 + tess_count_e3;
1599 new_v0 = new_other1 + other1_count;
1600 new_v1 = new_v0 + tess_count_e0;
1601 }
1602 else if (v3 < v0)
1603 {
1604 // [v3] (.... other1 ....) [v0][v1][v2]
1605 assert(v3 == 0 && v2 == (vert_count - 1));
1606 new_v3 = 0;
1607 new_other1 = new_v3 + tess_count_e3;
1608 new_v0 = new_other1 + other1_count;
1609 new_v1 = new_v0 + tess_count_e0;
1610 new_v2 = new_v1 + tess_count_e1;
1611 }
1612 else
1613 {
1614 // (.... other1 ....) [v0][v1][v2][v3] (...other2...)
1615 other2_count = vert_count - v3 - 1;
1616 old_other2 = v3 + 1;
1617 other1_count = vert_count - 4 - other2_count;
1618 old_other1 = 0;
1619 new_other1 = 0;
1620 new_v0 = new_other1 + other1_count;
1621 new_v1 = new_v0 + tess_count_e0;
1622 new_v2 = new_v1 + tess_count_e1;
1623 new_v3 = new_v2 + tess_count_e2;
1624 new_other2 = new_v3 + tess_count_e3;
1625 }
1626 uint32_t new_vert_count = vert_count - 4 + tess_count_e0 + tess_count_e1 + tess_count_e2 + tess_count_e3;
1627 // make sure we wouldn't exceed our polygon ring memory size by inserting these tessellated points
1628 assert(new_vert_count <= max_verts);
1629
1630 if (old_other2 != new_other2 && other2_count)
1631 memmove(&pVerts[new_other2], &pVerts[old_other2], other2_count * sizeof(ogt_mesh_vec2i));
1632 if (old_other1 != new_other1 && other1_count)
1633 memmove(&pVerts[new_other1], &pVerts[old_other1], other1_count * sizeof(ogt_mesh_vec2i));
1634 if (tess_count_e0)
1635 memcpy(&pVerts[new_v0], new_tess_e0, tess_count_e0 * sizeof(ogt_mesh_vec2i));
1636 if (tess_count_e1)
1637 memcpy(&pVerts[new_v1], new_tess_e1, tess_count_e1 * sizeof(ogt_mesh_vec2i));
1638 if (tess_count_e2)
1639 memcpy(&pVerts[new_v2], new_tess_e2, tess_count_e2 * sizeof(ogt_mesh_vec2i));
1640 if (tess_count_e3)
1641 memcpy(&pVerts[new_v3], new_tess_e3, tess_count_e3 * sizeof(ogt_mesh_vec2i));
1642 // grow the polygon ring size
1643 vert_count = new_vert_count;
1644
1645 // (3) set next edge to first tessellated edge of edge1.
1646 next_edge_index = new_v1;
1647
1648 // (4) we've progressed, clear the no-progress counter
1649 no_progress_counter = 0;
1650 }
1651 }
1652 return vert_count;
1653}
1654
1655void _polygon_meshify_voxels_in_face_direction(
1656 const uint8_t* pVoxels,
1657 const ogt_mesh_rgba* pPalette,
1658 int32_t size_x, int32_t size_y, int32_t size_z, // how many voxels in each of X,Y,Z dimensions
1659 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.
1660 const ogt_mesh_transform& transform, // transform to convert from X,Y,Z to "objectSpace"
1661 ogt_mesh* pMesh)
1662{
1663 // enable aggressive voxel optimization for now.
1664 uint32_t max_voxels_per_slice = size_x * size_y;
1665 assert(max_voxels_per_slice <= 65536); // voxel_polygonized and slice_colors requires this.
1666 ogt_mesh_bitset_64k voxel_polygonized;
1667 uint8_t slice_colors[65536];
1668
1669 // determine if the transform parity has flipped in a way that winding would have been switched.
1670 const ogt_mesh_vec3* side = _make_vec3_ptr(&transform.m00);
1671 const ogt_mesh_vec3* up = _make_vec3_ptr(&transform.m10);
1672 const ogt_mesh_vec3* fwd = _make_vec3_ptr(&transform.m20);
1673 bool is_parity_flipped = _dot3(*fwd, _cross3(*side, *up)) < 0.0f;
1674
1675 ogt_mesh_vec3 normal = _transform_vector(transform, _make_vec3(0.0f, 0.0f, 1.0f));
1676
1677 for (int32_t k = 0; k < (int32_t)size_z; k++)
1678 {
1679 bool is_last_slice = (k == (size_z - 1)) ? true : false;
1680
1681 // clear this slice
1682 voxel_polygonized.clear(max_voxels_per_slice);
1683
1684 // first, fill this slice with all colors for the voxel grid but set to zero where the
1685 // slice has a non-empty voxel in the corresponding location in the k+1 slice.
1686 uint32_t num_non_empty_cells = 0;
1687 for (int32_t j = 0; j < size_y; j++)
1688 {
1689 for (int32_t i = 0; i < size_x; i++)
1690 {
1691 int32_t index_in_slice = i + (j * size_x);
1692 uint8_t cell_color = pVoxels[i * k_stride_x + j * k_stride_y + (k + 0) * k_stride_z];
1693
1694 // if the this cell on this slice is occluded by the corresponding cell on the next slice, we
1695 // mark this polygon as voxelized already so it doesn't get included in any polygons for the current slice.
1696 // we also inherit the next slice's color to ensure the polygon flood fill inserts
1697 // discontinuities where necessary in order to generate a water-tight tessellation
1698 // to the next slice.
1699 uint8_t next_cell_color = !is_last_slice ? pVoxels[i * k_stride_x + j * k_stride_y + (k + 1) * k_stride_z] : 0;
1700 if (next_cell_color != 0)
1701 {
1702 cell_color = next_cell_color;
1703 voxel_polygonized.set(index_in_slice);
1704 }
1705 slice_colors[index_in_slice] = cell_color;
1706
1707 num_non_empty_cells += (cell_color != 0) ? 1 : 0;
1708 }
1709 }
1710 // skip to the next slice if the entire slice is empty.
1711 if (!num_non_empty_cells)
1712 continue;
1713
1714 // polygonize all voxels for this slice.
1715 for (int32_t j = 0; j < (int32_t)size_y; j++)
1716 {
1717 for (int32_t i = 0; i < (int32_t)size_x; i++)
1718 {
1719 int32_t index_in_slice = i + j * size_x;
1720 // this voxel does not need to be polygonized on this slice?
1721 // early out: empty-cell, we don't consider it.
1722 uint8_t color_index = slice_colors[index_in_slice];
1723 if (color_index == 0)
1724 continue;
1725
1726 // this voxel is already polygonized, skip it.
1727 if (voxel_polygonized.is_set(index_in_slice))
1728 continue;
1729
1730 // we always start polygon rasterization with any lower-left corner in (i,j)
1731 // space and fill outward from there. So skip any coords that don't match this
1732 // criteria.
1733 // if ((i > 0 && slice_colors[index_in_slice-1] == color_index) ||
1734 // (j > 0 && slice_colors[index_in_slice-size_x] == color_index))
1735 // continue;
1736
1737 const uint32_t MAX_VERTS = 4096;
1738 ogt_mesh_vec2i verts[MAX_VERTS];
1739 uint32_t vert_count = _construct_polygon_for_slice(verts, MAX_VERTS, i, j, size_x, size_y, slice_colors, voxel_polygonized);
1740
1741 const ogt_mesh_rgba& color = pPalette[color_index];
1742
1743 // generate the verts in the output mesh
1744 uint32_t base_vertex_index = pMesh->vertex_count;
1745 for (uint32_t idx = 0; idx < vert_count; idx++)
1746 {
1747 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);
1748 }
1749
1750 // generate the indices in the output mesh.
1751 uint32_t tessellated_index_count = _tessellate_polygon(&pMesh->indices[pMesh->index_count], verts, vert_count);
1752
1753 // flip the winding of tessellated triangles to account for an inversion in the transform.
1754 if (is_parity_flipped)
1755 {
1756 for (uint32_t idx = 0; idx < tessellated_index_count; idx += 3)
1757 {
1758 uint32_t i0 = pMesh->indices[pMesh->index_count + idx + 0];
1759 uint32_t i1 = pMesh->indices[pMesh->index_count + idx + 1];
1760 uint32_t i2 = pMesh->indices[pMesh->index_count + idx + 2];
1761 pMesh->indices[pMesh->index_count + idx + 0] = base_vertex_index + i2;
1762 pMesh->indices[pMesh->index_count + idx + 1] = base_vertex_index + i1;
1763 pMesh->indices[pMesh->index_count + idx + 2] = base_vertex_index + i0;
1764 }
1765 }
1766 else
1767 {
1768 for (uint32_t idx = 0; idx < tessellated_index_count; idx += 3)
1769 {
1770 uint32_t i0 = pMesh->indices[pMesh->index_count + idx + 0];
1771 uint32_t i1 = pMesh->indices[pMesh->index_count + idx + 1];
1772 uint32_t i2 = pMesh->indices[pMesh->index_count + idx + 2];
1773 pMesh->indices[pMesh->index_count + idx + 0] = base_vertex_index + i0;
1774 pMesh->indices[pMesh->index_count + idx + 1] = base_vertex_index + i1;
1775 pMesh->indices[pMesh->index_count + idx + 2] = base_vertex_index + i2;
1776 }
1777 }
1778
1779 pMesh->index_count += tessellated_index_count;
1780 }
1781 }
1782 }
1783
1784# undef SLICE_INDEX
1785}
1786
1787// for each slice
1788// for each voxel cell
1789// if not already polygonized
1790// initialize polygon to a single cell
1791// while (can expand polygon)
1792// choose an edge and expand it out as far as possible, tessellating surrounding edges if neccessary, marking newly expanded cells as polygonized
1793// triangulate the output polygon.
1794ogt_mesh* ogt_mesh_from_paletted_voxels_polygon(
1795 const ogt_voxel_meshify_context* pCtx,
1796 const uint8_t* pVoxels, uint32_t size_x, uint32_t size_y, uint32_t size_z, const ogt_mesh_rgba* pPalette)
1797{
1798 uint32_t max_face_count = _count_voxel_sized_faces(pVoxels, size_x, size_y, size_z);
1799 uint32_t max_vertex_count = max_face_count * 4;
1800 uint32_t max_index_count = max_face_count * 6;
1801
1802 uint32_t mesh_size = sizeof(ogt_mesh) + (max_vertex_count * sizeof(ogt_mesh_vertex)) + (max_index_count * sizeof(uint32_t));
1803 ogt_mesh* mesh = (ogt_mesh*)_voxel_meshify_malloc(pCtx, mesh_size);
1804 if (!mesh)
1805 return NULL;
1806
1807 mesh->vertices = (ogt_mesh_vertex*)&mesh[1];
1808 mesh->indices = (uint32_t*)&mesh->vertices[max_vertex_count];
1809 mesh->vertex_count = 0;
1810 mesh->index_count = 0;
1811
1812 const int32_t k_stride_x = 1;
1813 const int32_t k_stride_y = size_x;
1814 const int32_t k_stride_z = size_x * size_y;
1815
1816 // do the +y PASS
1817 {
1818 ogt_mesh_transform transform_pos_y = _make_transform(
1819 0.0f, 0.0f, 1.0f, 0.0f,
1820 1.0f, 0.0f, 0.0f, 0.0f,
1821 0.0f, 1.0f, 0.0f, 0.0f,
1822 0.0f, 0.0f, 0.0f, 0.0f);
1823
1824 _polygon_meshify_voxels_in_face_direction(
1825 pVoxels, pPalette,
1826 size_z, size_x, size_y,
1827 k_stride_z, k_stride_x, k_stride_y,
1828 transform_pos_y,
1829 mesh);
1830 }
1831
1832 // do the -y PASS
1833 {
1834 ogt_mesh_transform transform_neg_y = _make_transform(
1835 0.0f, 0.0f, 1.0f, 0.0f,
1836 1.0f, 0.0f, 0.0f, 0.0f,
1837 0.0f, -1.0f, 0.0f, 0.0f,
1838 0.0f, (float)(size_y), 0.0f, 0.0f);
1839
1840 _polygon_meshify_voxels_in_face_direction(
1841 pVoxels + (size_y - 1) * k_stride_y,
1842 pPalette,
1843 size_z, size_x, size_y,
1844 k_stride_z, k_stride_x, -k_stride_y,
1845 transform_neg_y,
1846 mesh);
1847 }
1848 // do the +X pass
1849 {
1850 ogt_mesh_transform transform_pos_x = _make_transform(
1851 0.0f, 1.0f, 0.0f, 0.0f,
1852 0.0f, 0.0f, 1.0f, 0.0f,
1853 1.0f, 0.0f, 0.0f, 0.0f,
1854 0.0f, 0.0f, 0.0f, 0.0f);
1855
1856 _polygon_meshify_voxels_in_face_direction(
1857 pVoxels, pPalette,
1858 size_y, size_z, size_x,
1859 k_stride_y, k_stride_z, k_stride_x,
1860 transform_pos_x,
1861 mesh);
1862 }
1863 // do the -X pass
1864 {
1865 ogt_mesh_transform transform_neg_x = _make_transform(
1866 0.0f, 1.0f, 0.0f, 0.0f,
1867 0.0f, 0.0f, 1.0f, 0.0f,
1868 -1.0f, 0.0f, 0.0f, 0.0f,
1869 (float)size_x, 0.0f, 0.0f, 0.0f);
1870
1871 _polygon_meshify_voxels_in_face_direction(
1872 pVoxels + (size_x - 1) * k_stride_x,
1873 pPalette,
1874 size_y, size_z, size_x,
1875 k_stride_y, k_stride_z, -k_stride_x,
1876 transform_neg_x,
1877 mesh);
1878 }
1879 // do the +Z pass
1880 {
1881 ogt_mesh_transform transform_pos_z = _make_transform(
1882 1.0f, 0.0f, 0.0f, 0.0f,
1883 0.0f, 1.0f, 0.0f, 0.0f,
1884 0.0f, 0.0f, 1.0f, 0.0f,
1885 0.0f, 0.0f, 0.0f, 0.0f);
1886
1887 _polygon_meshify_voxels_in_face_direction(
1888 pVoxels, pPalette,
1889 size_x, size_y, size_z,
1890 k_stride_x, k_stride_y, k_stride_z,
1891 transform_pos_z,
1892 mesh);
1893 }
1894 // do the -Z pass
1895 {
1896 ogt_mesh_transform transform_neg_z = _make_transform(
1897 1.0f, 0.0f, 0.0f, 0.0f,
1898 0.0f, 1.0f, 0.0f, 0.0f,
1899 0.0f, 0.0f, -1.0f, 0.0f,
1900 0.0f, 0.0f, (float)size_z, 0.0f);
1901
1902 _polygon_meshify_voxels_in_face_direction(
1903 pVoxels + (size_z - 1) * k_stride_z,
1904 pPalette,
1905 size_x, size_y, size_z,
1906 k_stride_x, k_stride_y, -k_stride_z,
1907 transform_neg_z,
1908 mesh);
1909 }
1910
1911 assert(mesh->vertex_count <= max_vertex_count);
1912 assert(mesh->index_count <= max_index_count);
1913
1914 return mesh;
1915}
1916
1917
1918void ogt_mesh_destroy(const ogt_voxel_meshify_context* pCtx, ogt_mesh* pMesh)
1919{
1920 _voxel_meshify_free(pCtx, pMesh);
1921}
1922
1923#endif // #ifdef OGT_VOXEL_MESHIFY_IMPLEMENTATION
1924
1925/* -------------------------------------------------------------------------------------------------------------------------------------------------
1926
1927 MIT License
1928
1929 Copyright (c) 2020 Justin Paver
1930
1931 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
1932 to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
1933 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:
1934
1935 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
1936
1937 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1938 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1939 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
1940 IN THE SOFTWARE.
1941
1942------------------------------------------------------------------------------------------------------------------------------------------------- */
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