star-3d

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

commit d259f044ba0b14a0b4539a6054e5cbaa8aeb85be
parent bb7592f99259806ef5d69045f43538a2a5eb2456
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Mon, 18 Nov 2019 15:04:26 +0100

Merge branch 'release_0.7'

Diffstat:
MREADME.md | 89++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
Mcmake/CMakeLists.txt | 11++++++++---
Msrc/s3d.h | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/s3d_device.c | 12+++++++++++-
Msrc/s3d_geometry.c | 21+++++----------------
Msrc/s3d_geometry.h | 3++-
Msrc/s3d_scene_view.c | 439+++++++++++++++++++++++++++++--------------------------------------------------
Msrc/s3d_scene_view_c.h | 3+++
Asrc/s3d_scene_view_closest_point.c | 450+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/s3d_scene_view_trace_ray.c | 302++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/s3d_sphere.h | 22++++++++++++++++++++++
Asrc/test_s3d_accel_struct_conf.c | 252+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/test_s3d_closest_point.c | 959+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/test_s3d_scene_view.c | 4++--
Asrc/test_s3d_scene_view_aabb.c | 438+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 2747 insertions(+), 350 deletions(-)

diff --git a/README.md b/README.md @@ -2,40 +2,42 @@ ## Overview -Star-3D is a C/C++ library whose purpose is to manage a virtual environment -composed of triangular meshes. The resulting virtual world can then be -ray-traced and sampled, providing an efficient way to deal with geometric data -of arbitrary 3D contents. In addition of purely geometric data, user defined -per vertex data can be setup, allowing the definition of attributes that -spatially vary over the geometry surface. To ensure high ray-tracing -efficiency, the Star-3D ray-tracing back-end relies on the [Intel(R) Rendering +Star-3D is a C library that *manages surfacic geometries* and provides +operators to access them efficiently by *uniform sampling*, *ray-tracing* or a +*closest point query*. To ensure the efficiency of these operators, Star-3D +internally relies on [Intel(R) Rendering Framework](https://software.intel.com/en-us/rendering-framework): -[Embree](https://embree.github.io) that provides several ray-tracing kernels -optimized for a wide range of data workloads. The target users of Star-3D are -thus programmers that have to efficiently deal with complex 3D environment as -numerical simulation engineers, researchers in complex systems or graphics -programmers. - -The main concept exposed by the Star-3D C API are *shapes*. A *shape* -represents a 3D object whose data is defined by the user and can be updated at -any time. A 3D environment is built by attaching one or several *shapes* to a -*scene*. To access the *scene* data through sampling, ray-tracing or indexing, -one have to create a *scene view* that commits the current *scene* geometry -as the geometry of the *view*. A *scene* can also be instantiated into one or -several *shapes*, each with its own attributes (e.g. position, orientation, -etc.). Since the *scene* geometry is stored once even though it is -instantiated several times, this feature can be used to create extremely -complex environment with a low memory footprint. - -Star-3D is currently used in several softwares dealing with complex arbitrary -3D contents, ranging from graphics applications and thermal simulations to +[Embree](https://embree.github.io) that provides highly optimized acceleration +structures as well as traversal kernels for a wide range of data workloads. The +main targets of Star-3D are thus programmers that want to efficiently handle +*complex and arbitrary 3D content.* + +The main concept exposed in Star-3D is the *shape*. A shape represents a 3D +object such as a *triangular mesh* or a *sphere*, whose data are user defined +and can be updated at any time. A virtual environment is built by attaching one +or several shapes to a *scene*. A scene can be *instantiated* into one or +several shapes that can be then attached to a scene as any regular shapes. Each +instance has its own position and orientation while the instantiated geometry +is stored once even though it is instantiated several times. This feature can +thus be used to create extremely complex environment with a low memory +footprint. + +To query the scene data one has to create a *view* of the scene. On its +creation, the view internally builds data structures required by the +aforementioned access operators. These data structures are built from the scene +geometry as defined at the moment of the view creation; a view is thus +insensitive to scene updates following its creation. This means that several +views can be used to register different states of the same scene, giving to the +caller a great flexibility to manage the scene data. + +Star-3D is currently used in several projects dealing with complex arbitrary 3D +contents, ranging from graphics applications and thermal simulations to electromagnetism. Please refer to these projects for informations on their purpose. * [High-Tune: RenDeRer](https://gitlab.com/meso-star/htrdr.git) * [Solstice-Solver](https://gitlab.com/meso-star/solstice-solver.git) * [Star-4V/S](https://gitlab.com/meso-star/star-4v_s.git) - * [Star-Display](https://gitlab.com/meso-star/star-display.git) * [Star-GebhartFactor](https://gitlab.com/meso-star/star-gf.git) * [Star-Schiff](https://gitlab.com/meso-star/star-schiff.git) @@ -45,7 +47,7 @@ purpose. Star-3D is compatible GNU/Linux as well as Microsoft Windows 7 and later, both in 64-bits. It was successfully built with the [GNU Compiler -Collection](https://gcc.gnu.org) (versions 4.7 and later) as well as with +Collection](https://gcc.gnu.org) (versions 4.9.2 and later) as well as with Microsoft Visual Studio 2015. It relies on [CMake](http://www.cmake.org) and the [RCMake](https://gitlab.com/vaplv/rcmake/) package to build. It also depends on the [RSys](https://gitlab.com/vaplv/rsys/) and @@ -99,6 +101,7 @@ Star-3D package to build an executable relying on the Star-3D library. # Use the Star-3D CMake package find_package(Star3D REQUIRED) + include_directories(${Star3D_INCLUDE_DIR}) # Define the program to build add_executable(my_program main.c) @@ -116,6 +119,29 @@ with `<STAR3D_INSTALL_DIR>` the install directory of Star-3D and ## Release notes +### Version 0.7 + +- Add the `s3d_scene_view_closest_point` function. This function retrieves the + closest point into the scene to a query position in a radius from ]0, INF]. + This closest point is returned as a regular hit which means that the function + returns to the caller not only the distance to the query position, but also + the geometric primitive onto which the point lies and its location onto this + primitive. If no surface lies around the query position in a distance lesser + than the one defined by the query radius, the returned hit is invalid which + can be checked with the regular `S3D_HIT_NONE` macro. Finally, this function + supports the filter functions so that the caller can choose to discard + closest hits according to its own criteria. +- Add the `s3d_scene_view_create2` function that allows the user to configure + the acceleration structure used to partition the scene geometry. The caller + can thus control the quality of the structure and thus the compromise between + the building time and the query performances. +- Improve the performances of the scene bounding box computation. First of all, + if the scene view is created with the `S3D_TRACE` flag, Star-3D directly + retrieves the scene bounding box from Embree that already computed it to + build the acceleration structure. Finally, it internally tries to avoid to + recompute the bounding box if the scene was not updated between 2 scene view + creations. + ### Version 0.6.2 - Fix an issue in `s3d_scene_view_compute_area`: the returned area was wrong @@ -174,8 +200,7 @@ with `<STAR3D_INSTALL_DIR>` the install directory of Star-3D and ## License -Star-3D is free software copyright (C) 2015-2019 |Meso|Star> -(<contact@meso-star.com>). It is released under the CeCILLv2.1 license. You are -welcome to redistribute it under certain conditions; refer to the COPYING files -for details. +Copyright (C) 2015-2019 |Méso|Star> (<contact@meso-star.com>). Star-3D is +released under the CeCILLv2.1 license. You are welcome to redistribute it under +certain conditions; refer to the COPYING files for details. diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt @@ -39,7 +39,7 @@ option(NO_TEST "Disable the test" OFF) ################################################################################ # Check dependencies ################################################################################ -find_package(Embree 3 REQUIRED) +find_package(Embree 3.6 REQUIRED) find_package(RCMake 0.2.2 REQUIRED) find_package(RSys 0.6 REQUIRED) @@ -57,8 +57,8 @@ endif() # Configure and define targets ################################################################################ set(VERSION_MAJOR 0) -set(VERSION_MINOR 6) -set(VERSION_PATCH 2) +set(VERSION_MINOR 7) +set(VERSION_PATCH 0) set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) set(S3D_FILES_SRC @@ -69,6 +69,8 @@ set(S3D_FILES_SRC s3d_primitive.c s3d_scene.c s3d_scene_view.c + s3d_scene_view_closest_point.c + s3d_scene_view_trace_ray.c s3d_shape.c s3d_sphere.c) set(S3D_FILES_INC_API s3d.h) @@ -130,11 +132,14 @@ if(NOT NO_TEST) register_test(${_name} ${_name}) endfunction() + new_test(test_s3d_accel_struct_conf) + new_test(test_s3d_closest_point) new_test(test_s3d_device) new_test(test_s3d_sampler) new_test(test_s3d_sample_sphere) new_test(test_s3d_scene) new_test(test_s3d_scene_view) + new_test(test_s3d_scene_view_aabb) new_test(test_s3d_seams) new_test(test_s3d_shape) new_test(test_s3d_sphere) diff --git a/src/s3d.h b/src/s3d.h @@ -144,9 +144,9 @@ static const struct s3d_vertex_data S3D_VERTEX_DATA_NULL = S3D_VERTEX_DATA_NULL_ /* Intersection point */ struct s3d_hit { struct s3d_primitive prim; /* Intersected primitive */ - float normal[3]; /* Unnormalized geometry normal */ + float normal[3]; /* Un-normalized geometry normal */ float uv[2]; /* Barycentric coordinates of the hit onto `prim' */ - float distance; /* Hit distance from the ray origin */ + float distance; /* Hit distance from the query origin */ }; /* Constant defining a NULL intersection. Should be used to initialize a hit */ @@ -169,16 +169,52 @@ enum s3d_scene_view_flag { * intersects a shape or not */ #define S3D_HIT_NONE(Hit) ((Hit)->distance >= FLT_MAX) +/* Quality of the partitioning data structure used to accelerate geometry + * queries. The lowest the structure quality is, the fastest it is built. On + * the counterpart, a weak structure quality means that the partitioning of the + * geometry is sub-optimal, leading to lower geometry query performances. */ +enum s3d_accel_struct_quality { + S3D_ACCEL_STRUCT_QUALITY_LOW, + S3D_ACCEL_STRUCT_QUALITY_MEDIUM, + S3D_ACCEL_STRUCT_QUALITY_HIGH +}; + +/* Define the properties of the partitioning data structure used to accelerate + * geometry queries */ +enum s3d_accel_struct_flag { + /* Avoid optimisations that reduce arithmetic accuracy */ + S3D_ACCEL_STRUCT_FLAG_ROBUST = BIT(0), + /* Improve the building performances of the acceleration structure for + * dynamic scenes */ + S3D_ACCEL_STRUCT_FLAG_DYNAMIC = BIT(1), + /* Reduce the memory consumption of the acceleration structure */ + S3D_ACCEL_STRUCT_FLAG_COMPACT = BIT(2) +}; + +/* Configuration of the partitioning structure used to accelerate geometry + * queries */ +struct s3d_accel_struct_conf { + enum s3d_accel_struct_quality quality; + int mask; /* combination of s3d_accel_struct_flag */ +}; +#define S3D_ACCEL_STRUCT_CONF_DEFAULT__ { \ + S3D_ACCEL_STRUCT_QUALITY_MEDIUM, \ + S3D_ACCEL_STRUCT_FLAG_ROBUST \ +} +static const struct s3d_accel_struct_conf S3D_ACCEL_STRUCT_CONF_DEFAULT = + S3D_ACCEL_STRUCT_CONF_DEFAULT__; + /* Filter function data type. One can define such function to discard - * intersections along a ray with respect to user defined criteria, e.g.: - * masked/transparent primitive, etc. Return 0 or the intersection is not - * discarded and a value not equal to zero otherwise. */ + * intersections along a ray or the result of a closest point query with + * respect to user defined criteria, e.g.: masked/transparent primitive, etc. + * Return 0 or the intersection is not discarded and a value not equal to zero + * otherwise. */ typedef int (*s3d_hit_filter_function_T) (const struct s3d_hit* hit, - const float ray_org[3], - const float ray_dir[3], - void* ray_data, /* User data submitted on trace ray(s) invocation */ + const float org[3], + const float dir[3], /* Direction from `org' to `hit' */ + void* query_data, /* User data submitted on query invocation */ void* filter_data); /* Data defined on the setup of the filter function */ /* Forward declaration of s3d opaque data types */ @@ -283,6 +319,15 @@ s3d_scene_view_create struct s3d_scene_view** scnview); S3D_API res_T +s3d_scene_view_create2 + (struct s3d_scene* scn, + const int mask, /* Combination of s3d_scene_view_flag */ + /* Ignored if (mask & S3D_TRACE) == 0 + * NULL <=> use S3D_ACCEL_STRUCT_CONF_DEFAULT */ + const struct s3d_accel_struct_conf* cfg, + struct s3d_scene_view** scnview); + +S3D_API res_T s3d_scene_view_ref_get (struct s3d_scene_view* scnview); @@ -295,6 +340,11 @@ s3d_scene_view_get_mask (struct s3d_scene_view* scnview, int* mask); +/* Trace a ray into the scene and return the closest intersection along it. The + * ray is defined by `origin' + t*`direction' = 0 with t in [`range[0]', + * `range[1]'). Note that if a range is degenerated (i.e. `range[0]' >= + * `range[1]') then the ray is not traced and `hit' is set to S3D_HIT_NULL. Can + * be called only if the scnview was created with the S3D_TRACE flag. */ S3D_API res_T s3d_scene_view_trace_ray (struct s3d_scene_view* scnview, @@ -304,12 +354,8 @@ s3d_scene_view_trace_ray void* ray_data, /* User ray data sent to the hit filter func. May be NULL */ struct s3d_hit* hit); -/* Trace a bundle of rays into the scene and return the closest intersection - * along them. The rays are defined by `origin' + t*`direction' = 0 with t in - * [`range[0]', `range[1]'). Note that if a range is degenerated (i.e. - * `range[0]' >= `range[1]') then its associated ray is not traced and `hit' is - * set to S3D_HIT_NULL. Can be called only if the scnview was created with the - * S3D_TRACE flag. */ +/* Trace a bundle of rays into the scene. Can be called only if the scnview was + * created with the S3D_TRACE flag. */ S3D_API res_T s3d_scene_view_trace_rays (struct s3d_scene_view* scnview, @@ -322,6 +368,24 @@ s3d_scene_view_trace_rays const size_t sizeof_ray_data, /* Size in Bytes of *one* ray data */ struct s3d_hit* hits); +/* Return the point onto the scene surfaces that is the closest of the + * submitted `pos'. Note that even though only one point is returned, several + * position can have the same minimal distance to the queried position. The + * `radius' parameter defines the maximum search distance around `pos'. Each + * candidate position are internally filtered by the hit_filter_function + * attached to the corresponding shape; the user can thus reject a candidate + * position according to its own criteria. This function can be called only if + * the scnview was created with the S3D_TRACE flag which is actually the flag + * used to tell Star-3D to internally build an acceleration structure on which + * this function relies. */ +S3D_API res_T +s3d_scene_view_closest_point + (struct s3d_scene_view* scnview, + const float pos[3], /* Position to query */ + const float radius, /* Search distance in ]0, radius[ */ + void* query_data, /* User data sent to the hit filter func. May be NULL */ + struct s3d_hit* hit); + /* Uniformly sample the scene and return the sampled primitive and its sample * uv position. Can be called only if the scnview was created with the * S3D_SAMPLE flag */ diff --git a/src/s3d_device.c b/src/s3d_device.c @@ -88,8 +88,11 @@ s3d_device_create const int verbose, struct s3d_device** out_dev) { + char embree_opts[512]; struct s3d_device* dev = NULL; struct mem_allocator* allocator; + const int verbosity = MMAX(MMIN(verbose, 3), 0); + int sz; res_T res = RES_OK; if(!out_dev) { @@ -109,7 +112,14 @@ s3d_device_create flist_name_init(allocator, &dev->names); ref_init(&dev->ref); - dev->rtc = rtcNewDevice(verbose ? "verbose=1" : NULL); + sz = snprintf(embree_opts, sizeof(embree_opts), "verbose=%d", verbosity); + if((size_t)sz >= sizeof(embree_opts)) { + log_error(dev, "Could not setup the Embree option string.\n"); + res = RES_MEM_ERR; + goto error; + } + + dev->rtc = rtcNewDevice(embree_opts); if(dev->rtc == NULL) { const enum RTCError err = rtcGetDeviceError(NULL); log_error(dev, "Could not create the embree device -- %s.\n", diff --git a/src/s3d_geometry.c b/src/s3d_geometry.c @@ -51,9 +51,8 @@ sphere_ray_hit_setup struct RTCHitN* hitN; struct RTCHit hit; struct RTCRay ray; - float cos_theta; float Ng[3]; - float u, v; + float uv[2]; size_t i; ASSERT(args && args->primID == 0 && args->N == 1 && args->valid[0] != 0); @@ -71,24 +70,13 @@ sphere_ray_hit_setup Ng[2] = ray.dir_z*tfar + ray.org_z - geom->data.sphere->pos[2]; f3_normalize(Ng, Ng); - cos_theta = Ng[2]; - - v = (1.f - cos_theta) * 0.5f; - if(absf(cos_theta) == 1) { - u = 0; - } else if(eq_epsf(Ng[0], 0.f, 1.e-6f)) { - u = Ng[1] > 0 ? 0.25f : 0.75f; - } else { - double phi = atan2f(Ng[1], Ng[0]); /* phi in [-PI, PI] */ - if(phi < 0) phi = 2*PI + phi; /* phi in [0, 2PI] */ - u = (float)(phi / (2*PI)); - } + sphere_normal_to_uv(Ng, uv); hit.Ng_x = Ng[0]; hit.Ng_y = Ng[1]; hit.Ng_z = Ng[2]; - hit.u = u; - hit.v = v; + hit.u = uv[0]; + hit.v = uv[1]; hit.primID = 0; hit.geomID = geom->rtc_id; FOR_EACH(i, 0, RTC_MAX_INSTANCE_LEVEL_COUNT) { @@ -167,6 +155,7 @@ geometry_create geom->data.mesh = NULL; geom->rtc = NULL; geom->rtc_id = RTC_INVALID_GEOMETRY_ID; + geom->rtc_build_quality = RTC_BUILD_QUALITY_MEDIUM; exit: *out_geom = geom; diff --git a/src/s3d_geometry.h b/src/s3d_geometry.h @@ -35,6 +35,7 @@ #include "s3d.h" #include "s3d_backend.h" +#include <rsys/float3.h> #include <rsys/ref_count.h> enum geometry_type { @@ -58,6 +59,7 @@ enum embree_attrib { struct geometry { unsigned name; /* Client side identifier */ RTCGeometry rtc; /* Embree geometry */ + enum RTCBuildQuality rtc_build_quality; /* BVH build quality */ unsigned rtc_id; /* Embree geometry identifier */ unsigned scene_prim_id_offset; /* Offset from local to scene prim_id */ @@ -99,4 +101,3 @@ geometry_rtc_sphere_intersect (const struct RTCIntersectFunctionNArguments* args); #endif /* S3D_GEOMETRY_H */ - diff --git a/src/s3d_scene_view.c b/src/s3d_scene_view.c @@ -41,14 +41,6 @@ #include <rsys/float33.h> #include <rsys/mem_allocator.h> -struct intersect_context { - struct RTCIntersectContext rtc; - struct s3d_scene_view* scnview; - void* data; /* Per ray user defined data */ - float ws_org[3]; /* World space ray origin */ - float ws_dir[3]; /* World space ray direction */ -}; - /******************************************************************************* * Helper functions ******************************************************************************/ @@ -126,11 +118,13 @@ on_shape_detach geom = *pgeom; if(scnview->mask == 0) { - /* The scnview is NOT in use. Directly rm the cached geometry */ + /* The scnview is NOT in use. Directly rm the cached geometry and notify + * that the scene AABB must be reevaluated */ size_t n; (void)n; scene_view_destroy_geometry(scnview, geom); n = htable_geom_erase(&scnview->cached_geoms, &shape_id); ASSERT(n == 1); + scnview->aabb_update = 1; } else { /* The scnview is in use. Delay the deletion of the cached geometry */ res_T res = darray_uint_push_back(&scnview->detached_shapes, &shape_id); @@ -138,115 +132,72 @@ on_shape_detach } } -static INLINE void -hit_setup - (struct s3d_scene_view* scnview, - const struct RTCRayHit* ray_hit, - struct s3d_hit* hit) +static INLINE enum RTCBuildQuality +accel_struct_quality_to_rtc_build_quality + (enum s3d_accel_struct_quality quality) { - float w; - char flip_surface = 0; - - ASSERT(scnview && hit && ray_hit); - - if(ray_hit->hit.geomID == RTC_INVALID_GEOMETRY_ID) { /* No hit */ - *hit = S3D_HIT_NULL; - return; - } - - hit->normal[0] = ray_hit->hit.Ng_x; - hit->normal[1] = ray_hit->hit.Ng_y; - hit->normal[2] = ray_hit->hit.Ng_z; - hit->distance = ray_hit->ray.tfar; - - if(ray_hit->hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { - struct geometry* geom_shape; - geom_shape = scene_view_geometry_from_embree_id(scnview, ray_hit->hit.geomID); - hit->prim.shape__ = geom_shape; - hit->prim.inst__ = NULL; - hit->prim.prim_id = ray_hit->hit.primID; - hit->prim.geom_id = geom_shape->name; - hit->prim.inst_id = S3D_INVALID_ID; - hit->prim.scene_prim_id = /* Compute the "scene space" primitive id */ - hit->prim.prim_id /* Mesh space */ - + geom_shape->scene_prim_id_offset; /* Scene space */ - - } else { /* The hit shape is instantiated */ - /* Retrieve the hit instance */ - struct geometry* geom_inst; - struct geometry* geom_shape; - float transform[9]; - geom_inst = scene_view_geometry_from_embree_id - (scnview, ray_hit->hit.instID[0]); - geom_shape = scene_view_geometry_from_embree_id - (geom_inst->data.instance->scnview, ray_hit->hit.geomID); - hit->prim.shape__ = geom_shape; - hit->prim.inst__ = geom_inst; - hit->prim.prim_id = ray_hit->hit.primID; - hit->prim.geom_id = geom_shape->name; - hit->prim.inst_id = geom_inst->name; - hit->prim.scene_prim_id = /* Compute the "scene space" primitive id */ - hit->prim.prim_id /* Shape space */ - + geom_shape->scene_prim_id_offset /* Inst space */ - + geom_inst->scene_prim_id_offset; /* Scene space */ - - flip_surface = geom_inst->flip_surface; - ASSERT(hit->prim.inst__); - ASSERT(((struct geometry*)hit->prim.inst__)->type == GEOM_INSTANCE); - - /* Transform the normal in world space */ - f33_invtrans(transform, geom_inst->data.instance->transform); - f33_mulf3(hit->normal, transform, hit->normal); - } - ASSERT(hit->prim.shape__); - ASSERT(((struct geometry*)hit->prim.shape__)->type == GEOM_MESH - ||((struct geometry*)hit->prim.shape__)->type == GEOM_SPHERE); - - hit->uv[0] = ray_hit->hit.u; - hit->uv[1] = ray_hit->hit.v; - - if(((struct geometry*)hit->prim.shape__)->type == GEOM_MESH) { - w = 1.f - hit->uv[0] - hit->uv[1]; - ASSERT(w <= 1.f); /* This may not occurs */ - if(w < 0.f) { /* Handle precision error */ - if(hit->uv[0] > hit->uv[1]) hit->uv[0] += w; - else hit->uv[1] += w; - w = 0.f; - } - - /* Embree stores on the u and v ray parameters the barycentric coordinates of - * the hit with respect to the second and third triangle vertices, - * respectively. The following code computes the barycentric coordinates of - * the hit for the first and second triangle vertices */ - hit->uv[1] = hit->uv[0]; - hit->uv[0] = w; - - /* In Embree3 the normal orientation is flipped wrt to Star-3D convention */ - #if RTC_VERSION_MAJOR >= 3 - f3_minus(hit->normal, hit->normal); - #endif + enum RTCBuildQuality rtc_quality = RTC_BUILD_QUALITY_MEDIUM; + switch(quality) { + case S3D_ACCEL_STRUCT_QUALITY_LOW: + rtc_quality = RTC_BUILD_QUALITY_LOW; + break; + case S3D_ACCEL_STRUCT_QUALITY_MEDIUM: + rtc_quality = RTC_BUILD_QUALITY_MEDIUM; + break; + case S3D_ACCEL_STRUCT_QUALITY_HIGH: + rtc_quality = RTC_BUILD_QUALITY_HIGH; + break; + default: FATAL("Unreachable code\n"); break; } + return rtc_quality; +} - /* Flip geometric normal with respect to the flip surface flag */ - flip_surface ^= ((struct geometry*)hit->prim.shape__)->flip_surface; - if(flip_surface) f3_minus(hit->normal, hit->normal); +static INLINE int +accel_struct_mask_to_rtc_scene_flags(const int mask) +{ + int rtc_scene_flags = 0; + if(mask & S3D_ACCEL_STRUCT_FLAG_ROBUST) + rtc_scene_flags |= RTC_SCENE_FLAG_ROBUST; + if(mask & S3D_ACCEL_STRUCT_FLAG_DYNAMIC) + rtc_scene_flags |= RTC_SCENE_FLAG_DYNAMIC; + if(mask & S3D_ACCEL_STRUCT_FLAG_COMPACT) + rtc_scene_flags |= RTC_SCENE_FLAG_COMPACT; + return rtc_scene_flags; } static res_T embree_geometry_register (struct s3d_scene_view* scnview, - struct geometry* geom) + struct geometry* geom, + const struct s3d_accel_struct_conf* accel_struct_conf) { - ASSERT(scnview && geom); + enum RTCBuildQuality rtc_build_quality = RTC_BUILD_QUALITY_MEDIUM; + ASSERT(scnview && geom && accel_struct_conf); + + rtc_build_quality = accel_struct_quality_to_rtc_build_quality + (accel_struct_conf->quality); /* Create the Embree geometry if it is not valid */ if(geom->rtc != NULL) { - if(geom->type == GEOM_INSTANCE) { - /* If the geometry is an instance one have to update it if the - * instantiated geometry was updated. Currently, we have no simple way to - * know if the geometry was upd or not so we simply force the update. */ - rtcCommitGeometry(geom->rtc); - scnview->rtc_scn_update = 1; + switch(geom->type) { + case GEOM_MESH: + if(geom->rtc_build_quality != rtc_build_quality) { + /* Update the build quality of the geometry */ + rtcSetGeometryBuildQuality(geom->rtc, rtc_build_quality); + rtcCommitGeometry(geom->rtc); + geom->rtc_build_quality = rtc_build_quality; + scnview->rtc_scn_update = 1; + } + break; + case GEOM_INSTANCE: + /* If the geometry is an instance one have to update it if the + * instantiated geometry was updated. Currently, we have no simple way to + * know if the geometry was upd or not so we simply force the update. */ + rtcCommitGeometry(geom->rtc); + scnview->rtc_scn_update = 1; + break; + case GEOM_SPHERE: /* Do nothing */ break; + default: FATAL("Unreachable code\n"); break; } } else { switch(geom->type) { @@ -273,6 +224,12 @@ embree_geometry_register if(geom->rtc == NULL) return RES_UNKNOWN_ERR; + if(geom->type == GEOM_MESH) { + /* Set the build quality of the geometry */ + rtcSetGeometryBuildQuality(geom->rtc, rtc_build_quality); + geom->rtc_build_quality = rtc_build_quality; + } + /* Set the Star-3D representation of the geometry to the Embree geometry */ rtcSetGeometryUserData(geom->rtc, geom); @@ -385,13 +342,22 @@ embree_geometry_setup_transform } static INLINE res_T -scene_view_setup_embree(struct s3d_scene_view* scnview) +scene_view_setup_embree + (struct s3d_scene_view* scnview, + const struct s3d_accel_struct_conf* accel_struct_conf) { struct htable_geom_iterator it, end; int rtc_outdated = 0; + int rtc_scn_flags = 0; + enum RTCBuildQuality rtc_scn_build_quality = 0; res_T res = RES_OK; ASSERT(scnview); + rtc_scn_flags = accel_struct_mask_to_rtc_scene_flags + (accel_struct_conf->mask); + rtc_scn_build_quality = accel_struct_quality_to_rtc_build_quality + (accel_struct_conf->quality); + /* The rtc_scn could be already allocated since the scene views are cached */ if(!scnview->rtc_scn) { scnview->rtc_scn = rtcNewScene(scnview->scn->dev->rtc); @@ -399,8 +365,22 @@ scene_view_setup_embree(struct s3d_scene_view* scnview) res = rtc_error_to_res_T(rtcGetDeviceError(scnview->scn->dev->rtc)); goto error; } - rtcSetSceneFlags - (scnview->rtc_scn, RTC_SCENE_FLAG_ROBUST | RTC_SCENE_FLAG_DYNAMIC); + rtcSetSceneFlags(scnview->rtc_scn, rtc_scn_flags); + scnview->rtc_scn_flags = rtc_scn_flags; + rtc_outdated = 1; + } + + /* Check if the scene flags were updated */ + if(scnview->rtc_scn_flags != rtc_scn_flags) { + rtcSetSceneFlags(scnview->rtc_scn, rtc_scn_flags); + scnview->rtc_scn_flags = rtc_scn_flags; + rtc_outdated = 1; + } + + /* Check if the build quality was updated */ + if(scnview->rtc_scn_build_quality != rtc_scn_build_quality) { + rtcSetSceneBuildQuality(scnview->rtc_scn, rtc_scn_build_quality); + scnview->rtc_scn_build_quality = rtc_scn_build_quality; rtc_outdated = 1; } @@ -419,7 +399,7 @@ scene_view_setup_embree(struct s3d_scene_view* scnview) rtc_outdated = 1; /* Register the embree geometry */ - res = embree_geometry_register(scnview, geom); + res = embree_geometry_register(scnview, geom, accel_struct_conf); if(res != RES_OK) goto error; /* Flush the embree geometry states */ @@ -550,6 +530,12 @@ scene_view_register_mesh geom->flip_surface = shape->flip_surface; + /* The geometry is updated => recompute the scene AABB */ + if(geom->embree_outdated_mask + & (EMBREE_VERTICES|EMBREE_INDICES|EMBREE_ENABLE)) { + scnview->aabb_update = 1; + } + exit: return res; error: @@ -605,6 +591,16 @@ scene_view_register_sphere geom->embree_outdated_mask |= EMBREE_USER_GEOMETRY; } + if(geom->is_enabled != shape->is_enabled) { + geom->is_enabled = shape->is_enabled; + geom->embree_outdated_mask |= EMBREE_ENABLE; + } + + /* The geometry is updated => recompute the scene AABB */ + if(geom->embree_outdated_mask & (EMBREE_USER_GEOMETRY|EMBREE_ENABLE)) { + scnview->aabb_update = 1; + } + /* Update the filter function. Actually, filter functions are supported for * built-in geometries only. For user defined geometries one has to * explicitly call the filter function in the user defined intersection @@ -618,11 +614,6 @@ scene_view_register_sphere geom->embree_outdated_mask |= EMBREE_USER_GEOMETRY; } - if(geom->is_enabled != shape->is_enabled) { - geom->is_enabled = shape->is_enabled; - geom->embree_outdated_mask |= EMBREE_ENABLE; - } - geom->flip_surface = shape->flip_surface; exit: @@ -702,6 +693,11 @@ scene_view_register_instance geom->embree_outdated_mask |= EMBREE_ENABLE; } + /* The instance transform was updated => recompute the scene AABB */ + if(geom->embree_outdated_mask & (EMBREE_TRANSFORM|EMBREE_ENABLE)) { + scnview->aabb_update = 1; + } + geom->flip_surface = shape->flip_surface; exit: @@ -805,7 +801,7 @@ scene_view_compute_nprims_cdf nprims += 1; break; case GEOM_INSTANCE: - /* The instance CDF was computed during its scnview synchronisation */ + /* The instance CDF was computed during its scnview synchronisation */ len = darray_nprims_cdf_size_get (&geom->data.instance->scnview->nprims_cdf); if(len) { @@ -936,13 +932,13 @@ scene_view_compute_volume static res_T scene_view_sync (struct s3d_scene_view* scnview, - const int mask) + const int mask, + const struct s3d_accel_struct_conf* accel_struct_conf) { struct htable_shape_iterator it, end; res_T res = RES_OK; - ASSERT(scnview); - ASSERT((mask & (S3D_TRACE|S3D_SAMPLE|S3D_GET_PRIMITIVE)) != 0); + ASSERT(scnview && accel_struct_conf); /* Commit the scene shape to the scnview */ htable_shape_begin(&scnview->scn->shapes, &it); @@ -967,11 +963,9 @@ scene_view_sync htable_shape_iterator_next(&it); } - scene_view_compute_scene_aabb(scnview); - /* Setup the scene for the S3D_TRACE scnview */ if((mask & S3D_TRACE) != 0) { - res = scene_view_setup_embree(scnview); + res = scene_view_setup_embree(scnview, accel_struct_conf); if(res != RES_OK) goto error; } /* Setup the scene for the S3D_SAMPLE scnview */ @@ -979,6 +973,29 @@ scene_view_sync res = scene_view_compute_cdf(scnview); if(res != RES_OK) goto error; } + + /* Compute the scene AABB if it is updated */ + if(scnview->aabb_update) { + if((mask & S3D_TRACE) == 0) { + /* Compute from scratch */ + f3_splat(scnview->lower, FLT_MAX); + f3_splat(scnview->upper,-FLT_MAX); + scene_view_compute_scene_aabb(scnview); + } else { + /* Retrieve the scene AABB from Embree since it was already computed to + * build the acceleration data structure */ + struct RTCBounds bounds; + rtcGetSceneBounds(scnview->rtc_scn, &bounds); + scnview->lower[0] = bounds.lower_x; + scnview->lower[1] = bounds.lower_y; + scnview->lower[2] = bounds.lower_z; + scnview->upper[0] = bounds.upper_x; + scnview->upper[1] = bounds.upper_y; + scnview->upper[2] = bounds.upper_z; + } + scnview->aabb_update = 0; + } + /* Setup the scene for the scene_primitive_id/S3D_GET_PRIMITIVE scnview */ res = scene_view_compute_nprims_cdf(scnview, (mask & S3D_GET_PRIMITIVE)!=0); if(res != RES_OK) goto error; @@ -1019,6 +1036,7 @@ scene_view_create(struct s3d_scene* scn, struct s3d_scene_view** out_scnview) f3_splat(scnview->lower, FLT_MAX); f3_splat(scnview->upper,-FLT_MAX); ref_init(&scnview->ref); + scnview->rtc_scn_build_quality = RTC_BUILD_QUALITY_MEDIUM; CLBK_INIT(&scnview->on_shape_detach_cb); CLBK_SETUP(&scnview->on_shape_detach_cb, on_shape_detach, scnview); @@ -1077,15 +1095,14 @@ scene_view_release(ref_T* ref) scene_view_destroy_geometry(scnview, geom); tmp = htable_geom_erase(&scnview->cached_geoms, &shape_id); ASSERT(tmp == 1); + scnview->aabb_update = 1; /* The scene AABB must be reevaluated */ } darray_uint_clear(&scnview->detached_shapes); - /* Clear the scnview data structures excepted the cache of geometries that - * will be used to speed up the future scnview creation */ + /* Clear the scnview data structures excepted the cache of geometries and the + * scene AABB that will be used to speed up the future scnview creation */ darray_fltui_clear(&scnview->cdf); darray_nprims_cdf_clear(&scnview->nprims_cdf); - f3_splat(scnview->lower, FLT_MAX); - f3_splat(scnview->upper,-FLT_MAX); scnview->mask = 0; scnview->rtc_commit = 0; @@ -1104,7 +1121,19 @@ s3d_scene_view_create const int mask, struct s3d_scene_view** out_scnview) { + return s3d_scene_view_create2 + (scn, mask, &S3D_ACCEL_STRUCT_CONF_DEFAULT, out_scnview); +} + +res_T +s3d_scene_view_create2 + (struct s3d_scene* scn, + const int mask, + const struct s3d_accel_struct_conf* cfg, + struct s3d_scene_view** out_scnview) +{ struct s3d_scene_view* scnview = NULL; + const struct s3d_accel_struct_conf* accel_struct_conf = cfg; res_T res = RES_OK; if(!scn || !out_scnview) { @@ -1112,18 +1141,14 @@ s3d_scene_view_create goto error; } - if(!(mask & S3D_TRACE) - && !(mask & S3D_SAMPLE) - && !(mask & S3D_GET_PRIMITIVE)) { - log_error(scn->dev, "%s: no valid scene view mask is defined.\n", FUNC_NAME); - res = RES_BAD_ARG; - goto error; + if(!accel_struct_conf && (mask & S3D_TRACE)) { + accel_struct_conf = &S3D_ACCEL_STRUCT_CONF_DEFAULT; } res = scene_view_create(scn, &scnview); if(res != RES_OK) goto error; - res = scene_view_sync(scnview, mask); + res = scene_view_sync(scnview, mask, accel_struct_conf); if(res != RES_OK) goto error; exit: @@ -1162,120 +1187,6 @@ s3d_scene_view_get_mask(struct s3d_scene_view* scnview, int* mask) } res_T -s3d_scene_view_trace_ray - (struct s3d_scene_view* scnview, - const float org[3], - const float dir[3], - const float range[2], - void* ray_data, - struct s3d_hit* hit) -{ - struct RTCRayHit ray_hit; - struct intersect_context intersect_ctx; - size_t i; - - if(!scnview || !org || !dir || !range || !hit) - return RES_BAD_ARG; - if(!f3_is_normalized(dir)) { - log_error(scnview->scn->dev, - "%s: unnormalized ray direction {%g, %g, %g}.\n", - FUNC_NAME, SPLIT3(dir)); - return RES_BAD_ARG; - } - if(range[0] < 0) { - log_error(scnview->scn->dev, - "%s: invalid ray range [%g, %g] - it must be in [0, INF).\n", - FUNC_NAME, range[0], range[1]); - return RES_BAD_ARG; - } - if((scnview->mask & S3D_TRACE) == 0) { - log_error(scnview->scn->dev, - "%s: the S3D_TRACE flag is not active onto the submitted scene view.\n", - FUNC_NAME); - return RES_BAD_OP; - } - if(range[0] > range[1]) { /* Degenerated range <=> disabled ray */ - *hit = S3D_HIT_NULL; - return RES_OK; - } - - /* Initialise the ray */ - ray_hit.ray.org_x = org[0]; - ray_hit.ray.org_y = org[1]; - ray_hit.ray.org_z = org[2]; - ray_hit.ray.dir_x = dir[0]; - ray_hit.ray.dir_y = dir[1]; - ray_hit.ray.dir_z = dir[2]; - ray_hit.ray.tnear = range[0]; - ray_hit.ray.tfar = range[1]; - ray_hit.ray.time = FLT_MAX; /* Invalid fields */ - ray_hit.ray.mask = UINT_MAX; /* Invalid fields */ - ray_hit.ray.id = UINT_MAX; /* Invalid fields */ - ray_hit.ray.flags = UINT_MAX; /* Invalid fields */ - - /* Initialise the hit */ - ray_hit.hit.geomID = RTC_INVALID_GEOMETRY_ID; - FOR_EACH(i, 0, RTC_MAX_INSTANCE_LEVEL_COUNT) { - ray_hit.hit.instID[i] = RTC_INVALID_GEOMETRY_ID; - } - - /* Initialise the intersect context */ - rtcInitIntersectContext(&intersect_ctx.rtc); - intersect_ctx.ws_org[0] = org[0]; - intersect_ctx.ws_org[1] = org[1]; - intersect_ctx.ws_org[2] = org[2]; - intersect_ctx.ws_dir[0] = dir[0]; - intersect_ctx.ws_dir[1] = dir[1]; - intersect_ctx.ws_dir[2] = dir[2]; - intersect_ctx.scnview = scnview; - intersect_ctx.data = ray_data; - - /* Here we go! */ - rtcIntersect1(scnview->rtc_scn, &intersect_ctx.rtc, &ray_hit); - - hit_setup(scnview, &ray_hit, hit); - return RES_OK; -} - -res_T -s3d_scene_view_trace_rays - (struct s3d_scene_view* scnview, - const size_t nrays, - const int mask, - const float* origins, - const float* directions, - const float* ranges, - void* rays_data, - const size_t sizeof_ray_data, - struct s3d_hit* hits) -{ - size_t iray; - size_t iorg, idir, irange, idata; - size_t org_step, dir_step, range_step, data_step; - res_T res = RES_OK; - - if(!scnview) return RES_BAD_ARG; - if(!nrays) return RES_OK; - - org_step = mask & S3D_RAYS_SINGLE_ORIGIN ? 0 : 3; - dir_step = mask & S3D_RAYS_SINGLE_DIRECTION ? 0 : 3; - range_step = mask & S3D_RAYS_SINGLE_RANGE ? 0 : 2; - data_step = (mask & S3D_RAYS_SINGLE_DATA) || !rays_data ? 0 : sizeof_ray_data; - iorg = idir = irange = idata = 0; - - FOR_EACH(iray, 0, nrays) { - res = s3d_scene_view_trace_ray(scnview, origins+iorg, directions+idir, - ranges+irange, (char*)rays_data+idata, hits+iray); - if(UNLIKELY(res != RES_OK)) break; - iorg += org_step; - idir += dir_step; - irange += range_step; - idata += data_step; - } - return res; -} - -res_T s3d_scene_view_sample (struct s3d_scene_view* scnview, const float u, @@ -1680,37 +1591,3 @@ scene_view_destroy(struct s3d_scene_view* scnview) MEM_RM(scnview->scn->dev->allocator, scnview); } -/* Wrapper between an Embree and a Star-3D filter function */ -void -rtc_hit_filter_wrapper(const struct RTCFilterFunctionNArguments* args) -{ - struct s3d_hit hit; - struct RTCRayHit ray_hit; - struct intersect_context* ctx; - struct geometry* geom; - struct hit_filter* filter; - ASSERT(args && args->N == 1 && args->context && args->valid[0] != 0); - - rtc_rayN_get_ray(args->ray, args->N, 0, &ray_hit.ray); - rtc_hitN_get_hit(args->hit, args->N, 0, &ray_hit.hit); - - ctx = CONTAINER_OF(args->context, struct intersect_context, rtc); - - geom = args->geometryUserPtr; - switch(geom->type) { - case GEOM_MESH: - filter = &geom->data.mesh->filter; - break; - case GEOM_SPHERE: - filter = &geom->data.sphere->filter; - break; - default: FATAL("Unreachable code\n"); break; - } - ASSERT(filter->func); - - hit_setup(ctx->scnview, &ray_hit, &hit); - if(filter->func(&hit, ctx->ws_org, ctx->ws_dir, ctx->data, filter->data)) { - args->valid[0] = 0; - } -} - diff --git a/src/s3d_scene_view_c.h b/src/s3d_scene_view_c.h @@ -88,9 +88,12 @@ struct s3d_scene_view { /* Callback attached to the sig_shape_detach signal of scn */ scene_shape_cb_T on_shape_detach_cb; + int aabb_update; /* Define if the geometry AABB must be updated */ int mask; /* Combination of enum s3d_scene_view_flag */ int rtc_scn_update; /* Define if Embree geometries were deleted/added */ int rtc_commit; /* Define whether or not the Embree scene was committed */ + int rtc_scn_flags; /* Flags used to configure the Embree scene */ + enum RTCBuildQuality rtc_scn_build_quality; /* Build quality of the BVH */ RTCScene rtc_scn; /* Embree scene */ ref_T ref; diff --git a/src/s3d_scene_view_closest_point.c b/src/s3d_scene_view_closest_point.c @@ -0,0 +1,450 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "s3d_device_c.h" +#include "s3d_instance.h" +#include "s3d_geometry.h" +#include "s3d_mesh.h" +#include "s3d_scene_view_c.h" +#include "s3d_sphere.h" + +#include <rsys/float3.h> +#include <rsys/float33.h> + +struct point_query_context { + struct RTCPointQueryContext rtc; + struct s3d_scene_view* scnview; + void* data; /* Per point query defined data */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE float* +closest_point_triangle + (const float p[3], /* Point */ + const float a[3], /* 1st triangle vertex */ + const float b[3], /* 2nd triangle vertex */ + const float c[3], /* 3rd triangle vertex */ + float closest_pt[3], /* Closest position */ + float uv[2]) /* UV of the closest position */ +{ + float ab[3], ac[3], ap[3], bp[3], cp[3]; + float d1, d2, d3, d4, d5, d6; + float va, vb, vc; + float rcp_triangle_area; + float v, w; + ASSERT(p && a && b && c && closest_pt && uv); + + f3_sub(ab, b, a); + f3_sub(ac, c, a); + + /* Check if the closest point is the triangle vertex 'a' */ + f3_sub(ap, p, a); + d1 = f3_dot(ab, ap); + d2 = f3_dot(ac, ap); + if(d1 <= 0.f && d2 <= 0.f) { + uv[0] = 1.f; + uv[1] = 0.f; + return f3_set(closest_pt, a); + } + + /* Check if the closest point is the triangle vertex 'b' */ + f3_sub(bp, p, b); + d3 = f3_dot(ab, bp); + d4 = f3_dot(ac, bp); + if(d3 >= 0.f && d4 <= d3) { + uv[0] = 0.f; + uv[1] = 1.f; + return f3_set(closest_pt, b); + } + + /* Check if the closest point is the triangle vertex 'c' */ + f3_sub(cp, p, c); + d5 = f3_dot(ab, cp); + d6 = f3_dot(ac, cp); + if(d6 >= 0.f && d5 <= d6) { + uv[0] = 0.f; + uv[1] = 0.f; + return f3_set(closest_pt, c); + } + + /* Check if the closest point is on the triangle edge 'ab' */ + vc = d1*d4 - d3*d2; + if(vc <= 0.f && d1 >= 0.f && d3 <= 0.f) { + const float s = d1 / (d1 - d3); + closest_pt[0] = a[0] + s*ab[0]; + closest_pt[1] = a[1] + s*ab[1]; + closest_pt[2] = a[2] + s*ab[2]; + uv[0] = 1.f - s; + uv[1] = s; + return closest_pt; + } + + /* Check if the closest point is on the triangle edge 'ac' */ + vb = d5*d2 - d1*d6; + if(vb <= 0.f && d2 >= 0.f && d6 <= 0.f) { + const float s = d2 / (d2 - d6); + closest_pt[0] = a[0] + s*ac[0]; + closest_pt[1] = a[1] + s*ac[1]; + closest_pt[2] = a[2] + s*ac[2]; + uv[0] = 1.f - s; + uv[1] = 0.f; + return closest_pt; + } + + /* Check if the closest point is on the triangle edge 'bc' */ + va = d3*d6 - d5*d4; + if(va <= 0.f && (d4 - d3) >= 0.f && (d5 - d6) >= 0.f) { + const float s = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + closest_pt[0] = b[0] + s*(c[0] - b[0]); + closest_pt[1] = b[1] + s*(c[1] - b[1]); + closest_pt[2] = b[2] + s*(c[2] - b[2]); + uv[0] = 0.f; + uv[1] = 1.f - s; + return closest_pt; + } + + /* The closest point lies in the triangle: compute its barycentric + * coordinates */ + rcp_triangle_area = 1.f / (va + vb + vc); + v = vb * rcp_triangle_area; + w = vc * rcp_triangle_area; + + /* Save the uv barycentric coordinates */ + uv[0] = 1.f - v - w; + uv[1] = v; + + ASSERT(eq_epsf(uv[0] + uv[1] + w, 1.f, 1.e-4f)); + + /* Use the barycentric coordinates to compute the world space position of the + * closest point onto the triangle */ + closest_pt[0] = a[0] + v*ab[0] + w*ac[0]; + closest_pt[1] = a[1] + v*ab[1] + w*ac[1]; + closest_pt[2] = a[2] + v*ab[2] + w*ac[2]; + return closest_pt; +} + +static bool +closest_point_mesh + (struct RTCPointQueryFunctionArguments* args, + struct geometry* geom, + struct geometry* inst, /* Can be NULL */ + void* query_data) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_hit* out_hit = NULL; + struct hit_filter* filter = NULL; + const uint32_t* ids = NULL; + float closest_point[3]; + float query_pos[3]; + float v0[3], v1[3], v2[3]; + float E0[3], E1[3], Ng[3]; + float vec[3]; + float uv[2]; + float dst; + int flip_surface = 0; + ASSERT(args && geom && geom->type == GEOM_MESH); + ASSERT(args->primID < mesh_get_ntris(geom->data.mesh)); + + /* Fetch triangle indices */ + ids = mesh_get_ids(geom->data.mesh) + args->primID*3/*#indices per triangle*/; + + /* Fetch triangle vertices */ + ASSERT(geom->data.mesh->attribs_type[S3D_POSITION] == S3D_FLOAT3); + f3_set(v0, mesh_get_pos(geom->data.mesh) + ids[0]*3/*#coords per vertex*/); + f3_set(v1, mesh_get_pos(geom->data.mesh) + ids[1]*3/*#coords per vertex*/); + f3_set(v2, mesh_get_pos(geom->data.mesh) + ids[2]*3/*#coords per vertex*/); + + /* Local copy of the query position */ + query_pos[0] = args->query->x; + query_pos[1] = args->query->y; + query_pos[2] = args->query->z; + + if(args->context->instStackSize) { /* The mesh is instantiated */ + const float* transform; + transform = inst->data.instance->transform; + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + ASSERT(inst && inst->type == GEOM_INSTANCE); + ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); + ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); + ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); + ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); + + /* Transform the triangle vertices in world space */ + f3_add(v0, f33_mulf3(v0, transform, v0), transform+9); + f3_add(v1, f33_mulf3(v1, transform, v1), transform+9); + f3_add(v2, f33_mulf3(v2, transform, v2), transform+9); + + flip_surface = inst->flip_surface; + } + + /* Compute the closest point onto the triangle from the submitted point */ + closest_point_triangle(query_pos, v0, v1, v2, closest_point, uv); + + f3_sub(vec, closest_point, query_pos); + dst = f3_len(vec); + if(dst >= args->query->radius) return 0; + + /* Compute the triangle normal in world space */ + f3_sub(E0, v2, v0); + f3_sub(E1, v1, v0); + f3_cross(Ng, E0, E1); + + /* Flip the geometric normal wrt the flip surface flag */ + flip_surface ^= geom->flip_surface; + if(flip_surface) f3_minus(Ng, Ng); + + /* Setup the hit */ + hit.prim.shape__ = geom; + hit.prim.inst__ = inst; + hit.distance = dst; + hit.uv[0] = uv[0]; + hit.uv[1] = uv[1]; + hit.normal[0] = Ng[0]; + hit.normal[1] = Ng[1]; + hit.normal[2] = Ng[2]; + hit.prim.prim_id = args->primID; + hit.prim.geom_id = geom->name; + hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; + hit.prim.scene_prim_id = + hit.prim.prim_id + + geom->scene_prim_id_offset + + (inst ? inst->scene_prim_id_offset : 0); + + /* `vec' is the direction along which the closest point was found. We thus + * submit it to the filter function as the direction corresponding to the + * computed hit */ + filter = &geom->data.mesh->filter; + if(filter->func + && filter->func(&hit, query_pos, vec, query_data, filter->data)) { + return 0; /* This point is filtered. Discard it! */ + } + + /* Update output data */ + out_hit = args->userPtr; + *out_hit = hit; + + /* Shrink the query radius */ + args->query->radius = dst; + + return 1; /* Notify that the query radius was updated */ +} + +static bool +closest_point_sphere + (struct RTCPointQueryFunctionArguments* args, + struct geometry* geom, + struct geometry* inst, + void* query_data) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_hit* out_hit = NULL; + struct hit_filter* filter = NULL; + float query_pos[3]; + float sphere_pos[3]; + float Ng[3]; + float dir[3]; + float uv[2]; + float dst; + float len; + int flip_surface = 0; + ASSERT(args && geom && geom->type == GEOM_SPHERE && args->primID == 0); + + /* Local copy of the query position */ + query_pos[0] = args->query->x; + query_pos[1] = args->query->y; + query_pos[2] = args->query->z; + + f3_set(sphere_pos, geom->data.sphere->pos); + if(args->context->instStackSize) { /* The sphere is instantiated */ + const float* transform; + transform = inst->data.instance->transform; + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + ASSERT(inst && inst->type == GEOM_INSTANCE); + ASSERT(f3_eq_eps(transform+0, args->context->inst2world[0]+0, 1.e-6f)); + ASSERT(f3_eq_eps(transform+3, args->context->inst2world[0]+4, 1.e-6f)); + ASSERT(f3_eq_eps(transform+6, args->context->inst2world[0]+8, 1.e-6f)); + ASSERT(f3_eq_eps(transform+9, args->context->inst2world[0]+12,1.e-6f)); + + /* Transform the sphere position in world space */ + f33_mulf3(sphere_pos, transform, sphere_pos); + f3_add(sphere_pos, transform+9, sphere_pos); + + flip_surface = inst->flip_surface; + } + + /* Compute the distance from the query pos to the sphere center */ + f3_sub(Ng, query_pos, sphere_pos); + len = f3_len(Ng); + + /* Evaluate the distance from the query pos to the sphere surface */ + dst = fabsf(len - geom->data.sphere->radius); + + /* The closest point onto the sphere is outside the query radius */ + if(dst >= args->query->radius) + return 0; + + /* Normalize the hit normal */ + if(len > 0) { + f3_divf(Ng, Ng, len); + } else { + /* The query position is equal to the sphere center. Arbitrarily choose the + * point along the +X axis as the closest point */ + Ng[0] = 1.f; + Ng[1] = 0.f; + Ng[2] = 0.f; + } + + /* Compute the uv of the found point */ + sphere_normal_to_uv(Ng, uv); + + /* Flip the geometric normal wrt the flip surface flag */ + flip_surface ^= geom->flip_surface; + if(flip_surface) f3_minus(Ng, Ng); + + /* Setup the hit */ + hit.prim.shape__ = geom; + hit.prim.inst__ = inst; + hit.distance = dst; + hit.uv[0] = uv[0]; + hit.uv[1] = uv[1]; + hit.normal[0] = Ng[0]; + hit.normal[1] = Ng[1]; + hit.normal[2] = Ng[2]; + hit.prim.prim_id = args->primID; + hit.prim.geom_id = geom->name; + hit.prim.inst_id = inst ? inst->name : S3D_INVALID_ID; + hit.prim.scene_prim_id = + hit.prim.prim_id + + geom->scene_prim_id_offset + + (inst ? inst->scene_prim_id_offset : 0); + + /* Use the reversed geometric normal as the hit direction since it is along + * this vector that the closest point was effectively computed */ + f3_minus(dir, Ng); + filter = &geom->data.sphere->filter; + if(filter->func + && filter->func(&hit, query_pos, dir, query_data, filter->data)) { + return 0; + } + + /* Update output data */ + out_hit = args->userPtr; + *out_hit = hit; + + /* Shrink the query radius */ + args->query->radius = dst; + + return 1; /* Notify that the query radius was updated */ +} + +static bool +closest_point(struct RTCPointQueryFunctionArguments* args) +{ + struct point_query_context* ctx = NULL; + struct geometry* geom = NULL; + struct geometry* inst = NULL; + bool query_radius_is_upd = false; + ASSERT(args); + + ctx = CONTAINER_OF(args->context, struct point_query_context, rtc); + if(args->context->instStackSize == 0) { + geom = scene_view_geometry_from_embree_id + (ctx->scnview, args->geomID); + } else { + ASSERT(args->context->instStackSize == 1); + ASSERT(args->similarityScale == 1); + inst = scene_view_geometry_from_embree_id + (ctx->scnview, args->context->instID[0]); + geom = scene_view_geometry_from_embree_id + (inst->data.instance->scnview, args->geomID); + } + + switch(geom->type) { + case GEOM_MESH: + query_radius_is_upd = closest_point_mesh(args, geom, inst, ctx->data); + break; + case GEOM_SPHERE: + query_radius_is_upd = closest_point_sphere(args, geom, inst, ctx->data); + break; + default: FATAL("Unreachable code\n"); break; + } + return query_radius_is_upd; +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +s3d_scene_view_closest_point + (struct s3d_scene_view* scnview, + const float pos[3], + const float radius, + void* query_data, + struct s3d_hit* hit) +{ + struct RTCPointQuery query; + struct point_query_context query_ctx; + + if(!scnview || !pos || radius <= 0 || !hit) + return RES_BAD_ARG; + if((scnview->mask & S3D_TRACE) == 0) { + log_error(scnview->scn->dev, + "%s: the S3D_TRACE flag is not active onto the submitted scene view.\n", + FUNC_NAME); + return RES_BAD_OP; + } + + *hit = S3D_HIT_NULL; + + /* Initialise the point query */ + query.x = pos[0]; + query.y = pos[1]; + query.z = pos[2]; + query.radius = radius; + query.time = FLT_MAX; /* Invalid fields */ + + /* Initialise the point query context */ + rtcInitPointQueryContext(&query_ctx.rtc); + query_ctx.scnview = scnview; + query_ctx.data = query_data; + + /* Here we go! */ + rtcPointQuery(scnview->rtc_scn, &query, &query_ctx.rtc, closest_point, hit); + + return RES_OK; +} + diff --git a/src/s3d_scene_view_trace_ray.c b/src/s3d_scene_view_trace_ray.c @@ -0,0 +1,302 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "s3d_c.h" +#include "s3d_device_c.h" +#include "s3d_instance.h" +#include "s3d_geometry.h" +#include "s3d_mesh.h" +#include "s3d_sphere.h" +#include "s3d_scene_view_c.h" + +#include <rsys/float33.h> +#include <limits.h> + +struct intersect_context { + struct RTCIntersectContext rtc; + struct s3d_scene_view* scnview; + void* data; /* Per ray user defined data */ + float ws_org[3]; /* World space ray origin */ + float ws_dir[3]; /* World space ray direction */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static INLINE void +hit_setup + (struct s3d_scene_view* scnview, + const struct RTCRayHit* ray_hit, + struct s3d_hit* hit) +{ + float w; + char flip_surface = 0; + + ASSERT(scnview && hit && ray_hit); + + if(ray_hit->hit.geomID == RTC_INVALID_GEOMETRY_ID) { /* No hit */ + *hit = S3D_HIT_NULL; + return; + } + + hit->normal[0] = ray_hit->hit.Ng_x; + hit->normal[1] = ray_hit->hit.Ng_y; + hit->normal[2] = ray_hit->hit.Ng_z; + hit->distance = ray_hit->ray.tfar; + + if(ray_hit->hit.instID[0] == RTC_INVALID_GEOMETRY_ID) { + struct geometry* geom_shape; + geom_shape = scene_view_geometry_from_embree_id(scnview, ray_hit->hit.geomID); + hit->prim.shape__ = geom_shape; + hit->prim.inst__ = NULL; + hit->prim.prim_id = ray_hit->hit.primID; + hit->prim.geom_id = geom_shape->name; + hit->prim.inst_id = S3D_INVALID_ID; + hit->prim.scene_prim_id = /* Compute the "scene space" primitive id */ + hit->prim.prim_id /* Mesh space */ + + geom_shape->scene_prim_id_offset; /* Scene space */ + + } else { /* The hit shape is instantiated */ + /* Retrieve the hit instance */ + struct geometry* geom_inst; + struct geometry* geom_shape; + float transform[9]; + geom_inst = scene_view_geometry_from_embree_id + (scnview, ray_hit->hit.instID[0]); + geom_shape = scene_view_geometry_from_embree_id + (geom_inst->data.instance->scnview, ray_hit->hit.geomID); + hit->prim.shape__ = geom_shape; + hit->prim.inst__ = geom_inst; + hit->prim.prim_id = ray_hit->hit.primID; + hit->prim.geom_id = geom_shape->name; + hit->prim.inst_id = geom_inst->name; + hit->prim.scene_prim_id = /* Compute the "scene space" primitive id */ + hit->prim.prim_id /* Shape space */ + + geom_shape->scene_prim_id_offset /* Inst space */ + + geom_inst->scene_prim_id_offset; /* Scene space */ + + flip_surface = geom_inst->flip_surface; + ASSERT(hit->prim.inst__); + ASSERT(((struct geometry*)hit->prim.inst__)->type == GEOM_INSTANCE); + + /* Transform the normal in world space */ + f33_invtrans(transform, geom_inst->data.instance->transform); + f33_mulf3(hit->normal, transform, hit->normal); + } + ASSERT(hit->prim.shape__); + ASSERT(((struct geometry*)hit->prim.shape__)->type == GEOM_MESH + ||((struct geometry*)hit->prim.shape__)->type == GEOM_SPHERE); + + hit->uv[0] = ray_hit->hit.u; + hit->uv[1] = ray_hit->hit.v; + + if(((struct geometry*)hit->prim.shape__)->type == GEOM_MESH) { + w = 1.f - hit->uv[0] - hit->uv[1]; + ASSERT(w <= 1.f); /* This may not occurs */ + if(w < 0.f) { /* Handle precision error */ + if(hit->uv[0] > hit->uv[1]) hit->uv[0] += w; + else hit->uv[1] += w; + w = 0.f; + } + + /* Embree stores on the u and v ray parameters the barycentric coordinates of + * the hit with respect to the second and third triangle vertices, + * respectively. The following code computes the barycentric coordinates of + * the hit for the first and second triangle vertices */ + hit->uv[1] = hit->uv[0]; + hit->uv[0] = w; + + /* In Embree3 the normal orientation is flipped wrt to Star-3D convention */ + #if RTC_VERSION_MAJOR >= 3 + f3_minus(hit->normal, hit->normal); + #endif + } + + /* Flip geometric normal with respect to the flip surface flag */ + flip_surface ^= ((struct geometry*)hit->prim.shape__)->flip_surface; + if(flip_surface) f3_minus(hit->normal, hit->normal); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +s3d_scene_view_trace_ray + (struct s3d_scene_view* scnview, + const float org[3], + const float dir[3], + const float range[2], + void* ray_data, + struct s3d_hit* hit) +{ + struct RTCRayHit ray_hit; + struct intersect_context intersect_ctx; + size_t i; + + if(!scnview || !org || !dir || !range || !hit) + return RES_BAD_ARG; + if(!f3_is_normalized(dir)) { + log_error(scnview->scn->dev, + "%s: unnormalized ray direction {%g, %g, %g}.\n", + FUNC_NAME, SPLIT3(dir)); + return RES_BAD_ARG; + } + if(range[0] < 0) { + log_error(scnview->scn->dev, + "%s: invalid ray range [%g, %g] - it must be in [0, INF).\n", + FUNC_NAME, range[0], range[1]); + return RES_BAD_ARG; + } + if((scnview->mask & S3D_TRACE) == 0) { + log_error(scnview->scn->dev, + "%s: the S3D_TRACE flag is not active onto the submitted scene view.\n", + FUNC_NAME); + return RES_BAD_OP; + } + if(range[0] > range[1]) { /* Degenerated range <=> disabled ray */ + *hit = S3D_HIT_NULL; + return RES_OK; + } + + /* Initialise the ray */ + ray_hit.ray.org_x = org[0]; + ray_hit.ray.org_y = org[1]; + ray_hit.ray.org_z = org[2]; + ray_hit.ray.dir_x = dir[0]; + ray_hit.ray.dir_y = dir[1]; + ray_hit.ray.dir_z = dir[2]; + ray_hit.ray.tnear = range[0]; + ray_hit.ray.tfar = range[1]; + ray_hit.ray.time = FLT_MAX; /* Invalid fields */ + ray_hit.ray.mask = UINT_MAX; /* Invalid fields */ + ray_hit.ray.id = UINT_MAX; /* Invalid fields */ + ray_hit.ray.flags = UINT_MAX; /* Invalid fields */ + + /* Initialise the hit */ + ray_hit.hit.geomID = RTC_INVALID_GEOMETRY_ID; + FOR_EACH(i, 0, RTC_MAX_INSTANCE_LEVEL_COUNT) { + ray_hit.hit.instID[i] = RTC_INVALID_GEOMETRY_ID; + } + + /* Initialise the intersect context */ + rtcInitIntersectContext(&intersect_ctx.rtc); + intersect_ctx.ws_org[0] = org[0]; + intersect_ctx.ws_org[1] = org[1]; + intersect_ctx.ws_org[2] = org[2]; + intersect_ctx.ws_dir[0] = dir[0]; + intersect_ctx.ws_dir[1] = dir[1]; + intersect_ctx.ws_dir[2] = dir[2]; + intersect_ctx.scnview = scnview; + intersect_ctx.data = ray_data; + + /* Here we go! */ + rtcIntersect1(scnview->rtc_scn, &intersect_ctx.rtc, &ray_hit); + + hit_setup(scnview, &ray_hit, hit); + return RES_OK; +} + +res_T +s3d_scene_view_trace_rays + (struct s3d_scene_view* scnview, + const size_t nrays, + const int mask, + const float* origins, + const float* directions, + const float* ranges, + void* rays_data, + const size_t sizeof_ray_data, + struct s3d_hit* hits) +{ + size_t iray; + size_t iorg, idir, irange, idata; + size_t org_step, dir_step, range_step, data_step; + res_T res = RES_OK; + + if(!scnview) return RES_BAD_ARG; + if(!nrays) return RES_OK; + + org_step = mask & S3D_RAYS_SINGLE_ORIGIN ? 0 : 3; + dir_step = mask & S3D_RAYS_SINGLE_DIRECTION ? 0 : 3; + range_step = mask & S3D_RAYS_SINGLE_RANGE ? 0 : 2; + data_step = (mask & S3D_RAYS_SINGLE_DATA) || !rays_data ? 0 : sizeof_ray_data; + iorg = idir = irange = idata = 0; + + FOR_EACH(iray, 0, nrays) { + res = s3d_scene_view_trace_ray(scnview, origins+iorg, directions+idir, + ranges+irange, (char*)rays_data+idata, hits+iray); + if(UNLIKELY(res != RES_OK)) break; + iorg += org_step; + idir += dir_step; + irange += range_step; + idata += data_step; + } + return res; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +/* Wrapper between an Embree and a Star-3D filter function */ +void +rtc_hit_filter_wrapper(const struct RTCFilterFunctionNArguments* args) +{ + struct s3d_hit hit; + struct RTCRayHit ray_hit; + struct intersect_context* ctx; + struct geometry* geom; + struct hit_filter* filter; + ASSERT(args && args->N == 1 && args->context && args->valid[0] != 0); + + rtc_rayN_get_ray(args->ray, args->N, 0, &ray_hit.ray); + rtc_hitN_get_hit(args->hit, args->N, 0, &ray_hit.hit); + + ctx = CONTAINER_OF(args->context, struct intersect_context, rtc); + + geom = args->geometryUserPtr; + switch(geom->type) { + case GEOM_MESH: + filter = &geom->data.mesh->filter; + break; + case GEOM_SPHERE: + filter = &geom->data.sphere->filter; + break; + default: FATAL("Unreachable code\n"); break; + } + ASSERT(filter->func); + + hit_setup(ctx->scnview, &ray_hit, &hit); + if(filter->func(&hit, ctx->ws_org, ctx->ws_dir, ctx->data, filter->data)) { + args->valid[0] = 0; + } +} diff --git a/src/s3d_sphere.h b/src/s3d_sphere.h @@ -100,4 +100,26 @@ sphere_compute_volume(const struct sphere* sphere) return (float)(4*PI*r*r*r/3); } +static FINLINE void +sphere_normal_to_uv(const float normal[3], float uv[2]) +{ + float u, v, cos_theta; + ASSERT(normal && uv && f3_is_normalized(normal)); + + cos_theta = normal[2]; + + v = (1.f - cos_theta) * 0.5f; + if(absf(cos_theta) == 1) { + u = 0; + } else if(eq_epsf(normal[0], 0.f, 1.e-6f)) { + u = normal[1] > 0 ? 0.25f : 0.75f; + } else { + double phi = atan2f(normal[1], normal[0]); /* phi in [-PI, PI] */ + if(phi < 0) phi = 2*PI + phi; /* phi in [0, 2PI] */ + u = (float)(phi / (2*PI)); + } + uv[0] = u; + uv[1] = v; +} + #endif /* S3D_SPHERE_H */ diff --git a/src/test_s3d_accel_struct_conf.c b/src/test_s3d_accel_struct_conf.c @@ -0,0 +1,252 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "test_s3d_utils.h" + +#include <rsys/clock_time.h> +#include <rsys/math.h> +#include <string.h> + +/******************************************************************************* + * Mesh functions and data structure + ******************************************************************************/ +struct mesh { + double* pos; + size_t* ids; + size_t nverts; + size_t ntris; + struct mem_allocator* allocator; +}; + +static void +mesh_init_sphere + (struct mesh* sphere, + struct mem_allocator* allocator, + const size_t nthetas) +{ + const size_t nphis = (size_t)(((double)nthetas + 0.5) * 0.5); + const double step_theta = 2*PI / (double)nthetas; + const double step_phi = PI / (double)nphis; + size_t itheta, iphi; + size_t i; + + CHK(sphere && allocator && nthetas); + memset(sphere, 0, sizeof(*sphere)); + + sphere->nverts = nthetas * (nphis-1)/*#contour verts*/ + 2 /*polar verts*/; + sphere->ntris = 2*nthetas * (nphis-2)/*#contour tris*/ + 2*nthetas/*#polar tris*/; + sphere->allocator = allocator; + + CHK(sphere->pos = MEM_CALLOC(allocator, sphere->nverts, sizeof(double[3]))); + CHK(sphere->ids = MEM_CALLOC(allocator, sphere->ntris, sizeof(size_t[3]))); + + /* Build the contour vertices */ + i = 0; + FOR_EACH(itheta, 0, nthetas) { + const double theta = -PI + (double)itheta * step_theta; + const double cos_theta = cos(theta); + const double sin_theta = sin(theta); + FOR_EACH(iphi, 0, nphis-1) { + const double phi = -PI*0.5 + (double)(iphi + 1) * step_phi; + const double cos_phi = cos(phi); + const double sin_phi = sin(phi); + sphere->pos[i++] = cos_phi * cos_theta; + sphere->pos[i++] = cos_phi * sin_theta; + sphere->pos[i++] = sin_phi; + } + } + /* polar vertices */ + sphere->pos[i++] = 0.0; sphere->pos[i++] = 0.0; sphere->pos[i++] =-1.0; + sphere->pos[i++] = 0.0; sphere->pos[i++] = 0.0; sphere->pos[i++] = 1.0; + CHK(i == sphere->nverts*3); + + /* Define the indices of the contour primitives */ + i = 0; + FOR_EACH(itheta, 0, nthetas) { + const size_t itheta0 = itheta * (nphis - 1); + const size_t itheta1 = ((itheta + 1) % nthetas) * (nphis - 1); + FOR_EACH(iphi, 0, nphis-2) { + const size_t iphi0 = iphi + 0; + const size_t iphi1 = iphi + 1; + sphere->ids[i++] = itheta0 + iphi0; /* First triangle */ + sphere->ids[i++] = itheta0 + iphi1; + sphere->ids[i++] = itheta1 + iphi0; + sphere->ids[i++] = itheta1 + iphi0; /* Second triangle */ + sphere->ids[i++] = itheta0 + iphi1; + sphere->ids[i++] = itheta1 + iphi1; + } + } + /* Define the indices of the polar primitives */ + FOR_EACH(itheta, 0, nthetas) { + const size_t itheta0 = itheta * (nphis - 1); + const size_t itheta1 = ((itheta + 1) % nthetas) * (nphis - 1); + sphere->ids[i++] = nthetas * (nphis - 1); + sphere->ids[i++] = itheta0; + sphere->ids[i++] = itheta1; + sphere->ids[i++] = nthetas * (nphis - 1) + 1; + sphere->ids[i++] = itheta1 + (nphis - 2); + sphere->ids[i++] = itheta0 + (nphis - 2); + } + CHK(i == sphere->ntris*3); +} + +static void +mesh_release(struct mesh* mesh) +{ + CHK(mesh); + MEM_RM(mesh->allocator, mesh->pos); + MEM_RM(mesh->allocator, mesh->ids); +} + +static INLINE void +mesh_dump(const struct mesh* mesh, FILE* stream) +{ + size_t i; + CHK(mesh && stream); + FOR_EACH(i, 0, mesh->nverts) { + fprintf(stream, "v %g %g %g\n", + mesh->pos[i*3+0], + mesh->pos[i*3+1], + mesh->pos[i*3+2]); + } + FOR_EACH(i, 0, mesh->ntris) { + fprintf(stream, "f %lu %lu %lu\n", + (unsigned long)mesh->ids[i*3+0]+1, + (unsigned long)mesh->ids[i*3+1]+1, + (unsigned long)mesh->ids[i*3+2]+1); + } +} + +static void +mesh_get_pos(const unsigned ivert, float pos[3], void* ctx) +{ + const struct mesh* mesh = ctx; + CHK(pos && ctx && ivert < mesh->nverts); + pos[0] = (float)mesh->pos[ivert*3+0]; + pos[1] = (float)mesh->pos[ivert*3+1]; + pos[2] = (float)mesh->pos[ivert*3+2]; +} + +static void +mesh_get_tri(const unsigned itri, unsigned ids[3], void* ctx) +{ + const struct mesh* mesh = ctx; + CHK(ids && ctx && itri < mesh->ntris); + ids[0] = (unsigned)mesh->ids[itri*3+0]; + ids[1] = (unsigned)mesh->ids[itri*3+1]; + ids[2] = (unsigned)mesh->ids[itri*3+2]; +} + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +time_scene_view_creation + (struct s3d_scene* scn, + const struct s3d_accel_struct_conf* cfg, + const char* string) +{ + char dump[128]; + struct time t0, t1; + struct s3d_scene_view* view; + CHK(scn); + + time_current(&t0); + CHK(s3d_scene_view_create2(scn, S3D_TRACE, cfg, &view) == RES_OK); + time_sub(&t0, time_current(&t1), &t0); + time_dump(&t0, TIME_ALL, NULL, dump, sizeof(dump)); + printf("%s: %s\n", string, dump); + CHK(s3d_scene_view_ref_put(view) == RES_OK); +} + +/******************************************************************************* + * Main test function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct mesh sphere; + struct s3d_device* dev; + struct s3d_shape* shape; + struct s3d_scene* scn; + struct s3d_scene_view* view; + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_accel_struct_conf cfg = S3D_ACCEL_STRUCT_CONF_DEFAULT; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + + mesh_init_sphere(&sphere, &allocator, 256); + /*mesh_dump(&sphere, stdout);*/ + + CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK); + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &shape) == RES_OK); + CHK(s3d_scene_attach_shape(scn, shape) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = mesh_get_pos; + CHK(s3d_mesh_setup_indexed_vertices(shape, (unsigned)sphere.ntris, mesh_get_tri, + (unsigned)sphere.nverts, &vdata, 1, &sphere) == RES_OK); + + CHK(s3d_scene_view_create2(NULL, S3D_TRACE, NULL, &view) == RES_BAD_ARG); + CHK(s3d_scene_view_create2(scn, S3D_TRACE, NULL, NULL) == RES_BAD_ARG); + + time_scene_view_creation(scn, NULL, "All default"); + + cfg.quality = S3D_ACCEL_STRUCT_QUALITY_LOW; + cfg.mask = S3D_ACCEL_STRUCT_FLAG_ROBUST | S3D_ACCEL_STRUCT_FLAG_DYNAMIC; + time_scene_view_creation(scn, &cfg, "Low quality, robust & dynamic"); + + cfg.quality = S3D_ACCEL_STRUCT_QUALITY_MEDIUM; + cfg.mask = S3D_ACCEL_STRUCT_FLAG_COMPACT; + time_scene_view_creation(scn, &cfg, "Medium quality, compact"); + + cfg.quality = S3D_ACCEL_STRUCT_QUALITY_HIGH; + cfg.mask = S3D_ACCEL_STRUCT_FLAG_ROBUST | S3D_ACCEL_STRUCT_FLAG_COMPACT; + time_scene_view_creation(scn, &cfg, "High quality, compact & robust"); + + CHK(s3d_shape_ref_put(shape) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_device_ref_put(dev) == RES_OK); + + mesh_release(&sphere); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} + diff --git a/src/test_s3d_closest_point.c b/src/test_s3d_closest_point.c @@ -0,0 +1,959 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "test_s3d_cbox.h" +#include "test_s3d_utils.h" + +#include <rsys/float3.h> +#include <limits.h> + +#define ON_EDGE_EPSILON 1.e-4f +#define POSITION_EPSILON 1.e-3f + +struct closest_pt { + float pos[3]; + float normal[3]; + float dst; + unsigned iprim; + unsigned igeom; + unsigned iinst; +}; + +#define CLOSEST_PT_NULL__ { \ + {0,0,0}, \ + {0,0,0}, \ + FLT_MAX, \ + S3D_INVALID_ID, \ + S3D_INVALID_ID, \ + S3D_INVALID_ID \ +} + +static const struct closest_pt CLOSEST_PT_NULL = CLOSEST_PT_NULL__; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +/* Function that computes the point onto the triangle that ensures the minimum + * distance between the submitted `pos' and the triangle. Use this routine to + * cross check the result of the s3d_scene_view_closest_point function that + * internally relies on a more efficient implementation */ +static float* +closest_point_triangle + (const float p[3], /* Input pos */ + const float a[3], /* 1st triangle vertex */ + const float b[3], /* 2nd triangle vertex */ + const float c[3], /* 3rd triangle vertex */ + float pt[3]) /* Closest point of pos onto the triangle */ +{ + float N[3]; /* Triangle normal */ + float Nab[3], Nbc[3], Nca[3]; /* Edge normals */ + float ab[3], ac[3], bc[3]; + float ap[3], bp[3], cp[3]; + float d1, d2, d3, d4, d5, d6, d; + CHK(p && a && b && c && pt); + + f3_normalize(ab, f3_sub(ab, b, a)); + f3_normalize(ac, f3_sub(ac, c, a)); + f3_normalize(bc, f3_sub(bc, c, b)); + + /* Compute the triangle normal */ + f3_cross(N, ac, ab); + + /* Check if the nearest point is the 1st triangle vertex */ + f3_sub(ap, p, a); + d1 = f3_dot(ab, ap); + d2 = f3_dot(ac, ap); + if(d1 <= 0 && d2 <= 0) return f3_set(pt, a); + + /* Check if the nearest point is the 2nd triangle vertex */ + f3_sub(bp, p, b); + d3 = f3_dot(bc, bp); + d4 =-f3_dot(ab, bp); + if(d3 <= 0 && d4 <= 0) return f3_set(pt, b); + + /* Check if the nearest point is the 3rd triangle vertex */ + f3_sub(cp, p, c); + d5 =-f3_dot(ac, cp); + d6 =-f3_dot(bc, cp); + if(d5 <= 0 && d6 <= 0) return f3_set(pt, c); + + /* Check if the nearest point is on the 1st triangle edge */ + f3_normalize(Nbc, f3_cross(Nab, ab, N)); + if(f3_dot(Nab, ap) <= 0) { + return f3_add(pt, a, f3_mulf(pt, ab, d1)); + } + + /* Check if the nearest point is on the 2nd triangle edge */ + f3_normalize(Nbc, f3_cross(Nbc, bc, N)); + if(f3_dot(Nbc, bp) <= 0) { + return f3_add(pt, b, f3_mulf(pt, bc, d3)); + } + + /* Check if the nearest point is on the 3rd triangle edge */ + f3_normalize(Nca, f3_cross(Nca, ac, N)); + f3_minus(Nca, Nca); + if(f3_dot(Nca, cp) <= 0) { + return f3_add(pt, c, f3_mulf(pt, ac,-d5)); + } + + /* The nearest point is in the triangle */ + f3_normalize(N, N); + d = f3_dot(N, ap); + return f3_add(pt, p, f3_mulf(pt, N, -d)); +} + +static void +closest_point_mesh + (const float pos[3], + const float* verts, + const unsigned* ids, + const unsigned ntris, + const unsigned geom_id, + const unsigned inst_id, + struct closest_pt* pt) +{ + unsigned itri; + CHK(pos && verts && ids && pt); + + *pt = CLOSEST_PT_NULL; + pt->igeom = geom_id; + pt->iinst = inst_id; + pt->dst = FLT_MAX; + + /* Find the closest point on the mesh */ + FOR_EACH(itri, 0, ntris) { + float v0[3]; + float v1[3]; + float v2[3]; + float closest_pt[3]; + float vec[3]; + float dst; + + f3_set(v0, verts+ids[itri*3+0]*3); + f3_set(v1, verts+ids[itri*3+1]*3); + f3_set(v2, verts+ids[itri*3+2]*3); + + closest_point_triangle(pos, v0, v1, v2, closest_pt); + dst = f3_len(f3_sub(vec, closest_pt, pos)); + + if(dst < pt->dst) { + float E0[3], E1[3]; + f3_set(pt->pos, closest_pt); + pt->dst = dst; + pt->iprim = itri; + f3_sub(E0, v1, v0); + f3_sub(E1, v2, v0); + f3_cross(pt->normal, E1, E0); + f3_normalize(pt->normal, pt->normal); + } + } +} + +static void +closest_point_sphere + (const float pos[3], + const float sphere_org[3], + const float sphere_radius, + const unsigned geom_id, + const unsigned inst_id, + struct closest_pt* pt) +{ + float vec[3]; + float len; + CHK(pos && sphere_org && sphere_radius > 0 && pt); + + f3_sub(vec, pos, sphere_org); + len = f3_normalize(vec, vec); + CHK(len > 0); + + pt->dst = (float)fabs(len - sphere_radius); + f3_set(pt->normal, vec); + f3_add(pt->pos, sphere_org, f3_mulf(pt->pos, vec, sphere_radius)); + pt->iprim = 0; + pt->igeom = geom_id; + pt->iinst = inst_id; +} + +/* Check that `hit' roughly lies on an edge. */ +static int +hit_on_edge(const struct s3d_hit* hit) +{ + struct s3d_attrib v0, v1, v2, pos; + float E0[3], E1[3], N[3]; + float tri_2area; + float hit_2area0; + float hit_2area1; + float hit_2area2; + float hit_pos[3]; + + CHK(hit && !S3D_HIT_NONE(hit)); + + /* Retrieve the triangle vertices */ + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 0, S3D_POSITION, &v0)==RES_OK); + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 1, S3D_POSITION, &v1)==RES_OK); + CHK(s3d_triangle_get_vertex_attrib(&hit->prim, 2, S3D_POSITION, &v2)==RES_OK); + + /* Compute the triangle area * 2 */ + f3_sub(E0, v1.value, v0.value); + f3_sub(E1, v2.value, v0.value); + tri_2area = f3_len(f3_cross(N, E0, E1)); + + /* Compute the hit position */ + CHK(s3d_primitive_get_attrib(&hit->prim, S3D_POSITION, hit->uv, &pos) == RES_OK); + f3_set(hit_pos, pos.value); + + /* Compute areas */ + f3_sub(E0, v0.value, hit_pos); + f3_sub(E1, v1.value, hit_pos); + hit_2area0 = f3_len(f3_cross(N, E0, E1)); + f3_sub(E0, v1.value, hit_pos); + f3_sub(E1, v2.value, hit_pos); + hit_2area1 = f3_len(f3_cross(N, E0, E1)); + f3_sub(E0, v2.value, hit_pos); + f3_sub(E1, v0.value, hit_pos); + hit_2area2 = f3_len(f3_cross(N, E0, E1)); + + if(hit_2area0 / tri_2area < ON_EDGE_EPSILON + || hit_2area1 / tri_2area < ON_EDGE_EPSILON + || hit_2area2 / tri_2area < ON_EDGE_EPSILON) + return 1; + + return 0; +} + +static void +check_closest_point + (const struct s3d_hit* hit, + const struct closest_pt* pt, + const int hit_triangle) /* Define if `hit' lies on a triangle */ +{ + struct s3d_attrib attr; + float N[3]; + + CHK(hit && pt); + CHK(!S3D_HIT_NONE(hit)); + + CHK(s3d_primitive_get_attrib + (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK); + f3_normalize(N, hit->normal); + + if(!hit_triangle || !hit_on_edge(hit)) { + CHK(hit->prim.prim_id == pt->iprim); + } + + if(hit->prim.prim_id == pt->iprim + && hit->prim.geom_id == pt->igeom + && hit->prim.inst_id == pt->iinst) { + /* Due to numerical inaccuracies and/or the arbitrary order in which + * primitives are treated, 2 points on different primitive can have the + * same distance from the query position while their respective + * coordinates are not equal wrt POSITION_EPSILON. To avoid wrong + * assertion, we thus check the position returned by Star-3D against the + * manually computed position only if these positions lies on the same + * primitive */ + CHK(f3_eq_eps(pt->pos, attr.value, POSITION_EPSILON)); + CHK(f3_eq_eps(pt->normal, N, 1.e-4f)); + } + CHK(eq_epsf(hit->distance, pt->dst, 1.e-3f)); +} + +/******************************************************************************* + * Cornell box and sphere test + ******************************************************************************/ +struct instance { + float translation[3]; + unsigned id; +}; + +static void +check_closest_point_cbox_sphere + (const float pos[3], + const float sphere_org[3], + const float sphere_radius, + const unsigned walls_id, + const unsigned sphere_id, + const struct instance* instances, + const size_t ninstances, + struct s3d_hit* hit) +{ + struct closest_pt pt_walls = CLOSEST_PT_NULL; + struct closest_pt pt_sphere = CLOSEST_PT_NULL; + const struct closest_pt* pt = NULL; + CHK(pos && hit); + + if(!ninstances) { + closest_point_mesh(pos, cbox_walls, cbox_walls_ids, cbox_walls_ntris, + walls_id, S3D_INVALID_ID, &pt_walls); + closest_point_sphere(pos, sphere_org, sphere_radius, sphere_id, + S3D_INVALID_ID, &pt_sphere); + } else { + size_t iinst; + + pt_walls.dst = FLT_MAX; + FOR_EACH(iinst, 0, ninstances) { + struct closest_pt pt_walls_tmp; + struct closest_pt pt_sphere_tmp; + float pos_instance_space[3]; + + /* Transform query position in instance space */ + f3_sub(pos_instance_space, pos, instances[iinst].translation); + + closest_point_mesh(pos_instance_space, cbox_walls, cbox_walls_ids, + cbox_walls_ntris, walls_id, instances[iinst].id, &pt_walls_tmp); + closest_point_sphere(pos_instance_space, sphere_org, sphere_radius, + sphere_id, instances[iinst].id, &pt_sphere_tmp); + + if(pt_walls_tmp.dst < pt_walls.dst) { + pt_walls = pt_walls_tmp; + /* Transform query closest point in world space */ + f3_add(pt_walls.pos, pt_walls.pos, instances[iinst].translation); + } + if(pt_sphere_tmp.dst < pt_sphere.dst) { + pt_sphere = pt_sphere_tmp; + /* Transform query closest point in world space */ + f3_add(pt_sphere.pos, pt_sphere.pos, instances[iinst].translation); + } + } + } + + if(pt_walls.dst< pt_sphere.dst) { + pt = &pt_walls; + } else { + pt = &pt_sphere; + } + + check_closest_point(hit, pt, hit->prim.geom_id == walls_id); +} + +static void +test_cbox_sphere(struct s3d_device* dev) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_scene* scn = NULL; + struct s3d_shape* walls = NULL; + struct s3d_shape* sphere = NULL; + struct s3d_shape* inst0 = NULL; + struct s3d_shape* inst1 = NULL; + struct s3d_scene_view* scnview = NULL; + struct instance instances[2]; + struct cbox_desc cbox_desc; + size_t i; + float low[3], upp[3], mid[3], sz[3]; + float pos[3]; + float sphere_org[3]; + float sphere_radius; + unsigned walls_id, sphere_id; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + + /* Setup the cornell box walls */ + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = cbox_get_position; + cbox_desc.vertices = cbox_walls; + cbox_desc.indices = cbox_walls_ids; + CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK); + CHK(s3d_shape_get_id(walls, &walls_id) == RES_OK); + CHK(s3d_scene_attach_shape(scn, walls) == RES_OK); + CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids, + cbox_walls_nverts, &vdata, 1, &cbox_desc) == RES_OK); + + /* Compute the Cornell box AABB */ + CHK(s3d_scene_view_create(scn, S3D_GET_PRIMITIVE, &scnview) == RES_OK); + CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK); + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); + + /* Setup the sphere at the center of the cornell box */ + f3_mulf(mid, f3_add(mid, low, upp), 0.5f); + f3_sub(sz, upp, low); + f3_set(sphere_org, mid); + sphere_radius = MMIN(MMIN(sz[0], sz[1]), sz[2]) * 0.125f; /* 1/8 of the box */ + CHK(s3d_shape_create_sphere(dev, &sphere) == RES_OK); + CHK(s3d_shape_get_id(sphere, &sphere_id) == RES_OK); + CHK(s3d_scene_attach_shape(scn, sphere) == RES_OK); + CHK(s3d_sphere_setup(sphere, sphere_org, sphere_radius) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + + /* Check point query on the scene */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the scene AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK); + check_closest_point_cbox_sphere(pos, sphere_org, sphere_radius, walls_id, + sphere_id, NULL, 0, &hit); + } + + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); + + /* Instantiate the cbox sphere scene */ + CHK(s3d_scene_instantiate(scn, &inst0) == RES_OK); + CHK(s3d_scene_instantiate(scn, &inst1) == RES_OK); + CHK(s3d_shape_get_id(inst0, &instances[0].id) == RES_OK); + CHK(s3d_shape_get_id(inst1, &instances[1].id) == RES_OK); + f3_mulf(instances[0].translation, sz, 0.5f); + CHK(s3d_instance_translate + (inst0, S3D_WORLD_TRANSFORM, instances[0].translation) == RES_OK); + f3_mulf(instances[1].translation, sz,-0.5f); + CHK(s3d_instance_translate + (inst1, S3D_WORLD_TRANSFORM, instances[1].translation) == RES_OK); + + /* Create a new scene with instantiated cbox sphere scenes */ + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_scene_attach_shape(scn, inst0) == RES_OK); + CHK(s3d_scene_attach_shape(scn, inst1) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK); + f3_mulf(mid, f3_add(mid, low, upp), 0.5f); + + /* Check point query on instances */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the scene AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK); + check_closest_point_cbox_sphere(pos, sphere_org, sphere_radius, walls_id, + sphere_id, instances, 2/*#instances*/, &hit); + } + + /* Clean up */ + CHK(s3d_shape_ref_put(inst0) == RES_OK); + CHK(s3d_shape_ref_put(inst1) == RES_OK); + CHK(s3d_shape_ref_put(walls) == RES_OK); + CHK(s3d_shape_ref_put(sphere) == RES_OK); + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Sphere test + ******************************************************************************/ +struct sphere_filter_data { + float query_pos[3]; +}; + +static int +sphere_filter + (const struct s3d_hit* hit, + const float org[3], + const float dir[3], + void* query_data, + void* filter_data) +{ + struct sphere_filter_data* data = query_data; + struct s3d_attrib attr; + float pos[3]; + float vec[3]; + + CHK(hit && org && dir && !S3D_HIT_NONE(hit)); + CHK((intptr_t)filter_data == (intptr_t)0xDECAFBAD); + CHK(f3_normalize(vec, dir) != 0); + + f3_add(pos, org, f3_mulf(pos, vec, hit->distance)); + CHK(s3d_primitive_get_attrib + (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK); + CHK(f3_eq_eps(attr.value, pos, POSITION_EPSILON)); + + CHK(f3_eq_eps(data->query_pos, org, POSITION_EPSILON)); + + return 1; +} + +static void +test_sphere(struct s3d_device* dev) +{ + struct s3d_attrib attr; + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_shape* sphere = NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene_view* scnview = NULL; + struct sphere_filter_data filter_data; + void* ptr = (void*)((intptr_t)0xDECAFBAD); + size_t i; + float sphere_pos[3]; + float query_pos[3]; + float sphere_radius; + float pos[3]; + float dir[3]; + unsigned sphere_id; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_sphere(dev, &sphere) == RES_OK); + CHK(s3d_shape_get_id(sphere, &sphere_id) == RES_OK); + CHK(s3d_scene_attach_shape(scn, sphere) == RES_OK); + + f3_splat(sphere_pos, 1); + sphere_radius = 2; + f3_set(query_pos, sphere_pos); + CHK(s3d_sphere_setup(sphere, query_pos, sphere_radius) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + + /* Check a closest point query exactly at the center of the sphere */ + CHK(s3d_scene_view_closest_point + (scnview, sphere_pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK); + + f3_normalize(dir, f3_sub(dir, attr.value, query_pos)); + f3_add(pos, attr.value, f3_mulf(pos, dir, -hit.distance)); + CHK(hit.distance == sphere_radius); + CHK(f3_eq_eps(pos, sphere_pos, POSITION_EPSILON)); + + /* Check the exclusive bound of the search radius */ + CHK(s3d_scene_view_closest_point + (scnview, sphere_pos, sphere_radius, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + + /* Check closest point query on a sphere */ + FOR_EACH(i, 0, 10000) { + struct closest_pt pt; + float Ng[3]; + query_pos[0] = sphere_pos[0] + (rand_canonic() * 2 - 1) * sphere_radius; + query_pos[1] = sphere_pos[1] + (rand_canonic() * 2 - 1) * sphere_radius; + query_pos[2] = sphere_pos[2] + (rand_canonic() * 2 - 1) * sphere_radius; + + CHK(s3d_scene_view_closest_point + (scnview, query_pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK); + + /* Cross check the closest point query result */ + closest_point_sphere(query_pos, sphere_pos, sphere_radius, + sphere_id, S3D_INVALID_ID, &pt); + + f3_normalize(Ng, hit.normal); + + CHK(pt.dst == hit.distance); + CHK(pt.iprim == hit.prim.prim_id); + CHK(pt.igeom == hit.prim.geom_id); + CHK(pt.iinst == hit.prim.inst_id); + CHK(f3_eq_eps(pt.pos, attr.value, POSITION_EPSILON)); + CHK(f3_eq_eps(pt.normal, Ng, 1.e-4f)); + + /* Check search radius exclusivity */ + CHK(s3d_scene_view_closest_point + (scnview, query_pos, hit.distance, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + hit.distance = nextafterf(hit.distance, 0.f); + CHK(s3d_scene_view_closest_point + (scnview, query_pos, hit.distance, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + } + + /* Check the filtering function */ + CHK(s3d_sphere_set_hit_filter_function(sphere, sphere_filter, ptr) == RES_OK); + + f3_splat(query_pos, 10); + f3_set(filter_data.query_pos, query_pos); + CHK(s3d_scene_view_closest_point + (scnview, query_pos, (float)INF, &filter_data, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + CHK(s3d_scene_view_closest_point + (scnview, query_pos, (float)INF, &filter_data, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + + CHK(s3d_shape_ref_put(sphere) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); +} + +/******************************************************************************* + * Cornell box test + ******************************************************************************/ +enum cbox_geom { + CBOX_WALLS, + CBOX_TALL_BLOCK, + CBOX_SHORT_BLOCK, + CBOX_GEOMS_COUNT__ +}; + +struct cbox_filter_data { + float query_pos[3]; + unsigned geom_to_filter[3]; +}; + +static int +cbox_filter + (const struct s3d_hit* hit, + const float org[3], + const float dir[3], + void* query_data, + void* filter_data) +{ + struct cbox_filter_data* data = query_data; + struct s3d_attrib attr; + float pos[3]; + float vec[3]; + + CHK(hit && org && dir && !S3D_HIT_NONE(hit)); + CHK((intptr_t)filter_data == (intptr_t)0xDECAFBAD); + CHK(f3_normalize(vec, dir) != 0); + + f3_add(pos, org, f3_mulf(pos, vec, hit->distance)); + CHK(s3d_primitive_get_attrib + (&hit->prim, S3D_POSITION, hit->uv, &attr) == RES_OK); + CHK(f3_eq_eps(attr.value, pos, POSITION_EPSILON)); + + if(!query_data) return 0; + + CHK(f3_eq_eps(data->query_pos, org, POSITION_EPSILON)); + + return data->geom_to_filter[0] == hit->prim.geom_id + || data->geom_to_filter[1] == hit->prim.geom_id + || data->geom_to_filter[2] == hit->prim.geom_id; +} + +static void +check_closest_point_cbox + (const float pos[3], + const unsigned geom_id[3], + struct s3d_hit* hit) +{ + struct closest_pt pt[CBOX_GEOMS_COUNT__] = { + CLOSEST_PT_NULL__, CLOSEST_PT_NULL__, CLOSEST_PT_NULL__ + }; + enum cbox_geom geom; + + CHK(pos && geom_id && hit); + + if(geom_id[CBOX_WALLS] != S3D_INVALID_ID) { /* Are the walls filtered */ + closest_point_mesh(pos, cbox_walls, cbox_walls_ids, cbox_walls_ntris, + geom_id[CBOX_WALLS], S3D_INVALID_ID, &pt[CBOX_WALLS]); + } + if(geom_id[CBOX_TALL_BLOCK] != S3D_INVALID_ID) { /* Is the block filtered */ + closest_point_mesh(pos, cbox_tall_block, cbox_block_ids, cbox_block_ntris, + geom_id[CBOX_TALL_BLOCK], S3D_INVALID_ID, &pt[CBOX_TALL_BLOCK]); + } + if(geom_id[CBOX_SHORT_BLOCK] != S3D_INVALID_ID) { /* Is the block filtered */ + closest_point_mesh(pos, cbox_short_block, cbox_block_ids, cbox_block_ntris, + geom_id[CBOX_SHORT_BLOCK], S3D_INVALID_ID, &pt[CBOX_SHORT_BLOCK]); + } + geom = pt[CBOX_WALLS].dst < pt[CBOX_TALL_BLOCK].dst + ? CBOX_WALLS : CBOX_TALL_BLOCK; + geom = pt[CBOX_SHORT_BLOCK].dst < pt[geom].dst + ? CBOX_SHORT_BLOCK : geom; + + if(pt[geom].dst >= FLT_MAX) { /* All geometries were filtered */ + CHK(S3D_HIT_NONE(hit)); + } else { + check_closest_point(hit, &pt[geom], 1); + } +} + +static void +test_cbox(struct s3d_device* dev) +{ + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_scene* scn = NULL; + struct s3d_shape* walls = NULL; + struct s3d_shape* tall_block = NULL; + struct s3d_shape* short_block = NULL; + struct s3d_scene_view* scnview = NULL; + struct cbox_desc walls_desc; + struct cbox_desc tall_block_desc; + struct cbox_desc short_block_desc; + struct cbox_filter_data filter_data; + void* ptr = (void*)((intptr_t)0xDECAFBAD); + float pos[3]; + float low[3], upp[3], mid[3]; + unsigned geom_id[CBOX_GEOMS_COUNT__]; + size_t i; + + /* Create the Star-3D scene */ + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &tall_block) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &short_block) == RES_OK); + CHK(s3d_shape_get_id(walls, &geom_id[CBOX_WALLS]) == RES_OK); + CHK(s3d_shape_get_id(tall_block, &geom_id[CBOX_TALL_BLOCK]) == RES_OK); + CHK(s3d_shape_get_id(short_block, &geom_id[CBOX_SHORT_BLOCK]) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(walls, cbox_filter, ptr) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(tall_block, cbox_filter, ptr) == RES_OK); + CHK(s3d_mesh_set_hit_filter_function(short_block, cbox_filter, ptr) == RES_OK); + CHK(s3d_scene_attach_shape(scn, walls) == RES_OK); + CHK(s3d_scene_attach_shape(scn, tall_block) == RES_OK); + CHK(s3d_scene_attach_shape(scn, short_block) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = cbox_get_position; + + /* Setup the Cornell box walls */ + walls_desc.vertices = cbox_walls; + walls_desc.indices = cbox_walls_ids; + CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids, + cbox_walls_nverts, &vdata, 1, &walls_desc) == RES_OK); + + /* Setup the Cornell box tall block */ + tall_block_desc.vertices = cbox_tall_block; + tall_block_desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(tall_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &tall_block_desc) == RES_OK); + + /* Setup the Cornell box short block */ + short_block_desc.vertices = cbox_short_block; + short_block_desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(short_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &short_block_desc) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + CHK(s3d_scene_view_get_aabb(scnview, low, upp) == RES_OK); + mid[0] = (low[0] + upp[0]) * 0.5f; + mid[1] = (low[1] + upp[1]) * 0.5f; + mid[2] = (low[2] + upp[2]) * 0.5f; + + /* Check closest point query on Cornell box */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + CHK(s3d_scene_view_closest_point(scnview, pos, (float)INF, NULL, &hit) == RES_OK); + check_closest_point_cbox(pos, geom_id, &hit); + } + + /* Filter the Cornell box blocks */ + filter_data.geom_to_filter[0] = geom_id[CBOX_TALL_BLOCK]; + filter_data.geom_to_filter[1] = geom_id[CBOX_SHORT_BLOCK]; + filter_data.geom_to_filter[2] = S3D_INVALID_ID; + geom_id[CBOX_TALL_BLOCK] = S3D_INVALID_ID; + geom_id[CBOX_SHORT_BLOCK] = S3D_INVALID_ID; + + /* Check closest point query filtering */ + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 2 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]); + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]); + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]); + + f3_set(filter_data.query_pos, pos); + + CHK(s3d_scene_view_closest_point + (scnview, pos, (float)INF, &filter_data, &hit) == RES_OK); + + check_closest_point_cbox(pos, geom_id, &hit); + } + + /* Clean up */ + CHK(s3d_shape_ref_put(walls) == RES_OK); + CHK(s3d_shape_ref_put(tall_block) == RES_OK); + CHK(s3d_shape_ref_put(short_block) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_scene_view_ref_put(scnview) == RES_OK); +} + +/******************************************************************************* + * Single triangle test + ******************************************************************************/ +static void +triangle_get_ids(const unsigned itri, unsigned ids[3], void* ctx) +{ + (void)ctx; + CHK(itri == 0); + CHK(ids); + ids[0] = 0; + ids[1] = 1; + ids[2] = 2; +} + +static void +triangle_get_pos(const unsigned ivert, float pos[3], void* ctx) +{ + (void)ctx; + CHK(ivert < 3); + CHK(pos); + switch(ivert) { /* Setup a random triangle */ + case 0: f3(pos, -0.5f, -0.3f, 0.1f); break; + case 1: f3(pos, -0.4f, 0.2f, 0.3f); break; + case 2: f3(pos, 0.7f, 0.01f, -0.5f); break; + default: FATAL("Unreachable code\n"); break; + } +} + +static void +test_single_triangle(struct s3d_device* dev) +{ + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene_view* view = NULL; + struct s3d_shape* msh = NULL; + struct s3d_attrib attr; + float v0[3], v1[3], v2[3]; + float pos[3] = {0,0,0}; + float closest_pos[3] = {0,0,0}; + float low[3], upp[3], mid[3]; + size_t i; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &msh) == RES_OK); + CHK(s3d_scene_attach_shape(scn, msh) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = triangle_get_pos; + CHK(s3d_mesh_setup_indexed_vertices + (msh, 1, triangle_get_ids, 3, &vdata, 1, NULL) == RES_OK); + + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + + triangle_get_pos(0, v0, NULL); + triangle_get_pos(1, v1, NULL); + triangle_get_pos(2, v2, NULL); + + /* Compute the triangle AABB */ + low[0] = MMIN(MMIN(v0[0], v1[0]), v2[0]); + low[1] = MMIN(MMIN(v0[1], v1[1]), v2[1]); + low[2] = MMIN(MMIN(v0[2], v1[2]), v2[2]); + upp[0] = MMAX(MMAX(v0[0], v1[0]), v2[0]); + upp[1] = MMAX(MMAX(v0[1], v1[1]), v2[1]); + upp[2] = MMAX(MMAX(v0[2], v1[2]), v2[2]); + mid[0] = (low[0] + upp[0]) * 0.5f; + mid[1] = (low[1] + upp[1]) * 0.5f; + mid[2] = (low[2] + upp[2]) * 0.5f; + + FOR_EACH(i, 0, 10000) { + /* Randomly generate a point in a bounding box that is 10 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f; + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f; + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f; + + CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(s3d_primitive_get_attrib(&hit.prim, S3D_POSITION, hit.uv, &attr) == RES_OK); + + /* Cross check the closest point query result */ + closest_point_triangle(pos, v0, v1, v2, closest_pos); + CHK(f3_eq_eps(closest_pos, attr.value, 1.e-4f)); + } + + FOR_EACH(i, 0, 10000) { + float radius; + + /* Randomly generate a point in a bounding box that is 10 times the size of + * the triangle AABB */ + pos[0] = mid[0] + (rand_canonic() * 2 - 1) * (upp[0] - low[0]) * 5.f; + pos[1] = mid[1] + (rand_canonic() * 2 - 1) * (upp[1] - low[1]) * 5.f; + pos[2] = mid[2] + (rand_canonic() * 2 - 1) * (upp[2] - low[2]) * 5.f; + + CHK(s3d_scene_view_closest_point(view, pos, (float)INF, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + + /* Check that the radius is an exclusive upper bound */ + radius = hit.distance; + CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + radius = nextafterf(radius, FLT_MAX); + CHK(s3d_scene_view_closest_point(view, pos, radius, NULL, &hit) == RES_OK); + CHK(!S3D_HIT_NONE(&hit)); + CHK(hit.distance == nextafterf(radius, 0.f)); + } + + CHK(s3d_shape_ref_put(msh) == RES_OK); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Miscellaneous test + ******************************************************************************/ +static void +test_api(struct s3d_device* dev) +{ + struct s3d_hit hit = S3D_HIT_NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene_view* view = NULL; + float pos[3] = {0,0,0}; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + + CHK(s3d_scene_view_closest_point(NULL, pos, 1.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, NULL, 1.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 0.f, NULL, &hit) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, NULL) == RES_BAD_ARG); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_OK); + CHK(S3D_HIT_NONE(&hit)); + + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_SAMPLE, &view) == RES_OK); + CHK(s3d_scene_view_closest_point(view, pos, 1.f, NULL, &hit) == RES_BAD_OP); + + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct s3d_device* dev = NULL; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK); + + test_api(dev); + test_single_triangle(dev); + test_cbox(dev); + test_sphere(dev); + test_cbox_sphere(dev); + + CHK(s3d_device_ref_put(dev) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +} diff --git a/src/test_s3d_scene_view.c b/src/test_s3d_scene_view.c @@ -172,9 +172,9 @@ test_miscellaneous CHK(s3d_scene_view_ref_put(scnview) == RES_OK); CHK(s3d_scene_detach_shape(scn, plane) == RES_OK); - CHK(s3d_scene_view_create(scn, S3D_TRACE, &scnview) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &scnview) == RES_OK); CHK(s3d_scene_view_get_mask(scnview, &mask) == RES_OK); - CHK(mask == S3D_TRACE); + CHK(mask == 0); CHK(s3d_scene_view_compute_volume(scnview, &V) == RES_OK); CHK(s3d_scene_view_compute_area(scnview, &A) == RES_OK); CHK(s3d_scene_view_ref_put(scnview) == RES_OK); diff --git a/src/test_s3d_scene_view_aabb.c b/src/test_s3d_scene_view_aabb.c @@ -0,0 +1,438 @@ +/* Copyright (C) 2015-2019 |Meso|Star> (contact@meso-star.com) + * + * This software is a computer program whose purpose is to describe a + * virtual 3D environment that can be ray-traced and sampled both robustly + * and efficiently. + * + * This software is governed by the CeCILL license under French law and + * abiding by the rules of distribution of free software. You can use, + * modify and/or redistribute the software under the terms of the CeCILL + * license as circulated by CEA, CNRS and INRIA at the following URL + * "http://www.cecill.info". + * + * As a counterpart to the access to the source code and rights to copy, + * modify and redistribute granted by the license, users are provided only + * with a limited warranty and the software's author, the holder of the + * economic rights, and the successive licensors have only limited + * liability. + * + * In this respect, the user's attention is drawn to the risks associated + * with loading, using, modifying and/or developing or reproducing the + * software by the user in light of its specific status of free software, + * that may mean that it is complicated to manipulate, and that also + * therefore means that it is reserved for developers and experienced + * professionals having in-depth computer knowledge. Users are therefore + * encouraged to load and test the software's suitability as regards their + * requirements in conditions enabling the security of their systems and/or + * data to be ensured and, more generally, to use and operate it in the + * same conditions as regards security. + * + * The fact that you are presently reading this means that you have had + * knowledge of the CeCILL license and that you accept its terms. */ + +#include "s3d.h" +#include "test_s3d_utils.h" +#include "test_s3d_cbox.h" + +#include <rsys/float3.h> + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static void +compute_mesh_aabb + (const float* pos, + const size_t nverts, + const unsigned* ids, + const size_t ntris, + float low[3], + float upp[3]) +{ + size_t i; + + CHK(low && upp && pos && nverts && ids && ntris); + f3_splat(low, FLT_MAX); + f3_splat(upp,-FLT_MAX); + + FOR_EACH(i, 0, ntris*3) { + const float* vertex = pos + ids[i]*3; + f3_min(low, vertex, low); + f3_max(upp, vertex, upp); + } +} + +static int +aabb_is_degenerated(const float low[3], const float upp[3]) +{ + CHK(low && upp); + return low[0] > upp[0] + && low[1] > upp[1] + && low[2] > upp[2]; +} + +/******************************************************************************* + * Instances + ******************************************************************************/ +static void +test_instances(struct s3d_device* dev) +{ + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene* scn2 = NULL; + struct s3d_scene_view* view = NULL; + struct s3d_shape* sphere = NULL; + struct s3d_shape* tall_block = NULL; + struct s3d_shape* inst0 = NULL; + struct s3d_shape* inst1 = NULL; + struct cbox_desc desc; + float low[3], upp[3]; + float scn_low[3], scn_upp[3]; + float scn2_low[3], scn2_upp[3]; + float inst0_low[3], inst0_upp[3]; + float inst1_low[3], inst1_upp[3]; + float tall_block_low[3], tall_block_upp[3]; + float sphere_low[3], sphere_upp[3]; + float inst0_trans[3]; + float inst1_trans[3]; + float pos[3]; + float radius; + + /* Create the tall block shape*/ + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = cbox_get_position; + desc.vertices = cbox_tall_block; + desc.indices = cbox_block_ids; + CHK(s3d_shape_create_mesh(dev, &tall_block) == RES_OK); + CHK(s3d_mesh_setup_indexed_vertices(tall_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &desc) == RES_OK); + compute_mesh_aabb(cbox_tall_block, cbox_block_nverts, cbox_block_ids, + cbox_block_ntris, tall_block_low, tall_block_upp); + + /* Create the sphere and ensure that it is not contained into the block */ + pos[0] = tall_block_low[0] - 10.f; + pos[1] = (tall_block_upp[1] + tall_block_low[1]) * 0.5f; + pos[2] = (tall_block_upp[2] + tall_block_low[2]) * 0.5f; + radius = 1.f; + CHK(s3d_shape_create_sphere(dev, &sphere) == RES_OK); + CHK(s3d_sphere_setup(sphere, pos, radius) == RES_OK); + f3_subf(sphere_low, pos, radius); + f3_addf(sphere_upp, pos, radius); + + /* Create the scene to instantiate */ + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_scene_attach_shape(scn, sphere) == RES_OK); + CHK(s3d_scene_attach_shape(scn, tall_block) == RES_OK); + + /* Compute the AABB of the original scene */ + f3_min(scn_low, tall_block_low, sphere_low); + f3_max(scn_upp, tall_block_upp, sphere_upp); + + /* Create two instances */ + inst0_trans[0] = -100.f; + inst0_trans[1] = 0.f; + inst0_trans[2] = -50.f; + inst1_trans[0] = 123.f; + inst1_trans[1] = 4.56f; + inst1_trans[2] = 0.789f; + CHK(s3d_scene_instantiate(scn, &inst0) == RES_OK); + CHK(s3d_scene_instantiate(scn, &inst1) == RES_OK); + CHK(s3d_instance_translate(inst0, S3D_WORLD_TRANSFORM, inst0_trans) == RES_OK); + CHK(s3d_instance_translate(inst1, S3D_WORLD_TRANSFORM, inst1_trans) == RES_OK); + f3_add(inst0_low, scn_low, inst0_trans); + f3_add(inst0_upp, scn_upp, inst0_trans); + f3_add(inst1_low, scn_low, inst1_trans); + f3_add(inst1_upp, scn_upp, inst1_trans); + + /* Create the scene with the 2 instances */ + CHK(s3d_scene_create(dev, &scn2) == RES_OK); + CHK(s3d_scene_attach_shape(scn2, inst0) == RES_OK); + CHK(s3d_scene_attach_shape(scn2, inst1) == RES_OK); + + /* Compute the AABB of the scene with instances */ + f3_min(scn2_low, inst0_low, inst1_low); + f3_max(scn2_upp, inst0_upp, inst1_upp); + + /* Original scene */ + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Retry the original scene to test the cache mechanism */ + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Scene with the 2 instances */ + CHK(s3d_scene_view_create(scn2, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn2_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn2_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Retry the scene with the 2 instances to check the cache mechanism */ + CHK(s3d_scene_view_create(scn2, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn2_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn2_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Scene with only one instance */ + CHK(s3d_shape_enable(inst0, 0) == RES_OK); + CHK(s3d_scene_view_create(scn2, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(inst1_low, low, 1.e-6f)); + CHK(f3_eq_eps(inst1_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Scene whose instances have only the tall_block */ + CHK(s3d_shape_enable(inst0, 1) == RES_OK); + CHK(s3d_shape_enable(sphere, 0) == RES_OK); + f3_add(inst0_low, tall_block_low, inst0_trans); + f3_add(inst0_upp, tall_block_upp, inst0_trans); + f3_add(inst1_low, tall_block_low, inst1_trans); + f3_add(inst1_upp, tall_block_upp, inst1_trans); + f3_min(scn2_low, inst0_low, inst1_low); + f3_max(scn2_upp, inst0_upp, inst1_upp); + CHK(s3d_scene_view_create(scn2, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn2_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn2_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Scene with one instance, one mesh and one sphere */ + CHK(s3d_scene_detach_shape(scn2, inst1) == RES_OK); + CHK(s3d_scene_attach_shape(scn2, sphere) == RES_OK); + CHK(s3d_scene_attach_shape(scn2, tall_block) == RES_OK); + f3_min(scn2_low, f3_min(scn2_low, tall_block_low, sphere_low), inst0_low); + f3_max(scn2_upp, f3_max(scn2_upp, tall_block_upp, sphere_upp), inst0_upp); + CHK(s3d_scene_view_create(scn2, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(scn2_low, low, 1.e-6f)); + CHK(f3_eq_eps(scn2_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Clean up */ + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_scene_ref_put(scn2) == RES_OK); + CHK(s3d_shape_ref_put(sphere) == RES_OK); + CHK(s3d_shape_ref_put(tall_block) == RES_OK); + CHK(s3d_shape_ref_put(inst0) == RES_OK); + CHK(s3d_shape_ref_put(inst1) == RES_OK); +} + +/******************************************************************************* + * Cornell box + ******************************************************************************/ +static void +test_cbox(struct s3d_device* dev) +{ + struct s3d_vertex_data vdata = S3D_VERTEX_DATA_NULL; + struct s3d_scene* scn = NULL; + struct s3d_scene_view* view = NULL; + struct s3d_shape* walls = NULL; + struct s3d_shape* tall_block = NULL; + struct s3d_shape* short_block = NULL; + struct cbox_desc desc; + float low[3], upp[3]; + float walls_low[3], walls_upp[3]; + float tall_block_low[3], tall_block_upp[3]; + float short_block_low[3], short_block_upp[3]; + float aabb_low[3], aabb_upp[3]; + + /* Create the Star-3D scene and the Cornell box meshes */ + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &walls) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &tall_block) == RES_OK); + CHK(s3d_shape_create_mesh(dev, &short_block) == RES_OK); + + vdata.usage = S3D_POSITION; + vdata.type = S3D_FLOAT3; + vdata.get = cbox_get_position; + + /* Setup the Cornell box walls */ + desc.vertices = cbox_walls; + desc.indices = cbox_walls_ids; + CHK(s3d_mesh_setup_indexed_vertices(walls, cbox_walls_ntris, cbox_get_ids, + cbox_walls_nverts, &vdata, 1, &desc) == RES_OK); + compute_mesh_aabb(cbox_walls, cbox_walls_nverts, cbox_walls_ids, + cbox_walls_ntris, walls_low, walls_upp); + + /* Setup the Cornell box tall block */ + desc.vertices = cbox_tall_block; + desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(tall_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &desc) == RES_OK); + compute_mesh_aabb(cbox_tall_block, cbox_block_nverts, cbox_block_ids, + cbox_block_ntris, tall_block_low, tall_block_upp); + + /* Setup the Cornell box short block */ + desc.vertices = cbox_short_block; + desc.indices = cbox_block_ids; + CHK(s3d_mesh_setup_indexed_vertices(short_block, cbox_block_ntris, cbox_get_ids, + cbox_block_nverts, &vdata, 1, &desc) == RES_OK); + compute_mesh_aabb(cbox_short_block, cbox_block_nverts, cbox_block_ids, + cbox_block_ntris, short_block_low, short_block_upp); + + /* Tall block only */ + CHK(s3d_scene_attach_shape(scn, tall_block) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(tall_block_low, low, 1.e-6f)); + CHK(f3_eq_eps(tall_block_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Disabled tall block only */ + CHK(s3d_shape_enable(tall_block, 0) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(aabb_is_degenerated(low, upp)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Tall block only with wo S3D_TRACE flag */ + CHK(s3d_shape_enable(tall_block, 1) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(tall_block_low, low, 1.e-6f)); + CHK(f3_eq_eps(tall_block_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* All blocks */ + CHK(s3d_scene_attach_shape(scn, short_block) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + f3_min(aabb_low, tall_block_low, short_block_low); + f3_max(aabb_upp, tall_block_upp, short_block_upp); + CHK(f3_eq_eps(aabb_low, low, 1.e-6f)); + CHK(f3_eq_eps(aabb_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Another try with all blocks to check the cache mechanism */ + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(aabb_low, low, 1.e-6f)); + CHK(f3_eq_eps(aabb_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* All blocks but the tall block is disabled */ + CHK(s3d_shape_enable(tall_block, 0) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(short_block_low, low, 1.e-6f)); + CHK(f3_eq_eps(short_block_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* All blocks but the short block is disabled */ + CHK(s3d_shape_enable(tall_block, 1) == RES_OK); + CHK(s3d_shape_enable(short_block, 0) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(tall_block_low, low, 1.e-6f)); + CHK(f3_eq_eps(tall_block_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* The whole Cornell box */ + CHK(s3d_shape_enable(short_block, 1) == RES_OK); + CHK(s3d_scene_attach_shape(scn, walls) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + f3_min(aabb_low, f3_min(aabb_low, tall_block_low, short_block_low), walls_low); + f3_max(aabb_upp, f3_max(aabb_upp, tall_block_upp, short_block_upp), walls_upp); + CHK(f3_eq_eps(aabb_low, low, 1.e-6f)); + CHK(f3_eq_eps(aabb_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Retry the whole Cornell box */ + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(aabb_low, low, 1.e-6f)); + CHK(f3_eq_eps(aabb_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Disable all */ + CHK(s3d_shape_enable(walls, 0) == RES_OK); + CHK(s3d_shape_enable(tall_block, 0) == RES_OK); + CHK(s3d_shape_enable(short_block, 0) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(aabb_is_degenerated(low, upp)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Only the short block */ + CHK(s3d_shape_enable(walls, 1) == RES_OK); + CHK(s3d_shape_enable(tall_block, 1) == RES_OK); + CHK(s3d_shape_enable(short_block, 1) == RES_OK); + CHK(s3d_scene_detach_shape(scn, walls) == RES_OK); + CHK(s3d_scene_detach_shape(scn, tall_block) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(f3_eq_eps(short_block_low, low, 1.e-6f)); + CHK(f3_eq_eps(short_block_upp, upp, 1.e-6f)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Only the short block */ + CHK(s3d_scene_clear(scn) == RES_OK); + CHK(s3d_scene_view_create(scn, S3D_TRACE, &view) == RES_OK); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(aabb_is_degenerated(low, upp)); + CHK(s3d_scene_view_ref_put(view) == RES_OK); + + /* Clean up the data */ + CHK(s3d_scene_ref_put(scn) == RES_OK); + CHK(s3d_shape_ref_put(walls) == RES_OK); + CHK(s3d_shape_ref_put(tall_block) == RES_OK); + CHK(s3d_shape_ref_put(short_block) == RES_OK); +} + +/******************************************************************************* + * Test the API + ******************************************************************************/ +static void +test_api(struct s3d_device* dev) +{ + struct s3d_scene* scn = NULL; + struct s3d_scene_view* view = NULL; + float low[3] = {0,0,0}; + float upp[3] = {0,0,0}; + + CHK(s3d_scene_create(dev, &scn) == RES_OK); + CHK(s3d_scene_view_create(scn, 0, &view) == RES_OK); + + CHK(s3d_scene_view_get_aabb(NULL, low, upp) == RES_BAD_ARG); + CHK(s3d_scene_view_get_aabb(view, NULL, upp) == RES_BAD_ARG); + CHK(s3d_scene_view_get_aabb(view, low, NULL) == RES_BAD_ARG); + CHK(s3d_scene_view_get_aabb(view, low, upp) == RES_OK); + CHK(aabb_is_degenerated(low, upp)); + + CHK(s3d_scene_view_ref_put(view) == RES_OK); + CHK(s3d_scene_ref_put(scn) == RES_OK); +} + +/******************************************************************************* + * Main function + ******************************************************************************/ +int +main(int argc, char** argv) +{ + struct mem_allocator allocator; + struct s3d_device* dev = NULL; + (void)argc, (void)argv; + + mem_init_proxy_allocator(&allocator, &mem_default_allocator); + CHK(s3d_device_create(NULL, &allocator, 1, &dev) == RES_OK); + + test_api(dev); + test_cbox(dev); + test_instances(dev); + + CHK(s3d_device_ref_put(dev) == RES_OK); + + check_memory_allocator(&allocator); + mem_shutdown_proxy_allocator(&allocator); + CHK(mem_allocated_size() == 0); + return 0; +}