star-uvm

Spatial structuring of unstructured volumetric meshes
git clone git://git.meso-star.fr/star-uvm.git
Log | Files | Refs | README | LICENSE

commit 92ab8fe9483159c087a6a222e1097284caec480f
parent 21b67b362f5bf7750699bdbcf4fc627f729c3b1f
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu,  7 Jan 2021 12:08:20 +0100

Merge branch 'feature_tetrahedron_aabb_intersection'

Diffstat:
Mcmake/CMakeLists.txt | 8++++++++
Msrc/suvm.h | 79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/suvm_device.c | 2+-
Asrc/suvm_primitive.c | 335+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/suvm_volume.c | 137+++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------
Msrc/suvm_volume.h | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/suvm_volume_at.c | 20+++++---------------
Msrc/suvm_volume_intersect_aabb.c | 25+++++++++++++------------
Asrc/suvm_voxelize_sth.c | 423+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_suvm_box.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_suvm_primitive_intersection.c | 310+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_suvm_volume.c | 172++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
12 files changed, 1499 insertions(+), 153 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -26,6 +26,7 @@ option(NO_TEST "Do not build tests" OFF) find_package(Embree 3.6 REQUIRED) find_package(RCMake 0.4 REQUIRED) find_package(RSys 0.10 REQUIRED) +find_package(StarTetraHedra) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${RCMAKE_SOURCE_DIR}) include(rcmake) @@ -47,6 +48,7 @@ set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(SUVM_FILES_SRC suvm_device.c + suvm_primitive.c suvm_volume.c suvm_volume_at.c suvm_volume_intersect_aabb.c) @@ -95,6 +97,12 @@ if(NOT NO_TEST) new_test(test_suvm_device) new_test(test_suvm_volume) + new_test(test_suvm_primitive_intersection) +endif() + +if(StarTetraHedra_FOUND) + add_executable(suvm_voxelize ${SUVM_SOURCE_DIR}/suvm_voxelize_sth.c) + target_link_libraries(suvm_voxelize RSys StarTetraHedra suvm) endif() ################################################################################ diff --git a/src/suvm.h b/src/suvm.h @@ -17,6 +17,7 @@ #define SUVM_H #include <rsys/rsys.h> +#include <float.h> /* Library symbol management */ #if defined(SUVM_SHARED_BUILD) /* Build shared library */ @@ -44,19 +45,48 @@ struct suvm_data { size_t size; /* Size of the data in bytes */ size_t alignment; /* Alignment of the data */ }; -static const struct suvm_data SUVM_DATA_NULL; +#define SUVM_DATA_NULL__ {NULL, 0, 0} +static const struct suvm_data SUVM_DATA_NULL = SUVM_DATA_NULL__; struct suvm_primitive { const void* data; /* Data of the primitive */ const void* vertex_data[SUVM_PRIMITIVE_MAX_VERTICES_COUNT]; /* Vertex data */ size_t iprim; /* Identifier of the primitive */ size_t nvertices; /* #vertices of the primitive */ + + /* Internal data */ + const struct suvm_volume* volume__; }; -#define SUVM_PRIMITIVE_NULL__ {NULL, {NULL}, SIZE_MAX, SIZE_MAX} +#define SUVM_PRIMITIVE_NULL__ { \ + NULL, /* Primitive data */ \ + {NULL}, /* Vertex data */ \ + SIZE_MAX, /* Primitive id */ \ + SIZE_MAX, /* #vertices */ \ + NULL /* Pointer toward its associated volume */ \ +} static const struct suvm_primitive SUVM_PRIMITIVE_NULL = SUVM_PRIMITIVE_NULL__; #define SUVM_PRIMITIVE_NONE(Prim) ((Prim)->iprim == SIZE_MAX) +/* Precomputed data from a suvm_primitive used to speed up AABB/primitive + * intersection tests */ +struct suvm_polyhedron { + float v[4/*#vertices*/][3/*#coords*/]; /* Vertices */ + float N[4/*#vertices*/][3/*#coords*/]; /* Normals */ + float D[4/*#facets*/]; /* Slope parameter of the plane (D = -(Ax+By+Cz)) */ + + /* 2D equation of the silhouette edges projected along the X, Y and Z axis of + * the form Ax + By + C = 0, with (A, B) the normals of the edge */ + float Ep[3/*#axes*/][4/*#edges max*/][3/*#equation parameters*/]; + + /* Number of silhouette edges in the ZY, XZ and XY planes (3 or 4) */ + int nEp[3/*#coords*/]; + + /* Tetrahedron axis aligned bounding box */ + float lower[3/*#coords*/]; + float upper[3/*#coords*/]; +}; + struct suvm_tetrahedral_mesh_args { size_t ntetrahedra; /* #tetrahedra */ size_t nvertices; /* #vertices */ @@ -78,7 +108,18 @@ struct suvm_tetrahedral_mesh_args { void* context; /* Client data set as the last param of the callbacks */ }; -static const struct suvm_tetrahedral_mesh_args SUVM_TETRAHEDRAL_MESH_ARGS_NULL; +#define SUVM_TETRAHEDRAL_MESH_ARGS_NULL__ { \ + 0, 0, NULL, NULL, SUVM_DATA_NULL__, SUVM_DATA_NULL__, 0, NULL \ +} +static const struct suvm_tetrahedral_mesh_args SUVM_TETRAHEDRAL_MESH_ARGS_NULL = + SUVM_TETRAHEDRAL_MESH_ARGS_NULL__; + +enum suvm_intersection_type { + SUVM_INTERSECT_NONE, + SUVM_INTERSECT_INCLUDE, + SUVM_INTERSECT_IS_INCLUDED, + SUVM_INTERSECT_PARTIAL +}; /* Callback invoked on suvm_volume_intersect_aabb invocation on each primitives * intersected by the submitted Axis Aligned Bounding Box */ @@ -151,11 +192,8 @@ suvm_volume_at struct suvm_primitive* prim, /* Geometric primitive where `pos' lies */ double barycentric_coords[4]); /* `pos' into the primitive */ -/* Iterate over the volumetric primitives that intersect the submitted Axis - * Aligned Bounding Box and invoke the `clbk' function onto them. A primitive - * is included in the AABB if at least one of its vertices is _strictly_ - * included into the submitted boundaries; a vertex lying exactly on `low' or - * `upp' are considered outside the AABB */ +/* Iterate over the volumetric primitives intersecting the submitted Axis + * Aligned Bounding Box and invoke the `clbk' function onto them. */ SUVM_API res_T suvm_volume_intersect_aabb (struct suvm_volume* volume, @@ -164,6 +202,31 @@ suvm_volume_intersect_aabb suvm_primitive_intersect_aabb_T clbk, void* context); /* User data sent as the last argument of clbk */ +SUVM_API res_T +suvm_volume_get_primitives_count + (const struct suvm_volume* volume, + size_t* nprims); + +SUVM_API res_T +suvm_volume_get_primitive + (const struct suvm_volume* volume, + const size_t iprim, /* In [0, suvm_volume_get_primitives_count[ */ + struct suvm_primitive* prim); + +/******************************************************************************* + * Primitive API + ******************************************************************************/ +SUVM_API res_T +suvm_primitive_setup_polyhedron + (const struct suvm_primitive* prim, + struct suvm_polyhedron* poly); + +SUVM_API enum suvm_intersection_type +suvm_polyhedron_intersect_aabb + (const struct suvm_polyhedron* poly, + const float low[3], + const float upp[3]); + END_DECLS #endif /* SUVM_H */ diff --git a/src/suvm_device.c b/src/suvm_device.c @@ -83,7 +83,7 @@ suvm_device_create dev->logger = logger; dev->verbose = verbose; - sz = snprintf(embree_opts, sizeof(embree_opts), "verbose=%d", verbose); + sz = snprintf(embree_opts, sizeof(embree_opts), "verbose=%d", verbose > 1 ? 1 : 0); if((size_t)sz >= sizeof(embree_opts)) { log_err(dev, "Could not setup the Embree option string.\n"); res = RES_MEM_ERR; diff --git a/src/suvm_primitive.c b/src/suvm_primitive.c @@ -0,0 +1,335 @@ +/* Copyright (C) 2020 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "suvm.h" +#include "suvm_volume.h" + +#include <rsys/float2.h> +#include <rsys/float3.h> + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +suvm_primitive_setup_polyhedron + (const struct suvm_primitive* prim, + struct suvm_polyhedron* poly) +{ + if(!prim || !poly) return RES_BAD_ARG; + tetrahedron_setup(prim->volume__, NULL, NULL, prim->iprim, poly); + return RES_OK; +} + +/* Define if an axis aligned bounding box and a tetrahedron are intersecting. + * Its implementation follows the algorithms proposed by N. Greene in + * "Detecting intersection of a rectangular solid and a convex polyhedron" + * (Graphics Gems IV, 1994, p74--82) */ +enum suvm_intersection_type +suvm_polyhedron_intersect_aabb + (const struct suvm_polyhedron* tetra, + const float low[3], + const float upp[3]) +{ + enum { X, Y, Z, AXES_COUNT }; /* Syntactic sugar */ + float intersect_aabb_low[AXES_COUNT]; + float intersect_aabb_upp[AXES_COUNT]; + int nplanes_including_aabb; + int i; + + ASSERT(tetra && low && upp); + ASSERT(low[0] < upp[0]); + ASSERT(low[1] < upp[1]); + ASSERT(low[2] < upp[2]); + + /* Check the intersection between the AABB and the tetra AABB */ + FOR_EACH(i, 0, AXES_COUNT) { + intersect_aabb_low[i] = MMAX(low[i], tetra->lower[i]); + intersect_aabb_upp[i] = MMIN(upp[i], tetra->upper[i]); + if(intersect_aabb_low[i] > intersect_aabb_upp[i]) /* Do not intersect */ + return SUVM_INTERSECT_NONE; + } + + /* Check if the tetrahedron is included into the aabb */ + if(intersect_aabb_low[X] == tetra->lower[X] + && intersect_aabb_low[Y] == tetra->lower[Y] + && intersect_aabb_low[Z] == tetra->lower[Z] + && intersect_aabb_upp[X] == tetra->upper[X] + && intersect_aabb_upp[Y] == tetra->upper[Y] + && intersect_aabb_upp[Z] == tetra->upper[Z]) { + return SUVM_INTERSECT_IS_INCLUDED; + } + + /* #planes that totally includes the aabb on its positive side */ + nplanes_including_aabb = 0; + + /* Check the tetrahedron planes against the aabb */ + FOR_EACH(i, 0, 4) { + float p[3], n[3]; + + /* Define the aabb vertices that is the farthest in the positive/negative + * direction of the face normal. */ + f3_set(p, upp); + f3_set(n, low); + if(tetra->N[i][X] < 0) SWAP(float, p[X], n[X]); + if(tetra->N[i][Y] < 0) SWAP(float, p[Y], n[Y]); + if(tetra->N[i][Z] < 0) SWAP(float, p[Z], n[Z]); + + /* Check that the box is totally outside the plane, To do this, check that + * 'p', aka the farthest aabb vertex in the positive direction of the plane + * normal, is not in the positive half space. In this case, the aabb is + * entirely outside the tetrahedron */ + if((f3_dot(tetra->N[i], p) + tetra->D[i]) < 0) { + return SUVM_INTERSECT_NONE; + } + + /* Check if the box is totally inside the given plane. To do this, check + * that 'n', aka the farthest aabb vertex in the negative direction of the + * plane normal, is inside the positive half space */ + if((f3_dot(tetra->N[i], n) + tetra->D[i]) >= 0) { + /* Register this plane as a plane including the aabb */ + nplanes_including_aabb += 1; + } + } + + /* Check if the aabb is entirely included into the tetrahedron */ + if(nplanes_including_aabb == 4) { + return SUVM_INTERSECT_INCLUDE; + } + + /* For each silhouette edge in each projection plane, check if it totally + * exclude the projected aabb */ + FOR_EACH(i, 0, AXES_COUNT) { + /* Compute the remaining axes. + * On 1st iteration 'j' and 'k' define the YZ plane + * On 2nd iteration 'j' and 'k' define the ZX plane + * On 3rd ietration 'j' and 'k' define the XY plane */ + const int j = (i + 1) % AXES_COUNT; + const int k = (j + 1) % AXES_COUNT; + + int iedge; + FOR_EACH(iedge, 0, tetra->nEp[i]) { + float p[2]; + + /* Define the aabb vertices that is the farthest in the positive/negative + * direction of the normal of the silhouette edge. */ + p[0] = tetra->Ep[i][iedge][0] > 0 ? upp[j] : low[j]; + p[1] = tetra->Ep[i][iedge][1] > 0 ? upp[k] : low[k]; + + /* Check that the projected aabb along the 'i'th axis is totally outside + * the current silhouette edge. That means that the aabb is entirely + * outside the tetrahedron and thus that they do not intersect */ + if((f2_dot(tetra->Ep[i][iedge], p) + tetra->Ep[i][iedge][2]) < 0) { + return SUVM_INTERSECT_NONE; + } + } + } + + /* The tetrahedron and the aabb are partially intersecting */ + return SUVM_INTERSECT_PARTIAL; +} + + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +tetrahedron_setup + (const struct suvm_volume* vol, + const float lower[3], + const float upper[3], + const size_t itetra, + struct suvm_polyhedron* tetra) +{ + enum { X, Y, Z, AXES_COUNT }; /* Syntactic sugar */ + const float* low = lower; + const float* upp = upper; + float low__[3]; + float upp__[3]; + float center[AXES_COUNT]; /* Center of the tetrahedron */ + float e[6][AXES_COUNT]; + float (*v)[AXES_COUNT]; + float (*N)[AXES_COUNT]; + float (*Ep)[4][3]; + int i; + ASSERT(vol && tetra); + + /* Fetch tetrahedron vertices */ + volume_primitive_get_vertex_position(vol, itetra, 0, tetra->v[0]); + volume_primitive_get_vertex_position(vol, itetra, 1, tetra->v[1]); + volume_primitive_get_vertex_position(vol, itetra, 2, tetra->v[2]); + volume_primitive_get_vertex_position(vol, itetra, 3, tetra->v[3]); + v = tetra->v; + + /* Compute the center of the tetrahedron */ + center[X] = (v[0][X] + v[1][X] + v[2][X] + v[3][X]) * 0.25f; + center[Y] = (v[0][Y] + v[1][Y] + v[2][Y] + v[3][Y]) * 0.25f; + center[Z] = (v[0][Z] + v[1][Z] + v[2][Z] + v[3][Z]) * 0.25f; + + /* Define the primitive AABB if necessary */ + if(!low) { + low__[0] = MMIN(MMIN(v[0][X], v[1][X]), MMIN(v[2][X], v[3][X])); + low__[1] = MMIN(MMIN(v[0][Y], v[1][Y]), MMIN(v[2][Y], v[3][Y])); + low__[2] = MMIN(MMIN(v[0][Z], v[1][Z]), MMIN(v[2][Z], v[3][Z])); + low = low__; + } + if(!upp) { + upp__[0] = MMAX(MMAX(v[0][X], v[1][X]), MMAX(v[2][X], v[3][X])); + upp__[1] = MMAX(MMAX(v[0][Y], v[1][Y]), MMAX(v[2][Y], v[3][Y])); + upp__[2] = MMAX(MMAX(v[0][Z], v[1][Z]), MMAX(v[2][Z], v[3][Z])); + upp = upp__; + } + +#ifndef NDEBUG + /* Check argument consistency */ + if(lower) { + ASSERT(MMIN(MMIN(v[0][X], v[1][X]), MMIN(v[2][X], v[3][X])) == lower[X]); + ASSERT(MMIN(MMIN(v[0][Y], v[1][Y]), MMIN(v[2][Y], v[3][Y])) == lower[Y]); + ASSERT(MMIN(MMIN(v[0][Z], v[1][Z]), MMIN(v[2][Z], v[3][Z])) == lower[Z]); + } + if(upper) { + ASSERT(MMAX(MMAX(v[0][X], v[1][X]), MMAX(v[2][X], v[3][X])) == upper[X]); + ASSERT(MMAX(MMAX(v[0][Y], v[1][Y]), MMAX(v[2][Y], v[3][Y])) == upper[Y]); + ASSERT(MMAX(MMAX(v[0][Z], v[1][Z]), MMAX(v[2][Z], v[3][Z])) == upper[Z]); + } +#endif + + /* Setup tetrahedron AABB */ + f3_set(tetra->lower, low); + f3_set(tetra->upper, upp); + + /* Fetch tetrahedron normals */ + volume_primitive_get_facet_normal(vol, itetra, 0, tetra->N[0]); + volume_primitive_get_facet_normal(vol, itetra, 1, tetra->N[1]); + volume_primitive_get_facet_normal(vol, itetra, 2, tetra->N[2]); + volume_primitive_get_facet_normal(vol, itetra, 3, tetra->N[3]); + N = tetra->N; + + /* Compute the slope of the planes */ + tetra->D[0] = -f3_dot(tetra->N[0], v[0]); + tetra->D[1] = -f3_dot(tetra->N[1], v[1]); + tetra->D[2] = -f3_dot(tetra->N[2], v[2]); + tetra->D[3] = -f3_dot(tetra->N[3], v[3]); + + /* Compute tetrahedron edges */ + f3_sub(e[0], v[1], v[0]); + f3_sub(e[1], v[2], v[1]); + f3_sub(e[2], v[0], v[2]); + f3_sub(e[3], v[0], v[3]); + f3_sub(e[4], v[1], v[3]); + f3_sub(e[5], v[2], v[3]); + + /* Detect the silhouette edges of the tetrahedron once projected in the YZ, + * ZX and XY planes, and compute their 2D equation in the projected plane. The + * variable 'i' defines the projection axis which is successively X (YZ + * plane), Y (ZX plane) and Z (XY plane). The axes of the corresponding 2D + * repairs are defined by the 'j' and 'k' variables. */ + Ep = tetra->Ep; + FOR_EACH(i, 0, AXES_COUNT) { + float c[2]; /* Projected tetrahedron center */ + + /* On 1st iteration 'j' and 'k' define the YZ plane + * On 2nd iteration 'j' and 'k' define the ZX plane + * On 3rd ietration 'j' and 'k' define the XY plane */ + const int j = (i + 1) % AXES_COUNT; + const int k = (j + 1) % AXES_COUNT; + + /* Register the number of detected silhouette edges */ + int n = 0; + + int iedge; + + /* Project the tetrahedron center */ + c[0] = center[j]; + c[1] = center[k]; + + /* To detect the silhouette edges, check the sign of the normals of two + * adjacent facets for the coordinate of the projection axis. If the signs + * are the same, the facets look at the same direction regarding the + * projection axis. The other case means that one facet points toward the + * projection axis whole the other one points to the opposite direction: + * this is the definition of a silhouette edge. + * + * Once detected, we compute the equation of the silhouette edge: + * + * Ax + By + C = 0 + * + * with A and B the coordinates of the edge normals and C the slope of the + * edge. Computing the normal is actually as simple as swizzling the + * coordinates of the edges projected in 2D and sign tuning. Let the + * projection axis X (i==X) and thus the YZ projection plane (j==Y && + * k==Z). Assuming that the first edge 'e[0]' is a silhouette edge, ie the + * edge between the facet0 and the facet1 (refers to the suvm_volume.h for + * the geometric layout of a tetrahedron) once projected in the YZ plane + * the edge is {0, e[0][Y], e[0][Z]}. Its normal N can is defined as the + * cross product between this projected edge ands the projection axis: + * + * | 0 | | 1 | | 0 | + * N = | e[0][Y] | X | 0 | = | e[0][Z] | + * | e[0][Z] | | 0 | |-e[0][Y] | + * + * In the projection plane, the _unormalized_ normal of the {e[0][Y], + * e[0][Z]} edge is thus {e[0][Z], -e[0][Y]}. More generally, the normal of + * an edge 'I' {e[I][j], e[I][k]} projected along the 'i' axis in the 'jk' + * plane is {e[I][k],-e[I]j]}. Anyway, one has to revert the normal + * orientation to ensure a specific convention wrt the tetrahedron + * footprint. In our case we ensure that the normal points toward the + * footprint. Finalle the edge slope 'C' can be computed as: + * | A | + * C =- | B | . | Pj, Pk | + * + * with {Pj, Pk} a point belonging to the edge as for instance one of its + * vertices. */ + if(signf(N[0][i]) != signf(N[1][i])) { /* The edge 0 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[0][k], -e[0][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[0][j] + Ep[i][n][1]*v[0][k]); + ++n; + } + if(signf(N[0][i]) != signf(N[2][i])) { /* The edge 1 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[1][k], -e[1][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[1][j] + Ep[i][n][1]*v[1][k]); + ++n; + } + if(signf(N[0][i]) != signf(N[3][i])) { /* The edge 2 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[2][k], -e[2][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[2][j] + Ep[i][n][1]*v[2][k]); + ++n; + } + if(signf(N[1][i]) != signf(N[3][i])) { /* The edge 3 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[3][k], -e[3][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[0][j] + Ep[i][n][1]*v[0][k]); + ++n; + } + if(signf(N[1][i]) != signf(N[2][i])) { /* The edge 4 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[4][k], -e[4][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[1][j] + Ep[i][n][1]*v[1][k]); + ++n; + } + if(signf(N[2][i]) != signf(N[3][i])) { /* The edge 5 is silhouette */ + f2_normalize(Ep[i][n], f2(Ep[i][n], e[5][k], -e[5][j])); + Ep[i][n][2] = -(Ep[i][n][0]*v[2][j] + Ep[i][n][1]*v[2][k]); + ++n; + } + /* A tetrahedron can have 3 or 4 silhouette edges once projected in 2D */ + ASSERT(n == 3 || n == 4); + tetra->nEp[i] = n; /* Register the #silouhette edges for this project axis */ + + /* Ensure that the edge normals point toward the tetrahedron center */ + FOR_EACH(iedge, 0, n) { + if(f2_dot(Ep[i][iedge], c) + Ep[i][iedge][2] < 0) + f3_minus(Ep[i][iedge], Ep[i][iedge]); + } + } +} + diff --git a/src/suvm_volume.c b/src/suvm_volume.c @@ -179,10 +179,10 @@ compute_tetrahedral_mesh_normals(struct suvm_volume* vol) float* n1 = darray_float_data_get(&vol->normals) + (itetra*4 + 1)*3; float* n2 = darray_float_data_get(&vol->normals) + (itetra*4 + 2)*3; float* n3 = darray_float_data_get(&vol->normals) + (itetra*4 + 3)*3; - volume_compute_tetrahedron_normal(vol, itetra, 0, n0); - volume_compute_tetrahedron_normal(vol, itetra, 1, n1); - volume_compute_tetrahedron_normal(vol, itetra, 2, n2); - volume_compute_tetrahedron_normal(vol, itetra, 3, n3); + volume_primitive_compute_facet_normal(vol, itetra, 0, n0); + volume_primitive_compute_facet_normal(vol, itetra, 1, n1); + volume_primitive_compute_facet_normal(vol, itetra, 2, n2); + volume_primitive_compute_facet_normal(vol, itetra, 3, n3); } exit: @@ -426,65 +426,92 @@ error: } static res_T -check_tetrahedra_normals(const struct suvm_volume* vol) +fixup_tetrahedra_normals(struct suvm_volume* vol) { size_t itetra, ntetra; - res_T res = RES_OK; + int fixup = 0; ASSERT(vol); ntetra = volume_get_primitives_count(vol); FOR_EACH(itetra, 0, ntetra) { - const uint32_t* ids = NULL; - const float* verts[4]; - float normals[4][3]; - float pt[3]; - float v0[3], v1[3]; - + uint32_t* ids = NULL; + const float* vert0 = NULL; + const float* vert3 = NULL; + float normal0[3]; + float v0[3]; /* Fetch tetrahedron indices */ - ids = darray_u32_cdata_get(&vol->indices) + itetra*4; + ids = darray_u32_data_get(&vol->indices) + itetra*4; ASSERT(ids[0] < volume_get_vertices_count(vol)); ASSERT(ids[1] < volume_get_vertices_count(vol)); ASSERT(ids[2] < volume_get_vertices_count(vol)); ASSERT(ids[3] < volume_get_vertices_count(vol)); /* Fetch the tetrahedron vertices */ - verts[0] = darray_float_cdata_get(&vol->positions) + ids[0]*3/*#coords*/; - verts[1] = darray_float_cdata_get(&vol->positions) + ids[1]*3/*#coords*/; - verts[2] = darray_float_cdata_get(&vol->positions) + ids[2]*3/*#coords*/; - verts[3] = darray_float_cdata_get(&vol->positions) + ids[3]*3/*#coords*/; + vert0 = darray_float_cdata_get(&vol->positions) + ids[0]*3/*#coords*/; + vert3 = darray_float_cdata_get(&vol->positions) + ids[3]*3/*#coords*/; + + /* Fetch the normal of the 1st facet */ + volume_primitive_get_facet_normal(vol, itetra, 0, normal0); + + /* Check that the normal of the 1st facet looks the fourth vertex */ + if(f3_dot(normal0, f3_sub(v0, vert3, vert0)) < 0) { + fixup = 1; + + /* Revert tetrahedron orientation, i.e. the vertices of the 1st facet*/ + SWAP(uint32_t, ids[1], ids[2]); + + /* Revert precomputed normals if any */ + if(darray_float_size_get(&vol->normals)) { + size_t ifacet; + FOR_EACH(ifacet, 0, 4) { + const size_t id = (itetra*4/*#facets*/ + ifacet)*3/*#coords*/; + float* N = darray_float_data_get(&vol->normals) + id; + N[0] = -N[0]; + N[1] = -N[1]; + N[2] = -N[2]; + } + } + } - /* Fetch the tetrahedron normals */ - volume_get_tetrahedron_normal(vol, itetra, 0, normals[0]); - volume_get_tetrahedron_normal(vol, itetra, 1, normals[1]); - volume_get_tetrahedron_normal(vol, itetra, 2, normals[2]); - volume_get_tetrahedron_normal(vol, itetra, 3, normals[3]); - - /* Compute the position at the center of the tetrahedron */ - pt[0] = (verts[0][0] + verts[1][0] + verts[2][0] + verts[3][0]) * 0.25f; - pt[1] = (verts[0][1] + verts[1][1] + verts[2][1] + verts[3][1]) * 0.25f; - pt[2] = (verts[0][2] + verts[1][2] + verts[2][2] + verts[3][2]) * 0.25f; - - /* Check that normals look toward the tetrahedron center */ - f3_sub(v0, pt, verts[0]); - f3_sub(v1, pt, verts[3]); - if(f3_dot(normals[0], v0) < 0 - || f3_dot(normals[1], v1) < 0 - || f3_dot(normals[2], v1) < 0 - || f3_dot(normals[3], v1) < 0) { - log_err(vol->dev, - "Invalid vertex orientation for the tetrahedron %lu.\n", - (unsigned long)itetra); - res = RES_BAD_ARG; - goto error; +#ifndef NDEBUG + { + const float* verts[4]; + float normals[4][3]; + float pt[3], v1[3]; + + verts[0] = vert0; + verts[1] = darray_float_cdata_get(&vol->positions) + ids[1]*3/*#coords*/; + verts[2] = darray_float_cdata_get(&vol->positions) + ids[2]*3/*#coords*/; + verts[3] = vert3; + + /* Compute the position at the center of the tetrahedron */ + pt[0] = (verts[0][0] + verts[1][0] + verts[2][0] + verts[3][0]) * 0.25f; + pt[1] = (verts[0][1] + verts[1][1] + verts[2][1] + verts[3][1]) * 0.25f; + pt[2] = (verts[0][2] + verts[1][2] + verts[2][2] + verts[3][2]) * 0.25f; + + /* Fetch the tetrahedron normals */ + volume_primitive_get_facet_normal(vol, itetra, 0, normals[0]); + volume_primitive_get_facet_normal(vol, itetra, 1, normals[1]); + volume_primitive_get_facet_normal(vol, itetra, 2, normals[2]); + volume_primitive_get_facet_normal(vol, itetra, 3, normals[3]); + + /* Check normals orientation */ + f3_sub(v0, pt, verts[0]); + f3_sub(v1, pt, verts[3]); + ASSERT(f3_dot(normals[0], v0) >= 0); + ASSERT(f3_dot(normals[1], v1) >= 0); + ASSERT(f3_dot(normals[2], v1) >= 0); + ASSERT(f3_dot(normals[3], v1) >= 0); } +#endif } -exit: - return res; -error: - goto exit; + if(fixup) { + log_warn(vol->dev, "Tetrahedrals were not correctly orientated.\n"); + } + return RES_OK; } static void @@ -540,7 +567,7 @@ suvm_tetrahedral_mesh_create /* Ensure that the vertices of the tetrahedra are well ordered regarding the * normal convention */ - res = check_tetrahedra_normals(vol); + res = fixup_tetrahedra_normals(vol); if(res != RES_OK) goto error; /* Build the BVH of the volumetric mesh */ @@ -607,3 +634,23 @@ suvm_volume_get_aabb upper[2] = volume->upp[2]; return RES_OK; } + +res_T +suvm_volume_get_primitives_count(const struct suvm_volume* vol, size_t* nprims) +{ + if(!vol || !nprims) return RES_BAD_ARG; + *nprims = volume_get_primitives_count(vol); + return RES_OK; +} + +res_T +suvm_volume_get_primitive + (const struct suvm_volume* vol, + const size_t iprim, + struct suvm_primitive* prim) +{ + if(!vol|| !prim || iprim >= volume_get_primitives_count(vol)) + return RES_BAD_ARG; + volume_primitive_setup(vol, iprim, prim); + return RES_OK; +} diff --git a/src/suvm_volume.h b/src/suvm_volume.h @@ -23,6 +23,22 @@ #include <embree3/rtcore.h> +/* + * Tetrahedron geometric layout + * + * 3 (top vertex) facet 0 = {0,1,2} + * + facet 1 = {0,3,1} + * /|`. facet 2 = {1,3,2} + * / | `. facet 3 = {2,3,0} + * / | `. + * / | `. + * 0 +....|.........+ 2 + * `. | _,-' + * `,|_,-' + * + + * 1 + */ + /* Forward declarations */ struct suvm_device; struct mem_allocator; @@ -68,7 +84,7 @@ struct ALIGN(16) node_leaf { struct suvm_volume { struct darray_u32 indices; /* List of uint32_t[4] */ - struct darray_float positions; /* List of double[3] */ + struct darray_float positions; /* List of float[3] */ struct darray_float normals; /* List of per tetrahedron facet normals */ struct buffer prim_data; /* Per primitive data */ struct buffer vert_data; /* Per vertex data */ @@ -105,10 +121,26 @@ volume_get_vertices_count(const struct suvm_volume* vol) return sz / 3; } +static FINLINE float* +volume_primitive_get_vertex_position + (const struct suvm_volume* vol, + const size_t iprim, + const size_t ivert, /* In [0, 3] */ + float pos[3]) +{ + const uint32_t* ids; + ASSERT(vol && iprim < volume_get_primitives_count(vol) && pos && ivert < 4); + ids = darray_u32_cdata_get(&vol->indices) + iprim*4/*#vertex per tetra*/; + pos[0] = darray_float_cdata_get(&vol->positions)[ids[ivert]*3 + 0]; + pos[1] = darray_float_cdata_get(&vol->positions)[ids[ivert]*3 + 1]; + pos[2] = darray_float_cdata_get(&vol->positions)[ids[ivert]*3 + 2]; + return pos; +} + static INLINE float* -volume_compute_tetrahedron_normal +volume_primitive_compute_facet_normal (const struct suvm_volume* vol, - const size_t itetra, + const size_t iprim, const int ifacet/*In [0,3]*/, float normal[3]) { @@ -122,10 +154,11 @@ volume_compute_tetrahedron_normal /* Facet indices, Ensure CCW ordering */ const int facet[4][3] = {{0,1,2}, {0,3,1}, {1,3,2}, {2,3,0}}; - ASSERT(vol && itetra < volume_get_primitives_count(vol) && normal); + ASSERT(vol && iprim < volume_get_primitives_count(vol) && normal); + ASSERT(ifacet < 4); /* Fetch tetrahedron indices */ - ids = darray_u32_cdata_get(&vol->indices) + itetra*4; + ids = darray_u32_cdata_get(&vol->indices) + iprim*4; /* Fetch facet vertices */ v0 = darray_float_cdata_get(&vol->positions) + ids[facet[ifacet][0]]*3; @@ -142,19 +175,20 @@ volume_compute_tetrahedron_normal } static FINLINE float* -volume_get_tetrahedron_normal +volume_primitive_get_facet_normal (const struct suvm_volume* vol, - const size_t itetra, - const int ifacet /* In [0, 3] */, + const size_t iprim, + const int ifacet, /* In [0, 3] */ float normal[3]) { - const size_t id = - (itetra*4/*#facets per tetra*/ + (size_t)ifacet)*3/*#coords per normal*/; - ASSERT(vol && itetra < volume_get_primitives_count(vol) && normal); + ASSERT(vol && iprim < volume_get_primitives_count(vol) && normal); + ASSERT(ifacet < 4); if(!darray_float_size_get(&vol->normals)) { - volume_compute_tetrahedron_normal(vol, itetra, ifacet, normal); + volume_primitive_compute_facet_normal(vol, iprim, ifacet, normal); } else { + const size_t id = + (iprim*4/*#facets per tetra*/ + (size_t)ifacet)*3/*#coords per normal*/; ASSERT(id + 3 <= darray_float_size_get(&vol->normals)); normal[0] = darray_float_cdata_get(&vol->normals)[id+0]; normal[1] = darray_float_cdata_get(&vol->normals)[id+1]; @@ -174,7 +208,7 @@ buffer_at(const struct buffer* buf, const size_t i) } static INLINE const void* -volume_get_primitive_data(const struct suvm_volume* vol, const size_t iprim) +volume_primitive_get_data(const struct suvm_volume* vol, const size_t iprim) { ASSERT(vol && vol->has_prim_data && iprim < volume_get_primitives_count(vol)); return buffer_at(&vol->prim_data, iprim); @@ -193,5 +227,34 @@ volume_primitive_get_vertex_data return buffer_at(&vol->vert_data, ids[ivert]); } +static INLINE void +volume_primitive_setup + (const struct suvm_volume* vol, + const size_t iprim, + struct suvm_primitive* prim) +{ + ASSERT(vol && prim && iprim < volume_get_primitives_count(vol)); + prim->nvertices = 4; + prim->iprim = iprim; + prim->volume__ = vol; + if(vol->has_prim_data) { + prim->data = volume_primitive_get_data(vol, prim->iprim); + } + if(vol->has_vert_data) { + prim->vertex_data[0] = volume_primitive_get_vertex_data(vol, iprim, 0); + prim->vertex_data[1] = volume_primitive_get_vertex_data(vol, iprim, 1); + prim->vertex_data[2] = volume_primitive_get_vertex_data(vol, iprim, 2); + prim->vertex_data[3] = volume_primitive_get_vertex_data(vol, iprim, 3); + } +} + +extern LOCAL_SYM void +tetrahedron_setup + (const struct suvm_volume* vol, + const float low[3], /* NULL <=> compute on the fly */ + const float upp[3], /* NULL <=> compute on the fly */ + const size_t iprim, + struct suvm_polyhedron* polyhedron); + #endif /* SUVM_VOLUME_H */ diff --git a/src/suvm_volume_at.c b/src/suvm_volume_at.c @@ -56,7 +56,7 @@ intersect_leaf v0 = darray_float_cdata_get(&vol->positions) + tetra[0]*3; /* Compute the distance from pos to the bottom facet */ - volume_get_tetrahedron_normal(vol, itetra, 0, n0); + volume_primitive_get_facet_normal(vol, itetra, 0, n0); d0 = f3_dot(n0, f3_sub(p, pos, v0)); if(d0 < 0) return 0; @@ -65,13 +65,13 @@ intersect_leaf f3_sub(p, pos, v3); /* Compute the distance from pos to the 'side' facets */ - volume_get_tetrahedron_normal(vol, itetra, 1, n1); + volume_primitive_get_facet_normal(vol, itetra, 1, n1); d1 = f3_dot(n1, p); if(d1 < 0) return 0; - volume_get_tetrahedron_normal(vol, itetra, 2, n2); + volume_primitive_get_facet_normal(vol, itetra, 2, n2); d2 = f3_dot(n2, p); if(d2 < 0) return 0; - volume_get_tetrahedron_normal(vol, itetra, 3, n3); + volume_primitive_get_facet_normal(vol, itetra, 3, n3); d3 = f3_dot(n3, p); if(d3 < 0) return 0; @@ -184,17 +184,7 @@ suvm_volume_at /* pos lies into a tetrahedron */ if(iprim != SIZE_MAX) { - prim->iprim = iprim; - prim->nvertices = 4; - if(vol->has_prim_data) { - prim->data = volume_get_primitive_data(vol, iprim); - } - if(vol->has_vert_data) { - prim->vertex_data[0] = volume_primitive_get_vertex_data(vol, iprim, 0); - prim->vertex_data[1] = volume_primitive_get_vertex_data(vol, iprim, 1); - prim->vertex_data[2] = volume_primitive_get_vertex_data(vol, iprim, 2); - prim->vertex_data[3] = volume_primitive_get_vertex_data(vol, iprim, 3); - } + volume_primitive_setup(vol, iprim, prim); } exit: diff --git a/src/suvm_volume_intersect_aabb.c b/src/suvm_volume_intersect_aabb.c @@ -17,6 +17,9 @@ #include "suvm_device.h" #include "suvm_volume.h" +#include <rsys/float2.h> +#include <rsys/float3.h> + /******************************************************************************* * Helper functions ******************************************************************************/ @@ -93,21 +96,19 @@ suvm_volume_intersect_aabb size_t istack; if(node->type == NODE_LEAF) { + struct suvm_polyhedron tetra; + enum suvm_intersection_type intersect; + leaf = CONTAINER_OF(node, struct node_leaf, node); - if(aabb_intersect_aabb(lowf, uppf, leaf->low, leaf->upp)) { + + /* Check the intersection between the tetrahedron and the AABB */ + tetrahedron_setup(vol, leaf->low, leaf->upp, leaf->prim_id, &tetra); + intersect = suvm_polyhedron_intersect_aabb(&tetra, lowf, uppf); + + if(intersect != SUVM_INTERSECT_NONE) { struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; /* Setup the intersected primitive */ - prim.nvertices = 4; - prim.iprim = leaf->prim_id; - if(vol->has_prim_data) { - prim.data = volume_get_primitive_data(vol, prim.iprim); - } - if(vol->has_vert_data) { - prim.vertex_data[0] = volume_primitive_get_vertex_data(vol, prim.iprim, 0); - prim.vertex_data[1] = volume_primitive_get_vertex_data(vol, prim.iprim, 1); - prim.vertex_data[2] = volume_primitive_get_vertex_data(vol, prim.iprim, 2); - prim.vertex_data[3] = volume_primitive_get_vertex_data(vol, prim.iprim, 3); - } + volume_primitive_setup(vol, leaf->prim_id, &prim); /* Invoke the user callback on the intersected primitive */ clbk(&prim, low, upp, context); } diff --git a/src/suvm_voxelize_sth.c b/src/suvm_voxelize_sth.c @@ -0,0 +1,423 @@ +/* Copyright (C) 2020 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#define _POSIX_C_SOURCE 200112L /* getopt/close support */ + +#include "suvm.h" +#include <star/sth.h> +#include <rsys/clock_time.h> +#include <rsys/cstr.h> +#include <rsys/mem_allocator.h> + +#include <string.h> +#include <unistd.h> /* getopt & close functions */ + +struct args { + const char* input_filename; + const char* output_filename; + unsigned def[3]; + int quit; + int verbose; +}; + +static const struct args ARGS_DEFAULT = { + NULL, /* Input file */ + NULL, /* Output file */ + {64, 64, 64}, /* Definition */ + 0, /* Quit */ + 0 /* Verbose */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +print_help(const char* cmd) +{ + ASSERT(cmd); + printf( +"Usage: %s [options] [file]\n" +"Voxelize the submitted Star-TetraHedra geometry and save the result\n" +"into a VTK file.\n", + cmd); + printf("\n"); + printf( +" -d X:Y:Z voxelisation definition along the 3 axes.\n" +" The default definition is %u:%u:%u.\n", + ARGS_DEFAULT.def[0], + ARGS_DEFAULT.def[1], + ARGS_DEFAULT.def[2]); + printf( +" -h display this help and exit.\n"); + printf( +" -o <output> filename of the output VTK file. If not defined, write\n" +" results to standard ouput\n"); + printf( +" -v make the program verobse.\n"); + printf("\n"); + printf( +"Copyright (C) 2020 |Meso|Star> <contact@meso-star.com>.\n" +"This is free software released under the GNU GPL license, version 3 or\n" +"later. You are free to change or redistribute it under certain\n" +"conditions <http://gnu.org.licenses/gpl.html>\n"); +} + +static void +args_release(struct args* args) +{ + ASSERT(args); + *args = ARGS_DEFAULT; +} + +static res_T +args_init(struct args* args, const int argc, char** argv) +{ + size_t n; + int opt; + res_T res = RES_OK; + ASSERT(args); + + *args = ARGS_DEFAULT; + + while((opt = getopt(argc, argv, "d:ho:v")) != -1) { + switch(opt) { + case 'd': + res = cstr_to_list_uint(optarg, ':', args->def, &n, 3); + if(res == RES_OK && n != 3) res = RES_BAD_ARG; + if(!args->def[0] || !args->def[1] || !args->def[2]) res = RES_BAD_ARG; + break; + case 'h': + print_help(argv[0]); + args_release(args); + args->quit = 1; + break; + case 'o': + args->output_filename = optarg; + break; + case 'v': args->verbose = 1; break; + default: res = RES_BAD_ARG; break; + } + if(res != RES_OK) { + if(optarg) { + fprintf(stderr, "%s: invalid option argument '%s' -- '%c'\n", + argv[0], optarg, opt); + } + goto error; + } + } + + if(optind < argc) { + args->input_filename = argv[optind]; + } + +exit: + optind = 1; + return res; +error: + args_release(args); + goto exit; +} + +static void +get_indices(const size_t itetra, size_t ids[4], void* ctx) +{ + const uint64_t* ui64; + struct sth_desc* desc = ctx; + ASSERT(desc); + ui64 = sth_desc_get_tetrahedron_indices(desc, itetra); + ids[0] = (size_t)ui64[0]; + ids[1] = (size_t)ui64[1]; + ids[2] = (size_t)ui64[2]; + ids[3] = (size_t)ui64[3]; +} + +static void +get_position(const size_t ivert, double pos[3], void* ctx) +{ + const double* dbl; + struct sth_desc* desc = ctx; + ASSERT(desc); + dbl = sth_desc_get_vertex_position(desc, ivert); + pos[0] = dbl[0]; + pos[1] = dbl[1]; + pos[2] = dbl[2]; +} + +static void +write_voxels + (FILE* stream, + const int* vxls, + const unsigned def[3], + const double vxl_sz[3], + const double org[3]) +{ + size_t ix, iy, iz; + size_t ivxl; + size_t i; + ASSERT(stream && vxls && def && def[0] && def[1] && def[2]); + + fprintf(stream, "# vtk DataFile Version 2.0\n"); + fprintf(stream, "Volumetric grid\n"); + fprintf(stream, "ASCII\n"); + + fprintf(stream, "DATASET RECTILINEAR_GRID\n"); + fprintf(stream, "DIMENSIONS %u %u %u\n", def[0]+1, def[1]+1, def[2]+1); + fprintf(stream, "X_COORDINATES %u float\n", def[0]+1); + FOR_EACH(i, 0, def[0]+1) fprintf(stream, "%g ", (double)i*vxl_sz[0] + org[0]); + fprintf(stream, "\n"); + fprintf(stream, "Y_COORDINATES %u float\n", def[1]+1); + FOR_EACH(i, 0, def[1]+1) fprintf(stream, "%g ", (double)i*vxl_sz[1] + org[1]); + fprintf(stream, "\n"); + fprintf(stream, "Z_COORDINATES %u float\n", def[2]+1); + FOR_EACH(i, 0, def[2]+1) fprintf(stream, "%g ", (double)i*vxl_sz[2] + org[2]); + fprintf(stream, "\n"); + + fprintf(stream, "CELL_DATA %u\n", def[0]*def[1]*def[2]); + fprintf(stream, "SCALARS intersect_type int 1\n"); + fprintf(stream, "LOOKUP_TABLE default\n"); + + ivxl = 0; + FOR_EACH(iz, 0, def[2]) { + FOR_EACH(iy, 0, def[1]) { + FOR_EACH(ix, 0, def[0]) { + fprintf(stream, "%i\n", vxls[ivxl]); + ++ivxl; + } + } + } +} + +static res_T +voxelize_suvm_volume + (const struct suvm_volume* vol, + const unsigned def[3], + int* vxls) +{ + double low[3], upp[3]; + double vxl_sz[3]; + size_t iprim; + size_t nprims; + int progress = 0; + res_T res = RES_OK; + ASSERT(vol && def && def[0] && def[1] && def[2] && vxls); + + /* Compute the voxel size */ + res = suvm_volume_get_aabb(vol, low, upp); + if(res != RES_OK) goto error; + vxl_sz[0] = (upp[0] - low[0]) / (double)def[0]; + vxl_sz[1] = (upp[1] - low[1]) / (double)def[1]; + vxl_sz[2] = (upp[2] - low[2]) / (double)def[2]; + + res = suvm_volume_get_primitives_count(vol, &nprims); + if(res != RES_OK) goto error; + + FOR_EACH(iprim, 0, nprims) { + struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; + struct suvm_polyhedron poly; + size_t ivxl_low[3]; + size_t ivxl_upp[3]; + size_t ivxl[3]; + size_t i = 0; + int pcent; + + CHK(suvm_volume_get_primitive(vol, iprim, &prim) == RES_OK); + CHK(suvm_primitive_setup_polyhedron(&prim, &poly) == RES_OK); + + /* Transform the polyhedron AABB in voxel space */ + ivxl_low[0] = (size_t)((poly.lower[0] - low[0]) / vxl_sz[0]); + ivxl_low[1] = (size_t)((poly.lower[1] - low[1]) / vxl_sz[1]); + ivxl_low[2] = (size_t)((poly.lower[2] - low[2]) / vxl_sz[2]); + ivxl_upp[0] = (size_t)ceil((poly.upper[0] - low[0]) / vxl_sz[0]); + ivxl_upp[1] = (size_t)ceil((poly.upper[1] - low[1]) / vxl_sz[1]); + ivxl_upp[2] = (size_t)ceil((poly.upper[2] - low[2]) / vxl_sz[2]); + CHK(ivxl_low[0] < def[0] && ivxl_upp[0] <= def[0]); + CHK(ivxl_low[1] < def[1] && ivxl_upp[1] <= def[1]); + CHK(ivxl_low[2] < def[2] && ivxl_upp[2] <= def[2]); + + FOR_EACH(ivxl[2], ivxl_low[2], ivxl_upp[2]) { + float vxl_low[3]; + float vxl_upp[3]; + vxl_low[0] = (float)low[0]; + vxl_low[1] = (float)low[1]; + vxl_low[2] = (float)((double)ivxl[2] * vxl_sz[2] + low[2]); + vxl_upp[0] = (float)upp[0]; + vxl_upp[1] = (float)upp[1]; + vxl_upp[2] = vxl_low[2] + (float)vxl_sz[2]; + + FOR_EACH(ivxl[1], ivxl_low[1], ivxl_upp[1]) { + vxl_low[1] = (float)((double)ivxl[1] * vxl_sz[1] + low[1]); + vxl_upp[1] = vxl_low[1] + (float)vxl_sz[1]; + FOR_EACH(ivxl[0], ivxl_low[0], ivxl_upp[0]) { + vxl_low[0] = (float)((double)ivxl[0] * vxl_sz[0] + low[0]); + vxl_upp[0] = vxl_low[0] + (float)vxl_sz[0]; + + i = ivxl[0] + ivxl[1]*def[0] + ivxl[2]*def[0]*def[1]; + vxls[i] += (int)suvm_polyhedron_intersect_aabb(&poly, vxl_low, vxl_upp); + } + } + } + pcent = (int)((double)iprim * 100 / (double)nprims + 0.5/*round*/); + if(pcent > progress) { + progress = pcent; + fprintf(stdout, "Voxelizing: %3d%%\r", progress); + fflush(stdout); + } + } + + printf("Voxelizing: %3d%%\n", progress); + +exit: + return res; +error: + goto exit; +} + +/******************************************************************************* + * Main functions + ******************************************************************************/ +int +main(int argc, char** argv) +{ + char dump[128]; + struct args args = ARGS_DEFAULT; + FILE* stream_out = stdout; + FILE* stream_in = stdin; + const char* stream_out_name = "stdout"; + const char* stream_in_name = "stdin"; + struct sth* sth = NULL; + struct sth_desc desc = STH_DESC_NULL; + struct suvm_device* suvm = NULL; + struct suvm_volume* vol = NULL; + struct suvm_tetrahedral_mesh_args msh_args = SUVM_TETRAHEDRAL_MESH_ARGS_NULL; + struct time t0, t1; + int* vxls = NULL; + double low[3]; + double upp[3]; + double vxl_sz[3]; + res_T res = RES_OK; + int err = 0; + + res = args_init(&args, argc, argv); + if(res != RES_OK) goto error; + if(args.quit) goto exit; + + /* Setup input stream */ + if(args.input_filename) { + stream_in = fopen(args.input_filename, "r"); + if(!stream_in) { + fprintf(stderr, "Could not open the input file '%s' -- %s.\n", + args.input_filename, strerror(errno)); + goto error; + } + stream_in_name = args.input_filename; + } + + /* Setup output stream */ + if(args.output_filename) { + stream_out = fopen(args.output_filename, "w"); + if(!stream_out) { + fprintf(stderr, "Could not open the output file '%s' -- %s.\n", + args.output_filename, strerror(errno)); + goto error; + } + stream_out_name = args.output_filename; + (void)stream_out_name; + } + + + /* Load the submitted file */ + res = sth_create(NULL, NULL, args.verbose, &sth); + if(res != RES_OK) goto error; + res = sth_load_stream(sth, stream_in, stream_in_name); + if(res != RES_OK) goto error; + res = sth_get_desc(sth, &desc); + if(res != RES_OK) goto error; + if(args.verbose) { + printf("#nodes: %lu; #tetrahedra: %lu\n", desc.nvertices, desc.ntetrahedra); + } + + res = suvm_device_create(NULL, NULL, args.verbose, &suvm); + if(res != RES_OK) goto error; + + msh_args.nvertices = desc.nvertices; + msh_args.ntetrahedra = desc.ntetrahedra; + msh_args.get_indices = get_indices; + msh_args.get_position = get_position; + msh_args.context = &desc; + + /* Setup the volume from the loaded data */ + if(args.verbose) { + printf("Partitionning the tetrahedral mesh '%s'\n", stream_in_name); + } + time_current(&t0); + res = suvm_tetrahedral_mesh_create(suvm, &msh_args, &vol); + if(res != RES_OK) goto error; + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, dump, sizeof(dump)); + if(args.verbose) { + printf("Build acceleration structure in %s\n", dump); + } + + /* Allocate the list of voxels */ + vxls = mem_calloc(args.def[0]*args.def[1]*args.def[2], sizeof(*vxls)); + if(!vxls) { + fprintf(stderr, "Could not allocate the list of voxels.\n"); + res = RES_MEM_ERR; + goto error; + } + + /* Voxelize the volume */ + time_current(&t0); + res = voxelize_suvm_volume(vol, args.def, vxls); + if(res != RES_OK) goto error; + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, dump, sizeof(dump)); + if(args.verbose) { + printf("Volume voxelized in %s\n", dump); + } + + /* Write the voxels */ + if(args.verbose) { + printf("Writing voxels in '%s'\n", stream_out_name); + } + SUVM(volume_get_aabb(vol, low, upp)); + vxl_sz[0] = (upp[0] - low[0]) / (double)args.def[0]; + vxl_sz[1] = (upp[1] - low[1]) / (double)args.def[1]; + vxl_sz[2] = (upp[2] - low[2]) / (double)args.def[2]; + time_current(&t0); + write_voxels(stream_out, vxls, args.def, vxl_sz, low); + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, dump, sizeof(dump)); + if(args.verbose) { + printf("Voxels written in %s\n", dump); + } + +exit: + if(stream_out && stream_out != stdout) fclose(stream_out); + if(stream_in && stream_in != stdin) fclose(stream_in); + if(suvm) SUVM(device_ref_put(suvm)); + if(vol) SUVM(volume_ref_put(vol)); + if(sth) STH(ref_put(sth)); + if(vxls) mem_rm(vxls); + args_release(&args); + if(mem_allocated_size() != 0) { + fprintf(stderr, "Memory leaks: %lu Bytes.\n", + (unsigned long)mem_allocated_size()); + } + return err; +error: + err = -1; + goto exit; +} diff --git a/src/test_suvm_box.h b/src/test_suvm_box.h @@ -0,0 +1,52 @@ +/* Copyright (C) 2020 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#ifndef TEST_SUVM_BOX_H +#define TEST_SUVM_BOX_H + +static const double box_vertices[9/*#vertices*/*3/*#coords per vertex*/] = { + 0.0, 0.0, 0.0, + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 1.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0, + 1.0, 1.0, 1.0, + 0.5, 0.5, 0.5 +}; +static const size_t box_nverts = sizeof(box_vertices) / sizeof(double[3]); + +/* The following array lists the indices toward the 3D vertices of each + * boundary triangle. The index 8 references the vertex at the center of the + * box. + * ,2---,3 ,2----3 + * ,' | ,'/| ,'/| \ | Y + * 6----7' / | 6' / | \ | | + * |', | / ,1 | / ,0---,1 o--X + * | ',|/,' |/,' | ,' / + * 4----5' 4----5' Z */ +static const size_t box_indices[12/*#tetras*/*4/*#indices per tetra*/] = { + 0, 1, 2, 8, 1, 3, 2, 8, /* -Z */ + 0, 2, 4, 8, 2, 6, 4, 8, /* -X */ + 4, 6, 5, 8, 6, 7, 5, 8, /* +Z */ + 3, 5, 7, 8, 5, 3, 1, 8, /* +X */ + 2, 7, 6, 8, 7, 2, 3, 8, /* +Y */ + 0, 5, 1, 8, 5, 0, 4, 8 /* -Y */ +}; +static const size_t box_ntetras = sizeof(box_indices) / sizeof(size_t[4]); + + +#endif /* TEST_SUVM_BOX_H */ diff --git a/src/test_suvm_primitive_intersection.c b/src/test_suvm_primitive_intersection.c @@ -0,0 +1,310 @@ +/* Copyright (C) 2020 |Meso|Star> (contact@meso-star.com) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. */ + +#include "suvm.h" +#include "test_suvm_ball.h" +#include "test_suvm_box.h" +#include "test_suvm_utils.h" + +#include <rsys/float3.h> +#include <string.h> + +struct mesh { + const double* vertices; /* List of double[3] */ + size_t nvertices; + const size_t* tetrahedra; /* List of size_t[4] */ + size_t ntetrahedra; +}; +static const struct mesh MESH_NULL; + +/******************************************************************************* + * Helper functions + *************v*****************************************************************/ +static void +get_indices(const size_t itetra, size_t ids[4], void* ctx) +{ + struct mesh* msh = ctx; + CHK(itetra < msh->ntetrahedra); + CHK(ids != NULL); + ids[0] = msh->tetrahedra[itetra*4+0]; + ids[1] = msh->tetrahedra[itetra*4+1]; + ids[2] = msh->tetrahedra[itetra*4+2]; + ids[3] = msh->tetrahedra[itetra*4+3]; +} + +static void +get_position(const size_t ivert, double pos[3], void* ctx) +{ + struct mesh* msh = ctx; + CHK(ctx != NULL); + CHK(pos != NULL); + CHK(ivert < msh->nvertices); + pos[0] = msh->vertices[ivert*3+0]; + pos[1] = msh->vertices[ivert*3+1]; + pos[2] = msh->vertices[ivert*3+2]; +} + +static void +write_voxels + (FILE* stream, + const int* vxls, + const size_t def[3], + const double vxl_sz[3]) +{ + size_t ix, iy, iz; + size_t ivxl; + size_t i; + CHK(stream && vxls && def && def[0] && def[1] && def[2]); + + fprintf(stream, "# vtk DataFile Version 2.0\n"); + fprintf(stream, "nothing\n"); + fprintf(stream, "ASCII\n"); + + fprintf(stream, "DATASET RECTILINEAR_GRID\n"); + fprintf(stream, "DIMENSIONS %lu %lu %lu\n", def[0]+1, def[1]+1, def[2]+1); + fprintf(stream, "X_COORDINATES %lu float\n", def[0]+1); + FOR_EACH(i, 0, def[0]+1) fprintf(stream, "%g ", (double)i*vxl_sz[0]); + fprintf(stream, "\n"); + fprintf(stream, "Y_COORDINATES %lu float\n", def[1]+1); + FOR_EACH(i, 0, def[1]+1) fprintf(stream, "%g ", (double)i*vxl_sz[1]); + fprintf(stream, "\n"); + fprintf(stream, "Z_COORDINATES %lu float\n", def[2]+1); + FOR_EACH(i, 0, def[2]+1) fprintf(stream, "%g ", (double)i*vxl_sz[2]); + fprintf(stream, "\n"); + + fprintf(stream, "CELL_DATA %lu\n", def[0]*def[1]*def[2]); + fprintf(stream, "SCALARS intersect_type int 1\n"); + fprintf(stream, "LOOKUP_TABLE default\n"); + + ivxl = 0; + FOR_EACH(iz, 0, def[2]) { + FOR_EACH(iy, 0, def[1]) { + FOR_EACH(ix, 0, def[0]) { + fprintf(stream, "%i\n", vxls[ivxl]); + ++ivxl; + } + } + } +} + +static void +voxelise_volume + (const struct suvm_volume* vol, + int* vxls, + const size_t def[3]) +{ + double low[3]; + double upp[3]; + double vxl_sz[3]; + size_t iprim; + size_t nprims; + + CHK(suvm_volume_get_aabb(vol, low, upp) == RES_OK); + CHK(suvm_volume_get_primitives_count(vol, &nprims) == RES_OK); + + memset(vxls, 0, sizeof(*vxls)*def[0]*def[1]*def[2]); + + vxl_sz[0] = (upp[0] - low[0]) / (double)def[0]; + vxl_sz[1] = (upp[1] - low[1]) / (double)def[1]; + vxl_sz[2] = (upp[2] - low[2]) / (double)def[2]; + + FOR_EACH(iprim, 0, nprims) { + struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; + struct suvm_polyhedron poly; + size_t ivxl_low[3]; + size_t ivxl_upp[3]; + size_t ivxl[3]; + size_t i = 0; + + CHK(suvm_volume_get_primitive(vol, iprim, &prim) == RES_OK); + CHK(suvm_primitive_setup_polyhedron(&prim, &poly) == RES_OK); + + /* Transform the polyhedron AABB in voxel space */ + ivxl_low[0] = (size_t)((poly.lower[0] - low[0]) / vxl_sz[0]); + ivxl_low[1] = (size_t)((poly.lower[1] - low[1]) / vxl_sz[1]); + ivxl_low[2] = (size_t)((poly.lower[2] - low[2]) / vxl_sz[2]); + ivxl_upp[0] = (size_t)ceil((poly.upper[0] - low[0]) / vxl_sz[0]); + ivxl_upp[1] = (size_t)ceil((poly.upper[1] - low[1]) / vxl_sz[1]); + ivxl_upp[2] = (size_t)ceil((poly.upper[2] - low[2]) / vxl_sz[2]); + CHK(ivxl_low[0] < def[0] && ivxl_upp[0] <= def[0]); + CHK(ivxl_low[1] < def[1] && ivxl_upp[1] <= def[1]); + CHK(ivxl_low[2] < def[2] && ivxl_upp[2] <= def[2]); + + FOR_EACH(ivxl[2], ivxl_low[2], ivxl_upp[2]) { + float vxl_low[3]; + float vxl_upp[3]; + vxl_low[2] = (float)((double)ivxl[2] * vxl_sz[2] + low[2]); + vxl_upp[2] = vxl_low[2] + (float)vxl_sz[2]; + FOR_EACH(ivxl[1], ivxl_low[1], ivxl_upp[1]) { + vxl_low[1] = (float)((double)ivxl[1] * vxl_sz[1] + low[1]); + vxl_upp[1] = vxl_low[1] + (float)vxl_sz[1]; + FOR_EACH(ivxl[0], ivxl_low[0], ivxl_upp[0]) { + vxl_low[0] = (float)((double)ivxl[0] * vxl_sz[0] + low[0]); + vxl_upp[0] = vxl_low[0] + (float)vxl_sz[0]; + + i = ivxl[0] + ivxl[1]*def[0] + ivxl[2]*def[0]*def[1]; + vxls[i] += (int)suvm_polyhedron_intersect_aabb(&poly, vxl_low, vxl_upp); + } + } + } + } +} + +static void +test_mesh_voxelization + (struct suvm_device* dev, + struct mem_allocator* allocator, + struct mesh* msh, + const size_t def[3], + const char* filename) /* NULL <=> do not write the result onto disk */ +{ + struct suvm_volume* vol = NULL; + struct suvm_tetrahedral_mesh_args args = SUVM_TETRAHEDRAL_MESH_ARGS_NULL; + double low[3], upp[3]; + double vxl_sz[3]; + int* vxls = NULL; + + args.ntetrahedra = msh->ntetrahedra; + args.nvertices = msh->nvertices; + args.get_indices = get_indices; + args.get_position = get_position; + args.context = msh; + + CHK(suvm_tetrahedral_mesh_create(dev, &args, &vol) == RES_OK); + CHK(suvm_volume_get_aabb(vol, low, upp) == RES_OK); + vxl_sz[0] = (upp[0] - low[0]) / (double)def[0]; + vxl_sz[1] = (upp[1] - low[1]) / (double)def[1]; + vxl_sz[2] = (upp[2] - low[2]) / (double)def[2]; + + CHK(allocator != NULL); + vxls = MEM_CALLOC(allocator, def[0]*def[1]*def[2], sizeof(*vxls)); + CHK(vxls != NULL); + + voxelise_volume(vol, vxls, def); + + if(filename) { + FILE* fp = fopen(filename, "w"); + CHK(fp != NULL); + write_voxels(fp, vxls, def, vxl_sz); + CHK(fclose(fp) == 0); + } + + MEM_RM(allocator, vxls); + CHK(suvm_volume_ref_put(vol) == RES_OK); +} + + +static void +test_tetra_aabb_intersection + (struct suvm_device* dev, + struct mem_allocator* allocator) +{ + const double vertices[] = { + 0.0, 0.0, 0.0, + 0.0, 0.0, 1.0, + 1.0, 0.0, 1.0, + 0.0, 1.0, 1.0 + }; + const size_t tetra[] = { 0, 1, 2, 3 }; + struct mesh msh = MESH_NULL; + struct suvm_tetrahedral_mesh_args args = SUVM_TETRAHEDRAL_MESH_ARGS_NULL; + struct suvm_polyhedron poly; + struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; + struct suvm_volume* vol= NULL; + float low[3]; + float upp[3]; + double vxl_sz[3]; + const size_t def[3] = {16, 16, 16}; + size_t nprims; + int* vxls = NULL; + (void)vxl_sz; + + msh.vertices = vertices; + msh.nvertices = 4; + msh.tetrahedra = tetra; + msh.ntetrahedra = 1; + + args.ntetrahedra = 1; + args.nvertices = 4; + args.get_indices = get_indices; + args.get_position = get_position; + args.context = &msh; + + CHK(suvm_tetrahedral_mesh_create(dev, &args, &vol) == RES_OK); + + CHK(suvm_volume_get_primitives_count(vol, &nprims) == RES_OK); + CHK(nprims == 1); + + CHK(suvm_volume_get_primitive(vol, 0, &prim) == RES_OK); + CHK(suvm_primitive_setup_polyhedron(&prim, &poly) == RES_OK); + + f3(low, 0.f, 0.f, 0.f); + f3(upp, 1.f, 1.f, 1.f); + CHK(suvm_polyhedron_intersect_aabb(&poly, low, upp) == SUVM_INTERSECT_IS_INCLUDED); + + f3(low, 0.f, 0.f, 0.5f); + f3(upp, 0.5f, 0.5f, 1.f); + CHK(suvm_polyhedron_intersect_aabb(&poly, low, upp) == SUVM_INTERSECT_PARTIAL); + + f3(low, 0.f, 0.f, 0.66667f /* ~1-1/3 */); + f3(upp, 0.33332f/*~1/3*/, 0.33332f/*~1-1/3*/, 1.f); + CHK(suvm_polyhedron_intersect_aabb(&poly, low, upp) == SUVM_INTERSECT_INCLUDE); + + f3(low, 0.33334f/*~1.3*/, 0.33334f/* ~1/3 */, 0); + f3(upp, 1.f, 1.f, 0.66665f /*~ 1-1/3 */); + CHK(suvm_polyhedron_intersect_aabb(&poly, low, upp) == SUVM_INTERSECT_NONE); + + CHK(allocator != NULL); + vxls = MEM_CALLOC(allocator, def[0]*def[1]*def[2], sizeof(*vxls)); + CHK(vxls != NULL); + + voxelise_volume(vol, vxls, def); + + MEM_RM(allocator, vxls); + CHK(suvm_volume_ref_put(vol) == RES_OK); +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct mesh msh = MESH_NULL; + struct suvm_device* dev = NULL; + size_t def[3] = {64, 64, 64}; + (void)argc, (void)argv; + + CHK(suvm_device_create(NULL, &mem_default_allocator, 1, &dev) == RES_OK); + + test_tetra_aabb_intersection(dev, &mem_default_allocator); + + msh.vertices = box_vertices; + msh.nvertices = box_nverts; + msh.tetrahedra = box_indices; + msh.ntetrahedra = box_ntetras; + test_mesh_voxelization(dev, &mem_default_allocator, &msh, def, "box.vtk"); + + msh.vertices = ball_vertices; + msh.nvertices = ball_nvertices; + msh.tetrahedra = ball_tetrahedra; + msh.ntetrahedra = ball_ntetrahedra; + test_mesh_voxelization(dev, &mem_default_allocator, &msh, def, "ball.vtk"); + + CHK(suvm_device_ref_put(dev) == RES_OK); + check_memory_allocator(&mem_default_allocator); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_suvm_volume.c b/src/test_suvm_volume.c @@ -16,46 +16,16 @@ #include "suvm.h" #include "test_suvm_utils.h" #include "test_suvm_ball.h" +#include "test_suvm_box.h" #include <rsys/dynamic_array.h> #include <rsys/double3.h> +#include <rsys/float3.h> #define DARRAY_NAME prim #define DARRAY_DATA struct suvm_primitive #include <rsys/dynamic_array.h> -static const double box_vertices[9/*#vertices*/*3/*#coords per vertex*/] = { - 0.0, 0.0, 0.0, - 1.0, 0.0, 0.0, - 0.0, 1.0, 0.0, - 1.0, 1.0, 0.0, - 0.0, 0.0, 1.0, - 1.0, 0.0, 1.0, - 0.0, 1.0, 1.0, - 1.0, 1.0, 1.0, - 0.5, 0.5, 0.5 -}; -static const size_t box_nverts = sizeof(box_vertices) / sizeof(double[3]); - -/* The following array lists the indices toward the 3D vertices of each - * boundary triangle. The index 8 references the vertex at the center of the - * box. - * ,2---,3 ,2----3 - * ,' | ,'/| ,'/| \ | Y - * 6----7' / | 6' / | \ | | - * |', | / ,1 | / ,0---,1 o--X - * | ',|/,' |/,' | ,' / - * 4----5' 4----5' Z */ -static const size_t box_indices[12/*#tetras*/*4/*#indices per tetra*/] = { - 0, 1, 2, 8, 1, 3, 2, 8, /* -Z */ - 0, 2, 4, 8, 2, 6, 4, 8, /* -X */ - 4, 6, 5, 8, 6, 7, 5, 8, /* +Z */ - 3, 5, 7, 8, 5, 3, 1, 8, /* +X */ - 2, 7, 6, 8, 7, 2, 3, 8, /* +Y */ - 0, 5, 1, 8, 5, 0, 4, 8 /* -Y */ -}; -static const size_t box_ntetras = sizeof(box_indices) / sizeof(size_t[4]); - struct mesh { const double* vertices; /* List of double[3] */ size_t nvertices; @@ -138,11 +108,75 @@ cmp_prim(const void* a, const void* b) } static void +check_volume_polyhedra(const struct suvm_volume* vol, struct mesh* msh) +{ + size_t iprim; + size_t nprims; + + CHK(suvm_volume_get_primitives_count(vol, &nprims) == RES_OK); + CHK(nprims == msh->ntetrahedra); + + FOR_EACH(iprim, 0, nprims) { + struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; + struct suvm_polyhedron poly; + double v[4][3]; + double E0[3]; + double E1[3]; + double N[3]; + size_t ids[4]; + float f[3]; + CHK(suvm_volume_get_primitive(vol, iprim, &prim) == RES_OK); + CHK(!SUVM_PRIMITIVE_NONE(&prim)); + CHK(prim.iprim == iprim); + CHK(prim.nvertices == 4); + CHK(suvm_primitive_setup_polyhedron(&prim, &poly) == RES_OK); + + get_indices(iprim, ids, msh); + get_position(ids[0], v[0], msh); + get_position(ids[1], v[1], msh); + get_position(ids[2], v[2], msh); + get_position(ids[3], v[3], msh); + + CHK(f3_eq(poly.v[0], f3_set_d3(f, v[0]))); + CHK(f3_eq(poly.v[1], f3_set_d3(f, v[1]))); + CHK(f3_eq(poly.v[2], f3_set_d3(f, v[2]))); + CHK(f3_eq(poly.v[3], f3_set_d3(f, v[3]))); + + d3_sub(E0, v[1], v[0]); + d3_sub(E1, v[2], v[0]); + d3_cross(N, E0, E1); + f3_normalize(f, f3_set_d3(f, N)); + CHK(f3_eq_eps(poly.N[0], f, 1.e-4f)); + + d3_sub(E0, v[3], v[0]); + d3_sub(E1, v[1], v[0]); + d3_cross(N, E0, E1); + f3_normalize(f, f3_set_d3(f, N)); + CHK(f3_eq_eps(poly.N[1], f, 1.e-4f)); + + d3_sub(E0, v[3], v[1]); + d3_sub(E1, v[2], v[1]); + d3_cross(N, E0, E1); + f3_normalize(f, f3_set_d3(f, N)); + CHK(f3_eq_eps(poly.N[2], f, 1.e-4f)); + + d3_sub(E0, v[3], v[2]); + d3_sub(E1, v[0], v[2]); + d3_cross(N, E0, E1); + f3_normalize(f, f3_set_d3(f, N)); + CHK(f3_eq_eps(poly.N[3], f, 1.e-4f)); + } +} + +static void test_tetrahedral_mesh_creation(struct suvm_device* dev) { struct mesh msh = MESH_NULL; struct suvm_tetrahedral_mesh_args args = SUVM_TETRAHEDRAL_MESH_ARGS_NULL; struct suvm_volume* vol = NULL; + struct suvm_primitive prim = SUVM_PRIMITIVE_NULL; + struct suvm_polyhedron poly; + size_t nprims; args.ntetrahedra = box_ntetras; args.nvertices = box_nverts; @@ -221,11 +255,31 @@ test_tetrahedral_mesh_creation(struct suvm_device* dev) args.vertex_data.alignment = 32; msh.vertex_data_alignment = 32; CHK(suvm_tetrahedral_mesh_create(dev, &args, &vol) == RES_OK); + + CHK(suvm_volume_get_primitives_count(NULL, &nprims) == RES_BAD_ARG); + CHK(suvm_volume_get_primitives_count(vol, NULL) == RES_BAD_ARG); + CHK(suvm_volume_get_primitives_count(vol, &nprims) == RES_OK); + CHK(nprims == msh.ntetrahedra); + + CHK(suvm_volume_get_primitive(NULL, 0, &prim) == RES_BAD_ARG); + CHK(suvm_volume_get_primitive(vol, nprims, &prim) == RES_BAD_ARG); + CHK(suvm_volume_get_primitive(vol, 0, NULL) == RES_BAD_ARG); + CHK(suvm_volume_get_primitive(vol, 0, &prim) == RES_OK); + + CHK(suvm_primitive_setup_polyhedron(NULL, &poly) == RES_BAD_ARG); + CHK(suvm_primitive_setup_polyhedron(&prim, NULL) == RES_BAD_ARG); + CHK(suvm_primitive_setup_polyhedron(&prim, &poly) == RES_OK); + + check_volume_polyhedra(vol, &msh); + CHK(suvm_volume_ref_put(vol) == RES_OK); } static void -check_prim(const struct suvm_primitive* prim, struct mesh* msh) +check_prim + (const struct suvm_primitive* prim, + const struct suvm_volume* vol, + struct mesh* msh) { size_t ids[4]; double verts[4][3]; @@ -234,6 +288,8 @@ check_prim(const struct suvm_primitive* prim, struct mesh* msh) /* Check primitive data */ CHK(prim != NULL); + CHK(vol != NULL); + CHK(prim->volume__ == vol); CHK(!SUVM_PRIMITIVE_NONE(prim)); CHK(prim->data != NULL); CHK(prim->data != NULL); @@ -271,6 +327,7 @@ check_prim(const struct suvm_primitive* prim, struct mesh* msh) static void check_prim_at (const struct suvm_primitive* prim, + const struct suvm_volume* vol, const double pos[3], const double bcoords[4], struct mesh* msh) @@ -283,7 +340,7 @@ check_prim_at double p[3]; const double* vert_data[4]; - check_prim(prim, msh); + check_prim(prim, vol, msh); /* Fetch tetrahedron vertices */ get_indices(prim->iprim, ids, msh); @@ -345,6 +402,7 @@ check_prim_at static void check_prims_intersect_aabb (struct darray_prim* primitives, + const struct suvm_volume* vol, const double low[3], const double upp[3], struct mesh* msh) @@ -357,13 +415,15 @@ check_prims_intersect_aabb double vtx[4][3]; double prim_low[3]; double prim_upp[3]; + float lowf[3]; + float uppf[3]; CHK(primitives); prims = darray_prim_data_get(primitives); nprims = darray_prim_size_get(primitives); FOR_EACH(iprim, 0, nprims) { - check_prim(prims + iprim, msh); + check_prim(prims + iprim, vol, msh); /* Fetch tetrahedron vertices */ get_indices(prims[iprim].iprim, ids, msh); @@ -393,26 +453,19 @@ check_prims_intersect_aabb * that the input primitives are effectively the same of the ones detected by * this brute force procedure */ iprim_challenge = 0; + f3_set_d3(lowf, low); + f3_set_d3(uppf, upp); FOR_EACH(iprim, 0, msh->ntetrahedra) { - /* Fetch tetrahedron vertices */ - get_indices(iprim, ids, msh); - get_position(ids[0], vtx[0], msh); - get_position(ids[1], vtx[1], msh); - get_position(ids[2], vtx[2], msh); - get_position(ids[3], vtx[3], msh); + struct suvm_primitive prim; + struct suvm_polyhedron tetra; + enum suvm_intersection_type intersect; - /* Compute the primitive AABB */ - prim_low[0] = MMIN(MMIN(vtx[0][0], vtx[1][0]), MMIN(vtx[2][0], vtx[3][0])); - prim_low[1] = MMIN(MMIN(vtx[0][1], vtx[1][1]), MMIN(vtx[2][1], vtx[3][1])); - prim_low[2] = MMIN(MMIN(vtx[0][2], vtx[1][2]), MMIN(vtx[2][2], vtx[3][2])); - prim_upp[0] = MMAX(MMAX(vtx[0][0], vtx[1][0]), MMAX(vtx[2][0], vtx[3][0])); - prim_upp[1] = MMAX(MMAX(vtx[0][1], vtx[1][1]), MMAX(vtx[2][1], vtx[3][1])); - prim_upp[2] = MMAX(MMAX(vtx[0][2], vtx[1][2]), MMAX(vtx[2][2], vtx[3][2])); + CHK(suvm_volume_get_primitive(vol, iprim, &prim) == RES_OK); + CHK(suvm_primitive_setup_polyhedron(&prim, &tetra) == RES_OK); - /* Check that the primitives intersect the AABB */ - if(prim_low[0] < upp[0] && prim_upp[0] > low[0] - && prim_low[1] < upp[1] && prim_upp[1] > low[1] - && prim_low[2] < upp[2] && prim_upp[2] > low[2]) { + /* Check that the primitive intersects the AABB */ + intersect = suvm_polyhedron_intersect_aabb(&tetra, lowf, uppf); + if(intersect != SUVM_INTERSECT_NONE) { CHK(iprim == prims[iprim_challenge].iprim); ++iprim_challenge; } @@ -485,7 +538,7 @@ test_volume_at_cube(struct suvm_device* dev) CHK(suvm_volume_at(vol, pos, &prim, bcoords) == RES_OK); CHK(!SUVM_PRIMITIVE_NONE(&prim)); CHK(prim.iprim == 2); - check_prim_at(&prim, pos, bcoords, &msh); + check_prim_at(&prim, vol, pos, bcoords, &msh); CHK(suvm_volume_get_aabb(NULL, low, upp) == RES_BAD_ARG); CHK(suvm_volume_get_aabb(vol, NULL, upp) == RES_BAD_ARG); @@ -499,7 +552,7 @@ test_volume_at_cube(struct suvm_device* dev) samp[1] = low[1] + rand_canonic()*(upp[1] - low[1]); samp[2] = low[2] + rand_canonic()*(upp[2] - low[2]); CHK(suvm_volume_at(vol, samp, &prim, bcoords) == RES_OK); - check_prim_at(&prim, samp, bcoords, &msh); + check_prim_at(&prim, vol, samp, bcoords, &msh); } pos[0] = upp[0] + 0.1; @@ -523,7 +576,7 @@ test_volume_at_cube(struct suvm_device* dev) (vol, low, upp, prim_intersect_aabb, &prims) == RES_OK); CHK(darray_prim_size_get(&prims) == box_ntetras); - check_prims_intersect_aabb(&prims, low, upp, &msh); + check_prims_intersect_aabb(&prims, vol, low, upp, &msh); CHK(suvm_volume_intersect_aabb (vol, upp, low, prim_intersect_aabb, &prims) == RES_BAD_ARG); @@ -548,8 +601,7 @@ test_volume_at_cube(struct suvm_device* dev) CHK(suvm_volume_intersect_aabb (vol, low, upp, prim_intersect_aabb, &prims) == RES_OK); CHK(darray_prim_size_get(&prims) == 10); - check_prims_intersect_aabb(&prims, low, upp, &msh); - + check_prims_intersect_aabb(&prims, vol, low, upp, &msh); CHK(suvm_volume_ref_put(vol) == RES_OK); darray_prim_release(&prims); @@ -610,6 +662,8 @@ test_volume_at_ball(struct suvm_device* dev) CHK(suvm_tetrahedral_mesh_create(dev, &args, &vol) == RES_OK); + check_volume_polyhedra(vol, &msh); + /* Check volume AABB */ CHK(suvm_volume_get_aabb(vol, low, upp) == RES_OK); CHK(d3_eq_eps(aabb_low, low, aabb_sz*1.e-6)); @@ -626,7 +680,7 @@ test_volume_at_ball(struct suvm_device* dev) if(!SUVM_PRIMITIVE_NONE(&prim)) { check_at += 1; - check_prim_at(&prim, samp, bcoords, &msh); + check_prim_at(&prim, vol, samp, bcoords, &msh); } } CHK(check_at != 0); @@ -657,7 +711,7 @@ test_volume_at_ball(struct suvm_device* dev) (vol, aabb_low, aabb_upp, prim_intersect_aabb, &prims) == RES_OK); check_intersect_aabb = darray_prim_size_get(&prims) != 0; - check_prims_intersect_aabb(&prims, aabb_low, aabb_upp, &msh); + check_prims_intersect_aabb(&prims, vol, aabb_low, aabb_upp, &msh); darray_prim_clear(&prims); } CHK(check_intersect_aabb != 0);