star-vx

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

commit 7fa5a5eb80eb280cb6d33ebf5da6d7f0d9aec4f4
parent 114cc20211fdf9ae828f36a2c400d0abc7ddc2d6
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Thu, 13 Sep 2018 16:30:02 +0200

Test the svx_bintree_trace_ray function

Diffstat:
Mcmake/CMakeLists.txt | 1+
Asrc/test_svx_bintree_trace_ray.c | 400+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 401 insertions(+), 0 deletions(-)

diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -99,6 +99,7 @@ if(NOT NO_TEST) new_test(test_svx_device) new_test(test_svx_octree) new_test(test_svx_octree_trace_ray ${MATH_LIB}) + new_test(test_svx_bintree_trace_ray ${MATH_LIB}) endif() rcmake_copy_runtime_libraries(test_svx_octree_trace_ray) diff --git a/src/test_svx_bintree_trace_ray.c b/src/test_svx_bintree_trace_ray.c @@ -0,0 +1,400 @@ +/* Copyright (C) 2018 Université Paul Sabatier, |Meso|Star> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.r.org/licenses/>. */ + +#include "svx.h" +#include "test_svx_utils.h" + +#include <rsys/double2.h> +#include <rsys/double3.h> +#include <rsys/math.h> + +#include <math.h> + +struct ray { + double org[3]; + double dir[3]; + double range[2]; +}; + +struct scene { + enum svx_axis axis; + double origin; + double vxsz; + double range_center; + double range_half_len; +}; + +static double +rand_canonic(void) +{ + return (double)rand() / (double)((size_t)RAND_MAX + 1); +} + +static void +voxel_get(const size_t xyz[3], void* dst, void* ctx) +{ + const struct scene* scn = ctx; + char* val = dst; + double low; + double upp; + double dst1, dst2; + double min_dst, max_dst; + CHK(xyz != NULL); + CHK(dst != NULL); + CHK(ctx != NULL); + + /* Compute the range of the voxel */ + low = (double)xyz[scn->axis] * scn->vxsz + scn->origin; + upp = low + scn->vxsz; + + /* Compute the distance from the voxel boundary to the range_center */ + dst1 = fabs(scn->range_center - low); + dst2 = fabs(scn->range_center - upp); + min_dst = MMIN(dst1, dst2); + max_dst = MMAX(dst1, dst2); + + /* Define if the voxel intersects a range boundary */ + *val = min_dst <= scn->range_half_len + && max_dst >= scn->range_half_len; +} + +static void +voxels_merge(void* dst, const void* voxels[], const size_t nvoxels, void* ctx) +{ + size_t ivoxel; + char tmp = 0; + char* val = dst; + + CHK(dst && voxels && nvoxels && ctx); + + for(ivoxel=0; !tmp && ivoxel<nvoxels; ++ivoxel) { + const char* voxel_data = voxels[ivoxel]; + tmp = *voxel_data; + } + *val = tmp; +} + +static int +voxels_challenge_merge(const void* voxels[], const size_t nvoxels, void* ctx) +{ + size_t ivoxel; + int merge = 1; + char ref; + + CHK(voxels && nvoxels && ctx); + + ref = *(char*)(voxels[0]); + + for(ivoxel=1; merge && ivoxel<nvoxels; ++ivoxel) { + const char* voxel_data = voxels[ivoxel]; + merge = (ref == *voxel_data); + } + return merge; +} + +static int +hit_challenge(const struct svx_hit* hit, void* context) +{ + (void)context; + CHK(hit); + CHK(!SVX_HIT_NONE(hit)); + return 1; +} + +static int +hit_filter + (const struct svx_hit* hit, + const double ray_org[3], + const double ray_dir[3], + const double ray_range[3], + void* context) +{ + const struct ray* ray = context; + CHK(hit && ray_org && ray_dir && ray_range && context); + CHK(d3_eq(ray->org, ray_org)); + CHK(d3_eq(ray->dir, ray_dir)); + CHK(d2_eq(ray->range, ray_range)); + CHK(!SVX_HIT_NONE(hit)); + return *((char*)hit->voxel.data) == 0; +} + +static int +hit_filter2 + (const struct svx_hit* hit, + const double ray_org[3], + const double ray_dir[3], + const double ray_range[3], + void* context) +{ + const struct svx_voxel* voxel = context; + CHK(hit && ray_org && ray_dir && ray_range && context); + CHK(!SVX_HIT_NONE(hit)); + return SVX_VOXEL_EQ(&hit->voxel, voxel) + || *((char*)hit->voxel.data) == 0; +} + +static int +hit_filter3 + (const struct svx_hit* hit, + const double ray_org[3], + const double ray_dir[3], + const double ray_range[3], + void* context) +{ + int* accum = context; + CHK(hit && ray_org && ray_dir && ray_range && context); + CHK(!SVX_HIT_NONE(hit)); + *accum += *(char*)hit->voxel.data; + return 1; +} + +int +main(int argc, char** argv) +{ + struct svx_device* dev = NULL; + struct svx_tree* btree = NULL; + struct svx_voxel_desc voxel_desc = SVX_VOXEL_DESC_NULL; + struct svx_voxel voxel = SVX_VOXEL_NULL; + struct svx_hit hit; + struct svx_hit hit2; + struct scene scn; + const double lower = -1; + const double upper = 1; + const size_t definition = 32; + const double scnsz = upper - lower; + struct ray r; + int accum; + (void)argc, (void)argv; + + scn.origin = lower; + scn.vxsz = scnsz / (double)definition; + scn.range_center = lower + scnsz*0.5; + scn.range_half_len = scnsz*0.25; + scn.axis = SVX_AXIS_X; + + CHK(svx_device_create(NULL, NULL, 1, &dev) == RES_OK); + voxel_desc.get = voxel_get; + voxel_desc.merge = voxels_merge; + voxel_desc.challenge_merge = voxels_challenge_merge; + voxel_desc.context = &scn; + voxel_desc.size = sizeof(char); + + CHK(svx_bintree_create(dev, lower, upper, definition, scn.axis, + &voxel_desc, &btree) == RES_OK); + + d3(r.org, -1.01, 0, 0); + d3(r.dir, -1, 0, 0); + d2(r.range, 0, DBL_MAX); + + #define RT svx_bintree_trace_ray + CHK(RT(NULL, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG); + CHK(RT(btree, NULL, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG); + CHK(RT(btree, r.org, NULL, r.range, NULL, NULL, NULL, &hit) == RES_BAD_ARG); + CHK(RT(btree, r.org, r.dir, NULL, NULL, NULL, NULL, &hit) == RES_BAD_ARG); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, NULL) == RES_BAD_ARG); + + /* Check failed hits */ + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + d3(r.org, 1.01, 0, 0); + d3(r.dir, 1, 0, 0); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + d3(r.dir, 0, 1, 0); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + d3(r.dir, 0, 0, -1); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + + /* Check first hit */ + d3(r.org, -1.01, 0, 0); + d3(r.dir, 1, 0, 0); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(eq_eps(hit.distance[0], 0.01, 1.e-6)); + CHK(eq_eps(hit.distance[0], hit.voxel.lower[0] - r.org[0], 1.e-6)); + CHK(eq_eps(hit.distance[1], hit.voxel.upper[0] - r.org[0], 1.e-6)); + CHK(hit.voxel.is_leaf); + CHK(hit.voxel.depth == 3); + CHK(*(char*)hit.voxel.data == 0); + CHK(IS_INF(hit.voxel.lower[1])); + CHK(IS_INF(hit.voxel.lower[2])); + CHK(IS_INF(hit.voxel.upper[1])); + CHK(IS_INF(hit.voxel.upper[2])); + + /* Check first hit with negative ray */ + d3(r.org, 1.01, 1234, 10); + d3(r.dir, -1, 0, 0); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit2) == RES_OK); + CHK(!SVX_HIT_NONE(&hit2)); + CHK(eq_eps(hit2.distance[0], 0.01, 1.e-6)); + CHK(eq_eps(hit2.distance[0], r.org[0] - hit2.voxel.upper[0], 1.e-6)); + CHK(eq_eps(hit2.distance[1], r.org[0] - hit2.voxel.lower[0], 1.e-6)); + CHK(hit2.voxel.is_leaf); + CHK(*(char*)hit2.voxel.data == 0); + CHK(eq_eps(hit2.distance[0], hit2.distance[0], 1.e-6)); + CHK(eq_eps(hit2.distance[1], hit2.distance[1], 1.e-6)); + + /* Check first hit with negative ray and and a non orthognal r.dir */ + d3_normalize(r.dir, d3(r.dir, -1, -1, -1)); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel)); + CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit.voxel.upper[0], 1, 1.e-6)); + CHK(eq_eps(hit.voxel.lower[0], 1-2*(1/(double)(1<<hit.voxel.depth)), 1.e-6)); + CHK(hit.voxel.is_leaf); + CHK(hit.voxel.depth == 3); + CHK(*(char*)hit.voxel.data == 0); + + /* Use challenge functor to intersect voxels that are note leaves */ + d3(r.org, 2, 1, 0); + d3(r.dir, -rand_canonic(), rand_canonic()*2-1, rand_canonic()*2-1); + d3_normalize(r.dir, r.dir); + CHK(RT(btree, r.org, r.dir, r.range, hit_challenge, NULL, NULL, &hit)==RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel)); + CHK(hit.voxel.is_leaf == 0); + CHK(hit.voxel.depth < 3); + CHK(*(char*)hit.voxel.data == 1); + CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6)); + + /* Use filter function to discard leaves with a value == 0 */ + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(hit.voxel.is_leaf == 1); + CHK(hit.voxel.depth == 5); + CHK(*(char*)hit.voxel.data == 1); + CHK(eq_eps(hit.distance[0], (hit.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit.distance[1], (hit.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit.voxel.lower[0], 0.5, 1.e-6)); + CHK(eq_eps(hit.voxel.upper[0], 0.5+2.0/32.0, 1.e-6)); + + /* Use the ray range to avoid the intersection with the closest voxel */ + r.range[1] = hit.distance[0] - 1.e-6; + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + r.range[1] = INF; + + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + + /* Use the ray range to discard the closest voxel */ + r.range[0] = hit.distance[1] + 1.e-6; + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit2) == RES_OK); + CHK(!SVX_HIT_NONE(&hit2)); + CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel)); + CHK(hit2.voxel.is_leaf == 1); + CHK(hit2.voxel.depth == 5); + CHK(*(char*)hit2.voxel.data == 1); + CHK(eq_eps(hit2.distance[0], (hit2.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit2.distance[1], (hit2.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit2.voxel.lower[0], 0.5-2.0/32.0, 1.e-6)); + CHK(eq_eps(hit2.voxel.upper[0], 0.5, 1.e-6)); + + /* Adjust the ray range to intersect the interior of the closest voxel */ + r.range[0] = hit.distance[0] + 1.e-6; + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit2) == RES_OK); + CHK(!SVX_HIT_NONE(&hit2)); + CHK(SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel)); + CHK(eq_eps(hit2.distance[0], r.range[0], 1.e-6)); + CHK(eq_eps(hit2.distance[1], hit.distance[1], 1.e-6)); + + /* Discard the closest voxel != 0 with the filter function */ + d2(r.range, 0, INF); + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter2, &hit.voxel, &hit2) == RES_OK); + CHK(!SVX_HIT_NONE(&hit2)); + CHK(!SVX_VOXEL_EQ(&hit.voxel, &hit2.voxel)); + CHK(eq_eps(hit2.distance[0], (hit2.voxel.upper[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit2.distance[1], (hit2.voxel.lower[0]-r.org[0])/r.dir[0], 1.e-6)); + CHK(eq_eps(hit2.voxel.lower[0], 0.5-2.0/32.0, 1.e-6)); + CHK(eq_eps(hit2.voxel.upper[0], 0.5, 1.e-6)); + + /* Use the filter functor to accumulate the leaves */ + accum = 0; + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter3, &accum, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + CHK(accum == 4); + + /* Check ray with null dir along the btree axis */ + d2(r.range, 0, INF); + d3(r.org, -0.875, 123, 456); + d3(r.dir, 0, rand_canonic()*2-1, rand_canonic()*2-1); + d3_normalize(r.dir, r.dir); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6)); + CHK(IS_INF(hit.distance[1])); + CHK(hit.voxel.is_leaf == 1); + CHK(hit.voxel.depth == 3); + CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK); + CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel)); + CHK(*(char*)hit.voxel.data == 0); + + /* Use hit filter to discard the hit */ + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter, &r, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + + /* Check ray with null dir along the btree axis */ + d3(r.org, -0.625, 456, 789); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(!SVX_HIT_NONE(&hit)); + CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6)); + CHK(IS_INF(hit.distance[1])); + CHK(hit.voxel.is_leaf == 1); + CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK); + CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel)); + CHK(*(char*)hit.voxel.data == 0); + + /* Use hit chalenge to intersect a non leaf node */ + CHK(RT(btree, r.org, r.dir, r.range, hit_challenge, NULL, NULL, &hit2) == RES_OK); + CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6)); + CHK(IS_INF(hit.distance[1])); + CHK(!SVX_HIT_NONE(&hit2)); + CHK(!SVX_VOXEL_EQ(&hit2.voxel, &hit.voxel)); + CHK(*(char*)hit2.voxel.data == 1); + CHK(hit2.voxel.depth < 3); + + /* Stil lcheck a ray with null dir along the tree axis */ + d3(r.org, -0.51, 31, 41); + CHK(RT(btree, r.org, r.dir, r.range, NULL, NULL, NULL, &hit) == RES_OK); + CHK(eq_eps(hit.distance[0], r.range[0], 1.e-6)); + CHK(IS_INF(hit.distance[1])); + CHK(hit.voxel.is_leaf == 1); + CHK(svx_tree_at(btree, r.org, NULL, NULL, &voxel) == RES_OK); + CHK(SVX_VOXEL_EQ(&voxel, &hit.voxel)); + CHK(*(char*)hit.voxel.data == 1); + CHK(eq_eps(hit.voxel.lower[0], -0.5-2.0/32.0, 1.e-6)); + CHK(eq_eps(hit.voxel.upper[0], -0.5, 1.e-6)); + CHK(IS_INF(hit.voxel.lower[1])); + CHK(IS_INF(hit.voxel.lower[2])); + CHK(IS_INF(hit.voxel.upper[1])); + CHK(IS_INF(hit.voxel.upper[2])); + + /* Use hit filter to accum data along the ray */ + accum = 0; + CHK(RT(btree, r.org, r.dir, r.range, NULL, hit_filter3, &accum, &hit) == RES_OK); + CHK(SVX_HIT_NONE(&hit)); + CHK(accum == 1); + + CHK(svx_tree_ref_put(btree) == RES_OK); + CHK(svx_device_ref_put(dev) == RES_OK); + CHK(mem_allocated_size() == 0); + return 0; +} +