star-3d

Surface structuring for efficient 3D geometric queries
git clone git://git.meso-star.fr/star-3d.git
Log | Files | Refs | README | LICENSE

commit 7b15260a20ba8199d3b699e7f8af2a0a93549eab
parent 6382123ac1077a616def64a6504dd30dfdc9a25f
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 10 Oct 2019 10:34:21 +0200

Perform advanced "point query" tests

Test the point query routine on the Cornell Box. Test the hit filtering
function with the point query routine.

Diffstat:
Msrc/s3d.h | 13+++++++------
Msrc/s3d_scene_view_point_query.c | 5++---
Msrc/test_s3d_point_query.c | 348+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
3 files changed, 342 insertions(+), 24 deletions(-)

diff --git a/src/s3d.h b/src/s3d.h @@ -170,15 +170,16 @@ enum s3d_scene_view_flag { #define S3D_HIT_NONE(Hit) ((Hit)->distance >= FLT_MAX) /* Filter function data type. One can define such function to discard - * intersections along a ray with respect to user defined criteria, e.g.: - * masked/transparent primitive, etc. Return 0 or the intersection is not - * discarded and a value not equal to zero otherwise. */ + * intersections along a ray or the result of a closest point query with + * respect to user defined criteria, e.g.: masked/transparent primitive, etc. + * Return 0 or the intersection is not discarded and a value not equal to zero + * otherwise. */ typedef int (*s3d_hit_filter_function_T) (const struct s3d_hit* hit, - const float ray_org[3], - const float ray_dir[3], - void* ray_data, /* User data submitted on trace ray(s) invocation */ + const float org[3], + const float dir[3], /* Direction from `org' to `hit' */ + void* query_data, /* User data submitted on query invocation */ void* filter_data); /* Data defined on the setup of the filter function */ /* Forward declaration of s3d opaque data types */ diff --git a/src/s3d_scene_view_point_query.c b/src/s3d_scene_view_point_query.c @@ -245,7 +245,7 @@ closest_point_mesh /* `vec' is the direction along which the closest point was found. We thus * submit it to the filter function as the direction corresponding to the - * computed hit. */ + * computed hit */ filter = &geom->data.mesh->filter; if(filter->func && filter->func(&hit, query_pos, vec, query_data, filter->data)) { @@ -317,7 +317,7 @@ closest_point_sphere if(dst >= args->query->radius) return 0; - /* Compute the uv the of found point */ + /* Compute the uv of the found point */ f3_divf(Ng, Ng, len); /* Normalize the hit normal */ sphere_normal_to_uv(Ng, uv); @@ -342,7 +342,6 @@ closest_point_sphere + geom->scene_prim_id_offset + (inst ? inst->scene_prim_id_offset : 0); - /* Use the reversed geometric normal as the hit direction since it is along * this vector that the closest point was effectively computed */ f3_minus(dir, Ng); diff --git a/src/test_s3d_point_query.c b/src/test_s3d_point_query.c @@ -31,17 +31,31 @@ * knowledge of the CeCILL license and that you accept its terms. */ #include "s3d.h" +#include "test_s3d_cbox.h" #include "test_s3d_utils.h" #include <rsys/float3.h> +#include <limits.h> + +#define ON_EDGE_EPSILON 1.e-4f +#define POSITION_EPSILON 1.e-3f + +struct closest_pt { + float pos[3]; + float normal[3]; + float dst; + unsigned iprim; +}; + +#define CLOSEST_PT_NULL__ {{0,0,0},{0,0,0},FLT_MAX,UINT_MAX} /******************************************************************************* * Helper functions ******************************************************************************/ -/* Inefficient function that computes the point onto the triangle that ensures - * the minimum distance between the submitted `pos' and the triangle. Use this - * routine to cross check the result of the s3d_scene_view_point_query function - * that internally rely on a more efficient implementation */ +/* Function that computes the point onto the triangle that ensures the minimum + * distance between the submitted `pos' and the triangle. Use this routine to + * cross check the result of the s3d_scene_view_point_query function that + * internally relies on a more efficient implementation */ static float* closest_point_triangle (const float p[3], /* Input pos */ @@ -64,7 +78,7 @@ closest_point_triangle /* Compute the triangle normal */ f3_cross(N, ac, ab); - /* Check if the nearest point is the 1st triangle vertex */ + /* Check if the nearest point is the 1st triangle vertex */ f3_sub(ap, p, a); d1 = f3_dot(ab, ap); d2 = f3_dot(ac, ap); @@ -83,19 +97,19 @@ closest_point_triangle if(d5 <= 0 && d6 <= 0) return f3_set(pt, c); /* Check if the nearest point is on the 1st triangle edge */ - f3_cross(Nab, ab, N); + f3_normalize(Nbc, f3_cross(Nab, ab, N)); if(f3_dot(Nab, ap) <= 0) { return f3_add(pt, a, f3_mulf(pt, ab, d1)); } /* Check if the nearest point is on the 2nd triangle edge */ - f3_cross(Nbc, bc, N); + f3_normalize(Nbc, f3_cross(Nbc, bc, N)); if(f3_dot(Nbc, bp) <= 0) { return f3_add(pt, b, f3_mulf(pt, bc, d3)); } /* Check if the nearest point is on the 3rd triangle edge */ - f3_cross(Nca, ac, N); + f3_normalize(Nca, f3_cross(Nca, ac, N)); f3_minus(Nca, Nca); if(f3_dot(Nca, cp) <= 0) { return f3_add(pt, c, f3_mulf(pt, ac,-d5)); @@ -107,6 +121,306 @@ closest_point_triangle return f3_add(pt, p, f3_mulf(pt, N, -d)); } +static void +closest_point_mesh + (const float pos[3], + const float* verts, + const unsigned* ids, + const unsigned ntris, + struct closest_pt* pt) +{ + unsigned itri; + CHK(pos && verts && ids && pt); + + pt->pos[0] = pt->pos[1] = pt->pos[2] = FLT_MAX; + pt->dst = FLT_MAX; + pt->iprim = UINT_MAX; + + /* Find the closest point on the mesh */ + FOR_EACH(itri, 0, ntris) { + float v0[3]; + float v1[3]; + float v2[3]; + float closest_pt[3]; + float vec[3]; + float dst; + + f3_set(v0, verts+ids[itri*3+0]*3); + f3_set(v1, verts+ids[itri*3+1]*3); + f3_set(v2, verts+ids[itri*3+2]*3); + + closest_point_triangle(pos, v0, v1, v2, closest_pt); + dst = f3_len(f3_sub(vec, closest_pt, pos)); + + if(dst < pt->dst) { + float E0[3], E1[3]; + f3_set(pt->pos, closest_pt); + pt->dst = dst; + pt->iprim = itri; + f3_sub(E0, v1, v0); + f3_sub(E1, v2, v0); + f3_cross(pt->normal, E1, E0); + f3_normalize(pt->normal, pt->normal); + } + } +} + +/* Check that `hit' roughly lies on an edge. */ +static int +hit_on_edge(const struct s3d_hit* hit) +{ + struct s3d_attrib v0, v1, v2, pos; + float E0[3], E1[3], N[3]; + float tri_2area; + float hit_2area0; + float hit_2area1; + float hit_2area2; + float hit_pos[3]; + + CHK(hit && !S3D_HIT_NONE(hit)); + + /* Retrieve the triangle vertices */ + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 0, S3D_POSITION, &v0)==RES_OK); + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 1, S3D_POSITION, &v1)==RES_OK); + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 2, S3D_POSITION, &v2)==RES_OK); + + /* Compute the triangle area * 2 */ + f3_sub(E0, v1.value, v0.value); + f3_sub(E1, v2.value, v0.value); + tri_2area = f3_len(f3_cross(N, E0, E1)); + + /* Compute the hit position */ + CHK(s3d_primitive_get_attrib(&hit->prim, S3D_POSITION, hit->uv, &pos) == RES_OK); + f3_set(hit_pos, pos.value); + + /* Compute areas */ + f3_sub(E0, v0.value, hit_pos); + f3_sub(E1, v1.value, hit_pos); + hit_2area0 = f3_len(f3_cross(N, E0, E1)); + f3_sub(E0, v1.value, hit_pos); + f3_sub(E1, v2.value, hit_pos); + hit_2area1 = f3_len(f3_cross(N, E0, E1)); + f3_sub(E0, v2.value, hit_pos); + f3_sub(E1, v0.value, hit_pos); + hit_2area2 = f3_len(f3_cross(N, E0, E1)); + + if(hit_2area0 / tri_2area < ON_EDGE_EPSILON + || hit_2area1 / tri_2area < ON_EDGE_EPSILON + || hit_2area2 / tri_2area < ON_EDGE_EPSILON) + return 1; + + return 0; +} + +/******************************************************************************* + * Cornell box test + ******************************************************************************/ +enum cbox_geom { + CBOX_WALLS, + CBOX_TALL_BLOCK, + CBOX_SHORT_BLOCK, + CBOX_GEOMS_COUNT__ +}; + +struct cbox_filter_data { + float query_pos[3]; + unsigned geom_to_filter[3]; +}; + +static int +cbox_filter + (const struct s3d_hit* hit, + const float org[3], + const float dir[3], + void* query_data, + void* filter_data) +{ + struct cbox_filter_data* data = query_data; + struct s3d_attrib attr; + float pos[3]; + float vec[3]; + + CHK(hit && org && dir && !S3D_HIT_NONE(hit)); + CHK((intptr_t)filter_data == (intptr_t)0xDECAFBAD); + f3_normalize(vec, dir); + + f3_add(pos, org, f3_mulf(pos, vec, hit->distance)); + CHK(s3d_primitive_get_attrib + (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK); + CHK(f3_eq_eps(attr.value, pos, POSITION_EPSILON)); + + if(!query_data) return 0; + + CHK(f3_eq_eps(data->query_pos, org, POSITION_EPSILON)); + + return data->geom_to_filter[0] == hit->prim.geom_id + || data->geom_to_filter[1] == hit->prim.geom_id + || data->geom_to_filter[2] == hit->prim.geom_id; +} + +static void +check_closest_point_cbox + (const float pos[3], + const unsigned geom_id[3], + struct s3d_hit* hit) +{ + struct closest_pt pt[CBOX_GEOMS_COUNT__] = { + CLOSEST_PT_NULL__, CLOSEST_PT_NULL__, CLOSEST_PT_NULL__ + }; + enum cbox_geom geom; + + CHK(pos && geom_id && hit); + + if(geom_id[CBOX_WALLS] != S3D_INVALID_ID) { + closest_point_mesh(pos, cbox_walls, cbox_walls_ids, cbox_walls_ntris, + &pt[CBOX_WALLS]); + geom = CBOX_WALLS; + } + if(geom_id[CBOX_TALL_BLOCK] != S3D_INVALID_ID) { + closest_point_mesh(pos, cbox_tall_block, cbox_block_ids, cbox_block_ntris, + &pt[CBOX_TALL_BLOCK]); + } + if(geom_id[CBOX_SHORT_BLOCK] != S3D_INVALID_ID) { + closest_point_mesh(pos, cbox_short_block, cbox_block_ids, cbox_block_ntris, + &pt[CBOX_SHORT_BLOCK]); + } + geom = pt[CBOX_WALLS].dst < pt[CBOX_TALL_BLOCK].dst + ? CBOX_WALLS : CBOX_TALL_BLOCK; + geom = pt[CBOX_SHORT_BLOCK].dst < pt[geom].dst + ? CBOX_SHORT_BLOCK : geom; + + if(pt[geom].dst >= FLT_MAX) { + CHK(S3D_HIT_NONE(hit)); + } else { + struct s3d_attrib attr; + float N[3]; + + CHK(!S3D_HIT_NONE(hit)); + + CHK(s3d_primitive_get_attrib + (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK); + f3_normalize(N, hit->normal); + + if(!hit_on_edge(hit)) { CHK(hit->prim.prim_id == pt[geom].iprim); } + if(hit->prim.prim_id == pt[geom].iprim) { + /* Due to numerical inaccuracies and the arbitrary order in which + * primitives are treated, 2 points on different primitive can have the + * same distance from the query position while their respective + * coordinates are not equal wrt POSITION_EPSILON. To avoid wrong + * assertion, we thus check the position returned by Star-3D against the + * manually computed position only if these positions lies on the same + * primitive */ + CHK(f3_eq_eps(pt[geom].pos, attr.value, POSITION_EPSILON)); + CHK(f3_eq_eps(pt[geom].normal, N, 1.e-4f)); + } + + CHK(hit->prim.geom_id == geom_id[geom]); + CHK(hit->prim.inst_id == S3D_INVALID_ID); + CHK(eq_epsf(hit->distance, pt[geom].dst, 1.e-4f)); + } +} + +static void +test_cbox(struct s3d_device* dev) +{ + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_scene* scn = NULL; + struct s3d_shape* walls = NULL; + struct s3d_shape* tall_block = NULL; + struct s3d_shape* short_block = NULL; + struct s3d_scene_view* scnview = NULL; + struct cbox_desc walls_desc; + struct cbox_desc tall_block_desc; + struct cbox_desc short_block_desc; + struct cbox_filter_data filter_data; + void* ptr = (void*)((intptr_t)0xDECAFBAD); + float pos[3]; + float low[3], upp[3], mid[3]; + unsigned geom_id[CBOX_GEOMS_COUNT__]; + size_t i; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &tall_block) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &short_block) == RES_OK); + CHK(s3d_shape_get_id(walls, &geom_id[CBOX_WALLS]) == RES_OK); + CHK(s3d_shape_get_id(tall_block, &geom_id[CBOX_TALL_BLOCK]) == RES_OK); + CHK(s3d_shape_get_id(short_block, &geom_id[CBOX_SHORT_BLOCK]) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(walls, cbox_filter, ptr) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(tall_block, cbox_filter, ptr) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(short_block, cbox_filter, ptr) == RES_OK); + CHK(s3d_scene_attach_shape(scn, walls) == RES_OK); + CHK(s3d_scene_attach_shape(scn, tall_block) == RES_OK); + CHK(s3d_scene_attach_shape(scn, short_block) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = cbox_get_position; + + walls_desc.vertices = cbox_walls; + walls_desc.indices = cbox_walls_ids; + CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids, + cbox_walls_nverts, &vdata, 1, &walls_desc) == RES_OK); + + tall_block_desc.vertices = cbox_tall_block; + tall_block_desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(tall_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &tall_block_desc) == RES_OK); + + short_block_desc.vertices = cbox_short_block; + short_block_desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(short_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &short_block_desc) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK); + mid[0] = (low[0] + upp[0]) * 0.5f; + mid[1] = (low[1] + upp[1]) * 0.5f; + mid[2] = (low[2] + upp[2]) * 0.5f; + + /* Check point query on Cornell box */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + CHK(s3d_scene_view_point_query(scnview, pos, (float)INF, NULL, &hit) == RES_OK); + check_closest_point_cbox(pos, geom_id, &hit); + } + + /* Setup filter data to filter the Cornell box blocks */ + filter_data.geom_to_filter[0] = geom_id[CBOX_TALL_BLOCK]; + filter_data.geom_to_filter[1] = geom_id[CBOX_SHORT_BLOCK]; + filter_data.geom_to_filter[2] = S3D_INVALID_ID; + geom_id[CBOX_TALL_BLOCK] = S3D_INVALID_ID; + geom_id[CBOX_SHORT_BLOCK] = S3D_INVALID_ID; + + /* Check point query filtering filtering */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + f3_set(filter_data.query_pos, pos); + + CHK(s3d_scene_view_point_query + (scnview, pos, (float)INF, &filter_data, &hit) == RES_OK); + + check_closest_point_cbox(pos, geom_id, &hit); + } + + CHK(s3d_shape_ref_put(walls) == RES_OK); + CHK(s3d_shape_ref_put(tall_block) == RES_OK); + CHK(s3d_shape_ref_put(short_block) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); +} + /******************************************************************************* * Single triangle test ******************************************************************************/ @@ -147,7 +461,7 @@ test_single_triangle(struct s3d_device* dev) float v0[3], v1[3], v2[3]; float pos[3] = {0,0,0}; float closest_pos[3] = {0,0,0}; - float low[3], upp[3]; + float low[3], upp[3], mid[3]; size_t i; CHK(s3d_scene_create(dev, &scn) == RES_OK); @@ -173,13 +487,16 @@ test_single_triangle(struct s3d_device* dev) upp[0] = MMAX(MMAX(v0[0], v1[0]), v2[0]); upp[1] = MMAX(MMAX(v0[1], v1[1]), v2[1]); upp[2] = MMAX(MMAX(v0[2], v1[2]), v2[2]); + mid[0] = (low[0] + upp[0]) * 0.5f; + mid[1] = (low[1] + upp[1]) * 0.5f; + mid[2] = (low[2] + upp[2]) * 0.5f; FOR_EACH(i, 0, 10000) { /* Randomly generate a point in a bounding box that is 10 times the size of * the triangle AABB */ - pos[0] = (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 10.f; - pos[1] = (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 10.f; - pos[2] = (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 10.f; + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f; + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f; + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f; CHK(s3d_scene_view_point_query(view, pos, (float)INF, NULL, &hit) == RES_OK); CHK(!S3D_HIT_NONE(&hit)); @@ -195,9 +512,9 @@ test_single_triangle(struct s3d_device* dev) /* Randomly generate a point in a bounding box that is 10 times the size of * the triangle AABB */ - pos[0] = (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 10.f; - pos[1] = (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 10.f; - pos[2] = (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 10.f; + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f; + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f; + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f; CHK(s3d_scene_view_point_query(view, pos, (float)INF, NULL, &hit) == RES_OK); CHK(!S3D_HIT_NONE(&hit)); @@ -261,6 +578,7 @@ main(int argc, char** argv) test_api(dev); test_single_triangle(dev); + test_cbox(dev); CHK(s3d_device_ref_put(dev) == RES_OK);