// // LICENCE: // The MIT License (MIT) // // Copyright (c) 2016 Karim Naaji, karim.naaji@gmail.com // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, 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: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE // // REFERENCES: // http://matthias-mueller-fischer.ch/publications/tetraederCollision.pdf // http://fileadmin.cs.lth.se/cs/Personal/Tomas_Akenine-Moller/code/tribox2.txt // // HOWTO: // #define VOXELIZER_IMPLEMENTATION // #define VOXELIZER_DEBUG // Only if assertions need to be checked // #include "voxelizer.h" // // HISTORY: // - version 0.9.0: Initial // // TODO: // - Triangle face merging // - Add colors from input mesh // - Potential issue with voxel bigger than triangle // #ifndef VOXELIZER_H #define VOXELIZER_H // ------------------------------------------------------------------------------------------------ // VOXELIZER PUBLIC API // #ifndef VOXELIZER_HELPERS #include // malloc, calloc, free #endif typedef struct vx_vertex { union { float v[3]; struct { float x; float y; float z; }; }; } vx_vertex_t; typedef struct vx_mesh { vx_vertex_t* vertices; // Contiguous mesh vertices vx_vertex_t* normals; // Contiguous mesh normals unsigned int* indices; // Mesh indices unsigned int* normalindices; // Mesh normal indices size_t nindices; // The number of normal indices size_t nvertices; // The number of vertices size_t nnormals; // The number of normals } vx_mesh_t; vx_mesh_t* vx_voxelize(vx_mesh_t* _mesh, // The input mesh float voxelsizex, // Voxel size on X-axis float voxelsizey, // Voxel size on Y-axis float voxelsizez, // Voxel size on Z-axis float precision); // A precision factor that reduces "holes" artifact // usually a precision = voxelsize / 10. works ok. void vx_mesh_free(vx_mesh_t* _mesh); vx_mesh_t* vx_mesh_alloc(int nindices, int nvertices); // Voxelizer Helpers, define your own if needed #ifndef VOXELIZER_HELPERS #define VOXELIZER_HELPERS 1 #define VX_MIN(a, b) (a > b ? b : a) #define VX_MAX(a, b) (a > b ? a : b) #define VX_FINDMINMAX(x0, x1, x2, min, max) \ min = max = x0; \ if (x1 < min) min = x1; \ if (x1 > max) max = x1; \ if (x2 < min) min = x2; \ if (x2 > max) max = x2; #define VX_CLAMP(v, lo, hi) VX_MAX(lo, VX_MIN(hi, v)) #define VX_MALLOC(T, N) ((T*) malloc(N * sizeof(T))) #define VX_FREE(T) free(T) #define VX_CALLOC(T, N) ((T*) calloc(N * sizeof(T), 1)) #define VX_SWAP(T, A, B) { T tmp = B; B = A; A = tmp; } #ifdef VOXELIZER_DEBUG #define VX_ASSERT(STMT) if (!(STMT)) { *(int *)0 = 0; } #else #define VX_ASSERT(STMT) #endif // VOXELIZER_DEBUG #endif // VOXELIZER_HELPERS // // END VOXELIZER PUBLIC API // ------------------------------------------------------------------------------------------------ #endif // VOXELIZER_H #ifdef VOXELIZER_IMPLEMENTATION #include // ceil, fabs & al. #include // hughh #include // memcpy #define VOXELIZER_EPSILON (0.0000001) #define VOXELIZER_NORMAL_INDICES_SIZE (6) #define VOXELIZER_INDICES_SIZE (36) #define VOXELIZER_HASH_TABLE_SIZE (4096) unsigned int vx_voxel_indices[VOXELIZER_INDICES_SIZE] = { 0, 1, 2, 0, 2, 3, 3, 2, 6, 3, 6, 7, 0, 7, 4, 0, 3, 7, 4, 7, 5, 7, 6, 5, 0, 4, 5, 0, 5, 1, 1, 5, 6, 1, 6, 2, }; float vx_normals[18] = { 0.0, -1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, -1.0, 0.0, 0.0, 0.0, 0.0, -1.0, }; unsigned int vx_normal_indices[VOXELIZER_NORMAL_INDICES_SIZE] = { 3, 2, 1, 5, 4, 0, }; typedef struct vx_aabb { vx_vertex_t min; vx_vertex_t max; } vx_aabb_t; typedef struct vx_edge { vx_vertex_t p1; vx_vertex_t p2; } vx_edge_t; typedef struct vx_triangle { vx_vertex_t p1; vx_vertex_t p2; vx_vertex_t p3; } vx_triangle_t; typedef struct vx_hash_table_node { struct vx_hash_table_node* next; struct vx_hash_table_node* prev; void* data; } vx_hash_table_node_t; typedef struct vx_hash_table { vx_hash_table_node_t** elements; size_t size; } vx_hash_table_t; vx_hash_table_t* vx__hash_table_alloc(size_t size) { vx_hash_table_t* table = VX_MALLOC(vx_hash_table_t, 1); if (!table) { return NULL; } table->size = size; table->elements = VX_MALLOC(vx_hash_table_node_t*, size); if (!table->elements) { return NULL; } for (size_t i = 0; i < table->size; ++i) { table->elements[i] = NULL; } return table; } void vx__hash_table_free(vx_hash_table_t* table, bool freedata) { for (size_t i = 0; i < table->size; ++i) { vx_hash_table_node_t* node = table->elements[i]; if (node) { if (node->next) { while (node->next) { node = node->next; if (freedata) { VX_FREE(node->prev->data); } VX_FREE(node->prev); } VX_FREE(node); } else { VX_FREE(node->data); VX_FREE(node); } } } VX_FREE(table->elements); VX_FREE(table); } bool vx__hash_table_insert(vx_hash_table_t* table, size_t hash, void* data, bool (*compfunc)(void* d1, void* d2)) { if (!table->elements[hash]) { table->elements[hash] = VX_MALLOC(vx_hash_table_node_t, 1); table->elements[hash]->prev = NULL; table->elements[hash]->next = NULL; table->elements[hash]->data = data; } else { vx_hash_table_node_t* node = table->elements[hash]; if (compfunc && compfunc(node->data, data)) { return false; } while (node->next) { node = node->next; if (compfunc && compfunc(node->data, data)) { return false; } } vx_hash_table_node_t* nnode = VX_MALLOC(vx_hash_table_node_t, 1); nnode->prev = node; nnode->next = NULL; nnode->data = data; node->next = nnode; } return true; } void vx_mesh_free(vx_mesh_t* m) { VX_FREE(m->vertices); m->vertices = NULL; m->nvertices = 0; VX_FREE(m->indices); m->indices = NULL; m->nindices = 0; if (m->normals) { VX_FREE(m->normals); } VX_FREE(m); } vx_mesh_t* vx_mesh_alloc(int nvertices, int nindices) { vx_mesh_t* m = VX_MALLOC(vx_mesh_t, 1); if (!m) { return NULL; } m->indices = VX_CALLOC(unsigned int, nindices); if (!m->indices) { return NULL; } m->vertices = VX_CALLOC(vx_vertex_t, nvertices); if (!m->vertices) { return NULL; } m->normals = NULL; m->nindices = nindices; m->nvertices = nvertices; return m; } float vx__map_to_voxel(float position, float voxelSize, bool min) { float vox = (position + (position < 0.f ? -1.f : 1.f) * voxelSize * 0.5f) / voxelSize; return (min ? floor(vox) : ceil(vox)) * voxelSize; } vx_vertex_t vx__vertex_cross(vx_vertex_t* v1, vx_vertex_t* v2) { vx_vertex_t cross; cross.x = v1->y * v2->z - v1->z * v2->y; cross.y = v1->z * v2->x - v1->x * v2->z; cross.z = v1->x * v2->y - v1->y * v2->x; return cross; } bool vx__vertex_equals(vx_vertex_t* v1, vx_vertex_t* v2) { return fabs(v1->x - v2->x) < VOXELIZER_EPSILON && fabs(v1->y - v2->y) < VOXELIZER_EPSILON && fabs(v1->z - v2->z) < VOXELIZER_EPSILON; } bool vx__vertex_comp_func(void* a, void* b) { return vx__vertex_equals((vx_vertex_t*) a, (vx_vertex_t*) b); } void vx__vertex_sub(vx_vertex_t* a, vx_vertex_t* b) { a->x -= b->x; a->y -= b->y; a->z -= b->z; } void vx__vertex_add(vx_vertex_t* a, vx_vertex_t* b) { a->x += b->x; a->y += b->y; a->z += b->z; } void vx__vertex_multiply(vx_vertex_t* a, float v) { a->x *= v; a->y *= v; a->z *= v; } float vx__vertex_dot(vx_vertex_t* v1, vx_vertex_t* v2) { return v1->x * v2->x + v1->y * v2->y + v1->z * v2->z; } int vx__plane_box_overlap(vx_vertex_t* normal, float d, vx_vertex_t* halfboxsize) { vx_vertex_t vmin, vmax; for (int dim = 0; dim <= 2; dim++) { if (normal->v[dim] > 0.0f) { vmin.v[dim] = -halfboxsize->v[dim]; vmax.v[dim] = halfboxsize->v[dim]; } else { vmin.v[dim] = halfboxsize->v[dim]; vmax.v[dim] = -halfboxsize->v[dim]; } } if (vx__vertex_dot(normal, &vmin) + d > 0.0f) { return false; } if (vx__vertex_dot(normal, &vmax) + d >= 0.0f) { return true; } return false; } #define AXISTEST_X01(a, b, fa, fb) \ p1 = a * v1.y - b * v1.z; \ p3 = a * v3.y - b * v3.z; \ if (p1 < p3) { \ min = p1; max = p3; \ } else { \ min = p3; max = p1; \ } \ rad = fa * halfboxsize.y + fb * halfboxsize.z; \ if (min > rad || max < -rad) { \ return false; \ } \ #define AXISTEST_X2(a, b, fa, fb) \ p1 = a * v1.y - b * v1.z; \ p2 = a * v2.y - b * v2.z; \ if (p1 < p2) { \ min = p1; max = p2; \ } else { \ min = p2; max = p1; \ } \ rad = fa * halfboxsize.y + fb * halfboxsize.z; \ if (min > rad || max < -rad) { \ return false; \ } \ #define AXISTEST_Y02(a, b, fa, fb) \ p1 = -a * v1.x + b * v1.z; \ p3 = -a * v3.x + b * v3.z; \ if (p1 < p3) { \ min = p1; max = p3; \ } else { \ min = p3; max = p1; \ } \ rad = fa * halfboxsize.x + fb * halfboxsize.z; \ if (min > rad || max < -rad) { \ return false; \ } \ #define AXISTEST_Y1(a, b, fa, fb) \ p1 = -a * v1.x + b * v1.z; \ p2 = -a * v2.x + b * v2.z; \ if (p1 < p2) { \ min = p1; max = p2; \ } else { \ min = p2; max = p1; \ } \ rad = fa * halfboxsize.x + fb * halfboxsize.z; \ if (min > rad || max < -rad) { \ return false; \ } #define AXISTEST_Z12(a, b, fa, fb) \ p2 = a * v2.x - b * v2.y; \ p3 = a * v3.x - b * v3.y; \ if (p3 < p2) { \ min = p3; max = p2; \ } else { \ min = p2; max = p3; \ } \ rad = fa * halfboxsize.x + fb * halfboxsize.y; \ if (min > rad || max < -rad) { \ return false; \ } #define AXISTEST_Z0(a, b, fa, fb) \ p1 = a * v1.x - b * v1.y; \ p2 = a * v2.x - b * v2.y; \ if (p1 < p2) { \ min = p1; max = p2; \ } else { \ min = p2; max = p1; \ } \ rad = fa * halfboxsize.x + fb * halfboxsize.y; \ if (min > rad || max < -rad) { \ return false; \ } int vx__triangle_box_overlap(vx_vertex_t boxcenter, vx_vertex_t halfboxsize, vx_triangle_t triangle) { vx_vertex_t v1, v2, v3, normal, e1, e2, e3; float min, max, d, p1, p2, p3, rad, fex, fey, fez; v1 = triangle.p1; v2 = triangle.p2; v3 = triangle.p3; vx__vertex_sub(&v1, &boxcenter); vx__vertex_sub(&v2, &boxcenter); vx__vertex_sub(&v3, &boxcenter); e1 = v2; e2 = v3; e3 = v1; vx__vertex_sub(&e1, &v1); vx__vertex_sub(&e2, &v2); vx__vertex_sub(&e3, &v3); fex = fabs(e1.x); fey = fabs(e1.y); fez = fabs(e1.z); AXISTEST_X01(e1.z, e1.y, fez, fey); AXISTEST_Y02(e1.z, e1.x, fez, fex); AXISTEST_Z12(e1.y, e1.x, fey, fex); fex = fabs(e2.x); fey = fabs(e2.y); fez = fabs(e2.z); AXISTEST_X01(e2.z, e2.y, fez, fey); AXISTEST_Y02(e2.z, e2.x, fez, fex); AXISTEST_Z0(e2.y, e2.x, fey, fex); fex = fabs(e3.x); fey = fabs(e3.y); fez = fabs(e3.z); AXISTEST_X2(e3.z, e3.y, fez, fey); AXISTEST_Y1(e3.z, e3.x, fez, fex); AXISTEST_Z12(e3.y, e3.x, fey, fex); VX_FINDMINMAX(v1.x, v2.x, v3.x, min, max); if (min > halfboxsize.x || max < -halfboxsize.x) { return false; } VX_FINDMINMAX(v1.y, v2.y, v3.y, min, max); if (min > halfboxsize.y || max < -halfboxsize.y) { return false; } VX_FINDMINMAX(v1.z, v2.z, v3.z, min, max); if (min > halfboxsize.z || max < -halfboxsize.z) { return false; } normal = vx__vertex_cross(&e1, &e2); d = -vx__vertex_dot(&normal, &v1); if (!vx__plane_box_overlap(&normal, d, &halfboxsize)) { return false; } return true; } #undef AXISTEST_X2 #undef AXISTEST_X01 #undef AXISTEST_Y1 #undef AXISTEST_Y02 #undef AXISTEST_Z0 #undef AXISTEST_Z12 float vx__triangle_area(vx_triangle_t* triangle) { vx_vertex_t ab = triangle->p2; vx_vertex_t ac = triangle->p3; vx__vertex_sub(&ab, &triangle->p1); vx__vertex_sub(&ac, &triangle->p1); float a0 = ab.y * ac.z - ab.z * ac.y; float a1 = ab.z * ac.x - ab.x * ac.z; float a2 = ab.x * ac.y - ab.y * ac.x; return sqrtf(powf(a0, 2.f) + powf(a1, 2.f) + powf(a2, 2.f)) * 0.5f; } void vx__aabb_init(vx_aabb_t* aabb) { aabb->max.x = aabb->max.y = aabb->max.z = -INFINITY; aabb->min.x = aabb->min.y = aabb->min.z = INFINITY; } vx_aabb_t vx__triangle_aabb(vx_triangle_t* triangle) { vx_aabb_t aabb; vx__aabb_init(&aabb); aabb.max.x = VX_MAX(aabb.max.x, triangle->p1.x); aabb.max.x = VX_MAX(aabb.max.x, triangle->p2.x); aabb.max.x = VX_MAX(aabb.max.x, triangle->p3.x); aabb.max.y = VX_MAX(aabb.max.y, triangle->p1.y); aabb.max.y = VX_MAX(aabb.max.y, triangle->p2.y); aabb.max.y = VX_MAX(aabb.max.y, triangle->p3.y); aabb.max.z = VX_MAX(aabb.max.z, triangle->p1.z); aabb.max.z = VX_MAX(aabb.max.z, triangle->p2.z); aabb.max.z = VX_MAX(aabb.max.z, triangle->p3.z); aabb.min.x = VX_MIN(aabb.min.x, triangle->p1.x); aabb.min.x = VX_MIN(aabb.min.x, triangle->p2.x); aabb.min.x = VX_MIN(aabb.min.x, triangle->p3.x); aabb.min.y = VX_MIN(aabb.min.y, triangle->p1.y); aabb.min.y = VX_MIN(aabb.min.y, triangle->p2.y); aabb.min.y = VX_MIN(aabb.min.y, triangle->p3.y); aabb.min.z = VX_MIN(aabb.min.z, triangle->p1.z); aabb.min.z = VX_MIN(aabb.min.z, triangle->p2.z); aabb.min.z = VX_MIN(aabb.min.z, triangle->p3.z); return aabb; } vx_vertex_t vx__aabb_center(vx_aabb_t* a) { vx_vertex_t boxcenter = a->min; vx__vertex_add(&boxcenter, &a->max); vx__vertex_multiply(&boxcenter, 0.5f); return boxcenter; } vx_vertex_t vx__aabb_half_size(vx_aabb_t* a) { vx_vertex_t size; size.x = fabs(a->max.x - a->min.x) * 0.5f; size.y = fabs(a->max.y - a->min.y) * 0.5f; size.z = fabs(a->max.z - a->min.z) * 0.5f; return size; } vx_aabb_t vx__aabb_merge(vx_aabb_t* a, vx_aabb_t* b) { vx_aabb_t merge; merge.min.x = VX_MIN(a->min.x, b->min.x); merge.min.y = VX_MIN(a->min.y, b->min.y); merge.min.z = VX_MIN(a->min.z, b->min.z); merge.max.x = VX_MAX(a->max.x, b->max.x); merge.max.y = VX_MAX(a->max.y, b->max.y); merge.max.z = VX_MAX(a->max.z, b->max.z); return merge; } size_t vx__vertex_hash(vx_vertex_t pos, size_t n) { size_t a = (size_t)(pos.x * 73856093); size_t b = (size_t)(pos.y * 19349663); size_t c = (size_t)(pos.z * 83492791); return (a ^ b ^ c) % n; } void vx__add_voxel(vx_mesh_t* mesh, vx_vertex_t* pos, float* vertices) { for (size_t i = 0; i < 8; ++i) { size_t index = i+mesh->nvertices; mesh->vertices[index].x = vertices[i*3+0] + pos->x; mesh->vertices[index].y = vertices[i*3+1] + pos->y; mesh->vertices[index].z = vertices[i*3+2] + pos->z; } int j = -1; for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { if (i % 6 == 0) { j++; } mesh->normalindices[i+mesh->nindices] = vx_normal_indices[j]; } for (size_t i = 0; i < VOXELIZER_INDICES_SIZE; ++i) { mesh->indices[i+mesh->nindices] = vx_voxel_indices[i] + mesh->nvertices; } mesh->nindices += VOXELIZER_INDICES_SIZE; mesh->nvertices += 8; } vx_mesh_t* vx_voxelize(vx_mesh_t* m, float voxelsizex, float voxelsizey, float voxelsizez, float precision) { vx_mesh_t* outmesh = NULL; vx_hash_table_t* table = NULL; size_t voxels = 0; float halfsizex = voxelsizex * 0.5f; float halfsizey = voxelsizey * 0.5f; float halfsizez = voxelsizez * 0.5f; table = vx__hash_table_alloc(VOXELIZER_HASH_TABLE_SIZE); for (int i = 0; i < m->nindices; i += 3) { vx_triangle_t triangle; VX_ASSERT(m->indices[i+0] < m->nvertices); VX_ASSERT(m->indices[i+1] < m->nvertices); VX_ASSERT(m->indices[i+2] < m->nvertices); triangle.p1 = m->vertices[m->indices[i+0]]; triangle.p2 = m->vertices[m->indices[i+1]]; triangle.p3 = m->vertices[m->indices[i+2]]; if (vx__triangle_area(&triangle) < VOXELIZER_EPSILON) { // triangle with 0 area continue; } vx_aabb_t aabb = vx__triangle_aabb(&triangle); aabb.min.x = vx__map_to_voxel(aabb.min.x, voxelsizex, true); aabb.min.y = vx__map_to_voxel(aabb.min.y, voxelsizey, true); aabb.min.z = vx__map_to_voxel(aabb.min.z, voxelsizez, true); aabb.max.x = vx__map_to_voxel(aabb.max.x, voxelsizex, false); aabb.max.y = vx__map_to_voxel(aabb.max.y, voxelsizey, false); aabb.max.z = vx__map_to_voxel(aabb.max.z, voxelsizez, false); for (float x = aabb.min.x; x < aabb.max.x; x += voxelsizex) { for (float y = aabb.min.y; y < aabb.max.y; y += voxelsizey) { for (float z = aabb.min.z; z < aabb.max.z; z += voxelsizez) { vx_aabb_t saabb; saabb.min.x = x - halfsizex; saabb.min.y = y - halfsizey; saabb.min.z = z - halfsizez; saabb.max.x = x + halfsizex; saabb.max.y = y + halfsizey; saabb.max.z = z + halfsizez; vx_vertex_t boxcenter = vx__aabb_center(&saabb); vx_vertex_t halfsize = vx__aabb_half_size(&saabb); // HACK: some holes might appear, this // precision factor reduces the artifact halfsize.x += precision; halfsize.y += precision; halfsize.z += precision; if (vx__triangle_box_overlap(boxcenter, halfsize, triangle)) { vx_vertex_t* nodedata = VX_MALLOC(vx_vertex_t, 1); *nodedata = boxcenter; size_t hash = vx__vertex_hash(boxcenter, VOXELIZER_HASH_TABLE_SIZE); bool insert = vx__hash_table_insert(table, hash, nodedata, vx__vertex_comp_func); if (insert) { voxels++; } } } } } } outmesh = VX_MALLOC(vx_mesh_t, 1); size_t nvertices = voxels * 8; size_t nindices = voxels * VOXELIZER_INDICES_SIZE; outmesh->nnormals = VOXELIZER_NORMAL_INDICES_SIZE; outmesh->vertices = VX_CALLOC(vx_vertex_t, nvertices); outmesh->normals = VX_CALLOC(vx_vertex_t, 6); outmesh->indices = VX_CALLOC(unsigned int, nindices); outmesh->normalindices = VX_CALLOC(unsigned int, nindices); outmesh->nindices = 0; outmesh->nvertices = 0; memcpy(outmesh->normals, vx_normals, 18 * sizeof(float)); float vertices[24] = { -halfsizex, halfsizey, halfsizez, -halfsizex, -halfsizey, halfsizez, halfsizex, -halfsizey, halfsizez, halfsizex, halfsizey, halfsizez, -halfsizex, halfsizey, -halfsizez, -halfsizex, -halfsizey, -halfsizez, halfsizex, -halfsizey, -halfsizez, halfsizex, halfsizey, -halfsizez, }; for (size_t i = 0; i < table->size; ++i) { if (table->elements[i] != NULL) { vx_hash_table_node_t* node = table->elements[i]; if (!node) { continue; } vx_vertex_t* p = (vx_vertex_t*) node->data; vx__add_voxel(outmesh, p, vertices); while (node->next) { node = node->next; p = (vx_vertex_t*) node->data; vx__add_voxel(outmesh, p, vertices); } } } vx__hash_table_free(table, true); return outmesh; } #undef VOXELIZER_EPSILON #undef VOXELIZER_INDICES_SIZE #undef VOXELIZER_HASH_TABLE_SIZE #endif // VX_VOXELIZER_IMPLEMENTATION