star-vx

Structuring voxels for ray-tracing
git clone git://git.meso-star.fr/star-vx.git
Log | Files | Refs | README | LICENSE

test_svx_bintree_trace_ray.c (17791B)


      1 /* Copyright (C) 2018, 2020-2025 |Méso|Star> (contact@meso-star.com)
      2  * Copyright (C) 2018 Université Paul Sabatier
      3  *
      4  * This program is free software: you can redistribute it and/or modify
      5  * it under the terms of the GNU General Public License as published by
      6  * the Free Software Foundation, either version 3 of the License, or
      7  * (at your option) any later version.
      8  *
      9  * This program is distributed in the hope that it will be useful,
     10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
     12  * GNU General Public License for more details.
     13  *
     14  * You should have received a copy of the GNU General Public License
     15  * along with this program. If not, see <http://www.gnu.r.org/licenses/>. */
     16 
     17 #include "svx.h"
     18 #include "test_svx_utils.h"
     19 
     20 #include <rsys/image.h>
     21 #include <rsys/double2.h>
     22 #include <rsys/double3.h>
     23 #include <rsys/math.h>
     24 
     25 #include <math.h>
     26 
     27 struct scene {
     28   enum svx_axis axis;
     29   double origin;
     30   double vxsz;
     31   double range_center;
     32   double range_half_len;
     33 };
     34 
     35 static double
     36 rand_canonic(void)
     37 {
     38   return (double)rand() / (double)((size_t)RAND_MAX + 1);
     39 }
     40 
     41 static void
     42 voxel_get(const size_t xyz[3], const uint64_t mcode, void* dst, void* ctx)
     43 {
     44   const struct scene* scn = ctx;
     45   char* val = dst;
     46   double low;
     47   double upp;
     48   double dst1, dst2;
     49   double min_dst, max_dst;
     50   CHK(xyz != NULL);
     51   CHK(dst != NULL);
     52   CHK(ctx != NULL);
     53   CHK(mcode == xyz[scn->axis]);
     54 
     55   /* Compute the range of the voxel */
     56   low = (double)xyz[scn->axis] * scn->vxsz + scn->origin;
     57   upp = low + scn->vxsz;
     58 
     59   /* Compute the distance from the voxel boundary to the range_center */
     60   dst1 = fabs(scn->range_center - low);
     61   dst2 = fabs(scn->range_center - upp);
     62   min_dst = MMIN(dst1, dst2);
     63   max_dst = MMAX(dst1, dst2);
     64 
     65   /* Define if the voxel intersects a range boundary */
     66   *val = min_dst <= scn->range_half_len
     67       && max_dst >= scn->range_half_len;
     68 }
     69 
     70 static void
     71 voxels_merge(void* dst, const void* voxels[], const size_t nvoxels, void* ctx)
     72 {
     73   size_t ivoxel;
     74   char tmp = 0;
     75   char* val = dst;
     76 
     77   CHK(dst && voxels && nvoxels && ctx);
     78 
     79   for(ivoxel=0; !tmp && ivoxel<nvoxels; ++ivoxel) {
     80     const char* voxel_data = voxels[ivoxel];
     81     tmp = *voxel_data;
     82   }
     83   *val = tmp;
     84 }
     85 
     86 static int
     87 voxels_challenge_merge
     88   (const struct svx_voxel voxels[], const size_t nvoxels, void* ctx)
     89 {
     90   size_t ivoxel;
     91   int merge = 1;
     92   char ref;
     93 
     94   CHK(voxels && nvoxels && ctx);
     95 
     96   ref = *(char*)(voxels[0].data);
     97 
     98   for(ivoxel=1; merge && ivoxel<nvoxels; ++ivoxel) {
     99     const char* voxel_data = voxels[ivoxel].data;
    100     merge = (ref == *voxel_data);
    101   }
    102   return merge;
    103 }
    104 
    105 static int
    106 hit_challenge
    107   (const struct svx_hit* hit,
    108    const double ray_org[3],
    109    const double ray_dir[3],
    110    const double ray_range[2],
    111    void* context)
    112 {
    113   (void)context;
    114   CHK(hit && ray_org && ray_dir && ray_range);
    115   CHK(!SVX_HIT_NONE(hit));
    116   return 1;
    117 }
    118 
    119 static int
    120 hit_challenge_root
    121   (const struct svx_hit* hit,
    122    const double ray_org[3],
    123    const double ray_dir[3],
    124    const double ray_range[2],
    125    void* context)
    126 {
    127   (void)hit, (void)ray_org, (void)ray_dir, (void)ray_range, (void)context;
    128   return hit->voxel.depth == 0;
    129 }
    130 
    131 static int
    132 hit_challenge_pass_through
    133   (const struct svx_hit* hit,
    134    const double ray_org[3],
    135    const double ray_dir[3],
    136    const double ray_range[2],
    137    void* context)
    138 {
    139   const struct ray* ray = context;
    140   CHK(hit && ray_org && ray_dir && ray_range && context);
    141   CHK(!SVX_HIT_NONE(hit));
    142   CHK(d3_eq(ray->org, ray_org));
    143   CHK(d2_eq(ray->range, ray_range));
    144 
    145   /* The direction could be adjusted internally when crossing a binary tree.
    146    * This avoids numerical inaccuracies in the case of distant intersections,
    147    * i.e. when the ray direction is roughly aligned with the third dimension
    148    * (i.e. the one that goes to infinity). In this case, the ray direction is
    149    * forced to be 0 on this dimension before being renormalized. The ray
    150    * therefore never intersects the binary tree. But its direction has changed.
    151    * Hence the approximate equality on the direction */
    152   CHK(d3_eq_eps(ray->dir, ray_dir, 1.e-6));
    153 
    154   return 0;
    155 }
    156 
    157 static int
    158 hit_filter
    159   (const struct svx_hit* hit,
    160    const double ray_org[3],
    161    const double ray_dir[3],
    162    const double ray_range[3],
    163    void* context)
    164 {
    165   const struct ray* ray = context;
    166   CHK(hit && ray_org && ray_dir && ray_range && context);
    167   CHK(d3_eq(ray->org, ray_org));
    168   CHK(d2_eq(ray->range, ray_range));
    169   CHK(!SVX_HIT_NONE(hit));
    170 
    171   /* The direction could be adjusted internally when crossing a binary tree.
    172    * Refer to the hit_challenge_pass_through function */
    173   CHK(d3_eq_eps(ray->dir, ray_dir, 1.e-6));
    174 
    175   return *((char*)hit->voxel.data) == 0;
    176 }
    177 
    178 static int
    179 hit_filter2
    180   (const struct svx_hit* hit,
    181    const double ray_org[3],
    182    const double ray_dir[3],
    183    const double ray_range[3],
    184    void* context)
    185 {
    186   const struct svx_voxel* voxel = context;
    187   CHK(hit && ray_org && ray_dir && ray_range && context);
    188   CHK(!SVX_HIT_NONE(hit));
    189   return SVX_VOXEL_EQ(&hit->voxel, voxel)
    190       || *((char*)hit->voxel.data) == 0;
    191 }
    192 
    193 static int
    194 hit_filter3
    195   (const struct svx_hit* hit,
    196    const double ray_org[3],
    197    const double ray_dir[3],
    198    const double ray_range[3],
    199    void* context)
    200 {
    201   int* accum = context;
    202   CHK(hit && ray_org && ray_dir && ray_range && context);
    203   CHK(!SVX_HIT_NONE(hit));
    204   *accum += *(char*)hit->voxel.data;
    205   return 1;
    206 }
    207 
    208 static void
    209 draw_image(struct image* img, struct svx_tree* btree)
    210 {
    211   struct camera cam;
    212   unsigned char* pixels = NULL;
    213   const size_t width = 512;
    214   const size_t height = 512;
    215   const double pos[3] = { 0, 0, 0};
    216   const double tgt[3] = { 0, 0, 1};
    217   const double up[3] =  { 1, 0, 0};
    218   double pix[2];
    219   size_t ix, iy;
    220 
    221   CHK(btree && img);
    222   camera_init(&cam, pos, tgt, up, (double)width/(double)height);
    223 
    224   CHK(image_setup(img, width, height, sizeof_image_format(IMAGE_RGB8)*width,
    225     IMAGE_RGB8, NULL) == RES_OK);
    226   pixels = (unsigned char*)img->pixels;
    227 
    228   FOR_EACH(iy, 0, height) {
    229     pix[1] = (double)iy / (double)height;
    230     FOR_EACH(ix, 0, width) {
    231       struct svx_hit hit;
    232       struct ray r;
    233       size_t ipix = (iy*width + ix)*3/*#channels per pixel*/;
    234       pix[0] = (double)ix / (double)width;
    235       camera_ray(&cam, pix, &r);
    236 
    237       CHK(svx_tree_trace_ray(btree, r.org, r.dir, r.range,
    238         hit_challenge_pass_through, hit_filter, &r, &hit) == RES_OK);
    239       pixels[ipix+0] = 0;
    240       pixels[ipix+1] = 0;
    241       pixels[ipix+2] = 0;
    242 
    243       if(!SVX_HIT_NONE(&hit)) {
    244         double N[3] = {1, 0, 0};
    245         const double dot = d3_dot(N, r.dir);
    246         if(dot < 0) {
    247           pixels[ipix+0] =(unsigned char)(255 * -dot);
    248         } else {
    249           pixels[ipix+2] =(unsigned char)(255 * dot);
    250         }
    251       }
    252     }
    253   }
    254 }
    255 
    256 int
    257 main(int argc, char** argv)
    258 {
    259   struct image img, img2;
    260   FILE* stream = NULL;
    261   struct svx_device* dev = NULL;
    262   struct svx_tree* btree = NULL;
    263   struct svx_tree* btree2 = NULL;
    264   struct svx_voxel_desc voxel_desc = SVX_VOXEL_DESC_NULL;
    265   struct svx_voxel voxel = SVX_VOXEL_NULL;
    266   struct svx_hit hit;
    267   struct svx_hit hit2;
    268   struct scene scn;
    269   const double lower = -1;
    270   const double upper =  1;
    271   const size_t definition = 32;
    272   const double scnsz = upper - lower;
    273   struct ray r;
    274   int accum;
    275   (void)argc, (void)argv;
    276 
    277   scn.origin = lower;
    278   scn.vxsz = scnsz / (double)definition;
    279   scn.range_center = lower + scnsz*0.5;
    280   scn.range_half_len = scnsz*0.25;
    281   scn.axis = SVX_AXIS_X;
    282 
    283   CHK(svx_device_create(NULL, NULL, 1, &dev) == RES_OK);
    284   voxel_desc.get = voxel_get;
    285   voxel_desc.merge = voxels_merge;
    286   voxel_desc.challenge_merge = voxels_challenge_merge;
    287   voxel_desc.context = &scn;
    288   voxel_desc.size = sizeof(char);
    289 
    290   CHK(svx_bintree_create(dev, lower, upper, definition, scn.axis,
    291     &voxel_desc, &btree) == RES_OK);
    292 
    293   /* Duplicate the binary tree through serialization */
    294   CHK(stream = tmpfile());
    295   CHK(svx_tree_write(btree, stream) == RES_OK);
    296   CHK(svx_tree_create_from_stream(dev, stream, &btree2) == RES_BAD_ARG);
    297   rewind(stream);
    298   CHK(svx_tree_create_from_stream(dev, stream, &btree2) == RES_OK);
    299   fclose(stream);
    300 
    301   #define RT svx_tree_trace_ray
    302   d3(r.org, -1.01, 0, 0);
    303   d3(r.dir, -1, 0, 0);
    304   d2(r.range, 0, DBL_MAX);
    305 
    306   CHK(RT(NULL, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG);
    307   CHK(RT(btree, NULL, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG);
    308   CHK(RT(btree, r.org, NULL, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG);
    309   CHK(RT(btree, r.org, r.dir, NULL, NULL, NULL, NULL, &hit) == RES_BAD_ARG);
    310   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, NULL) == RES_BAD_ARG);
    311 
    312   /* Check failed hits */
    313   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    314   CHK(SVX_HIT_NONE(&hit));
    315   d3(r.org, 1.01, 0, 0);
    316   d3(r.dir, 1, 0, 0);
    317   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    318   CHK(SVX_HIT_NONE(&hit));
    319   d3(r.dir, 0, 1, 0);
    320   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    321   CHK(SVX_HIT_NONE(&hit));
    322   d3(r.dir, 0, 0, -1);
    323   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    324   CHK(SVX_HIT_NONE(&hit));
    325 
    326   /* Check first hit */
    327   d3(r.org, -1.01, 0, 0);
    328   d3(r.dir, 1, 0, 0);
    329   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    330   CHK(!SVX_HIT_NONE(&hit));
    331   CHK(eq_eps(hit.distance[0], 0.01, 1.e-6));
    332   CHK(eq_eps(hit.distance[0], hit.voxel.lower[0] - r.org[0], 1.e-6));
    333   CHK(eq_eps(hit.distance[1], hit.voxel.upper[0] - r.org[0], 1.e-6));
    334   CHK(hit.voxel.is_leaf);
    335   CHK(hit.voxel.depth == 3);
    336   CHK(*(char*)hit.voxel.data == 0);
    337   CHK(IS_INF(hit.voxel.lower[1]));
    338   CHK(IS_INF(hit.voxel.lower[2]));
    339   CHK(IS_INF(hit.voxel.upper[1]));
    340   CHK(IS_INF(hit.voxel.upper[2]));
    341 
    342   /* Check first hit with negative ray */
    343   d3(r.org, 1.01, 1234, 10);
    344   d3(r.dir, -1, 0, 0);
    345   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit2) == RES_OK);
    346   CHK(!SVX_HIT_NONE(&hit2));
    347   CHK(eq_eps(hit2.distance[0], 0.01, 1.e-6));
    348   CHK(eq_eps(hit2.distance[0], r.org[0] - hit2.voxel.upper[0], 1.e-6));
    349   CHK(eq_eps(hit2.distance[1], r.org[0] - hit2.voxel.lower[0], 1.e-6));
    350   CHK(hit2.voxel.is_leaf);
    351   CHK(*(char*)hit2.voxel.data == 0);
    352   CHK(eq_eps(hit2.distance[0], hit2.distance[0], 1.e-6));
    353   CHK(eq_eps(hit2.distance[1], hit2.distance[1], 1.e-6));
    354 
    355   /* Check first hit with negative ray and and a non orthognal r.dir */
    356   d3_normalize(r.dir, d3(r.dir, -1, -1, -1));
    357   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    358   CHK(!SVX_HIT_NONE(&hit));
    359   CHK(SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel));
    360   CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-4));
    361   CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-4));
    362   CHK(eq_eps(hit.voxel.upper[0], 1, 1.e-6));
    363   CHK(eq_eps(hit.voxel.lower[0], 1-2*(1/(double)(1<<hit.voxel.depth)), 1.e-6));
    364   CHK(hit.voxel.is_leaf);
    365   CHK(hit.voxel.depth == 3);
    366   CHK(*(char*)hit.voxel.data == 0);
    367 
    368   /* Use challenge functor to intersect voxels that are note leaves */
    369   d3(r.org, 2, 1, 0);
    370   d3(r.dir, -rand_canonic(), rand_canonic()*2-1, rand_canonic()*2-1);
    371   d3_normalize(r.dir, r.dir);
    372   CHK(RT(btree, r.org, r.dir, r.range, hit_challenge, NULL, NULL, &hit)==RES_OK);
    373   CHK(!SVX_HIT_NONE(&hit));
    374   CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel));
    375   CHK(hit.voxel.is_leaf == 0);
    376   CHK(hit.voxel.depth < 3);
    377   CHK(*(char*)hit.voxel.data == 1);
    378   CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6));
    379   CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6));
    380 
    381   /* Use filter function to discard leaves with a value == 0 */
    382   CHK(RT(btree, r.org, r.dir, r.range, hit_challenge_pass_through, hit_filter,
    383     &r, &hit) == RES_OK);
    384   CHK(!SVX_HIT_NONE(&hit));
    385   CHK(hit.voxel.is_leaf == 1);
    386   CHK(hit.voxel.depth == 5);
    387   CHK(*(char*)hit.voxel.data == 1);
    388   CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-4));
    389   CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-4));
    390   CHK(eq_eps(hit.voxel.lower[0], 0.5, 1.e-6));
    391   CHK(eq_eps(hit.voxel.upper[0], 0.5+2.0/32.0, 1.e-6));
    392 
    393   /* Use the ray range to avoid the intersection with the closest voxel */
    394   r.range[1] = hit.distance[0] - 1.e-6;
    395   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK);
    396   CHK(SVX_HIT_NONE(&hit));
    397   r.range[1] = INF;
    398 
    399   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK);
    400   CHK(!SVX_HIT_NONE(&hit));
    401 
    402   /* Use the ray range to discard the closest voxel */
    403   r.range[0] = hit.distance[1] + 1.e-6;
    404   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit2) == RES_OK);
    405   CHK(!SVX_HIT_NONE(&hit2));
    406   CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel));
    407   CHK(hit2.voxel.is_leaf == 1);
    408   CHK(hit2.voxel.depth == 5);
    409   CHK(*(char*)hit2.voxel.data == 1);
    410   CHK(eq_eps(hit2.distance[0], (hit2.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-4));
    411   CHK(eq_eps(hit2.distance[1], (hit2.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-4));
    412   CHK(eq_eps(hit2.voxel.lower[0], 0.5-2.0/32.0, 1.e-6));
    413   CHK(eq_eps(hit2.voxel.upper[0], 0.5, 1.e-6));
    414 
    415   /* Adjust the ray range to intersect the interior of the closest voxel */
    416   r.range[0] = hit.distance[0] + 1.e-6;
    417   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit2) == RES_OK);
    418   CHK(!SVX_HIT_NONE(&hit2));
    419   CHK(SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel));
    420   CHK(eq_eps(hit2.distance[0], r.range[0], 1.e-6));
    421   CHK(eq_eps(hit2.distance[1], hit.distance[1], 1.e-6));
    422 
    423   /* Discard the closest voxel != 0 with the filter function */
    424   d2(r.range, 0, INF);
    425   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter2, &hit.voxel, &hit2) == RES_OK);
    426   CHK(!SVX_HIT_NONE(&hit2));
    427   CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel));
    428   CHK(eq_eps(hit2.distance[0], (hit2.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-4));
    429   CHK(eq_eps(hit2.distance[1], (hit2.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-4));
    430   CHK(eq_eps(hit2.voxel.lower[0], 0.5-2.0/32.0, 1.e-6));
    431   CHK(eq_eps(hit2.voxel.upper[0], 0.5, 1.e-6));
    432 
    433   /* Use the filter functor to accumulate the leaves */
    434   accum = 0;
    435   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter3, &accum, &hit) == RES_OK);
    436   CHK(SVX_HIT_NONE(&hit));
    437   CHK(accum == 4);
    438 
    439   /* Check ray with null dir along the btree axis */
    440   d2(r.range, 0, INF);
    441   d3(r.org, -0.875, 123, 456);
    442   d3(r.dir, 0, rand_canonic()*2-1, rand_canonic()*2-1);
    443   d3_normalize(r.dir, r.dir);
    444   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    445   CHK(!SVX_HIT_NONE(&hit));
    446   CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6));
    447   CHK(IS_INF(hit.distance[1]));
    448   CHK(hit.voxel.is_leaf == 1);
    449   CHK(hit.voxel.depth == 3);
    450   CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK);
    451   CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel));
    452   CHK(*(char*)hit.voxel.data == 0);
    453 
    454   /* Use hit filter to discard the hit */
    455   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK);
    456   CHK(SVX_HIT_NONE(&hit));
    457 
    458   /* Check ray with null dir along the btree axis */
    459   d3(r.org, -0.625, 456, 789);
    460   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    461   CHK(!SVX_HIT_NONE(&hit));
    462   CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6));
    463   CHK(IS_INF(hit.distance[1]));
    464   CHK(hit.voxel.is_leaf == 1);
    465   CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK);
    466   CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel));
    467   CHK(*(char*)hit.voxel.data == 0);
    468 
    469   /* Use hit chalenge to intersect a non leaf node */
    470   CHK(RT(btree, r.org, r.dir, r.range, hit_challenge, NULL, NULL, &hit2) == RES_OK);
    471   CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6));
    472   CHK(IS_INF(hit.distance[1]));
    473   CHK(!SVX_HIT_NONE(&hit2));
    474   CHK(!SVX_VOXEL_EQ(&hit2.voxel, &hit.voxel));
    475   CHK(*(char*)hit2.voxel.data == 1);
    476   CHK(hit2.voxel.depth < 3);
    477 
    478   /* Still check a ray with null dir along the tree axis */
    479   d3(r.org, -0.51, 31, 41);
    480   CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK);
    481   CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6));
    482   CHK(IS_INF(hit.distance[1]));
    483   CHK(hit.voxel.is_leaf == 1);
    484   CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK);
    485   CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel));
    486   CHK(*(char*)hit.voxel.data == 1);
    487   CHK(eq_eps(hit.voxel.lower[0], -0.5-2.0/32.0, 1.e-6));
    488   CHK(eq_eps(hit.voxel.upper[0], -0.5, 1.e-6));
    489   CHK(IS_INF(hit.voxel.lower[1]));
    490   CHK(IS_INF(hit.voxel.lower[2]));
    491   CHK(IS_INF(hit.voxel.upper[1]));
    492   CHK(IS_INF(hit.voxel.upper[2]));
    493 
    494   /* Use hit filter to accum data along the ray */
    495   accum = 0;
    496   CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter3, &accum, &hit) == RES_OK);
    497   CHK(SVX_HIT_NONE(&hit));
    498   CHK(accum == 1);
    499 
    500   /* Check the root node challenge */
    501   d3(r.org, 1.01, 1234, 10);
    502   d3_normalize(r.dir, d3(r.dir, -1, -1, -1));
    503   CHK(RT(btree, r.org, r.dir, r.range, hit_challenge_root, NULL, NULL, &hit)
    504     == RES_OK);
    505   CHK(!SVX_HIT_NONE(&hit));
    506   CHK(hit.voxel.lower[0] == -1);
    507   CHK(hit.voxel.upper[0] ==  1);
    508   CHK(IS_INF(hit.voxel.lower[1]));
    509   CHK(IS_INF(hit.voxel.upper[1]));
    510   CHK(IS_INF(hit.voxel.lower[2]));
    511   CHK(IS_INF(hit.voxel.upper[2]));
    512   CHK(hit.voxel.depth == 0);
    513   CHK(hit.voxel.is_leaf == 0);
    514   CHK(*((char*)hit.voxel.data) == 1);
    515   CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-4));
    516   CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-4));
    517 
    518   image_init(NULL, &img);
    519   image_init(NULL, &img2);
    520   draw_image(&img, btree);
    521   draw_image(&img2, btree2);
    522 
    523   /* Check that using oct or oct2 produces effectively the same image */
    524   check_img_eq(&img, &img2);
    525 
    526   /* Write the drawn image on stdout */
    527   CHK(image_write_ppm_stream(&img, 0/*binary*/, stdout) == RES_OK);
    528   image_release(&img);
    529   image_release(&img2);
    530 
    531   CHK(svx_tree_ref_put(btree) == RES_OK);
    532   CHK(svx_tree_ref_put(btree2) == RES_OK);
    533   CHK(svx_device_ref_put(dev) == RES_OK);
    534   CHK(mem_allocated_size() == 0);
    535   return 0;
    536 }
    537