star-3d

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

test_s3d_closest_point.c (42425B)


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