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:
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);