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 8612a4bbebddf7cbd57d9049d3bace3a2b184f0b
parent 7b15260a20ba8199d3b699e7f8af2a0a93549eab
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 10 Oct 2019 11:42:12 +0200

Rename the point_query API in closest_point

Diffstat:
Mcmake/CMakeLists.txt | 4++--
Msrc/s3d.h | 2+-
Asrc/s3d_scene_view_closest_point.c | 440+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/s3d_scene_view_point_query.c | 440-------------------------------------------------------------------------------
Asrc/test_s3d_closest_point.c | 589+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/test_s3d_point_query.c | 589-------------------------------------------------------------------------------
6 files changed, 1032 insertions(+), 1032 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -69,7 +69,7 @@ set(S3D_FILES_SRC s3d_primitive.c s3d_scene.c s3d_scene_view.c - s3d_scene_view_point_query.c + s3d_scene_view_closest_point.c s3d_shape.c s3d_sphere.c) set(S3D_FILES_INC_API s3d.h) @@ -131,6 +131,7 @@ if(NOT NO_TEST) register_test(${_name} ${_name}) endfunction() + new_test(test_s3d_closest_point) new_test(test_s3d_device) new_test(test_s3d_sampler) new_test(test_s3d_sample_sphere) @@ -140,7 +141,6 @@ if(NOT NO_TEST) new_test(test_s3d_shape) new_test(test_s3d_sphere) new_test(test_s3d_sphere_box) - new_test(test_s3d_point_query) new_test(test_s3d_primitive) new_test(test_s3d_trace_ray_instance) new_test(test_s3d_trace_ray_sphere) diff --git a/src/s3d.h b/src/s3d.h @@ -331,7 +331,7 @@ s3d_scene_view_trace_rays * reject a candidate position according to its own criteria. Cab ne called * only if the scenview was created with the S3D_TRACE flag */ S3D_API res_T -s3d_scene_view_point_query +s3d_scene_view_closest_point (struct s3d_scene_view* scnview, const float pos[3], /* Position to query */ const float radius, /* Search distance in [0, radius[ */ diff --git a/src/s3d_scene_view_closest_point.c b/src/s3d_scene_view_closest_point.c @@ -0,0 +1,440 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "s3d_device_c.h" +#include "s3d_instance.h" +#include "s3d_geometry.h" +#include "s3d_mesh.h" +#include "s3d_scene_view_c.h" +#include "s3d_sphere.h" + +#include <rsys/float3.h> +#include <rsys/float33.h> + +struct point_query_context { + struct RTCPointQueryContext rtc; + struct s3d_scene_view* scnview; + void* data; /* Per point query defined data */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE float* +closest_point_triangle + (const float p[3], /* Point */ + const float a[3], /* 1st triangle vertex */ + const float b[3], /* 2nd triangle vertex */ + const float c[3], /* 3rd triangle vertex */ + float closest_pt[3], /* Closest position */ + float uv[2]) /* UV of the closest position */ +{ + float ab[3], ac[3], ap[3], bp[3], cp[3]; + float d1, d2, d3, d4, d5, d6; + float va, vb, vc; + float rcp_triangle_area; + float v, w; + ASSERT(p && a && b && c && closest_pt && uv); + + f3_sub(ab, b, a); + f3_sub(ac, c, a); + + /* Check if the closest point is the triangle vertex 'a' */ + f3_sub(ap, p, a); + d1 = f3_dot(ab, ap); + d2 = f3_dot(ac, ap); + if(d1 <= 0.f && d2 <= 0.f) { + uv[0] = 1.f; + uv[1] = 0.f; + return f3_set(closest_pt, a); + } + + /* Check if the closest point is the triangle vertex 'b' */ + f3_sub(bp, p, b); + d3 = f3_dot(ab, bp); + d4 = f3_dot(ac, bp); + if(d3 >= 0.f && d4 <= d3) { + uv[0] = 0.f; + uv[1] = 1.f; + return f3_set(closest_pt, b); + } + + /* Check if the closest point is the triangle vertex 'c' */ + f3_sub(cp, p, c); + d5 = f3_dot(ab, cp); + d6 = f3_dot(ac, cp); + if(d6 >= 0.f && d5 <= d6) { + uv[0] = 0.f; + uv[1] = 0.f; + return f3_set(closest_pt, c); + } + + /* Check if the closest point is on the triangle edge 'ab' */ + vc = d1*d4 - d3*d2; + if(vc <= 0.f && d1 >= 0.f && d3 <= 0.f) { + const float s = d1 / (d1 - d3); + closest_pt[0] = a[0] + s*ab[0]; + closest_pt[1] = a[1] + s*ab[1]; + closest_pt[2] = a[2] + s*ab[2]; + uv[0] = 1.f - s; + uv[1] = s; + return closest_pt; + } + + /* Check if the closest point is on the triangle edge 'ac' */ + vb = d5*d2 - d1*d6; + if(vb <= 0.f && d2 >= 0.f && d6 <= 0.f) { + const float s = d2 / (d2 - d6); + closest_pt[0] = a[0] + s*ac[0]; + closest_pt[1] = a[1] + s*ac[1]; + closest_pt[2] = a[2] + s*ac[2]; + uv[0] = 1.f - s; + uv[1] = 0.f; + return closest_pt; + } + + /* Check if the closest point is on the triangle edge 'bc' */ + va = d3*d6 - d5*d4; + if(va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) { + const float s = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + closest_pt[0] = b[0] + s*(c[0] - b[0]); + closest_pt[1] = b[1] + s*(c[1] - b[1]); + closest_pt[2] = b[2] + s*(c[2] - b[2]); + uv[0] = 0.f; + uv[1] = 1.f - s; + return closest_pt; + } + + /* The closest point lies in the triangle: compute its barycentric + * coordinates */ + rcp_triangle_area = 1.f / (va + vb + vc); + v = vb * rcp_triangle_area; + w = vc * rcp_triangle_area; + + /* Save the uv barycentric coordinates */ + uv[0] = 1.f - v - w; + uv[1] = v; + + ASSERT(eq_epsf(uv[0] + uv[1] + w, 1.f, 1.e-4f)); + + /* Use the barycentric coordinates to compute the world space position of the + * closest point onto the triangle */ + closest_pt[0] = a[0] + v*ab[0] + w*ac[0]; + closest_pt[1] = a[1] + v*ab[1] + w*ac[1]; + closest_pt[2] = a[2] + v*ab[2] + w*ac[2]; + return closest_pt; +} + +static bool +closest_point_mesh + (struct RTCPointQueryFunctionArguments* args, + struct geometry* geom, + struct geometry* inst, /* Can be NULL */ + void* query_data) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_hit* out_hit = NULL; + struct hit_filter* filter = NULL; + const uint32_t* ids = NULL; + float closest_point[3]; + float query_pos[3]; + float v0[3], v1[3], v2[3]; + float E0[3], E1[3], Ng[3]; + float vec[3]; + float uv[2]; + float dst; + int flip_surface = 0; + ASSERT(args && geom && geom->type == GEOM_MESH); + ASSERT(args->primID < mesh_get_ntris(geom->data.mesh)); + + /* Fetch triangle indices */ + ids = mesh_get_ids(geom->data.mesh) + args->primID*3/*#indices per triangle*/; + + /* Fetch triangle vertices */ + ASSERT(geom->data.mesh->attribs_type[S3D_POSITION] == S3D_FLOAT3); + f3_set(v0, mesh_get_pos(geom->data.mesh) + ids[0]*3/*#coords per vertex*/); + f3_set(v1, mesh_get_pos(geom->data.mesh) + ids[1]*3/*#coords per vertex*/); + f3_set(v2, mesh_get_pos(geom->data.mesh) + ids[2]*3/*#coords per vertex*/); + + /* Local copy of the query position */ + query_pos[0] = args->query->x; + query_pos[1] = args->query->y; + query_pos[2] = args->query->z; + + if(args->context->instStackSize) { /* The mesh is instantiated */ + const float* transform; + transform = inst->data.instance->transform; + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + ASSERT(inst && inst->type == GEOM_INSTANCE); + ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); + ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); + ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); + ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); + + /* Transform the triangle vertices in world space */ + f3_add(v0, f33_mulf3(v0, transform, v0), transform+9); + f3_add(v1, f33_mulf3(v1, transform, v1), transform+9); + f3_add(v2, f33_mulf3(v2, transform, v2), transform+9); + + flip_surface = inst->flip_surface; + } + + /* Compute the closest point onto the triangle from the submitted point */ + closest_point_triangle(query_pos, v0, v1, v2, closest_point, uv); + + f3_sub(vec, closest_point, query_pos); + dst = f3_len(vec); + if(dst >= args->query->radius) return 0; + + /* Compute the triangle normal in world space */ + f3_sub(E0, v2, v0); + f3_sub(E1, v1, v0); + f3_cross(Ng, E0, E1); + + /* Flip the geometric normal wrt the flip surface flag */ + flip_surface ^= geom->flip_surface; + if(flip_surface) f3_minus(Ng, Ng); + + /* Setup the hit */ + hit.prim.shape__ = geom; + hit.prim.inst__ = inst; + hit.distance = dst; + hit.uv[0] = uv[0]; + hit.uv[1] = uv[1]; + hit.normal[0] = Ng[0]; + hit.normal[1] = Ng[1]; + hit.normal[2] = Ng[2]; + hit.prim.prim_id = args->primID; + hit.prim.geom_id = geom->name; + hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; + hit.prim.scene_prim_id = + hit.prim.prim_id + + geom->scene_prim_id_offset + + (inst ? inst->scene_prim_id_offset : 0); + + /* `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 */ + filter = &geom->data.mesh->filter; + if(filter->func + && filter->func(&hit, query_pos, vec, query_data, filter->data)) { + return 0; /* This point is filtered. Discard it! */ + } + + /* Update output data */ + out_hit = args->userPtr; + *out_hit = hit; + + /* Shrink the query radius */ + args->query->radius = dst; + + return 1; /* Notify that the query radius was updated */ +} + +static bool +closest_point_sphere + (struct RTCPointQueryFunctionArguments* args, + struct geometry* geom, + struct geometry* inst, + void* query_data) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_hit* out_hit = NULL; + struct hit_filter* filter = NULL; + float query_pos[3]; + float sphere_pos[3]; + float Ng[3]; + float dir[3]; + float uv[2]; + float dst; + float len; + int flip_surface = 0; + ASSERT(args && geom && geom->type == GEOM_SPHERE && args->primID == 1); + + /* Local copy of the query position */ + query_pos[0] = args->query->x; + query_pos[1] = args->query->y; + query_pos[2] = args->query->z; + + f3_set(sphere_pos, geom->data.sphere->pos); + if(args->context->instStackSize) { /* The sphere is instantiated */ + const float* transform; + transform = inst->data.instance->transform; + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + ASSERT(inst && inst->type == GEOM_INSTANCE); + ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); + ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); + ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); + ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); + + /* Transform the sphere position in world space */ + f33_mulf3(sphere_pos, transform, sphere_pos); + f3_add(sphere_pos, transform+9, sphere_pos); + + flip_surface = inst->flip_surface; + } + + /* Compute the distance from the the query pos to the sphere center */ + f3_sub(Ng, query_pos, sphere_pos); + len = f3_len(Ng); + + /* Evaluate the distance from the query pos to the sphere surface */ + dst = fabsf(len - geom->data.sphere->radius); + + /* The closest point onto the sphere is outside the query radius */ + if(dst >= args->query->radius) + return 0; + + /* Compute the uv of the found point */ + f3_divf(Ng, Ng, len); /* Normalize the hit normal */ + sphere_normal_to_uv(Ng, uv); + + /* Flip the geometric normal wrt the flip surface flag */ + flip_surface ^= geom->flip_surface; + if(flip_surface) f3_minus(Ng, Ng); + + /* Setup the hit */ + hit.prim.shape__ = geom; + hit.prim.inst__ = inst; + hit.distance = dst; + hit.uv[0] = uv[0]; + hit.uv[1] = uv[1]; + hit.normal[0] = Ng[0]; + hit.normal[1] = Ng[1]; + hit.normal[2] = Ng[2]; + hit.prim.prim_id = args->primID; + hit.prim.geom_id = geom->name; + hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; + hit.prim.scene_prim_id = + hit.prim.prim_id + + 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); + filter = &geom->data.sphere->filter; + if(filter->func + && filter->func(&hit, query_pos, dir, query_data, filter->data)) { + return 0; + } + + /* Update output data */ + out_hit = args->userPtr; + *out_hit = hit; + + /* Shrink the query radius */ + args->query->radius = dst; + + return 1; /* Notify that the query radius was updated */ +} + +static bool +closest_point(struct RTCPointQueryFunctionArguments* args) +{ + struct point_query_context* ctx = NULL; + struct geometry* geom = NULL; + struct geometry* inst = NULL; + bool query_radius_is_upd = false; + ASSERT(args); + + ctx = CONTAINER_OF(args->context, struct point_query_context, rtc); + if(args->context->instStackSize == 0) { + geom = scene_view_geometry_from_embree_id + (ctx->scnview, args->geomID); + } else { + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + inst = scene_view_geometry_from_embree_id + (ctx->scnview, args->context->instID[0]); + geom = scene_view_geometry_from_embree_id + (inst->data.instance->scnview, args->geomID); + } + + switch(geom->type) { + case GEOM_MESH: + query_radius_is_upd = closest_point_mesh(args, geom, inst, ctx->data); + break; + case GEOM_SPHERE: + query_radius_is_upd = closest_point_sphere(args, geom, inst, ctx->data); + break; + default: FATAL("Unreachable code\n"); break; + } + return query_radius_is_upd; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +s3d_scene_view_closest_point + (struct s3d_scene_view* scnview, + const float pos[3], + const float radius, + void* query_data, + struct s3d_hit* hit) +{ + struct RTCPointQuery query; + struct point_query_context query_ctx; + + if(!scnview || !pos || radius <= 0 || !hit) + return RES_BAD_ARG; + if((scnview->mask & S3D_TRACE) == 0) { + log_error(scnview->scn->dev, + "%s: the S3D_TRACE flag is not active onto the submitted scene view.\n", + FUNC_NAME); + return RES_BAD_OP; + } + + *hit = S3D_HIT_NULL; + + /* Initialise the point query */ + query.x = pos[0]; + query.y = pos[1]; + query.z = pos[2]; + query.radius = radius; + query.time = FLT_MAX; /* Invalid fields */ + + /* Initialise the point query context */ + rtcInitPointQueryContext(&query_ctx.rtc); + query_ctx.scnview = scnview; + query_ctx.data = query_data; + + /* Here we go! */ + rtcPointQuery(scnview->rtc_scn, &query, &query_ctx.rtc, closest_point, hit); + + return RES_OK; +} + diff --git a/src/s3d_scene_view_point_query.c b/src/s3d_scene_view_point_query.c @@ -1,440 +0,0 @@ -/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) - * - * This software is a computer program whose purpose is to describe a - * virtual 3D environment that can be ray-traced and sampled both robustly - * and efficiently. - * - * This software is governed by the CeCILL license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/or redistribute the software under the terms of the CeCILL - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL license and that you accept its terms. */ - -#include "s3d.h" -#include "s3d_device_c.h" -#include "s3d_instance.h" -#include "s3d_geometry.h" -#include "s3d_mesh.h" -#include "s3d_scene_view_c.h" -#include "s3d_sphere.h" - -#include <rsys/float3.h> -#include <rsys/float33.h> - -struct point_query_context { - struct RTCPointQueryContext rtc; - struct s3d_scene_view* scnview; - void* data; /* Per point query defined data */ -}; - -/******************************************************************************* - * Helper functions - ******************************************************************************/ -static INLINE float* -closest_point_triangle - (const float p[3], /* Point */ - const float a[3], /* 1st triangle vertex */ - const float b[3], /* 2nd triangle vertex */ - const float c[3], /* 3rd triangle vertex */ - float closest_pt[3], /* Closest position */ - float uv[2]) /* UV of the closest position */ -{ - float ab[3], ac[3], ap[3], bp[3], cp[3]; - float d1, d2, d3, d4, d5, d6; - float va, vb, vc; - float rcp_triangle_area; - float v, w; - ASSERT(p && a && b && c && closest_pt && uv); - - f3_sub(ab, b, a); - f3_sub(ac, c, a); - - /* Check if the closest point is the triangle vertex 'a' */ - f3_sub(ap, p, a); - d1 = f3_dot(ab, ap); - d2 = f3_dot(ac, ap); - if(d1 <= 0.f && d2 <= 0.f) { - uv[0] = 1.f; - uv[1] = 0.f; - return f3_set(closest_pt, a); - } - - /* Check if the closest point is the triangle vertex 'b' */ - f3_sub(bp, p, b); - d3 = f3_dot(ab, bp); - d4 = f3_dot(ac, bp); - if(d3 >= 0.f && d4 <= d3) { - uv[0] = 0.f; - uv[1] = 1.f; - return f3_set(closest_pt, b); - } - - /* Check if the closest point is the triangle vertex 'c' */ - f3_sub(cp, p, c); - d5 = f3_dot(ab, cp); - d6 = f3_dot(ac, cp); - if(d6 >= 0.f && d5 <= d6) { - uv[0] = 0.f; - uv[1] = 0.f; - return f3_set(closest_pt, c); - } - - /* Check if the closest point is on the triangle edge 'ab' */ - vc = d1*d4 - d3*d2; - if(vc <= 0.f && d1 >= 0.f && d3 <= 0.f) { - const float s = d1 / (d1 - d3); - closest_pt[0] = a[0] + s*ab[0]; - closest_pt[1] = a[1] + s*ab[1]; - closest_pt[2] = a[2] + s*ab[2]; - uv[0] = 1.f - s; - uv[1] = s; - return closest_pt; - } - - /* Check if the closest point is on the triangle edge 'ac' */ - vb = d5*d2 - d1*d6; - if(vb <= 0.f && d2 >= 0.f && d6 <= 0.f) { - const float s = d2 / (d2 - d6); - closest_pt[0] = a[0] + s*ac[0]; - closest_pt[1] = a[1] + s*ac[1]; - closest_pt[2] = a[2] + s*ac[2]; - uv[0] = 1.f - s; - uv[1] = 0.f; - return closest_pt; - } - - /* Check if the closest point is on the triangle edge 'bc' */ - va = d3*d6 - d5*d4; - if(va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) { - const float s = (d4 - d3) / ((d4 - d3) + (d5 - d6)); - closest_pt[0] = b[0] + s*(c[0] - b[0]); - closest_pt[1] = b[1] + s*(c[1] - b[1]); - closest_pt[2] = b[2] + s*(c[2] - b[2]); - uv[0] = 0.f; - uv[1] = 1.f - s; - return closest_pt; - } - - /* The closest point lies in the triangle: compute its barycentric - * coordinates */ - rcp_triangle_area = 1.f / (va + vb + vc); - v = vb * rcp_triangle_area; - w = vc * rcp_triangle_area; - - /* Save the uv barycentric coordinates */ - uv[0] = 1.f - v - w; - uv[1] = v; - - ASSERT(eq_epsf(uv[0] + uv[1] + w, 1.f, 1.e-4f)); - - /* Use the barycentric coordinates to compute the world space position of the - * closest point onto the triangle */ - closest_pt[0] = a[0] + v*ab[0] + w*ac[0]; - closest_pt[1] = a[1] + v*ab[1] + w*ac[1]; - closest_pt[2] = a[2] + v*ab[2] + w*ac[2]; - return closest_pt; -} - -static bool -closest_point_mesh - (struct RTCPointQueryFunctionArguments* args, - struct geometry* geom, - struct geometry* inst, /* Can be NULL */ - void* query_data) -{ - struct s3d_hit hit = S3D_HIT_NULL; - struct s3d_hit* out_hit = NULL; - struct hit_filter* filter = NULL; - const uint32_t* ids = NULL; - float closest_point[3]; - float query_pos[3]; - float v0[3], v1[3], v2[3]; - float E0[3], E1[3], Ng[3]; - float vec[3]; - float uv[2]; - float dst; - int flip_surface = 0; - ASSERT(args && geom && geom->type == GEOM_MESH); - ASSERT(args->primID < mesh_get_ntris(geom->data.mesh)); - - /* Fetch triangle indices */ - ids = mesh_get_ids(geom->data.mesh) + args->primID*3/*#indices per triangle*/; - - /* Fetch triangle vertices */ - ASSERT(geom->data.mesh->attribs_type[S3D_POSITION] == S3D_FLOAT3); - f3_set(v0, mesh_get_pos(geom->data.mesh) + ids[0]*3/*#coords per vertex*/); - f3_set(v1, mesh_get_pos(geom->data.mesh) + ids[1]*3/*#coords per vertex*/); - f3_set(v2, mesh_get_pos(geom->data.mesh) + ids[2]*3/*#coords per vertex*/); - - /* Local copy of the query position */ - query_pos[0] = args->query->x; - query_pos[1] = args->query->y; - query_pos[2] = args->query->z; - - if(args->context->instStackSize) { /* The mesh is instantiated */ - const float* transform; - transform = inst->data.instance->transform; - ASSERT(args->context->instStackSize == 1); - ASSERT(args->similarityScale == 1); - ASSERT(inst && inst->type == GEOM_INSTANCE); - ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); - ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); - ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); - ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); - - /* Transform the triangle vertices in world space */ - f3_add(v0, f33_mulf3(v0, transform, v0), transform+9); - f3_add(v1, f33_mulf3(v1, transform, v1), transform+9); - f3_add(v2, f33_mulf3(v2, transform, v2), transform+9); - - flip_surface = inst->flip_surface; - } - - /* Compute the closest point onto the triangle from the submitted point */ - closest_point_triangle(query_pos, v0, v1, v2, closest_point, uv); - - f3_sub(vec, closest_point, query_pos); - dst = f3_len(vec); - if(dst >= args->query->radius) return 0; - - /* Compute the triangle normal in world space */ - f3_sub(E0, v2, v0); - f3_sub(E1, v1, v0); - f3_cross(Ng, E0, E1); - - /* Flip the geometric normal wrt the flip surface flag */ - flip_surface ^= geom->flip_surface; - if(flip_surface) f3_minus(Ng, Ng); - - /* Setup the hit */ - hit.prim.shape__ = geom; - hit.prim.inst__ = inst; - hit.distance = dst; - hit.uv[0] = uv[0]; - hit.uv[1] = uv[1]; - hit.normal[0] = Ng[0]; - hit.normal[1] = Ng[1]; - hit.normal[2] = Ng[2]; - hit.prim.prim_id = args->primID; - hit.prim.geom_id = geom->name; - hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; - hit.prim.scene_prim_id = - hit.prim.prim_id - + geom->scene_prim_id_offset - + (inst ? inst->scene_prim_id_offset : 0); - - /* `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 */ - filter = &geom->data.mesh->filter; - if(filter->func - && filter->func(&hit, query_pos, vec, query_data, filter->data)) { - return 0; /* This point is filtered. Discard it! */ - } - - /* Update output data */ - out_hit = args->userPtr; - *out_hit = hit; - - /* Shrink the query radius */ - args->query->radius = dst; - - return 1; /* Notify that the query radius was updated */ -} - -static bool -closest_point_sphere - (struct RTCPointQueryFunctionArguments* args, - struct geometry* geom, - struct geometry* inst, - void* query_data) -{ - struct s3d_hit hit = S3D_HIT_NULL; - struct s3d_hit* out_hit = NULL; - struct hit_filter* filter = NULL; - float query_pos[3]; - float sphere_pos[3]; - float Ng[3]; - float dir[3]; - float uv[2]; - float dst; - float len; - int flip_surface = 0; - ASSERT(args && geom && geom->type == GEOM_SPHERE && args->primID == 1); - - /* Local copy of the query position */ - query_pos[0] = args->query->x; - query_pos[1] = args->query->y; - query_pos[2] = args->query->z; - - f3_set(sphere_pos, geom->data.sphere->pos); - if(args->context->instStackSize) { /* The sphere is instantiated */ - const float* transform; - transform = inst->data.instance->transform; - ASSERT(args->context->instStackSize == 1); - ASSERT(args->similarityScale == 1); - ASSERT(inst && inst->type == GEOM_INSTANCE); - ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); - ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); - ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); - ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); - - /* Transform the sphere position in world space */ - f33_mulf3(sphere_pos, transform, sphere_pos); - f3_add(sphere_pos, transform+9, sphere_pos); - - flip_surface = inst->flip_surface; - } - - /* Compute the distance from the the query pos to the sphere center */ - f3_sub(Ng, query_pos, sphere_pos); - len = f3_len(Ng); - - /* Evaluate the distance from the query pos to the sphere surface */ - dst = fabsf(len - geom->data.sphere->radius); - - /* The closest point onto the sphere is outside the query radius */ - if(dst >= args->query->radius) - return 0; - - /* Compute the uv of the found point */ - f3_divf(Ng, Ng, len); /* Normalize the hit normal */ - sphere_normal_to_uv(Ng, uv); - - /* Flip the geometric normal wrt the flip surface flag */ - flip_surface ^= geom->flip_surface; - if(flip_surface) f3_minus(Ng, Ng); - - /* Setup the hit */ - hit.prim.shape__ = geom; - hit.prim.inst__ = inst; - hit.distance = dst; - hit.uv[0] = uv[0]; - hit.uv[1] = uv[1]; - hit.normal[0] = Ng[0]; - hit.normal[1] = Ng[1]; - hit.normal[2] = Ng[2]; - hit.prim.prim_id = args->primID; - hit.prim.geom_id = geom->name; - hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; - hit.prim.scene_prim_id = - hit.prim.prim_id - + 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); - filter = &geom->data.sphere->filter; - if(filter->func - && filter->func(&hit, query_pos, dir, query_data, filter->data)) { - return 0; - } - - /* Update output data */ - out_hit = args->userPtr; - *out_hit = hit; - - /* Shrink the query radius */ - args->query->radius = dst; - - return 1; /* Notify that the query radius was updated */ -} - -static bool -closest_point(struct RTCPointQueryFunctionArguments* args) -{ - struct point_query_context* ctx = NULL; - struct geometry* geom = NULL; - struct geometry* inst = NULL; - bool query_radius_is_upd = false; - ASSERT(args); - - ctx = CONTAINER_OF(args->context, struct point_query_context, rtc); - if(args->context->instStackSize == 0) { - geom = scene_view_geometry_from_embree_id - (ctx->scnview, args->geomID); - } else { - ASSERT(args->context->instStackSize == 1); - ASSERT(args->similarityScale == 1); - inst = scene_view_geometry_from_embree_id - (ctx->scnview, args->context->instID[0]); - geom = scene_view_geometry_from_embree_id - (inst->data.instance->scnview, args->geomID); - } - - switch(geom->type) { - case GEOM_MESH: - query_radius_is_upd = closest_point_mesh(args, geom, inst, ctx->data); - break; - case GEOM_SPHERE: - query_radius_is_upd = closest_point_sphere(args, geom, inst, ctx->data); - break; - default: FATAL("Unreachable code\n"); break; - } - return query_radius_is_upd; -} - -/******************************************************************************* - * Exported functions - ******************************************************************************/ -res_T -s3d_scene_view_point_query - (struct s3d_scene_view* scnview, - const float pos[3], - const float radius, - void* query_data, - struct s3d_hit* hit) -{ - struct RTCPointQuery query; - struct point_query_context query_ctx; - - if(!scnview || !pos || radius <= 0 || !hit) - return RES_BAD_ARG; - if((scnview->mask & S3D_TRACE) == 0) { - log_error(scnview->scn->dev, - "%s: the S3D_TRACE flag is not active onto the submitted scene view.\n", - FUNC_NAME); - return RES_BAD_OP; - } - - *hit = S3D_HIT_NULL; - - /* Initialise the point query */ - query.x = pos[0]; - query.y = pos[1]; - query.z = pos[2]; - query.radius = radius; - query.time = FLT_MAX; /* Invalid fields */ - - /* Initialise the point query context */ - rtcInitPointQueryContext(&query_ctx.rtc); - query_ctx.scnview = scnview; - query_ctx.data = query_data; - - /* Here we go! */ - rtcPointQuery(scnview->rtc_scn, &query, &query_ctx.rtc, closest_point, hit); - - return RES_OK; -} - diff --git a/src/test_s3d_closest_point.c b/src/test_s3d_closest_point.c @@ -0,0 +1,589 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * 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 + ******************************************************************************/ +/* 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_closest_point function that + * internally relies on a more efficient implementation */ +static float* +closest_point_triangle + (const float p[3], /* Input pos */ + const float a[3], /* 1st triangle vertex */ + const float b[3], /* 2nd triangle vertex */ + const float c[3], /* 3rd triangle vertex */ + float pt[3]) /* Closest point of pos onto the triangle */ +{ + float N[3]; /* Triangle normal */ + float Nab[3], Nbc[3], Nca[3]; /* Edge normals */ + float ab[3], ac[3], bc[3]; + float ap[3], bp[3], cp[3]; + float d1, d2, d3, d4, d5, d6, d; + CHK(p && a && b && c && pt); + + f3_normalize(ab, f3_sub(ab, b, a)); + f3_normalize(ac, f3_sub(ac, c, a)); + f3_normalize(bc, f3_sub(bc, c, b)); + + /* Compute the triangle normal */ + f3_cross(N, ac, ab); + + /* 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); + if(d1 <= 0 && d2 <= 0) return f3_set(pt, a); + + /* Check if the nearest point is the 2nd triangle vertex */ + f3_sub(bp, p, b); + d3 = f3_dot(bc, bp); + d4 =-f3_dot(ab, bp); + if(d3 <= 0 && d4 <= 0) return f3_set(pt, b); + + /* Check if the nearest point is the 3rd triangle vertex */ + f3_sub(cp, p, c); + d5 =-f3_dot(ac, cp); + d6 =-f3_dot(bc, cp); + if(d5 <= 0 && d6 <= 0) return f3_set(pt, c); + + /* Check if the nearest point is on the 1st triangle edge */ + 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_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_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)); + } + + /* The nearest point is in the triangle */ + f3_normalize(N, N); + d = f3_dot(N, ap); + 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); + CHK(f3_normalize(vec, dir) != 0); + + 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) { /* Are the walls filtered */ + 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) { /* Is the block filtered */ + 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) { /* Is the block filtered */ + 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) { /* All geometries were filtered */ + 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_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK); + check_closest_point_cbox(pos, geom_id, &hit); + } + + /* 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_closest_point + (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 + ******************************************************************************/ +static void +triangle_get_ids(const unsigned itri, unsigned ids[3], void* ctx) +{ + (void)ctx; + CHK(itri == 0); + CHK(ids); + ids[0] = 0; + ids[1] = 1; + ids[2] = 2; +} + +static void +triangle_get_pos(const unsigned ivert, float pos[3], void* ctx) +{ + (void)ctx; + CHK(ivert < 3); + CHK(pos); + switch(ivert) { /* Setup a random triangle */ + case 0: f3(pos, -0.5f, -0.3f, 0.1f); break; + case 1: f3(pos, -0.4f, 0.2f, 0.3f); break; + case 2: f3(pos, 0.7f, 0.01f, -0.5f); break; + default: FATAL("Unreachable code\n"); break; + } +} + +static void +test_single_triangle(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_scene_view* view = NULL; + struct s3d_shape* msh = NULL; + struct s3d_attrib attr; + 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], mid[3]; + size_t i; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &msh) == RES_OK); + CHK(s3d_scene_attach_shape(scn, msh) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = triangle_get_pos; + CHK(s3d_mesh_setup_indexed_vertices + (msh, 1, triangle_get_ids, 3, &vdata, 1, NULL) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + + triangle_get_pos(0, v0, NULL); + triangle_get_pos(1, v1, NULL); + triangle_get_pos(2, v2, NULL); + + /* Compute the triangle AABB */ + low[0] = MMIN(MMIN(v0[0], v1[0]), v2[0]); + low[1] = MMIN(MMIN(v0[1], v1[1]), v2[1]); + low[2] = MMIN(MMIN(v0[2], v1[2]), v2[2]); + 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] = 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_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK); + + /* Cross check the point query result */ + closest_point_triangle(pos, v0, v1, v2, closest_pos); + CHK(f3_eq_eps(closest_pos, attr.value, 1.e-4f)); + } + + FOR_EACH(i, 0, 10000) { + float radius; + + /* Randomly generate a point in a bounding box that is 10 times the size of + * the triangle AABB */ + 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_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + + /* Check that the radius is an exclusive upper bound */ + radius = hit.distance; + CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + radius = nextafterf(radius, FLT_MAX); + CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(hit.distance == nextafterf(radius, 0.f)); + } + + CHK(s3d_shape_ref_put(msh) == RES_OK); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Miscellaneous test + ******************************************************************************/ +static void +test_api(struct s3d_device* dev) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene_view* view = NULL; + float pos[3] = {0,0,0}; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + + CHK(s3d_scene_view_closest_point(NULL, pos, 1.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, NULL, 1.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 0.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, NULL) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_SAMPLE, &view) == RES_OK); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_BAD_OP); + + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct s3d_device* dev = NULL; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK); + + test_api(dev); + test_single_triangle(dev); + test_cbox(dev); + + CHK(s3d_device_ref_put(dev) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_s3d_point_query.c b/src/test_s3d_point_query.c @@ -1,589 +0,0 @@ -/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) - * - * This software is a computer program whose purpose is to describe a - * virtual 3D environment that can be ray-traced and sampled both robustly - * and efficiently. - * - * This software is governed by the CeCILL license under French law and - * abiding by the rules of distribution of free software. You can use, - * modify and/or redistribute the software under the terms of the CeCILL - * license as circulated by CEA, CNRS and INRIA at the following URL - * "http://www.cecill.info". - * - * As a counterpart to the access to the source code and rights to copy, - * modify and redistribute granted by the license, users are provided only - * with a limited warranty and the software's author, the holder of the - * economic rights, and the successive licensors have only limited - * liability. - * - * In this respect, the user's attention is drawn to the risks associated - * with loading, using, modifying and/or developing or reproducing the - * software by the user in light of its specific status of free software, - * that may mean that it is complicated to manipulate, and that also - * therefore means that it is reserved for developers and experienced - * professionals having in-depth computer knowledge. Users are therefore - * encouraged to load and test the software's suitability as regards their - * requirements in conditions enabling the security of their systems and/or - * data to be ensured and, more generally, to use and operate it in the - * same conditions as regards security. - * - * The fact that you are presently reading this means that you have had - * 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 - ******************************************************************************/ -/* 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 */ - const float a[3], /* 1st triangle vertex */ - const float b[3], /* 2nd triangle vertex */ - const float c[3], /* 3rd triangle vertex */ - float pt[3]) /* Closest point of pos onto the triangle */ -{ - float N[3]; /* Triangle normal */ - float Nab[3], Nbc[3], Nca[3]; /* Edge normals */ - float ab[3], ac[3], bc[3]; - float ap[3], bp[3], cp[3]; - float d1, d2, d3, d4, d5, d6, d; - CHK(p && a && b && c && pt); - - f3_normalize(ab, f3_sub(ab, b, a)); - f3_normalize(ac, f3_sub(ac, c, a)); - f3_normalize(bc, f3_sub(bc, c, b)); - - /* Compute the triangle normal */ - f3_cross(N, ac, ab); - - /* 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); - if(d1 <= 0 && d2 <= 0) return f3_set(pt, a); - - /* Check if the nearest point is the 2nd triangle vertex */ - f3_sub(bp, p, b); - d3 = f3_dot(bc, bp); - d4 =-f3_dot(ab, bp); - if(d3 <= 0 && d4 <= 0) return f3_set(pt, b); - - /* Check if the nearest point is the 3rd triangle vertex */ - f3_sub(cp, p, c); - d5 =-f3_dot(ac, cp); - d6 =-f3_dot(bc, cp); - if(d5 <= 0 && d6 <= 0) return f3_set(pt, c); - - /* Check if the nearest point is on the 1st triangle edge */ - 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_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_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)); - } - - /* The nearest point is in the triangle */ - f3_normalize(N, N); - d = f3_dot(N, ap); - 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 - ******************************************************************************/ -static void -triangle_get_ids(const unsigned itri, unsigned ids[3], void* ctx) -{ - (void)ctx; - CHK(itri == 0); - CHK(ids); - ids[0] = 0; - ids[1] = 1; - ids[2] = 2; -} - -static void -triangle_get_pos(const unsigned ivert, float pos[3], void* ctx) -{ - (void)ctx; - CHK(ivert < 3); - CHK(pos); - switch(ivert) { /* Setup a random triangle */ - case 0: f3(pos, -0.5f, -0.3f, 0.1f); break; - case 1: f3(pos, -0.4f, 0.2f, 0.3f); break; - case 2: f3(pos, 0.7f, 0.01f, -0.5f); break; - default: FATAL("Unreachable code\n"); break; - } -} - -static void -test_single_triangle(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_scene_view* view = NULL; - struct s3d_shape* msh = NULL; - struct s3d_attrib attr; - 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], mid[3]; - size_t i; - - CHK(s3d_scene_create(dev, &scn) == RES_OK); - CHK(s3d_shape_create_mesh(dev, &msh) == RES_OK); - CHK(s3d_scene_attach_shape(scn, msh) == RES_OK); - - vdata.usage = S3D_POSITION; - vdata.type = S3D_FLOAT3; - vdata.get = triangle_get_pos; - CHK(s3d_mesh_setup_indexed_vertices - (msh, 1, triangle_get_ids, 3, &vdata, 1, NULL) == RES_OK); - - CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); - - triangle_get_pos(0, v0, NULL); - triangle_get_pos(1, v1, NULL); - triangle_get_pos(2, v2, NULL); - - /* Compute the triangle AABB */ - low[0] = MMIN(MMIN(v0[0], v1[0]), v2[0]); - low[1] = MMIN(MMIN(v0[1], v1[1]), v2[1]); - low[2] = MMIN(MMIN(v0[2], v1[2]), v2[2]); - 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] = 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)); - CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK); - - /* Cross check the point query result */ - closest_point_triangle(pos, v0, v1, v2, closest_pos); - CHK(f3_eq_eps(closest_pos, attr.value, 1.e-4f)); - } - - FOR_EACH(i, 0, 10000) { - float radius; - - /* Randomly generate a point in a bounding box that is 10 times the size of - * the triangle AABB */ - 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)); - - /* Check that the radius is an exclusive upper bound */ - radius = hit.distance; - CHK(s3d_scene_view_point_query(view, pos, radius, NULL, &hit) == RES_OK); - CHK(S3D_HIT_NONE(&hit)); - radius = nextafterf(radius, FLT_MAX); - CHK(s3d_scene_view_point_query(view, pos, radius, NULL, &hit) == RES_OK); - CHK(!S3D_HIT_NONE(&hit)); - CHK(hit.distance == nextafterf(radius, 0.f)); - } - - CHK(s3d_shape_ref_put(msh) == RES_OK); - CHK(s3d_scene_view_ref_put(view) == RES_OK); - CHK(s3d_scene_ref_put(scn) == RES_OK); -} - -/******************************************************************************* - * Miscellaneous test - ******************************************************************************/ -static void -test_api(struct s3d_device* dev) -{ - struct s3d_hit hit = S3D_HIT_NULL; - struct s3d_scene* scn = NULL; - struct s3d_scene_view* view = NULL; - float pos[3] = {0,0,0}; - - CHK(s3d_scene_create(dev, &scn) == RES_OK); - CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); - - CHK(s3d_scene_view_point_query(NULL, pos, 1.f, NULL, &hit) == RES_BAD_ARG); - CHK(s3d_scene_view_point_query(view, NULL, 1.f, NULL, &hit) == RES_BAD_ARG); - CHK(s3d_scene_view_point_query(view, pos, 0.f, NULL, &hit) == RES_BAD_ARG); - CHK(s3d_scene_view_point_query(view, pos, 1.f, NULL, NULL) == RES_BAD_ARG); - CHK(s3d_scene_view_point_query(view, pos, 1.f, NULL, &hit) == RES_OK); - CHK(S3D_HIT_NONE(&hit)); - - CHK(s3d_scene_view_ref_put(view) == RES_OK); - CHK(s3d_scene_view_create(scn, S3D_SAMPLE, &view) == RES_OK); - CHK(s3d_scene_view_point_query(view, pos, 1.f, NULL, &hit) == RES_BAD_OP); - - CHK(s3d_scene_view_ref_put(view) == RES_OK); - CHK(s3d_scene_ref_put(scn) == RES_OK); -} - -/******************************************************************************* - * Main function - ******************************************************************************/ -int -main(int argc, char** argv) -{ - struct mem_allocator allocator; - struct s3d_device* dev = NULL; - (void)argc, (void)argv; - - mem_init_proxy_allocator(&allocator, &mem_default_allocator); - CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK); - - test_api(dev); - test_single_triangle(dev); - test_cbox(dev); - - CHK(s3d_device_ref_put(dev) == RES_OK); - - check_memory_allocator(&allocator); - mem_shutdown_proxy_allocator(&allocator); - CHK(mem_allocated_size() == 0); - return 0; -}