star-3d

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

test_s3d_closest_point.c (42452B)


      1 /* Copyright (C) 2015-2023, 2026 |Méso|Star> (contact@meso-star.com)
      2  *
      3  * This file is part of Star-3D.
      4  *
      5  * Star-3D is free software: you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation, either version 3 of the License, or
      8  * (at your option) any later version.
      9  *
     10  * Star-3D is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with Star-3D. If not, see <http://www.gnu.org/licenses/>. */
     17 
     18 #define _POSIX_C_SOURCE 200112L /* exp2f, fabsf, nextafterf */
     19 
     20 #include "s3d.h"
     21 #include "test_s3d_cbox.h"
     22 #include "test_s3d_utils.h"
     23 
     24 #include <rsys/float2.h>
     25 #include <rsys/float3.h>
     26 #include <rsys/float33.h>
     27 #include <limits.h>
     28 
     29 #define ON_EDGE_EPSILON 1.e-4f
     30 #define POSITION_EPSILON 1.e-3f
     31 
     32 struct closest_pt {
     33   float pos[3];
     34   float normal[3];
     35   float dst;
     36   unsigned iprim;
     37   unsigned igeom;
     38   unsigned iinst;
     39 };
     40 
     41 #define CLOSEST_PT_NULL__ {                                                    \
     42   {0,0,0},                                                                     \
     43   {0,0,0},                                                                     \
     44   FLT_MAX,                                                                     \
     45   S3D_INVALID_ID,                                                              \
     46   S3D_INVALID_ID,                                                              \
     47   S3D_INVALID_ID                                                               \
     48 }
     49 
     50 static const struct closest_pt CLOSEST_PT_NULL = CLOSEST_PT_NULL__;
     51 
     52 /*******************************************************************************
     53  * Helper functions
     54  ******************************************************************************/
     55 /* Function that computes the point onto the triangle that ensures the minimum
     56  * distance between the submitted `pos' and the triangle. Use this routine to
     57  * cross check the result of the s3d_scene_view_closest_point function that
     58  * internally relies on a more efficient implementation */
     59 static float*
     60 closest_point_triangle
     61   (const float p[3], /* Input pos */
     62    const float a[3], /* 1st triangle vertex */
     63    const float b[3], /* 2nd triangle vertex */
     64    const float c[3], /* 3rd triangle vertex */
     65    float pt[3]) /* Closest point of pos onto the triangle */
     66 {
     67   float N[3]; /* Triangle normal */
     68   float Nab[3], Nbc[3], Nca[3]; /* Edge normals */
     69   float ab[3], ac[3], bc[3];
     70   float ap[3], bp[3], cp[3];
     71   float d1, d2, d3, d4, d5, d6, d;
     72   float lab, lac, lbc;
     73   CHK(p && a && b && c && pt);
     74 
     75   lab = f3_normalize(ab, f3_sub(ab, b, a));
     76   lac = f3_normalize(ac, f3_sub(ac, c, a));
     77   lbc = f3_normalize(bc, f3_sub(bc, c, b));
     78 
     79   /* Compute the triangle normal */
     80   f3_cross(N, ac, ab);
     81 
     82   /* Check if the nearest point is the 1st triangle vertex */
     83   f3_sub(ap, p, a);
     84   d1 = f3_dot(ab, ap);
     85   d2 = f3_dot(ac, ap);
     86   if(d1 <= 0 && d2 <= 0) return f3_set(pt, a);
     87 
     88   /* Check if the nearest point is the 2nd triangle vertex */
     89   f3_sub(bp, p, b);
     90   d3 = f3_dot(bc, bp);
     91   d4 =-f3_dot(ab, bp);
     92   if(d3 <= 0 && d4 <= 0) return f3_set(pt, b);
     93 
     94   /* Check if the nearest point is the 3rd triangle vertex */
     95   f3_sub(cp, p, c);
     96   d5 =-f3_dot(ac, cp);
     97   d6 =-f3_dot(bc, cp);
     98   if(d5 <= 0 && d6 <= 0) return f3_set(pt, c);
     99 
    100   /* Check if the nearest point is on the 1st triangle edge */
    101   f3_normalize(Nab, f3_cross(Nab, ab, N));
    102   if(f3_dot(Nab, ap) <= 0) {
    103     return f3_add(pt, a, f3_mulf(pt, ab, MMIN(d1, lab)));
    104   }
    105 
    106   /* Check if the nearest point is on the 2nd triangle edge */
    107   f3_normalize(Nbc, f3_cross(Nbc, bc, N));
    108   if(f3_dot(Nbc, bp) <= 0) {
    109     return f3_add(pt, b, f3_mulf(pt, bc, MMIN(d3, lbc)));
    110   }
    111 
    112   /* Check if the nearest point is on the 3rd triangle edge */
    113   f3_normalize(Nca, f3_cross(Nca, ac, N));
    114   f3_minus(Nca, Nca);
    115   if(f3_dot(Nca, cp) <= 0) {
    116     return f3_add(pt, c, f3_mulf(pt, ac,-MMIN(d5, lac)));
    117   }
    118 
    119   /* The nearest point is in the triangle */
    120   f3_normalize(N, N);
    121   d = f3_dot(N, ap);
    122   return f3_add(pt, p, f3_mulf(pt, N, -d));
    123 }
    124 
    125 static void
    126 closest_point_mesh
    127   (const float pos[3],
    128    const float* verts,
    129    const unsigned* ids,
    130    const unsigned ntris,
    131    const unsigned geom_id,
    132    const unsigned inst_id,
    133    struct closest_pt* pt)
    134 {
    135   unsigned itri;
    136   CHK(pos && verts && ids && pt);
    137 
    138   *pt = CLOSEST_PT_NULL;
    139   pt->igeom = geom_id;
    140   pt->iinst = inst_id;
    141   pt->dst = FLT_MAX;
    142 
    143   /* Find the closest point on the mesh */
    144   FOR_EACH(itri, 0, ntris) {
    145     float v0[3];
    146     float v1[3];
    147     float v2[3];
    148     float closest_pt[3];
    149     float vec[3];
    150     float dst;
    151 
    152     f3_set(v0, verts+ids[itri*3+0]*3);
    153     f3_set(v1, verts+ids[itri*3+1]*3);
    154     f3_set(v2, verts+ids[itri*3+2]*3);
    155 
    156     closest_point_triangle(pos, v0, v1, v2, closest_pt);
    157     dst = f3_len(f3_sub(vec, closest_pt, pos));
    158 
    159     if(dst < pt->dst) {
    160       float E0[3], E1[3];
    161       f3_set(pt->pos, closest_pt);
    162       pt->dst = dst;
    163       pt->iprim = itri;
    164       f3_sub(E0, v1, v0);
    165       f3_sub(E1, v2, v0);
    166       f3_cross(pt->normal, E1, E0);
    167       f3_normalize(pt->normal, pt->normal);
    168     }
    169   }
    170 }
    171 
    172 static void
    173 closest_point_sphere
    174   (const float pos[3],
    175    const float sphere_org[3],
    176    const float sphere_radius,
    177    const unsigned geom_id,
    178    const unsigned inst_id,
    179    struct closest_pt* pt)
    180 {
    181   float vec[3];
    182   float len;
    183   CHK(pos && sphere_org && sphere_radius > 0 && pt);
    184 
    185   f3_sub(vec, pos, sphere_org);
    186   len = f3_normalize(vec, vec);
    187   CHK(len > 0);
    188 
    189   pt->dst = (float)fabs(len - sphere_radius);
    190   f3_set(pt->normal, vec);
    191   f3_add(pt->pos, sphere_org, f3_mulf(pt->pos, vec, sphere_radius));
    192   pt->iprim = 0;
    193   pt->igeom = geom_id;
    194   pt->iinst = inst_id;
    195 }
    196 
    197 /* Check that `hit' roughly lies on an edge. */
    198 static int
    199 hit_on_edge(const struct s3d_hit* hit)
    200 {
    201   struct s3d_attrib v0, v1, v2, pos;
    202   float E0[3], E1[3], N[3];
    203   float tri_2area;
    204   float hit_2area0;
    205   float hit_2area1;
    206   float hit_2area2;
    207   float hit_pos[3];
    208 
    209   CHK(hit && !S3D_HIT_NONE(hit));
    210 
    211   /* Retrieve the triangle vertices */
    212   CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 0, S3D_POSITION, &v0)==RES_OK);
    213   CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 1, S3D_POSITION, &v1)==RES_OK);
    214   CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 2, S3D_POSITION, &v2)==RES_OK);
    215 
    216   /* Compute the triangle area * 2 */
    217   f3_sub(E0, v1.value, v0.value);
    218   f3_sub(E1, v2.value, v0.value);
    219   tri_2area = f3_len(f3_cross(N, E0, E1));
    220 
    221   /* Compute the hit position */
    222   CHK(s3d_primitive_get_attrib(&hit->prim, S3D_POSITION, hit->uv, &pos) == RES_OK);
    223   f3_set(hit_pos, pos.value);
    224 
    225   /* Compute areas */
    226   f3_sub(E0, v0.value, hit_pos);
    227   f3_sub(E1, v1.value, hit_pos);
    228   hit_2area0 = f3_len(f3_cross(N, E0, E1));
    229   f3_sub(E0, v1.value, hit_pos);
    230   f3_sub(E1, v2.value, hit_pos);
    231   hit_2area1 = f3_len(f3_cross(N, E0, E1));
    232   f3_sub(E0, v2.value, hit_pos);
    233   f3_sub(E1, v0.value, hit_pos);
    234   hit_2area2 = f3_len(f3_cross(N, E0, E1));
    235 
    236   if(hit_2area0 / tri_2area < ON_EDGE_EPSILON
    237   || hit_2area1 / tri_2area < ON_EDGE_EPSILON
    238   || hit_2area2 / tri_2area < ON_EDGE_EPSILON)
    239     return 1;
    240 
    241   return 0;
    242 }
    243 
    244 static void
    245 check_closest_point
    246   (const struct s3d_hit* hit,
    247    const struct closest_pt* pt,
    248    const int hit_triangle) /* Define if `hit' lies on a triangle */
    249 {
    250   struct s3d_attrib attr;
    251   float N[3];
    252 
    253   CHK(hit && pt);
    254   CHK(!S3D_HIT_NONE(hit));
    255 
    256   CHK(s3d_primitive_get_attrib
    257     (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK);
    258   f3_normalize(N, hit->normal);
    259 
    260   if(!hit_triangle || !hit_on_edge(hit)) {
    261     CHK(hit->prim.prim_id == pt->iprim);
    262   }
    263 
    264   if(hit->prim.prim_id == pt->iprim
    265   && hit->prim.geom_id == pt->igeom
    266   && hit->prim.inst_id == pt->iinst) {
    267     /* Due to numerical inaccuracies and/or the arbitrary order in which
    268      * primitives are treated, 2 points on different primitive can have the
    269      * same distance from the query position while their respective
    270      * coordinates are not equal wrt POSITION_EPSILON. To avoid wrong
    271      * assertion, we thus check the position returned by Star-3D against the
    272      * manually computed position only if these positions lies on the same
    273      * primitive */
    274     CHK(f3_eq_eps(pt->pos, attr.value, POSITION_EPSILON));
    275     CHK(f3_eq_eps(pt->normal, N, 1.e-4f));
    276   }
    277   CHK(eq_epsf(hit->distance, pt->dst, 1.e-3f));
    278 }
    279 
    280 /*******************************************************************************
    281  * Cornell box and sphere test
    282  ******************************************************************************/
    283 struct instance {
    284   float translation[3];
    285   unsigned id;
    286 };
    287 
    288 static void
    289 check_closest_point_cbox_sphere
    290   (const float pos[3],
    291    const float sphere_org[3],
    292    const float sphere_radius,
    293    const unsigned walls_id,
    294    const unsigned sphere_id,
    295    const struct instance* instances,
    296    const size_t ninstances,
    297    struct s3d_hit* hit)
    298 {
    299   struct closest_pt pt_walls = CLOSEST_PT_NULL;
    300   struct closest_pt pt_sphere = CLOSEST_PT_NULL;
    301   const struct closest_pt* pt = NULL;
    302   CHK(pos && hit);
    303 
    304   if(!ninstances) {
    305     closest_point_mesh(pos, cbox_walls, cbox_walls_ids, cbox_walls_ntris,
    306       walls_id, S3D_INVALID_ID, &pt_walls);
    307     closest_point_sphere(pos, sphere_org, sphere_radius, sphere_id,
    308       S3D_INVALID_ID, &pt_sphere);
    309   } else {
    310     size_t iinst;
    311 
    312     pt_walls.dst = FLT_MAX;
    313     FOR_EACH(iinst, 0, ninstances) {
    314       struct closest_pt pt_walls_tmp;
    315       struct closest_pt pt_sphere_tmp;
    316       float pos_instance_space[3];
    317 
    318       /* Transform query position in instance space */
    319       f3_sub(pos_instance_space, pos, instances[iinst].translation);
    320 
    321       closest_point_mesh(pos_instance_space, cbox_walls, cbox_walls_ids,
    322         cbox_walls_ntris, walls_id, instances[iinst].id, &pt_walls_tmp);
    323       closest_point_sphere(pos_instance_space, sphere_org, sphere_radius,
    324         sphere_id, instances[iinst].id, &pt_sphere_tmp);
    325 
    326       if(pt_walls_tmp.dst < pt_walls.dst) {
    327         pt_walls = pt_walls_tmp;
    328         /* Transform query closest point in world space */
    329         f3_add(pt_walls.pos, pt_walls.pos, instances[iinst].translation);
    330       }
    331       if(pt_sphere_tmp.dst < pt_sphere.dst) {
    332         pt_sphere = pt_sphere_tmp;
    333         /* Transform query closest point in world space */
    334         f3_add(pt_sphere.pos, pt_sphere.pos, instances[iinst].translation);
    335       }
    336     }
    337   }
    338 
    339   if(pt_walls.dst< pt_sphere.dst) {
    340     pt = &pt_walls;
    341   } else {
    342     pt = &pt_sphere;
    343   }
    344 
    345   check_closest_point(hit, pt, hit->prim.geom_id == walls_id);
    346 }
    347 
    348 static void
    349 test_cbox_sphere(struct s3d_device* dev)
    350 {
    351   struct s3d_hit hit = S3D_HIT_NULL;
    352   struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
    353   struct s3d_scene* scn = NULL;
    354   struct s3d_shape* walls = NULL;
    355   struct s3d_shape* sphere = NULL;
    356   struct s3d_shape* inst0 = NULL;
    357   struct s3d_shape* inst1 = NULL;
    358   struct s3d_scene_view* scnview = NULL;
    359   struct instance instances[2];
    360   struct cbox_desc cbox_desc;
    361   size_t i;
    362   float low[3], upp[3], mid[3], sz[3];
    363   float pos[3];
    364   float sphere_org[3];
    365   float sphere_radius;
    366   unsigned walls_id, sphere_id;
    367 
    368   CHK(s3d_scene_create(dev, &scn) == RES_OK);
    369 
    370   /* Setup the cornell box walls */
    371   vdata.usage = S3D_POSITION;
    372   vdata.type = S3D_FLOAT3;
    373   vdata.get = cbox_get_position;
    374   cbox_desc.vertices = cbox_walls;
    375   cbox_desc.indices = cbox_walls_ids;
    376   CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK);
    377   CHK(s3d_shape_get_id(walls, &walls_id) == RES_OK);
    378   CHK(s3d_scene_attach_shape(scn, walls) == RES_OK);
    379   CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids,
    380     cbox_walls_nverts, &vdata, 1, &cbox_desc) == RES_OK);
    381 
    382   /* Compute the Cornell box AABB */
    383   CHK(s3d_scene_view_create(scn, S3D_GET_PRIMITIVE, &scnview) == RES_OK);
    384   CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK);
    385   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    386 
    387   /* Setup the sphere at the center of the cornell box */
    388   f3_mulf(mid, f3_add(mid, low, upp), 0.5f);
    389   f3_sub(sz, upp, low);
    390   f3_set(sphere_org, mid);
    391   sphere_radius = MMIN(MMIN(sz[0], sz[1]), sz[2]) * 0.125f; /* 1/8 of the box */
    392   CHK(s3d_shape_create_sphere(dev, &sphere) == RES_OK);
    393   CHK(s3d_shape_get_id(sphere, &sphere_id) == RES_OK);
    394   CHK(s3d_scene_attach_shape(scn, sphere) == RES_OK);
    395   CHK(s3d_sphere_setup(sphere, sphere_org, sphere_radius) == RES_OK);
    396 
    397   CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK);
    398 
    399   /* Check point query on the scene */
    400   FOR_EACH(i, 0, 10000) {
    401     /* Randomly generate a point in a bounding box that is 2 times the size of
    402      * the scene AABB */
    403     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]);
    404     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]);
    405     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]);
    406 
    407     CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK);
    408     check_closest_point_cbox_sphere(pos, sphere_org, sphere_radius, walls_id,
    409       sphere_id, NULL, 0, &hit);
    410   }
    411 
    412   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    413 
    414   /* Instantiate the cbox sphere scene */
    415   CHK(s3d_scene_instantiate(scn, &inst0) == RES_OK);
    416   CHK(s3d_scene_instantiate(scn, &inst1) == RES_OK);
    417   CHK(s3d_shape_get_id(inst0, &instances[0].id) == RES_OK);
    418   CHK(s3d_shape_get_id(inst1, &instances[1].id) == RES_OK);
    419   f3_mulf(instances[0].translation, sz, 0.5f);
    420   CHK(s3d_instance_translate
    421     (inst0, S3D_WORLD_TRANSFORM, instances[0].translation) == RES_OK);
    422   f3_mulf(instances[1].translation, sz,-0.5f);
    423   CHK(s3d_instance_translate
    424     (inst1, S3D_WORLD_TRANSFORM, instances[1].translation) == RES_OK);
    425 
    426   /* Create a new scene with instantiated cbox sphere scenes */
    427   CHK(s3d_scene_ref_put(scn) == RES_OK);
    428   CHK(s3d_scene_create(dev, &scn) == RES_OK);
    429   CHK(s3d_scene_attach_shape(scn, inst0) == RES_OK);
    430   CHK(s3d_scene_attach_shape(scn, inst1) == RES_OK);
    431 
    432   CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK);
    433   CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK);
    434   f3_mulf(mid, f3_add(mid, low, upp), 0.5f);
    435 
    436   /* Check point query on instances */
    437   FOR_EACH(i, 0, 10000) {
    438     /* Randomly generate a point in a bounding box that is 2 times the size of
    439      * the scene AABB */
    440     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]);
    441     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]);
    442     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]);
    443 
    444     CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK);
    445     check_closest_point_cbox_sphere(pos, sphere_org, sphere_radius, walls_id,
    446       sphere_id, instances, 2/*#instances*/, &hit);
    447   }
    448 
    449   /* Clean up */
    450   CHK(s3d_shape_ref_put(inst0) == RES_OK);
    451   CHK(s3d_shape_ref_put(inst1) == RES_OK);
    452   CHK(s3d_shape_ref_put(walls) == RES_OK);
    453   CHK(s3d_shape_ref_put(sphere) == RES_OK);
    454   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    455   CHK(s3d_scene_ref_put(scn) == RES_OK);
    456 }
    457 
    458 /*******************************************************************************
    459  * Sphere test
    460  ******************************************************************************/
    461 struct sphere_filter_data {
    462   float query_pos[3];
    463   float query_radius;
    464 };
    465 
    466 static int
    467 sphere_filter
    468   (const struct s3d_hit* hit,
    469    const float org[3],
    470    const float dir[3],
    471    const float range[2],
    472    void* query_data,
    473    void* filter_data)
    474 {
    475   struct sphere_filter_data* data = query_data;
    476   struct s3d_attrib attr;
    477   float pos[3];
    478   float vec[3];
    479 
    480   CHK(hit && org && dir && range && !S3D_HIT_NONE(hit));
    481   CHK((intptr_t)filter_data == (intptr_t)0xDECAFBAD);
    482   CHK(f3_normalize(vec, dir) != 0);
    483 
    484   f3_add(pos, org, f3_mulf(pos, vec, hit->distance));
    485   CHK(s3d_primitive_get_attrib
    486     (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK);
    487   CHK(f3_eq_eps(attr.value, pos, POSITION_EPSILON));
    488 
    489   CHK(f3_eq_eps(data->query_pos, org, POSITION_EPSILON));
    490   CHK(range[0] == 0);
    491   CHK(range[1] == data->query_radius);
    492 
    493   return 1;
    494 }
    495 
    496 static void
    497 test_sphere(struct s3d_device* dev)
    498 {
    499   struct s3d_attrib attr;
    500   struct s3d_hit hit = S3D_HIT_NULL;
    501   struct s3d_shape* sphere = NULL;
    502   struct s3d_scene* scn = NULL;
    503   struct s3d_scene_view* scnview = NULL;
    504   struct sphere_filter_data filter_data;
    505   void* ptr = (void*)((intptr_t)0xDECAFBAD);
    506   size_t i;
    507   float sphere_pos[3];
    508   float query_pos[3];
    509   float sphere_radius;
    510   float pos[3];
    511   float dir[3];
    512   unsigned sphere_id;
    513 
    514   CHK(s3d_scene_create(dev, &scn) == RES_OK);
    515   CHK(s3d_shape_create_sphere(dev, &sphere) == RES_OK);
    516   CHK(s3d_shape_get_id(sphere, &sphere_id) == RES_OK);
    517   CHK(s3d_scene_attach_shape(scn, sphere) == RES_OK);
    518 
    519   f3_splat(sphere_pos, 1);
    520   sphere_radius = 2;
    521   f3_set(query_pos, sphere_pos);
    522   CHK(s3d_sphere_setup(sphere, query_pos, sphere_radius) == RES_OK);
    523   CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK);
    524 
    525   /* Check a closest point query exactly at the center of the sphere */
    526   CHK(s3d_scene_view_closest_point
    527     (scnview, sphere_pos, (float)INF, NULL, &hit) == RES_OK);
    528   CHK(!S3D_HIT_NONE(&hit));
    529   CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK);
    530 
    531   f3_normalize(dir, f3_sub(dir, attr.value, query_pos));
    532   f3_add(pos, attr.value, f3_mulf(pos, dir, -hit.distance));
    533   CHK(hit.distance == sphere_radius);
    534   CHK(f3_eq_eps(pos, sphere_pos, POSITION_EPSILON));
    535 
    536   /* Check the exclusive bound of the search radius */
    537   CHK(s3d_scene_view_closest_point
    538     (scnview, sphere_pos, sphere_radius, NULL, &hit) == RES_OK);
    539   CHK(S3D_HIT_NONE(&hit));
    540 
    541   /* Check closest point query on a sphere */
    542   FOR_EACH(i, 0, 10000) {
    543     struct closest_pt pt;
    544     float Ng[3];
    545     query_pos[0] = sphere_pos[0] + (rand_canonic() * 2 - 1) * sphere_radius;
    546     query_pos[1] = sphere_pos[1] + (rand_canonic() * 2 - 1) * sphere_radius;
    547     query_pos[2] = sphere_pos[2] + (rand_canonic() * 2 - 1) * sphere_radius;
    548 
    549     CHK(s3d_scene_view_closest_point
    550       (scnview, query_pos, (float)INF, NULL, &hit) == RES_OK);
    551     CHK(!S3D_HIT_NONE(&hit));
    552     CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK);
    553 
    554     /* Cross check the closest point query result */
    555     closest_point_sphere(query_pos, sphere_pos, sphere_radius,
    556       sphere_id, S3D_INVALID_ID, &pt);
    557 
    558     f3_normalize(Ng, hit.normal);
    559 
    560     CHK(pt.dst == hit.distance);
    561     CHK(pt.iprim == hit.prim.prim_id);
    562     CHK(pt.igeom == hit.prim.geom_id);
    563     CHK(pt.iinst == hit.prim.inst_id);
    564     CHK(f3_eq_eps(pt.pos, attr.value, POSITION_EPSILON));
    565     CHK(f3_eq_eps(pt.normal, Ng, 1.e-4f));
    566 
    567     /* Check search radius exclusivity */
    568     CHK(s3d_scene_view_closest_point
    569       (scnview, query_pos, hit.distance, NULL, &hit) == RES_OK);
    570     CHK(S3D_HIT_NONE(&hit));
    571     hit.distance = nextafterf(hit.distance, 0.f);
    572     CHK(s3d_scene_view_closest_point
    573       (scnview, query_pos, hit.distance, NULL, &hit) == RES_OK);
    574     CHK(!S3D_HIT_NONE(&hit));
    575   }
    576 
    577   /* Check the filtering function */
    578   CHK(s3d_sphere_set_hit_filter_function(sphere, sphere_filter, ptr) == RES_OK);
    579 
    580   f3_splat(query_pos, 10);
    581   f3_set(filter_data.query_pos, query_pos);
    582   filter_data.query_radius = (float)INF;
    583   CHK(s3d_scene_view_closest_point
    584     (scnview, query_pos, (float)INF, &filter_data, &hit) == RES_OK);
    585   CHK(!S3D_HIT_NONE(&hit));
    586 
    587   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    588   CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK);
    589   CHK(s3d_scene_view_closest_point
    590     (scnview, query_pos, (float)INF, &filter_data, &hit) == RES_OK);
    591   CHK(S3D_HIT_NONE(&hit));
    592 
    593   CHK(s3d_shape_ref_put(sphere) == RES_OK);
    594   CHK(s3d_scene_ref_put(scn) == RES_OK);
    595   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    596 }
    597 
    598 /*******************************************************************************
    599  * Cornell box test
    600  ******************************************************************************/
    601 enum cbox_geom {
    602   CBOX_WALLS,
    603   CBOX_TALL_BLOCK,
    604   CBOX_SHORT_BLOCK,
    605   CBOX_GEOMS_COUNT__
    606 };
    607 
    608 struct cbox_filter_data {
    609   float query_pos[3];
    610   float query_radius;
    611   unsigned geom_to_filter[3];
    612 };
    613 
    614 static int
    615 cbox_filter
    616   (const struct s3d_hit* hit,
    617    const float org[3],
    618    const float dir[3],
    619    const float range[2],
    620    void* query_data,
    621    void* filter_data)
    622 {
    623   struct cbox_filter_data* data = query_data;
    624   struct s3d_attrib attr;
    625   float pos[3];
    626   float vec[3];
    627 
    628   CHK(hit && org && dir && range && !S3D_HIT_NONE(hit));
    629   CHK((intptr_t)filter_data == (intptr_t)0xDECAFBAD);
    630   CHK(f3_normalize(vec, dir) != 0);
    631 
    632   f3_add(pos, org, f3_mulf(pos, vec, hit->distance));
    633   CHK(s3d_primitive_get_attrib
    634     (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK);
    635   CHK(f3_eq_eps(attr.value, pos, POSITION_EPSILON));
    636 
    637   if(!query_data) return 0;
    638 
    639   CHK(f3_eq_eps(data->query_pos, org, POSITION_EPSILON));
    640   CHK(range[0] == 0);
    641   CHK(range[1] == data->query_radius);
    642 
    643   return data->geom_to_filter[0] == hit->prim.geom_id
    644       || data->geom_to_filter[1] == hit->prim.geom_id
    645       || data->geom_to_filter[2] == hit->prim.geom_id;
    646 }
    647 
    648 static void
    649 check_closest_point_cbox
    650   (const float pos[3],
    651    const unsigned geom_id[3],
    652    struct s3d_hit* hit)
    653 {
    654   struct closest_pt pt[CBOX_GEOMS_COUNT__] = {
    655     CLOSEST_PT_NULL__, CLOSEST_PT_NULL__, CLOSEST_PT_NULL__
    656   };
    657   enum cbox_geom geom;
    658 
    659   CHK(pos && geom_id && hit);
    660 
    661   if(geom_id[CBOX_WALLS] != S3D_INVALID_ID) { /* Are the walls filtered */
    662     closest_point_mesh(pos, cbox_walls, cbox_walls_ids, cbox_walls_ntris,
    663       geom_id[CBOX_WALLS], S3D_INVALID_ID, &pt[CBOX_WALLS]);
    664   }
    665   if(geom_id[CBOX_TALL_BLOCK] != S3D_INVALID_ID) { /* Is the block filtered */
    666     closest_point_mesh(pos, cbox_tall_block, cbox_block_ids, cbox_block_ntris,
    667       geom_id[CBOX_TALL_BLOCK], S3D_INVALID_ID, &pt[CBOX_TALL_BLOCK]);
    668   }
    669   if(geom_id[CBOX_SHORT_BLOCK] != S3D_INVALID_ID) { /* Is the block filtered */
    670     closest_point_mesh(pos, cbox_short_block, cbox_block_ids, cbox_block_ntris,
    671       geom_id[CBOX_SHORT_BLOCK], S3D_INVALID_ID, &pt[CBOX_SHORT_BLOCK]);
    672   }
    673   geom = pt[CBOX_WALLS].dst < pt[CBOX_TALL_BLOCK].dst
    674     ? CBOX_WALLS : CBOX_TALL_BLOCK;
    675   geom = pt[CBOX_SHORT_BLOCK].dst < pt[geom].dst
    676     ? CBOX_SHORT_BLOCK : geom;
    677 
    678   if(pt[geom].dst >= FLT_MAX) { /* All geometries were filtered */
    679     CHK(S3D_HIT_NONE(hit));
    680   } else {
    681     check_closest_point(hit, &pt[geom], 1);
    682   }
    683 }
    684 
    685 static void
    686 test_cbox(struct s3d_device* dev)
    687 {
    688   struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
    689   struct s3d_hit hit = S3D_HIT_NULL;
    690   struct s3d_scene* scn = NULL;
    691   struct s3d_shape* walls = NULL;
    692   struct s3d_shape* tall_block = NULL;
    693   struct s3d_shape* short_block = NULL;
    694   struct s3d_scene_view* scnview = NULL;
    695   struct cbox_desc walls_desc;
    696   struct cbox_desc tall_block_desc;
    697   struct cbox_desc short_block_desc;
    698   struct cbox_filter_data filter_data;
    699   void* ptr = (void*)((intptr_t)0xDECAFBAD);
    700   float pos[3];
    701   float low[3], upp[3], mid[3];
    702   unsigned geom_id[CBOX_GEOMS_COUNT__];
    703   size_t i;
    704 
    705   /* Create the Star-3D scene */
    706   CHK(s3d_scene_create(dev, &scn) == RES_OK);
    707   CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK);
    708   CHK(s3d_shape_create_mesh(dev, &tall_block) == RES_OK);
    709   CHK(s3d_shape_create_mesh(dev, &short_block) == RES_OK);
    710   CHK(s3d_shape_get_id(walls, &geom_id[CBOX_WALLS]) == RES_OK);
    711   CHK(s3d_shape_get_id(tall_block, &geom_id[CBOX_TALL_BLOCK]) == RES_OK);
    712   CHK(s3d_shape_get_id(short_block, &geom_id[CBOX_SHORT_BLOCK]) == RES_OK);
    713   CHK(s3d_mesh_set_hit_filter_function(walls, cbox_filter, ptr) == RES_OK);
    714   CHK(s3d_mesh_set_hit_filter_function(tall_block, cbox_filter, ptr) == RES_OK);
    715   CHK(s3d_mesh_set_hit_filter_function(short_block, cbox_filter, ptr) == RES_OK);
    716   CHK(s3d_scene_attach_shape(scn, walls) == RES_OK);
    717   CHK(s3d_scene_attach_shape(scn, tall_block) == RES_OK);
    718   CHK(s3d_scene_attach_shape(scn, short_block) == RES_OK);
    719 
    720   vdata.usage = S3D_POSITION;
    721   vdata.type = S3D_FLOAT3;
    722   vdata.get = cbox_get_position;
    723 
    724   /* Setup the Cornell box walls */
    725   walls_desc.vertices = cbox_walls;
    726   walls_desc.indices = cbox_walls_ids;
    727   CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids,
    728     cbox_walls_nverts, &vdata, 1, &walls_desc) == RES_OK);
    729 
    730   /* Setup the Cornell box tall block  */
    731   tall_block_desc.vertices = cbox_tall_block;
    732   tall_block_desc.indices = cbox_block_ids;
    733   CHK(s3d_mesh_setup_indexed_vertices(tall_block, cbox_block_ntris, cbox_get_ids,
    734     cbox_block_nverts, &vdata, 1, &tall_block_desc) == RES_OK);
    735 
    736   /* Setup the Cornell box short block */
    737   short_block_desc.vertices = cbox_short_block;
    738   short_block_desc.indices = cbox_block_ids;
    739   CHK(s3d_mesh_setup_indexed_vertices(short_block, cbox_block_ntris, cbox_get_ids,
    740     cbox_block_nverts, &vdata, 1, &short_block_desc) == RES_OK);
    741 
    742   CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK);
    743   CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK);
    744   mid[0] = (low[0] + upp[0]) * 0.5f;
    745   mid[1] = (low[1] + upp[1]) * 0.5f;
    746   mid[2] = (low[2] + upp[2]) * 0.5f;
    747 
    748   /* Filter nothing */
    749   filter_data.geom_to_filter[0] = S3D_INVALID_ID;
    750   filter_data.geom_to_filter[1] = S3D_INVALID_ID;
    751   filter_data.geom_to_filter[2] = S3D_INVALID_ID;
    752 
    753   /* Check a specific position that exhibits a precision issues of the
    754    * closest_point_triangle test routine */
    755   {
    756     union { float f; uint32_t ui; } ucast;
    757     pos[0] = (ucast.ui = 0xc386cc9a, ucast.f);
    758     pos[1] = (ucast.ui = 0x43e635b8, ucast.f);
    759     pos[2] = (ucast.ui = 0x4319ab78, ucast.f);
    760     f3_set(filter_data.query_pos, pos);
    761     filter_data.query_radius = (float)INF;
    762     CHK(s3d_scene_view_closest_point
    763       (scnview, pos, (float)INF, &filter_data, &hit) == RES_OK);
    764     check_closest_point_cbox(pos, geom_id, &hit);
    765   }
    766 
    767   /* Check closest point query on Cornell box */
    768   FOR_EACH(i, 0, 10000) {
    769     /* Randomly generate a point in a bounding box that is 2 times the size of
    770      * the cornell box AABB */
    771     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]);
    772     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]);
    773     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]);
    774 
    775     CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK);
    776     check_closest_point_cbox(pos, geom_id, &hit);
    777   }
    778 
    779   /* Filter the Cornell box blocks */
    780   filter_data.geom_to_filter[0] = geom_id[CBOX_TALL_BLOCK];
    781   filter_data.geom_to_filter[1] = geom_id[CBOX_SHORT_BLOCK];
    782   filter_data.geom_to_filter[2] = S3D_INVALID_ID;
    783   geom_id[CBOX_TALL_BLOCK] = S3D_INVALID_ID;
    784   geom_id[CBOX_SHORT_BLOCK] = S3D_INVALID_ID;
    785 
    786   /* Check closest point query filtering */
    787   FOR_EACH(i, 0, 10000) {
    788     /* Randomly generate a point in a bounding box that is 2 times the size of
    789      * the cornell box AABB */
    790     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]);
    791     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]);
    792     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]);
    793 
    794     f3_set(filter_data.query_pos, pos);
    795     filter_data.query_radius = (float)INF;
    796 
    797     CHK(s3d_scene_view_closest_point
    798       (scnview, pos, (float)INF, &filter_data, &hit) == RES_OK);
    799 
    800     check_closest_point_cbox(pos, geom_id, &hit);
    801   }
    802 
    803   /* Clean up */
    804   CHK(s3d_shape_ref_put(walls) == RES_OK);
    805   CHK(s3d_shape_ref_put(tall_block) == RES_OK);
    806   CHK(s3d_shape_ref_put(short_block) == RES_OK);
    807   CHK(s3d_scene_ref_put(scn) == RES_OK);
    808   CHK(s3d_scene_view_ref_put(scnview) == RES_OK);
    809 }
    810 
    811 /*******************************************************************************
    812  * Single triangle test
    813  ******************************************************************************/
    814 static void
    815 triangle_get_ids(const unsigned itri, unsigned ids[3], void* ctx)
    816 {
    817   (void)ctx;
    818   CHK(itri == 0);
    819   CHK(ids);
    820   ids[0] = 0;
    821   ids[1] = 1;
    822   ids[2] = 2;
    823 }
    824 
    825 static void
    826 triangle_get_pos(const unsigned ivert, float pos[3], void* ctx)
    827 {
    828   float* vertices = ctx;
    829   CHK(ctx);
    830   CHK(ivert < 3);
    831   CHK(pos);
    832   switch(ivert) { /* Setup a random triangle */
    833     case 0: f3_set(pos, vertices+0); break;
    834     case 1: f3_set(pos, vertices+3); break;
    835     case 2: f3_set(pos, vertices+6); break;
    836     default: FATAL("Unreachable code\n"); break;
    837   }
    838 }
    839 
    840 static void
    841 test_single_triangle(struct s3d_device* dev)
    842 {
    843   float vertices[9];
    844   struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
    845   struct s3d_hit hit = S3D_HIT_NULL;
    846   struct s3d_scene* scn = NULL;
    847   struct s3d_scene_view* view = NULL;
    848   struct s3d_shape* msh = NULL;
    849   struct s3d_attrib attr;
    850   float v0[3], v1[3], v2[3];
    851   float pos[3] = {0,0,0};
    852   float closest_pos[3] = {0,0,0};
    853   float low[3], upp[3], mid[3];
    854   union { float f; uint32_t ui32; } ucast;
    855   size_t a, i;
    856 
    857   f3(vertices+0, -0.5f, -0.3f,   0.1f);
    858   f3(vertices+3, -0.4f,  0.2f,   0.3f);
    859   f3(vertices+6,  0.7f,  0.01f, -0.5f);
    860 
    861   CHK(s3d_scene_create(dev, &scn) == RES_OK);
    862   CHK(s3d_shape_create_mesh(dev, &msh) == RES_OK);
    863   CHK(s3d_scene_attach_shape(scn, msh) == RES_OK);
    864 
    865   vdata.usage = S3D_POSITION;
    866   vdata.type = S3D_FLOAT3;
    867   vdata.get = triangle_get_pos;
    868   CHK(s3d_mesh_setup_indexed_vertices
    869     (msh, 1, triangle_get_ids, 3, &vdata, 1, vertices) == RES_OK);
    870 
    871   CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK);
    872 
    873   triangle_get_pos(0, v0, vertices);
    874   triangle_get_pos(1, v1, vertices);
    875   triangle_get_pos(2, v2, vertices);
    876 
    877   /* Compute the triangle AABB */
    878   low[0] = MMIN(MMIN(v0[0], v1[0]), v2[0]);
    879   low[1] = MMIN(MMIN(v0[1], v1[1]), v2[1]);
    880   low[2] = MMIN(MMIN(v0[2], v1[2]), v2[2]);
    881   upp[0] = MMAX(MMAX(v0[0], v1[0]), v2[0]);
    882   upp[1] = MMAX(MMAX(v0[1], v1[1]), v2[1]);
    883   upp[2] = MMAX(MMAX(v0[2], v1[2]), v2[2]);
    884   mid[0] = (low[0] + upp[0]) * 0.5f;
    885   mid[1] = (low[1] + upp[1]) * 0.5f;
    886   mid[2] = (low[2] + upp[2]) * 0.5f;
    887 
    888   FOR_EACH(i, 0, 10000) {
    889     /* Randomly generate a point in a bounding box that is 10 times the size of
    890      * the triangle AABB */
    891     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f;
    892     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f;
    893     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f;
    894 
    895     CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK);
    896     CHK(!S3D_HIT_NONE(&hit));
    897     CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK);
    898 
    899     /* Cross check the closest point query result */
    900     closest_point_triangle(pos, v0, v1, v2, closest_pos);
    901     CHK(f3_eq_eps(closest_pos, attr.value, 1.e-4f));
    902   }
    903 
    904   FOR_EACH(i, 0, 10000) {
    905     float radius;
    906 
    907     /* Randomly generate a point in a bounding box that is 10 times the size of
    908      * the triangle AABB */
    909     pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f;
    910     pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f;
    911     pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f;
    912 
    913     CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK);
    914     CHK(!S3D_HIT_NONE(&hit));
    915 
    916     /* Check that the radius is an exclusive upper bound */
    917     radius = hit.distance;
    918     CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK);
    919     CHK(S3D_HIT_NONE(&hit));
    920     radius = nextafterf(radius, FLT_MAX);
    921     CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK);
    922     CHK(!S3D_HIT_NONE(&hit));
    923     CHK(hit.distance == nextafterf(radius, 0.f));
    924   }
    925   CHK(s3d_scene_view_ref_put(view) == RES_OK);
    926 
    927   /* Setup a triangle and a query position that exhibited a precision issue on
    928    * the returned barycentric coordinate and check that this bug is now fixed */
    929   ucast.ui32 = 0x40400000; vertices[0] = ucast.f;
    930   ucast.ui32 = 0xc1200000; vertices[1] = ucast.f;
    931   ucast.ui32 = 0xbfc00000; vertices[2] = ucast.f;
    932   ucast.ui32 = 0x40400000; vertices[3] = ucast.f;
    933   ucast.ui32 = 0xc1200000; vertices[4] = ucast.f;
    934   ucast.ui32 = 0x3fc00000; vertices[5] = ucast.f;
    935   ucast.ui32 = 0x3f6d5337; vertices[6] = ucast.f;
    936   ucast.ui32 = 0xc0e4b2d5; vertices[7] = ucast.f;
    937   ucast.ui32 = 0xbfc00000; vertices[8] = ucast.f;
    938   f3(pos, 2, -10, 1);
    939 
    940   CHK(s3d_mesh_setup_indexed_vertices
    941     (msh, 1, triangle_get_ids, 3, &vdata, 1, vertices) == RES_OK);
    942   CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK);
    943   CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK);
    944   CHK(!S3D_HIT_NONE(&hit));
    945   CHK(0 <= hit.uv[0] && hit.uv[0] <= 1);
    946   CHK(0 <= hit.uv[1] && hit.uv[1] <= 1);
    947   CHK(hit.uv[0] + hit.uv[1] <= 1);
    948 
    949   CHK(s3d_shape_ref_put(msh) == RES_OK);
    950   CHK(s3d_scene_view_ref_put(view) == RES_OK);
    951   CHK(s3d_scene_ref_put(scn) == RES_OK);
    952 
    953   /* Check accuracy on a configuration whose analytic distance is known */
    954   FOR_EACH(a, 0, 16) {
    955     const float amplitude = exp2f((float)a);
    956     const float eps = 5e-6f * amplitude;
    957     FOR_EACH(i, 0, 1000) {
    958       float A[3], B[3], C[3], AB[3], AC[3], BC[3], N[3], hit_N[3];
    959       int j, n;
    960 
    961       /* Randomly generate a triangle ABC */
    962       FOR_EACH(n, 0, 3)
    963         A[n] = (rand_canonic() - 0.5f) * amplitude;
    964       do {
    965         FOR_EACH(n, 0, 3) B[n] = (rand_canonic() - 0.5f) * amplitude;
    966       } while (f3_eq_eps(A, B, eps));
    967       do {
    968         FOR_EACH(n, 0, 3) C[n] = (rand_canonic() - 0.5f) * amplitude;
    969       } while (f3_eq_eps(A, C, eps) || f3_eq_eps(B, C, eps));
    970 
    971       f3_sub(AB, B, A);
    972       f3_sub(AC, C, A);
    973       f3_sub(BC, C, B);
    974       f3_cross(N, AC, AB); /* Left hand convention */
    975       f3_normalize(N, N);
    976 
    977       f3_set(vertices + 0, A);
    978       f3_set(vertices + 3, B);
    979       f3_set(vertices + 6, C);
    980 
    981       CHK(s3d_scene_create(dev, &scn) == RES_OK);
    982       CHK(s3d_shape_create_mesh(dev, &msh) == RES_OK);
    983       CHK(s3d_scene_attach_shape(scn, msh) == RES_OK);
    984 
    985       vdata.usage = S3D_POSITION;
    986       vdata.type = S3D_FLOAT3;
    987       vdata.get = triangle_get_pos;
    988       CHK(s3d_mesh_setup_indexed_vertices
    989         (msh, 1, triangle_get_ids, 3, &vdata, 1, vertices) == RES_OK);
    990 
    991       CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK);
    992 
    993       FOR_EACH(j, 0, 1000) {
    994         float proj[3]; /* Projection of pos on the line */
    995         float AP[3], BP[3], CP[3], tmp[3];
    996         float closest[3] = {0,0,0};
    997         float u, v, w, h, x, dist, d;
    998 
    999         /* Randomly generate a pos not on the triangle
   1000          * with know position wrt the problem: pos = A + u.AB + v.AC + k.N */
   1001         u = 3 * rand_canonic() - 1;
   1002         v = 3 * rand_canonic() - 1;
   1003         w = 1 - u - v;
   1004         h = (2 * rand_canonic() - 1) * amplitude;
   1005         f3_add(proj, A, f3_add(proj, f3_mulf(proj, AB, u), f3_mulf(tmp, AC, v)));
   1006         f3_add(pos, proj, f3_mulf(pos, N, h));
   1007         f3_sub(AP, proj, A);
   1008         f3_sub(BP, proj, B);
   1009         f3_sub(CP, proj, C);
   1010 
   1011         /* Compute closest point */
   1012         CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit)
   1013           == RES_OK);
   1014         CHK(!S3D_HIT_NONE(&hit));
   1015         CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr)
   1016           == RES_OK);
   1017 
   1018         /* Check result
   1019          * Due to known uv lack of accuracy we mainly check distance */
   1020         if(u >= 0 && v >= 0 && w >= 0) {
   1021           /* proj is inside the triangle and is the closest point */
   1022           f3_set(closest, proj);
   1023           dist = fabsf(h);
   1024         } else {
   1025           /* proj is outside the triangle */
   1026           float lab2 = f3_dot(AB, AB);
   1027           float lac2 = f3_dot(AC, AC);
   1028           float lbc2 = f3_dot(BC, BC);
   1029           if(w >= 0 && u < 0) {
   1030             /* proj is closest to either AB or AC */
   1031             x = f3_dot(AP, AB);
   1032             if(v < 0 && x > 0) {
   1033               /* proj is closest to AB */
   1034               f3_add(closest, A, f3_mulf(tmp, AB, MMIN(1, x / lab2)));
   1035             } else {
   1036               /* proj is closest to AC */
   1037               f3_add(closest, A,
   1038                 f3_mulf(tmp, AC, MMIN(1, MMAX(0, f3_dot(AP, AC) / lac2))));
   1039             }
   1040           }
   1041           else if(u >= 0 && v < 0) {
   1042             /* proj is closest to either BC or BA */
   1043             x = f3_dot(BP, BC);
   1044             if(w < 0 && x > 0) {
   1045               /* proj is closest to BC */
   1046               f3_add(closest, B, f3_mulf(tmp, BC, MMIN(1, x / lbc2)));
   1047             } else {
   1048               /* proj is closest to BA */
   1049               f3_add(closest, B,
   1050                 f3_mulf(tmp, AB, -MMIN(1, MMAX(0, -f3_dot(BP, AB) / lab2))));
   1051             }
   1052           }
   1053           else if(v >= 0 && w < 0) {
   1054             /* proj is closest to either CA or CB */
   1055             x = -f3_dot(CP, AC);
   1056             if(u < 0 && x > 0) {
   1057               /* proj is closest to CA */
   1058               f3_add(closest, C, f3_mulf(tmp, AC, -MMIN(1, x / lac2)));
   1059             } else {
   1060               /* proj is closest to CB */
   1061               f3_add(closest, C,
   1062                 f3_mulf(tmp, BC, -MMIN(1, MMAX(0, -f3_dot(CP, BC) / lbc2))));
   1063             }
   1064           }
   1065           else { FATAL("Unreachable code\n"); }
   1066           dist = f3_len(f3_sub(tmp, pos, closest));
   1067         }
   1068         CHK(eq_epsf(hit.distance, dist, eps));
   1069         /* Intersection-point's position is less accurate than hit distance */
   1070         d = f3_len(f3_sub(tmp, closest, attr.value));
   1071         CHK(d <= 10 * eps);
   1072         f3_normalize(hit_N, hit.normal);
   1073         CHK(f3_eq_eps(N, hit_N, FLT_EPSILON));
   1074       }
   1075 
   1076       CHK(s3d_shape_ref_put(msh) == RES_OK);
   1077       CHK(s3d_scene_view_ref_put(view) == RES_OK);
   1078       CHK(s3d_scene_ref_put(scn) == RES_OK);
   1079     }
   1080   }
   1081 }
   1082 
   1083 static void
   1084 test_single_triangle_instantiated(struct s3d_device* dev)
   1085 {
   1086   union { float f; uint32_t u32; } ucast;
   1087   struct s3d_scene* scn = NULL;
   1088   struct s3d_shape* shape = NULL;
   1089   struct s3d_scene_view* view0 = NULL;
   1090   struct s3d_scene_view* view1 = NULL;
   1091   struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL;
   1092   struct s3d_hit hit0 = S3D_HIT_NULL;
   1093   struct s3d_hit hit1 = S3D_HIT_NULL;
   1094   float transform[12];
   1095   float vertices[9];
   1096   float transformed_vertices[9];
   1097   float query_pos[3];
   1098 
   1099   vdata.usage = S3D_POSITION;
   1100   vdata.type = S3D_FLOAT3;
   1101   vdata.get = triangle_get_pos;
   1102 
   1103   /* Setup the query position. The following data are retrieved from a user
   1104    * case and are thus setuped as it, in its raw binary format */
   1105   query_pos[0] = (ucast.u32 = 0xc1dc7a9e, ucast.f);
   1106   query_pos[1] = (ucast.u32 = 0xc382179f, ucast.f);
   1107   query_pos[2] = (ucast.u32 = 0xc32181b0, ucast.f);
   1108 
   1109   f3(vertices+0, -28.5f, -298.5f, 69.964429f);
   1110   f3(vertices+3, -27.0f, -298.5f, 69.899651f);
   1111   f3(vertices+6, -27.0f, -297.0f, 69.204593f);
   1112 
   1113   /* Setup the triangle transformation */
   1114   f33_rotation(transform, (float)MDEG2RAD(45.0), 0, 0);
   1115   f3_splat(transform+9, 0);
   1116 
   1117   /* Transform the triangle directly */
   1118   f33_mulf3(transformed_vertices+0, transform, vertices+0);
   1119   f33_mulf3(transformed_vertices+3, transform, vertices+3);
   1120   f33_mulf3(transformed_vertices+6, transform, vertices+6);
   1121   f3_add(transformed_vertices+0, transformed_vertices+0, transform+9);
   1122   f3_add(transformed_vertices+1, transformed_vertices+1, transform+9);
   1123   f3_add(transformed_vertices+2, transformed_vertices+2, transform+9);
   1124 
   1125   /* Setup the scene with the pre-transformed triangle */
   1126   CHK(s3d_scene_create(dev, &scn) == RES_OK);
   1127   CHK(s3d_shape_create_mesh(dev, &shape) == RES_OK);
   1128   CHK(s3d_scene_attach_shape(scn, shape) == RES_OK);
   1129   CHK(s3d_mesh_setup_indexed_vertices
   1130     (shape, 1, triangle_get_ids, 3, &vdata, 1, transformed_vertices) == RES_OK);
   1131   CHK(s3d_scene_view_create(scn, S3D_TRACE, &view0) == RES_OK);
   1132   CHK(s3d_scene_ref_put(scn) == RES_OK);
   1133   CHK(s3d_shape_ref_put(shape) == RES_OK);
   1134 
   1135   /* Setup the same scene with the transformation performed by Star-3D through
   1136    * instantiation */
   1137   CHK(s3d_scene_create(dev, &scn) == RES_OK);
   1138   CHK(s3d_shape_create_mesh(dev, &shape) == RES_OK);
   1139   CHK(s3d_scene_attach_shape(scn, shape) == RES_OK);
   1140   CHK(s3d_mesh_setup_indexed_vertices
   1141     (shape, 1, triangle_get_ids, 3, &vdata, 1, vertices) == RES_OK);
   1142   CHK(s3d_shape_ref_put(shape) == RES_OK);
   1143   CHK(s3d_scene_instantiate(scn, &shape) == RES_OK);
   1144   CHK(s3d_instance_set_transform(shape, transform) == RES_OK);
   1145   CHK(s3d_scene_ref_put(scn) == RES_OK);
   1146   CHK(s3d_scene_create(dev, &scn) == RES_OK);
   1147   CHK(s3d_scene_attach_shape(scn, shape) == RES_OK);
   1148   CHK(s3d_scene_view_create(scn, S3D_TRACE, &view1) == RES_OK);
   1149   CHK(s3d_scene_ref_put(scn) == RES_OK);
   1150   CHK(s3d_shape_ref_put(shape) == RES_OK);
   1151 
   1152   /* Find the closest point */
   1153   CHK(s3d_scene_view_closest_point
   1154     (view0, query_pos, (float)INF, NULL, &hit0) == RES_OK);
   1155   CHK(s3d_scene_view_closest_point
   1156     (view1, query_pos, (float)INF, NULL, &hit1) == RES_OK);
   1157 
   1158   /* Check that the found hits are the same */
   1159   CHK(!S3D_HIT_NONE(&hit0));
   1160   CHK(!S3D_HIT_NONE(&hit1));
   1161   CHK(eq_epsf(hit0.distance, hit1.distance, 1.e-6f));
   1162 
   1163   CHK(s3d_scene_view_ref_put(view0) == RES_OK);
   1164   CHK(s3d_scene_view_ref_put(view1) == RES_OK);
   1165 }
   1166 
   1167 /*******************************************************************************
   1168  * Miscellaneous test
   1169  ******************************************************************************/
   1170 static void
   1171 test_api(struct s3d_device* dev)
   1172 {
   1173   struct s3d_hit hit = S3D_HIT_NULL;
   1174   struct s3d_scene* scn = NULL;
   1175   struct s3d_scene_view* view = NULL;
   1176   float pos[3] = {0,0,0};
   1177 
   1178   CHK(s3d_scene_create(dev, &scn) == RES_OK);
   1179   CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK);
   1180 
   1181   CHK(s3d_scene_view_closest_point(NULL, pos, 1.f, NULL, &hit) == RES_BAD_ARG);
   1182   CHK(s3d_scene_view_closest_point(view, NULL, 1.f, NULL, &hit) == RES_BAD_ARG);
   1183   CHK(s3d_scene_view_closest_point(view, pos, 0.f, NULL, &hit) == RES_BAD_ARG);
   1184   CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, NULL) == RES_BAD_ARG);
   1185   CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_OK);
   1186   CHK(S3D_HIT_NONE(&hit));
   1187 
   1188   CHK(s3d_scene_view_ref_put(view) == RES_OK);
   1189   CHK(s3d_scene_view_create(scn, S3D_SAMPLE, &view) == RES_OK);
   1190   CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_BAD_OP);
   1191 
   1192   CHK(s3d_scene_view_ref_put(view) == RES_OK);
   1193   CHK(s3d_scene_ref_put(scn) == RES_OK);
   1194 }
   1195 
   1196 /*******************************************************************************
   1197  * Main function
   1198  ******************************************************************************/
   1199 int
   1200 main(int argc, char** argv)
   1201 {
   1202   struct mem_allocator allocator;
   1203   struct s3d_device* dev = NULL;
   1204   (void)argc, (void)argv;
   1205 
   1206   mem_init_proxy_allocator(&allocator, &mem_default_allocator);
   1207   CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK);
   1208 
   1209   test_api(dev);
   1210   test_single_triangle(dev);
   1211   test_single_triangle_instantiated(dev);
   1212   test_cbox(dev);
   1213   test_sphere(dev);
   1214   test_cbox_sphere(dev);
   1215 
   1216   CHK(s3d_device_ref_put(dev) == RES_OK);
   1217 
   1218   check_memory_allocator(&allocator);
   1219   mem_shutdown_proxy_allocator(&allocator);
   1220   CHK(mem_allocated_size() == 0);
   1221   return 0;
   1222 }