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 455c86e80a55f0f8d852639c8917e74456752ae0
parent 6ee0993dc458aa15b6a3773cd9605cf37d67fd8c
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Tue, 26 Jul 2016 15:38:27 +0200

First draft of the session data structure (does not compile yet)

A session represents the current state of the scene from which it was
created. The ray tracing, sampling, surface/volume computation, etc. are
now invoked through a session rather than directly onto a scene. The
explicit management of session objects allows the creation of several
sessions on the same scene. In addition, the scene can be now updated
while one or several sessions are still active.

Anyway, this commit is used only to register the current state of the
code. It must be seen as a draft used to try this indeep architecture
update; the code does not compile and the tests were not updated to take
into account the new API.

Diffstat:
Msrc/s3d.h | 95+++++++++++++++++++++++++++++++++++--------------------------------------------
Msrc/s3d_scene.c | 1355+------------------------------------------------------------------------------
Msrc/s3d_scene_c.h | 56+++++++-------------------------------------------------
Asrc/s3d_session.c | 1408+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/s3d_session_c.h | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1579 insertions(+), 1446 deletions(-)

diff --git a/src/s3d.h b/src/s3d.h @@ -185,6 +185,7 @@ typedef int struct s3d_device; /* Entry point of the library */ struct s3d_scene; /* Collection of shapes */ struct s3d_shape; /* Untyped geometry */ +struct s3d_session; /* TODO comment */ /* Forward declaration of external data types */ struct logger; @@ -263,49 +264,46 @@ S3D_API res_T s3d_scene_clear (struct s3d_scene* scn); -/* Synchronize the scene geometry with the geometry of its attached shapes. If - * a s3d_scene_begin_session is already active on `scn' or one of its attached - * instance a RES_BAD_OP error is returned. On success neither another begin - * session nor a clear or shape_detach can be invoked on `scn' and its attached - * instances until s3d_scene_end_session is called. */ +/******************************************************************************* + * Session API - View of the current scene geometry + ******************************************************************************/ S3D_API res_T -s3d_scene_begin_session +s3d_session_create (struct s3d_scene* scn, - const int session_mask); /* Combination of s3d_session_flag */ + const int session_mask /* Combination of s3d_session_flag */ + struct s3d_session** session); -/* End the session on the `scn' */ S3D_API res_T -s3d_scene_end_session - (struct s3d_scene* scn); +s3d_session_ref_get + (struct s3d_session* session); S3D_API res_T -s3d_scene_get_session_mask - (struct s3d_scene* scn, +s3d_session_ref_put + (struct s3d_session* session); + +S3D_API res_T +s3d_session_get_mask + (struct s3d_session* session, int* mask); -/* Trace a ray into the `scn' and return the closest intersection. The ray is - * defined by `origin' + t*`direction' = 0 with t in [`range[0]', `range[1]'). - * Note that if 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 an - * S3D_TRACE session is active on `scn' */ S3D_API res_T -s3d_scene_trace_ray - (struct s3d_scene* scn, +s3d_session_trace_ray + (struct s3d_session* session, const float origin[3], /* Ray origin */ const float direction[3], /* Ray direction. Must be normalized */ const float range[2], /* In [0, INF)^2 */ 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 `scn' and return the closest intersection along - * them. The rays are defined by `origin' + t*`direction' = 0 with t in +/* 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 an S3D_TRACE session is active on - * `scn' */ + * set to S3D_HIT_NULL. Can be called only if the session was created with the + * S3D_TRACE flag. */ S3D_API res_T -s3d_scene_trace_rays - (struct s3d_scene* scn, +s3d_session_trace_rays + (struct s3d_session* session, const size_t nrays, /* # rays */ const int mask, /* Combination of s3d_ray_flag */ const float* origins, /* List of 3D ray origins */ @@ -316,58 +314,49 @@ s3d_scene_trace_rays struct s3d_hit* hits); /* Uniformly sample the scene and returned the sampled primitive and its sample - * uv position. Can be called only if a S3D_SAMPLE session is active on `scn'*/ + * uv position. Can be called only if the session was created with the + * S3D_SAMPLE flag */ S3D_API res_T -s3d_scene_sample - (struct s3d_scene* scn, +s3d_session_sample + (struct s3d_session* session, const float u, const float v, const float w, /* Random numbers in [0, 1) */ struct s3d_primitive* primitive, /* Sampled primitive */ float st[2]); /* Sampled parametric coordinates on the primitive */ -/* Retrieve a primitive from the scene. Can be called only if a - * S3D_GET_PRIMITIVE session is active on `scn' */ +/* Retrieve a primitive from the scene. Can be called only if the session was + * created with the S3D_GET_PRIMITIVE flag */ S3D_API res_T -s3d_scene_get_primitive - (struct s3d_scene* scn, +s3d_session_get_primitive + (struct s3d_session* session, const unsigned iprim, /* in [0, #prims) */ struct s3d_primitive* prim); -/* Retrieve the number of scene primitives. Can be called only if a sessio is - * active on `scn' */ +/* Return the overall number of scene primitives */ S3D_API res_T -s3d_scene_primitives_count - (struct s3d_scene* scn, +s3d_session_primitives_count + (struct s3d_session* session, size_t* primitives_count); -/* Compute the overall scene surface area. Can be called only if a session is - * active on `scn' */ +/* Compute the overall scene surface area */ S3D_API res_T -s3d_scene_compute_area - (struct s3d_scene* scn, +s3d_session_compute_area + (struct s3d_session* session, float* area); /* This function assumes that the scene defines a closed volume and that the - * normals point into the volume. Can be called only if a session is active on - * `scn' */ + * normals point into the volume. */ S3D_API res_T -s3d_scene_compute_volume - (struct s3d_scene* scn, +s3d_session_compute_volume + (struct s3d_session* session, float* volume); -/* Retrieve the Axis Aligned Bounding Box of the scene. Can be called only if a - * session is active on `scn' */ +/* Retrieve the Axis Aligned Bounding Box of the scene */ S3D_API res_T s3d_scene_get_aabb - (struct s3d_scene* scn, + (struct s3d_session* session, float lower[3], /* AABB lower bound */ float upper[3]); /* AABB upper bound */ -/* Retrieve the device from which the scene was created */ -S3D_API res_T -s3d_scene_get_device - (struct s3d_scene* scn, - struct s3d_device** dev); - /******************************************************************************* * Shape API - A shape defines a geometry that can be attached to *one* scene. ******************************************************************************/ diff --git a/src/s3d_scene.c b/src/s3d_scene.c @@ -33,813 +33,28 @@ #include "s3d.h" #include "s3d_device_c.h" #include "s3d_scene_c.h" -#include "s3d_shape_c.h" +#include "s3d_session_c.h" -#include <rsys/float3.h> -#include <rsys/float33.h> +#include <rsys/list.h> #include <rsys/mem_allocator.h> -#include <rsys/mutex.h> - -#include <algorithm> - -struct ray_extended : public RTCRay { - struct s3d_scene* scene; - void* data; /* User defined data */ -}; - -#define INTERNAL_SESSION ((unsigned char)(-1)) /******************************************************************************* * Helper functions ******************************************************************************/ -static INLINE void -hit_setup(struct s3d_scene* scn, const RTCRay* ray, struct s3d_hit* hit) -{ - float w; - char flip_surface = 0; - - ASSERT(scn && hit && ray); - - if((unsigned)ray->geomID == RTC_INVALID_GEOMETRY_ID) { /* No hit */ - *hit = S3D_HIT_NULL; - return; - } - - f3_set(hit->normal, ray->Ng); - hit->distance = ray->tfar; - - hit->uv[0] = ray->u; - hit->uv[1] = ray->v; - 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; - - if((unsigned)ray->instID == RTC_INVALID_GEOMETRY_ID) { - struct geometry* geom_mesh; - ASSERT((unsigned)ray->geomID < darray_geom_size_get(&scn->embree2geoms)); - geom_mesh = darray_geom_data_get(&scn->embree2geoms)[ray->geomID]; - hit->prim.mesh__ = geom_mesh; - hit->prim.inst__ = NULL; - hit->prim.prim_id = ray->primID; - hit->prim.geom_id = geom_mesh->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_mesh->scene_prim_id_offset; /* Scene space */ - - } else { /* The hit shape is instantiated */ - /* Retrieve the hit instance */ - struct geometry* geom_inst; - struct geometry* geom_mesh; - ASSERT((unsigned)ray->instID < darray_geom_size_get(&scn->embree2geoms)); - geom_inst = darray_geom_data_get(&scn->embree2geoms)[ray->instID]; - geom_mesh = scene_get_mesh(geom_inst->data.instance->scene, ray->geomID); - hit->prim.mesh__ = geom_mesh; - hit->prim.inst__ = geom_inst; - hit->prim.prim_id = ray->primID; - hit->prim.geom_id = geom_mesh->name; - hit->prim.inst_id = geom_inst->name; - hit->prim.scene_prim_id = /* Compute the "scene space" */ - hit->prim.prim_id /* Mesh space */ - + geom_mesh->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); - } - ASSERT(hit->prim.mesh__); - ASSERT(((struct geometry*)hit->prim.mesh__)->type == GEOM_MESH); - - /* Flip geometric normal with respect to the flip surface flag */ - flip_surface ^= ((struct geometry*)hit->prim.mesh__)->flip_surface; - if(flip_surface) f3_minus(hit->normal, hit->normal); -} - -/* Wrapper between an Embree and a Star-3D filter function */ -static void -filter_wrapper(void* user_ptr, RTCRay& ray) -{ - struct s3d_hit hit; - struct hit_filter* filter = (struct hit_filter*)user_ptr; - struct ray_extended* ray_ex = static_cast<struct ray_extended*>(&ray); - - hit_setup(ray_ex->scene, &ray, &hit); - if(filter->func(&hit, ray_ex->org, ray_ex->dir, ray_ex->data, filter->data)) { - /* Discard the intersection */ - ray.geomID = RTC_INVALID_GEOMETRY_ID; - } -} - -static res_T -scene_sync - (struct s3d_scene* scn, - const int mask, /* Combination of s3d_session_flag */ - const int internal); /* Is the synchronized session internal? */ - -static res_T -scene_register_embree_geometry(struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom && (geom->type==GEOM_MESH || geom->type==GEOM_INSTANCE)); - - /* Create the Embree geometry if it is not valid */ - if(geom->irtc == RTC_INVALID_GEOMETRY_ID) { - switch(geom->type) { - case GEOM_MESH: - geom->irtc = rtcNewTriangleMesh(scn->rtc_scn, RTC_GEOMETRY_DYNAMIC, - mesh_get_ntris(geom->data.mesh), mesh_get_nverts(geom->data.mesh)); - break; - case GEOM_INSTANCE: - geom->irtc = rtcNewInstance - (scn->rtc_scn, geom->data.instance->scene->rtc_scn); - break; - default: FATAL("Unreachable code\n"); break; - } - if(geom->irtc == RTC_INVALID_GEOMETRY_ID) - return RES_UNKNOWN_ERR; - } - - if(geom->irtc >= darray_geom_size_get(&scn->embree2geoms)) { - const res_T res = darray_geom_resize(&scn->embree2geoms, geom->irtc + 1); - if(res != RES_OK) { - rtcDeleteGeometry(scn->rtc_scn, geom->irtc); - geom->irtc = RTC_INVALID_GEOMETRY_ID; - return res; - } - } - darray_geom_data_get(&scn->embree2geoms)[geom->irtc] = geom; - return RES_OK; -} - -static INLINE void -scene_setup_embree_geometry_positions - (struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom && geom->type == GEOM_MESH); - ASSERT(geom->irtc != RTC_INVALID_GEOMETRY_ID); - rtcSetBuffer(scn->rtc_scn, geom->irtc, RTC_VERTEX_BUFFER, - mesh_get_pos(geom->data.mesh), 0, sizeof(float[3])); - rtcUpdateBuffer(scn->rtc_scn, geom->irtc, RTC_VERTEX_BUFFER); -} - -static INLINE void -scene_setup_embree_geometry_indices - (struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom && geom->type == GEOM_MESH); - ASSERT(geom->irtc != RTC_INVALID_GEOMETRY_ID); - rtcSetBuffer(scn->rtc_scn, geom->irtc, RTC_INDEX_BUFFER, - mesh_get_ids(geom->data.mesh), 0, sizeof(uint32_t[3])); - rtcUpdateBuffer(scn->rtc_scn, geom->irtc, RTC_INDEX_BUFFER); -} - -static INLINE void -scene_setup_embree_geometry_enable_state - (struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom); - if(geom->is_enabled) { - rtcEnable(scn->rtc_scn, geom->irtc); - } else { - rtcDisable(scn->rtc_scn, geom->irtc); - } -} - -static INLINE void -scene_setup_embree_geometry_filter_function - (struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom && geom->irtc != RTC_INVALID_GEOMETRY_ID); - ASSERT(geom->type == GEOM_MESH); - - if(!geom->data.mesh->filter.func) { - rtcSetIntersectionFilterFunction(scn->rtc_scn, geom->irtc, NULL); - } else { - rtcSetIntersectionFilterFunction(scn->rtc_scn, geom->irtc, filter_wrapper); - rtcSetUserData(scn->rtc_scn, geom->irtc, &geom->data.mesh->filter); - } -} - -static INLINE void -scene_setup_embree_geometry_transform - (struct s3d_scene* scn, struct geometry* geom) -{ - ASSERT(scn && geom && geom->irtc != RTC_INVALID_GEOMETRY_ID); - ASSERT(geom->type == GEOM_INSTANCE); - rtcSetTransform - (scn->rtc_scn, - geom->irtc, - RTC_MATRIX_COLUMN_MAJOR, - geom->data.instance->transform); -} - -static INLINE res_T -scene_setup_embree(struct s3d_scene* scn) -{ - struct htable_shape_iterator it, end; - int rtc_outdated = 0; - res_T res = RES_OK; - ASSERT(scn); - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - while(!htable_shape_iterator_eq(&it, &end)) { - struct geometry** pgeom; - struct geometry* geom; - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - - /* Define whether or not the embree scene is outdated */ - if(geom->embree_outdated_mask) rtc_outdated = 1; - - /* Register the embree geometry */ - res = scene_register_embree_geometry(scn, geom); - if(res != RES_OK) goto error; - - /* Flush the embree geometry states */ - if((geom->embree_outdated_mask & EMBREE_VERTICES) != 0) - scene_setup_embree_geometry_positions(scn, geom); - if((geom->embree_outdated_mask & EMBREE_INDICES) != 0) - scene_setup_embree_geometry_indices(scn, geom); - if((geom->embree_outdated_mask & EMBREE_ENABLE) != 0) - scene_setup_embree_geometry_enable_state(scn, geom); - if((geom->embree_outdated_mask & EMBREE_FILTER_FUNCTION) != 0) - scene_setup_embree_geometry_filter_function(scn, geom); - if((geom->embree_outdated_mask & EMBREE_TRANSFORM) != 0) - scene_setup_embree_geometry_transform(scn, geom); - - geom->embree_outdated_mask = 0; - } - - /* Commit the embree changes */ - if(rtc_outdated) { - rtcCommit(scn->rtc_scn); - } - -exit: - return res; -error: - darray_geom_clear(&scn->embree2geoms); - goto exit; -} - -static void -scene_session_clear - (struct s3d_scene* scn, - const int internal) /* Is the cleared session internal? */ -{ - ASSERT(scn && scn->session_mask != 0); - - if(internal) { - --scn->nsessions_internal; - } else { - --scn->nsessions; - } - - if(scn->nsessions || scn->nsessions_internal) - return; /* There is still an active session */ - - /* Recursively end the session on instantiated scene */ - if(scn->instances_count != 0) { - struct htable_geom_iterator it, end; - size_t ninstances = 0; - - htable_geom_begin(&scn->cached_geoms, &it); - htable_geom_end(&scn->cached_geoms, &end); - while(ninstances < scn->instances_count /* Early iteration stop */ - && !htable_geom_iterator_eq(&it, &end)) { - struct geometry** pgeom = htable_geom_iterator_data_get(&it); - struct geometry* geom = *pgeom; - htable_geom_iterator_next(&it); - - if(geom->type == GEOM_INSTANCE) { - scene_session_clear(geom->data.instance->scene, 1); - ++ninstances; - } - } - } - - /* Clean-up session data */ - if(scn->session_mask & S3D_TRACE) - darray_geom_clear(&scn->embree2geoms); - if(scn->session_mask & S3D_SAMPLE) - darray_fltui_clear(&scn->cdf); - if(scn->session_mask & S3D_GET_PRIMITIVE) - darray_nprims_cdf_clear(&scn->nprims_cdf); - - /* Reset the session mask */ - scn->session_mask = 0; -} - -static res_T -scene_register_mesh - (struct s3d_scene* scn, - struct s3d_shape* shape) -{ - struct geometry** pgeom = NULL; - struct geometry* geom = NULL; - size_t iattr; - unsigned shape_id; - - res_T res = RES_OK; - ASSERT(shape && shape->type == GEOM_MESH); - - /* Retrieve the cached geometry */ - S3D(shape_get_id(shape, &shape_id)); - pgeom = htable_geom_find(&scn->cached_geoms, &shape_id); - if(pgeom) { - geom = *pgeom; - } else { - res = geometry_create(scn->dev, &geom); - if(res != RES_OK) goto error; - res = mesh_create(scn->dev, &geom->data.mesh); - if(res != RES_OK) goto error; - geom->type = GEOM_MESH; - res = htable_geom_set(&scn->cached_geoms, &shape_id, &geom); - if(res != RES_OK) goto error; - geom->name = shape->id.index; - } - - /* Discard the geometry that is not geometrically valid */ - if(!shape->data.mesh->indices || !shape->data.mesh->attribs[S3D_POSITION]) { - if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { - rtcDeleteGeometry(scn->rtc_scn, geom->irtc); - geom->irtc = RTC_INVALID_GEOMETRY_ID; - } - mesh_clear(geom->data.mesh); - goto exit; - } - - /* Get a reference onto the shape mesh indices */ - if(geom->data.mesh->indices != shape->data.mesh->indices) { - geom->embree_outdated_mask |= EMBREE_INDICES; - if(geom->data.mesh->indices) { /* Release the previous index buffer */ - index_buffer_ref_put(geom->data.mesh->indices); - geom->data.mesh->indices = NULL; - } - ASSERT(shape->data.mesh->indices); - index_buffer_ref_get(shape->data.mesh->indices); - geom->data.mesh->indices = shape->data.mesh->indices; - } - - /* Get a reference onto the shape mesh attribs */ - FOR_EACH(iattr, 0, S3D_ATTRIBS_COUNT__) { - geom->embree_outdated_mask |= EMBREE_VERTICES; - if(geom->data.mesh->attribs[iattr] == shape->data.mesh->attribs[iattr]) - continue; - - if(geom->data.mesh->attribs[iattr]) { /* Release the previous buffer */ - vertex_buffer_ref_put(geom->data.mesh->attribs[iattr]); - geom->data.mesh->attribs[iattr] = NULL; - } - if(!shape->data.mesh->attribs[iattr]) - continue; - - vertex_buffer_ref_get(shape->data.mesh->attribs[iattr]); - geom->data.mesh->attribs[iattr] = shape->data.mesh->attribs[iattr]; - geom->data.mesh->attribs_type[iattr] = shape->data.mesh->attribs_type[iattr]; - } - - /* Update the enable flag */ - if(geom->is_enabled != shape->is_enabled) { - geom->is_enabled = shape->is_enabled; - geom->embree_outdated_mask |= EMBREE_ENABLE; - } - - /* Update the filter function */ - if(geom->data.mesh->filter.func != shape->data.mesh->filter.func - || geom->data.mesh->filter.data != shape->data.mesh->filter.data) { - geom->data.mesh->filter = shape->data.mesh->filter; - geom->embree_outdated_mask |= EMBREE_FILTER_FUNCTION; - } - - if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { - struct index_buffer* shape_ids = shape->data.mesh->indices; - struct index_buffer* geom_ids = geom->data.mesh->indices; - struct vertex_buffer* shape_verts = shape->data.mesh->attribs[S3D_POSITION]; - struct vertex_buffer* geom_verts = geom->data.mesh->attribs[S3D_POSITION]; - const size_t shape_nids = darray_u32_size_get(&shape_ids->data); - const size_t geom_nids = darray_u32_size_get(&geom_ids->data); - const size_t shape_nverts = darray_float_size_get(&shape_verts->data); - const size_t geom_nverts = darray_float_size_get(&geom_verts->data); - - /* The shape mesh was resize => the Embree geometry is no more valid */ - if(shape_nids != geom_nids || shape_nverts != geom_nverts) { - rtcDeleteGeometry(scn->rtc_scn, geom->irtc); - geom->irtc = RTC_INVALID_GEOMETRY_ID; - } - } - - geom->flip_surface = shape->flip_surface; - -exit: - return res; -error: - goto exit; -} - -static res_T -scene_register_instance - (struct s3d_scene* scn, - struct s3d_shape* shape, - const int session_mask) -{ - struct geometry** pgeom = NULL; - struct geometry* geom = NULL; - unsigned shape_id; - res_T res = RES_OK; - ASSERT(scn && shape && shape->type == GEOM_INSTANCE); - - /* The instance cannot contain instances, i.e. one instancing level is - * supported */ - if(shape->data.instance->scene->instances_count != 0) { - res = RES_BAD_ARG; - goto error; - } - - /* Recursively update the scene */ - res = scene_sync(shape->data.instance->scene, session_mask, 1); - if(res != RES_OK) goto error; - - S3D(shape_get_id(shape, &shape_id)); - pgeom = htable_geom_find(&scn->cached_geoms, &shape_id); - /* Create the scene instance of the geometry if necessary */ - if(pgeom) { - geom = *pgeom; - } else { - res = geometry_create(scn->dev, &geom); - if(res != RES_OK) goto error; - geom->type = GEOM_INSTANCE; - res = instance_create(shape->data.instance->scene, &geom->data.instance); - if(res != RES_OK) goto error; - res = htable_geom_set(&scn->cached_geoms, &shape_id, &geom); - if(res != RES_OK) goto error; - geom->name = shape->id.index; - } - ASSERT(geom->data.instance->scene == shape->data.instance->scene); - - /* Update the Embree instance transformation if necessary */ - if(!f33_eq(shape->data.instance->transform, geom->data.instance->transform) - || !f3_eq(shape->data.instance->transform+9, geom->data.instance->transform+9)) { - geom->embree_outdated_mask |= EMBREE_TRANSFORM; - f33_set(geom->data.instance->transform, shape->data.instance->transform); - f3_set(geom->data.instance->transform+9, shape->data.instance->transform+9); - } - - /* Update the enable flag */ - if(geom->is_enabled != shape->is_enabled) { - geom->is_enabled = shape->is_enabled; - geom->embree_outdated_mask |= EMBREE_TRANSFORM; - } - - geom->flip_surface = shape->flip_surface; - -exit: - return res; -error: - goto exit; -} - -static res_T -scene_clear_cached_geometry - (struct s3d_scene* scn, - struct s3d_shape* shape, - const char* caller_name) -{ - struct geometry** pgeom; - unsigned shape_id; - ASSERT(scn && shape); - ASSERT(shape->type == GEOM_MESH || shape->type == GEOM_INSTANCE); - - S3D(shape_get_id(shape, &shape_id)); - ASSERT(htable_shape_find(&scn->shapes, &shape_id) != NULL); - - pgeom = htable_geom_find(&scn->cached_geoms, &shape_id); - if(pgeom) { /* Remove the cached shape mesh */ - struct geometry* geom = *pgeom; - size_t nerased; (void)nerased; - if(scn->session_mask != 0) { - log_error(scn->dev, - "%s: the shape is currently used in a scene session.\n", caller_name); - return RES_BAD_OP; - } - - if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { - rtcDeleteGeometry(scn->rtc_scn, geom->irtc); - geom->irtc = RTC_INVALID_GEOMETRY_ID; - } - geometry_ref_put(geom); - nerased = htable_geom_erase(&scn->cached_geoms, &shape_id); - ASSERT(nerased == 1); - } - - if(shape->type == GEOM_INSTANCE) { - ASSERT(scn->instances_count != 0); - --scn->instances_count; - } - - return RES_OK; -} - -static res_T -scene_compute_cdf(struct s3d_scene* scn) -{ - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - size_t len; - float area = 0.f; - res_T res = RES_OK; - ASSERT(scn); - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - darray_fltui_clear(&scn->cdf); - - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - struct fltui fltui; - - if(!geom->is_enabled) continue; - - switch(geom->type) { - case GEOM_MESH: - res = mesh_compute_cdf(geom->data.mesh); - if(res != RES_OK) goto error; - len = darray_float_size_get(&geom->data.mesh->cdf); - if(len) { - area += darray_float_cdata_get(&geom->data.mesh->cdf)[len - 1]; - } - break; - case GEOM_INSTANCE: - res = scene_compute_cdf(geom->data.instance->scene); - if(res != RES_OK) goto error; - len = darray_fltui_size_get(&geom->data.instance->scene->cdf); - if(len) { - area += darray_fltui_cdata_get - (&geom->data.instance->scene->cdf)[len - 1].flt; - } - break; - default: FATAL("Unreachable code\n"); break; - } - fltui.ui = *shape_id; - fltui.flt = area; - if(len) { - res = darray_fltui_push_back(&scn->cdf, &fltui); - if(res != RES_OK) goto error; - } - } -exit: - return res; -error: - darray_fltui_clear(&scn->cdf); - goto exit; -} - -static void -scene_compute_aabb(struct s3d_scene* scn) -{ - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - struct instance* inst; - float lower[3], upper[3]; - - f3_splat(scn->lower, FLT_MAX); - f3_splat(scn->upper,-FLT_MAX); - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - - if(!geom->is_enabled) continue; - - switch(geom->type) { - case GEOM_MESH: mesh_compute_aabb(geom->data.mesh, lower, upper); break; - case GEOM_INSTANCE: - inst = geom->data.instance; - scene_compute_aabb(inst->scene); - /* Transform local scene AABB in world space */ - f33_mulf3(lower, inst->transform, inst->scene->lower); - f33_mulf3(upper, inst->transform, inst->scene->upper); - f3_add(lower, inst->transform + 9, lower); - f3_add(upper, inst->transform + 9, upper); - break; - default: FATAL("Unreachable code\n"); break; - } - f3_min(scn->lower, scn->lower, lower); - f3_max(scn->upper, scn->upper, upper); - } -} - -static FINLINE bool -operator < (const struct fltui& it, const float val) -{ - /* This operator is used by the std::lower_bound algorithm that returns an - * iterator to the first element that is not less than val while one expect - * an iterator on the first element that is not less *or equal* than val. - * That's why we use <= rather than < */ - return it.flt <= val; -} - -static res_T -scene_compute_nprims_cdf - (struct s3d_scene* scn, - const char store_cdf) -{ - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - size_t len; - unsigned nprims; - res_T res = RES_OK; - ASSERT(scn); - - darray_nprims_cdf_clear(&scn->nprims_cdf); - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - nprims = 0; - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - struct nprims_cdf cdf; - - if(!geom->is_enabled) continue; - - geom->scene_prim_id_offset = nprims; - switch(geom->type) { - case GEOM_MESH: - len = mesh_get_ntris(geom->data.mesh); - nprims += (unsigned)len; - break; - case GEOM_INSTANCE: - res = scene_compute_nprims_cdf(geom->data.instance->scene, store_cdf); - if(res != RES_OK) goto error; - len = darray_nprims_cdf_size_get(&geom->data.instance->scene->nprims_cdf); - if(len) { - nprims += darray_nprims_cdf_cdata_get - (&geom->data.instance->scene->nprims_cdf)[len - 1].nprims; - } - break; - default: FATAL("Unreachable code\n"); break; - } - - cdf.nprims = nprims; - cdf.ishape = *shape_id; - if(store_cdf && len) { - res = darray_nprims_cdf_push_back(&scn->nprims_cdf, &cdf); - if(res != RES_OK) goto error; - } - } -exit: - return res; -error: - darray_nprims_cdf_clear(&scn->nprims_cdf); - goto exit; -} - -static FINLINE bool -operator < (const struct nprims_cdf& it, const size_t iprim) -{ - /* This operator is used by the std::lower_bound algorithm that returns an - * iterator to the first element that is not less than iprim while one expect - * an iterator on the first element that is not less *or equal* than iprim. - * That's why we use <= rather than < */ - return it.nprims <= iprim; -} - -static res_T -scene_sync - (struct s3d_scene* scn, - const int session_mask, - const int internal) /* Is the synchronized session internal? */ -{ - res_T res = RES_OK; - ASSERT(scn); - - /* The scene is already synced with respect to te session mask */ - if((scn->session_mask & session_mask) == session_mask) - goto exit; - - if(scn->session_mask == 0) { /* Are the scene shapes already commited ? */ - struct htable_shape_iterator it, end; - - /* Commit the shape data to the back-end */ - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - while(!htable_shape_iterator_eq(&it, &end)) { - struct s3d_shape** pshape = htable_shape_iterator_data_get(&it); - struct s3d_shape* shape = *pshape; - - switch(shape->type) { - case GEOM_INSTANCE: - res = scene_register_instance(scn, shape, session_mask); - break; - case GEOM_MESH: - res = scene_register_mesh(scn, shape); - break; - default: FATAL("Unreachable code\n"); break; - } - if(res != RES_OK) { - struct htable_shape_iterator it2; - /* Clear the session on instances that are already registered into the - * current session to invalidate */ - htable_shape_begin(&scn->shapes, &it2); - while(!htable_shape_iterator_eq(&it2, &it)) { - struct s3d_shape** pshape = htable_shape_iterator_data_get(&it2); - struct s3d_shape* shape = *pshape; - htable_shape_iterator_next(&it2); - - if(shape->type == GEOM_INSTANCE) { - scene_session_clear(shape->data.instance->scene, 1); - } - } - goto error; - } - htable_shape_iterator_next(&it); - } - scene_compute_aabb(scn); - } - - /* Setup the scene for the S3D_TRACE session */ - if((session_mask & S3D_TRACE) != 0) { - res = scene_setup_embree(scn); - if(res != RES_OK) goto error; - } - /* Setup the scene for the S3D_SAMPLE session */ - if((session_mask & S3D_SAMPLE) != 0) { - res = scene_compute_cdf(scn); - if(res != RES_OK) goto error; - } - /* Setup the scene for the scene_primitive_id/S3D_GET_PRIMITIVE session */ - res = scene_compute_nprims_cdf(scn, (session_mask & S3D_GET_PRIMITIVE)!=0); - if(res != RES_OK) goto error; - - scn->session_mask |= session_mask; - -exit: - if(res == RES_OK) { - if(internal) { - ++scn->nsessions_internal; - } else { - ++scn->nsessions; - } - } - return res; -error: - goto exit; -} - static void scene_release(ref_T* ref) { struct s3d_scene* scn; struct s3d_device* dev; + struct list_node node, tmp; + ASSERT(ref); scn = CONTAINER_OF(ref, struct s3d_scene, ref); - S3D(scene_clear(scn)); dev = scn->dev; - if(scn->rtc_scn) rtcDeleteScene(scn->rtc_scn); + LIST_FOR_EACH_SAFE(node, scn->sessions) { + session_destroy(CONTAINER_OF(node, struct s3d_session, node)); + } htable_shape_release(&scn->shapes); - htable_geom_release(&scn->cached_geoms); - darray_geom_release(&scn->embree2geoms); - darray_fltui_release(&scn->cdf); - darray_nprims_cdf_release(&scn->nprims_cdf); MEM_RM(dev->allocator, scn); S3D(device_ref_put(dev)); } @@ -864,23 +79,11 @@ s3d_scene_create(struct s3d_device* dev, struct s3d_scene** out_scn) goto error; } htable_shape_init(dev->allocator, &scn->shapes); - htable_geom_init(dev->allocator, &scn->cached_geoms); - darray_geom_init(dev->allocator, &scn->embree2geoms); - darray_fltui_init(dev->allocator, &scn->cdf); - darray_nprims_cdf_init(dev->allocator, &scn->nprims_cdf); + SIG_INIT(&scn->sig_shape_detach); + list_init(&scn->sessions); ref_init(&scn->ref); S3D(device_ref_get(dev)); scn->dev = dev; - scn->session_mask = 0; - scn->rtc_scn = rtcDeviceNewScene - (dev->rtc, RTC_SCENE_DYNAMIC | RTC_SCENE_INCOHERENT | RTC_SCENE_ROBUST, - RTC_INTERSECT1 | RTC_INTERSECT4); - if(!scn->rtc_scn) { - res = RES_MEM_ERR; - goto error; - } - f3_splat(scn->lower, FLT_MAX); - f3_splat(scn->upper,-FLT_MAX); exit: if(out_scn) *out_scn = scn; @@ -989,12 +192,11 @@ s3d_scene_detach_shape(struct s3d_scene* scn, struct s3d_shape* shape) return RES_BAD_ARG; } - res = scene_clear_cached_geometry(scn, shape, FUNC_NAME); - if(res != RES_OK) return res; - n = htable_shape_erase(&scn->shapes, &shape_id); ASSERT(n == 1); (void)n; + SIG_BROADCAST(&scn->sig_shape_detach, scene_shape_cb_T, ARG2(scn, shape)); + S3D(shape_ref_put(shape)); return RES_OK; } @@ -1025,539 +227,4 @@ s3d_scene_clear(struct s3d_scene* scn) return RES_OK; } -res_T -s3d_scene_begin_session(struct s3d_scene* scn, const int session_mask) -{ - if(!scn) - return RES_BAD_ARG; - - if(!(session_mask&S3D_TRACE) - && !(session_mask&S3D_SAMPLE) - && !(session_mask&S3D_GET_PRIMITIVE)) { - log_error(scn->dev, "%s: no valid session is defined.\n", FUNC_NAME); - return RES_BAD_ARG; - } - return scene_sync(scn, session_mask, 0/*Not an internal session*/); -} - -res_T -s3d_scene_end_session(struct s3d_scene* scn) -{ - if(!scn) - return RES_BAD_ARG; - - if(!scn->nsessions) { - log_error(scn->dev, "%s: the scene has no active session.\n", FUNC_NAME); - return RES_BAD_OP; - } - ASSERT(scn->session_mask); - scene_session_clear(scn, 0/*Not an internal session*/); - return RES_OK; -} - -res_T -s3d_scene_get_session_mask(struct s3d_scene* scn, int* session_mask) -{ - if(!scn || !session_mask) return RES_BAD_ARG; - *session_mask = scn->session_mask; - return RES_OK; -} - -res_T -s3d_scene_trace_ray - (struct s3d_scene* scn, - const float org[3], - const float dir[3], - const float range[2], - void* ray_data, - struct s3d_hit* hit) -{ - struct ray_extended ray_ex; - if(!scn || !org || !dir || !range || !hit) - return RES_BAD_ARG; - if(!f3_is_normalized(dir)) { - log_error(scn->dev, - "%s: unnormalized ray direction {%g, %g, %g}.\n", - FUNC_NAME, SPLIT3(dir)); - return RES_BAD_ARG; - } - if((scn->session_mask & S3D_TRACE) == 0) { - log_error(scn->dev, - "%s: no active S3D_TRACE session on the submitted scene.\n", FUNC_NAME); - return RES_BAD_OP; - } - if(range[0] > range[1]) { /* Degenerated range <=> disabled ray */ - *hit = S3D_HIT_NULL; - return RES_OK; - } - - f3_set(ray_ex.org, org); - f3_set(ray_ex.dir, dir); - ray_ex.tnear = range[0]; - ray_ex.tfar = range[1]; - ray_ex.geomID = RTC_INVALID_GEOMETRY_ID; - ray_ex.primID = RTC_INVALID_GEOMETRY_ID; - ray_ex.instID = RTC_INVALID_GEOMETRY_ID; - ray_ex.mask = 0xFFFFFFFF; - ray_ex.time = 0.f; - ray_ex.scene = scn; - ray_ex.data = ray_data; - - rtcIntersect(scn->rtc_scn, ray_ex); - - hit_setup(scn, &ray_ex, hit); - - return RES_OK; -} - -res_T -s3d_scene_trace_rays - (struct s3d_scene* scn, - 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(!scn) 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_trace_ray(scn, 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_sample - (struct s3d_scene* scn, - const float u, - const float v, - const float w, - struct s3d_primitive* primitive, /* sampled primitive */ - float st[2]) -{ - struct geometry** pgeom; - struct geometry* geom; - const struct fltui* fltui_begin, *fltui_end, *fltui_found; - const float* flt_begin, *flt_end, *flt_found; - unsigned ishape; - float f; - res_T res = RES_OK; - - if(!scn || !primitive || !st) { - res = RES_BAD_ARG; - goto error; - } - /* Expecting canonic numbers */ - if(u < 0.f || u >= 1.f || v < 0.f || v >= 1.f || w < 0.f || w >= 1.f) { - log_error(scn->dev, - "%s: the submitted numbers are not canonical, i.e. they are not in [0, 1[.\n", - FUNC_NAME); - res = RES_BAD_ARG; - goto error; - } - if((scn->session_mask & S3D_SAMPLE) == 0) { - log_error(scn->dev, - "%s: no active S3D_SAMPLE session on the submitted scene.\n", - FUNC_NAME); - res = RES_BAD_OP; - goto error; - } - - /* Find the sampled geometry */ - if(darray_fltui_size_get(&scn->cdf) == 0) { - /* No geometry to sample */ - *primitive = S3D_PRIMITIVE_NULL; - goto exit; - } else if(darray_fltui_size_get(&scn->cdf) == 1) { - ishape = darray_fltui_cdata_get(&scn->cdf)[0].ui; - f = u * darray_fltui_cdata_get(&scn->cdf)[0].flt;/* Map u to the CDF bounds */ - } else { - fltui_begin = darray_fltui_cdata_get(&scn->cdf); - fltui_end = fltui_begin + darray_fltui_size_get(&scn->cdf); - f = u * fltui_end[-1].flt; /* Map u to the CDF bounds */ - fltui_found = std::lower_bound(fltui_begin, fltui_end, f); - ASSERT(fltui_found != fltui_end); - ishape = fltui_found->ui; - - /* Transform u to the geometry CDF bounds */ - if(fltui_found != fltui_begin) - f -= fltui_found[-1].flt; - } - pgeom = htable_geom_find(&scn->cached_geoms, &ishape); - ASSERT(pgeom); - geom = *pgeom; - - if(geom->type == GEOM_MESH) { - primitive->inst__ = NULL; - primitive->inst_id = S3D_INVALID_ID; - primitive->scene_prim_id = 0; - } else { - /* Find the sampled instantiated geometry */ - ASSERT(geom->type == GEOM_INSTANCE); - primitive->inst__ = geom; - primitive->inst_id = geom->name; - primitive->scene_prim_id = geom->scene_prim_id_offset; - if(darray_fltui_size_get(&geom->data.instance->scene->cdf) == 1) { - ishape = darray_fltui_cdata_get(&geom->data.instance->scene->cdf)[0].ui; - } else { - fltui_begin = darray_fltui_cdata_get(&geom->data.instance->scene->cdf); - fltui_end = fltui_begin + darray_fltui_size_get(&geom->data.instance->scene->cdf); - fltui_found = std::lower_bound(fltui_begin, fltui_end, f); - ASSERT(fltui_found != fltui_end); - ishape = fltui_found->ui; - - /* Transform u to the geometry CDF bounds */ - if(fltui_found != fltui_begin) - f -= fltui_found[-1].flt; - } - pgeom = htable_geom_find(&geom->data.instance->scene->cached_geoms, &ishape); - ASSERT(pgeom); - geom = *pgeom; - } - ASSERT(geom->type == GEOM_MESH); - - /* Find the sampled triangle */ - primitive->mesh__ = geom; - primitive->geom_id = geom->name; - primitive->scene_prim_id += geom->scene_prim_id_offset; - flt_begin = darray_float_cdata_get(&geom->data.mesh->cdf); - flt_end = flt_begin + darray_float_size_get(&geom->data.mesh->cdf); - flt_found = std::lower_bound(flt_begin, flt_end, f); - ASSERT(flt_found != flt_end); - - primitive->prim_id = (unsigned)(flt_found - flt_begin); - primitive->scene_prim_id += primitive->prim_id; - S3D(primitive_sample(primitive, v, w, st)); - -exit: - return res; -error: - goto exit; -} - -res_T -s3d_scene_get_primitive - (struct s3d_scene* scn, const unsigned iprim, struct s3d_primitive* prim) -{ - struct geometry** pgeom; - struct geometry* geom; - const struct nprims_cdf* begin, *end, *found; - size_t nprims; - unsigned ishape; - size_t i; - res_T res = RES_OK; - - if(!scn || !prim) { - res = RES_BAD_ARG; - goto error; - } - if((scn->session_mask & S3D_GET_PRIMITIVE) == 0) { - log_error(scn->dev, - "%s: no active S3D_GET_PRIMITIVE session on the submitted scene.\n", - FUNC_NAME); - res = RES_BAD_OP; - goto error; - } - S3D(scene_primitives_count(scn, &nprims)); - if(iprim >= nprims) { - log_error(scn->dev, - "%s: the primitive index %u exceeds the number of scene primitives %u.\n", - FUNC_NAME, iprim, (unsigned)nprims); - res = RES_BAD_ARG; - goto error; - } - - i = iprim; - if(darray_nprims_cdf_size_get(&scn->nprims_cdf) == 1) { - ishape = darray_nprims_cdf_cdata_get(&scn->nprims_cdf)[0].ishape; - } else { - begin = darray_nprims_cdf_cdata_get(&scn->nprims_cdf); - end = begin + darray_nprims_cdf_size_get(&scn->nprims_cdf); - found = std::lower_bound(begin, end, i); - ASSERT(found != end); - ishape = found->ishape; - if(found != begin) { - ASSERT(i >= found[-1].nprims); - i -= found[-1].nprims; - } - } - pgeom = htable_geom_find(&scn->cached_geoms, &ishape); - ASSERT(pgeom); - geom = *pgeom; - - if(geom->type == GEOM_MESH) { - prim->inst__ = NULL; - prim->inst_id = S3D_INVALID_ID; - prim->scene_prim_id = 0; - } else { - ASSERT(geom->type == GEOM_INSTANCE); - prim->inst__ = geom; - prim->inst_id = geom->name; - prim->scene_prim_id = geom->scene_prim_id_offset; - if(darray_nprims_cdf_size_get(&geom->data.instance->scene->nprims_cdf)==1) { - ishape = darray_nprims_cdf_cdata_get - (&geom->data.instance->scene->nprims_cdf)[0].ishape; - } else { - begin = darray_nprims_cdf_cdata_get - (&geom->data.instance->scene->nprims_cdf); - end = begin + darray_nprims_cdf_size_get - (&geom->data.instance->scene->nprims_cdf); - found = std::lower_bound(begin, end, i); - ASSERT(found != end); - ishape = found->ishape; - if(found != begin) { - ASSERT(i >= found[-1].nprims); - i -= found[-1].nprims; - } - } - pgeom = htable_geom_find(&geom->data.instance->scene->cached_geoms, &ishape); - ASSERT(pgeom); - geom = *pgeom; - } - ASSERT(geom->type == GEOM_MESH); - ASSERT(i < mesh_get_ntris(geom->data.mesh)); - prim->mesh__ = geom; - prim->geom_id = geom->name; - prim->prim_id = (unsigned)i; - prim->scene_prim_id += geom->scene_prim_id_offset; - prim->scene_prim_id += prim->prim_id; - -exit: - return res; -error: - goto exit; -} - -res_T -s3d_scene_primitives_count(struct s3d_scene* scn, size_t* prims_count) -{ - res_T res = RES_OK; - - if(!scn || !prims_count) { - res = RES_BAD_ARG; - goto error; - } - if(!scn->session_mask) { - log_error(scn->dev, - "%s: no active session on the submitted scene.\n", FUNC_NAME); - res = RES_BAD_OP; - goto error; - } - if((scn->session_mask & S3D_GET_PRIMITIVE) != 0) { - const size_t len = darray_nprims_cdf_size_get(&scn->nprims_cdf); - if(!len) { - *prims_count = 0; - } else { - *prims_count = darray_nprims_cdf_cdata_get(&scn->nprims_cdf)[len - 1].nprims; - } - } else { - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - size_t inst_count; - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - *prims_count = 0; - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - - if(!geom->is_enabled) continue; - - switch(geom->type) { - case GEOM_MESH: - *prims_count += mesh_get_ntris(geom->data.mesh); - break; - case GEOM_INSTANCE: - S3D(scene_primitives_count(geom->data.instance->scene, &inst_count)); - *prims_count += inst_count; - break; - default: FATAL("Unreachable code\n"); break; - } - } - } -exit: - return res; -error: - goto exit; -} - -res_T -s3d_scene_compute_area(struct s3d_scene* scn, float* out_area) -{ - float area; - res_T res = RES_OK; - - if(!scn || !out_area) { - res = RES_BAD_ARG; - goto error; - } - if(!scn->session_mask) { - log_error(scn->dev, - "%s: no active session on the submitted scene.\n", FUNC_NAME); - res = RES_BAD_OP; - goto error; - } - - if((scn->session_mask & S3D_SAMPLE) != 0) { - /* Retrieve the overall scene area from the scene cumulative distribution - * function. Note that the CDF stores the cumulative triangle area - * multiplied by 2; the real scene area is thus the CDF upper bound / 2 */ - size_t len = darray_fltui_size_get(&scn->cdf); - if(!len) { - area = 0.f; - } else { - area = darray_fltui_cdata_get(&scn->cdf)[len - 1].flt * 0.5f; - } - } else { - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - float inst_area; - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - area = 0.f; - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - - if(!geom->is_enabled) continue; - - switch(geom->type) { - case GEOM_MESH: - area += mesh_compute_area(geom->data.mesh); - break; - case GEOM_INSTANCE: - /* TODO take into account the instance scale factor */ - S3D(scene_compute_area(geom->data.instance->scene, &inst_area)); - area += inst_area; - break; - default: FATAL("Unreachable code\n"); break; - } - } - } - -exit: - if(out_area) *out_area = area; - return res; -error: - area = -1.f; - goto exit; -} - -res_T -s3d_scene_compute_volume(struct s3d_scene* scn, float* out_volume) -{ - struct htable_shape_iterator it, end; - struct geometry** pgeom; - struct geometry* geom; - float volume; - res_T res = RES_OK; - - if(!scn || !out_volume) { - res = RES_BAD_ARG; - goto error; - } - if(!scn->session_mask) { - log_error(scn->dev, - "%s: no active session on the submitted scene.\n", FUNC_NAME); - res = RES_BAD_OP; - goto error; - } - - htable_shape_begin(&scn->shapes, &it); - htable_shape_end(&scn->shapes, &end); - - volume = 0.f; - while(!htable_shape_iterator_eq(&it, &end)) { - const unsigned* shape_id = htable_shape_iterator_key_get(&it); - htable_shape_iterator_next(&it); - - pgeom = htable_geom_find(&scn->cached_geoms, shape_id); - ASSERT(pgeom != NULL); - geom = *pgeom; - - if(!geom->is_enabled) continue; - - switch(geom->type) { - case GEOM_MESH: - volume += mesh_compute_volume(geom->data.mesh, geom->flip_surface); - break; - case GEOM_INSTANCE: - volume += instance_compute_volume(geom->data.instance, geom->flip_surface); - break; - default: FATAL("Unreachable code\n"); break; - } - } - - if(volume < 0.f) { - log_warning(scn->dev, -"%s:\n" -"\tthe volume is negative. The scene shapes might not represent closed 2D\n" -"\tmanifold volumes, or their surface normal might not point inward the volume.\n", - FUNC_NAME); - } - -exit: - if(out_volume) *out_volume = volume; - return res; -error: - volume = -1.f; - goto exit; -} - -res_T -s3d_scene_get_aabb(struct s3d_scene* scn, float lower[3], float upper[3]) -{ - if(!scn || !lower || !upper) return RES_BAD_ARG; - if(!scn->session_mask) { - log_error(scn->dev, - "%s: no active session on the submitted scene.\n", FUNC_NAME); - return RES_BAD_OP; - } - f3_set(lower, scn->lower); - f3_set(upper, scn->upper); - return RES_OK; -} - -res_T -s3d_scene_get_device(struct s3d_scene* scn, struct s3d_device** dev) -{ - if(!scn || !dev) return RES_BAD_ARG; - *dev = scn->dev; - return RES_OK; -} diff --git a/src/s3d_scene_c.h b/src/s3d_scene_c.h @@ -33,34 +33,10 @@ #ifndef S3D_SCENE_C_H #define S3D_SCENE_C_H -#include "s3d_backend.h" - -#include <rsys/dynamic_array.h> #include <rsys/hash_table.h> #include <rsys/list.h> #include <rsys/ref_count.h> - -/* - * The geometry pointers must be initialized to NULL in order to define - * which pointers are valid or not - */ -static FINLINE void -geom_ptr_init__(struct mem_allocator* alloc, struct geometry** geom) -{ - (void)alloc; *geom = NULL; -} - -/* Generate the darray_geom dynamic array */ -#define DARRAY_NAME geom -#define DARRAY_DATA struct geometry* -#define DARRAY_FUNCTOR_INIT geom_ptr_init__ -#include <rsys/dynamic_array.h> - -/* Generate the htable_geom hash table */ -#define HTABLE_NAME geom -#define HTABLE_DATA struct geometry* -#define HTABLE_KEY unsigned /* Id of the shape */ -#include <rsys/hash_table.h> +#include <rsys/signal.h> /* Generate the htable_shape hash table */ #define HTABLE_NAME shape @@ -68,35 +44,17 @@ geom_ptr_init__(struct mem_allocator* alloc, struct geometry** geom) #define HTABLE_KEY unsigned /* Id of the shape */ #include <rsys/hash_table.h> -/* Generate the darray_fltui dynamic array */ -struct fltui { float flt; unsigned ui; }; -#define DARRAY_NAME fltui -#define DARRAY_DATA struct fltui -#include <rsys/dynamic_array.h> - -/* Generate the darray_geom_nprims array */ -struct nprims_cdf { unsigned nprims, ishape; }; -#define DARRAY_NAME nprims_cdf -#define DARRAY_DATA struct nprims_cdf -#include <rsys/dynamic_array.h> +/* Declare the scene_shape_cb_T callback data type */ +CLBK(scene_shape_cb_T, ARG2 + (const struct s3d_scene* scn, + const struct s3d_shape* shape)); struct s3d_scene { struct htable_shape shapes; /* List of attached shapes */ - struct htable_geom cached_geoms; /* Cached shape geometries */ - struct darray_geom embree2geoms; /* Shape geometries index by embree id */ - struct darray_fltui cdf; /* Unormalized CDF */ - struct darray_nprims_cdf nprims_cdf; - - float lower[3], upper[3]; /* AABB of the scene */ - size_t instances_count; /* # instances in the scene */ + struct list_node sessions; /* Pool of available s3d_sessions */ - RTCScene rtc_scn; /* Embree scene */ - - int session_mask; /* Combination of enum s3d_session_flag */ - size_t nsessions; /* # sessions active onto the scene */ - /* # sessions internally enabled (i.e. through instancing) */ - size_t nsessions_internal; + signal_T sig_shape_detach; struct s3d_device* dev; ref_T ref; diff --git a/src/s3d_session.c b/src/s3d_session.c @@ -0,0 +1,1408 @@ +/* Copyright (C) |Meso|Star> 2015-2016 (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_scene_c.h" +#include "s3d_session_c.h" +#include "s3d_shape_c.h" + +#include <rsys/float3.h> +#include <rsys/float33.h> +#include <rsys/mem_allocator.h> + +#include <algorithm> + +struct ray_extended : public RTCRay { + struct s3d_session* session; + void* data; /* User defined data */ +}; + +/******************************************************************************* + * Helper functions + ******************************************************************************/ +static FINLINE bool +operator < (const struct fltui& it, const float val) +{ + /* This operator is used by the std::lower_bound algorithm that returns an + * iterator to the first element that is not less than val while one expect + * an iterator on the first element that is not less *or equal* than val. + * That's why we use <= rather than < */ + return it.flt <= val; +} + +static FINLINE bool +operator < (const struct nprims_cdf& it, const size_t iprim) +{ + /* This operator is used by the std::lower_bound algorithm that returns an + * iterator to the first element that is not less than iprim while one expect + * an iterator on the first element that is not less *or equal* than iprim. + * That's why we use <= rather than < */ + return it.nprims <= iprim; +} + +static INLINE void +session_destroy_geometry(struct s3d_session* session, struct geometry* geom) +{ + ASSERT(geom); + if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { + rtcDeleteGeometry(session->rtc_scn, geom->irtc); + geom->irtc = RTC_INVALID_GEOMETRY_ID; + session->rtc_delete_geometry = 1; /* Notify the scene upd */ + } + geometry_ref_put(geom); +} + +static void +on_shape_detach + (const struct s3d_scene* scn, + const struct s3d_shape* shape, + void* data) +{ + struct geometry** pgeom; + struct geometry* geom; + struct s3d_session* session = data; + unsigned shape_id; + ASSERT(scn && shape && data); + + S3D(shape_get_id(shape, &shape_id)); + pgeom = htable_geom_find(session->cached_geoms, &shape_id); + + /* The session did not register a geometry for this shape. Ignore the signal */ + if(!pgeom) return; + + geom = *pgeom; + if(session->mask == 0) { + /* The session is NOT in use. Directly rm the cached geometry */ + session_destroy_geometry(session, geom); + } else { + /* The session is in use. Delay the deletion of the cached geometry */ + res_T res = darray_uint_push_back(&session->detached_shapes, &shape_id); + if(res != RES_OK) FATAL("Insufficient memory.\n"); + } +} + +static INLINE void +hit_setup(struct s3d_session* session, const RTCRay* ray, struct s3d_hit* hit) +{ + float w; + char flip_surface = 0; + + ASSERT(session && hit && ray); + + if((unsigned)ray->geomID == RTC_INVALID_GEOMETRY_ID) { /* No hit */ + *hit = S3D_HIT_NULL; + return; + } + + f3_set(hit->normal, ray->Ng); + hit->distance = ray->tfar; + + hit->uv[0] = ray->u; + hit->uv[1] = ray->v; + 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; + + if((unsigned)ray->instID == RTC_INVALID_GEOMETRY_ID) { + struct geometry* geom_mesh; + ASSERT((unsigned)ray->geomID < darray_geom_size_get(&session->embree2geoms)); + geom_mesh = darray_geom_data_get(&session->embree2geoms)[ray->geomID]; + hit->prim.mesh__ = geom_mesh; + hit->prim.inst__ = NULL; + hit->prim.prim_id = ray->primID; + hit->prim.geom_id = geom_mesh->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_mesh->scene_prim_id_offset; /* Scene space */ + + } else { /* The hit shape is instantiated */ + /* Retrieve the hit instance */ + struct geometry* geom_inst; + struct geometry* geom_mesh; + ASSERT((unsigned)ray->instID < darray_geom_size_get(&session->embree2geoms)); + geom_inst = darray_geom_data_get(&session->embree2geoms)[ray->instID]; + geom_mesh = scene_get_mesh(geom_inst->data.instance->scene, ray->geomID); + hit->prim.mesh__ = geom_mesh; + hit->prim.inst__ = geom_inst; + hit->prim.prim_id = ray->primID; + hit->prim.geom_id = geom_mesh->name; + hit->prim.inst_id = geom_inst->name; + hit->prim.scene_prim_id = /* Compute the "scene space" */ + hit->prim.prim_id /* Mesh space */ + + geom_mesh->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); + } + ASSERT(hit->prim.mesh__); + ASSERT(((struct geometry*)hit->prim.mesh__)->type == GEOM_MESH); + + /* Flip geometric normal with respect to the flip surface flag */ + flip_surface ^= ((struct geometry*)hit->prim.mesh__)->flip_surface; + if(flip_surface) f3_minus(hit->normal, hit->normal); +} + +/* Wrapper between an Embree and a Star-3D filter function */ +static void +filter_wrapper(void* user_ptr, RTCRay& ray) +{ + struct s3d_hit hit; + struct hit_filter* filter = (struct hit_filter*)user_ptr; + struct ray_extended* ray_ex = static_cast<struct ray_extended*>(&ray); + + hit_setup(ray_ex->session, &ray, &hit); + if(filter->func(&hit, ray_ex->org, ray_ex->dir, ray_ex->data, filter->data)) { + /* Discard the intersection */ + ray.geomID = RTC_INVALID_GEOMETRY_ID; + } +} + +static res_T +embree_geometry_register + (struct s3d_session* session, + struct geometry* geom) +{ + ASSERT(session && geom && (geom->type==GEOM_MESH || geom->type==GEOM_INSTANCE)); + + /* Create the Embree geometry if it is not valid */ + if(geom->irtc == RTC_INVALID_GEOMETRY_ID) { + switch(geom->type) { + case GEOM_MESH: + geom->irtc = rtcNewTriangleMesh(session->rtc_scn, RTC_GEOMETRY_DYNAMIC, + mesh_get_ntris(geom->data.mesh), mesh_get_nverts(geom->data.mesh)); + break; + case GEOM_INSTANCE: + geom->irtc = rtcNewInstance + (session->rtc_scn, geom->data.instance->session->rtc_scn); + break; + default: FATAL("Unreachable code\n"); break; + } + if(geom->irtc == RTC_INVALID_GEOMETRY_ID) + return RES_UNKNOWN_ERR; + } + + /* Register the embree geometry in the embree2geoms associative array */ + if(geom->irtc >= darray_geom_size_get(&session->embree2geoms)) { + const res_T res = darray_geom_resize(&session->embree2geoms, geom->irtc+1); + if(res != RES_OK) { + rtcDeleteGeometry(session->rtc_scn, geom->irtc); + geom->irtc = RTC_INVALID_GEOMETRY_ID; + return res; + } + } + darray_geom_data_get(&session->embree2geoms)[geom->irtc] = geom; + return RES_OK; +} + +static INLINE void +embree_geometry_setup_positions + (struct s3d_session* session, struct geometry* geom) +{ + ASSERT(session && geom && geom->type == GEOM_MESH); + ASSERT(geom->irtc != RTC_INVALID_GEOMETRY_ID); + rtcSetBuffer(session->rtc_scn, geom->irtc, RTC_VERTEX_BUFFER, + mesh_get_pos(geom->data.mesh), 0, sizeof(float[3])); + rtcUpdateBuffer(session->rtc_scn, geom->irtc, RTC_VERTEX_BUFFER); +} + +static INLINE void +embree_geometry_setup_indices + (struct s3d_session* session, struct geometry* geom) +{ + ASSERT(session && geom && geom->type == GEOM_MESH); + ASSERT(geom->irtc != RTC_INVALID_GEOMETRY_ID); + rtcSetBuffer(session->rtc_scn, geom->irtc, RTC_INDEX_BUFFER, + mesh_get_ids(geom->data.mesh), 0, sizeof(uint32_t[3])); + rtcUpdateBuffer(session->rtc_scn, geom->irtc, RTC_INDEX_BUFFER); +} + +static INLINE void +mebree_geometry_setup_enable_state + (struct s3d_scene* session, struct geometry* geom) +{ + ASSERT(session && geom); + if(geom->is_enabled) { + rtcEnable(session->rtc_scn, geom->irtc); + } else { + rtcDisable(session->rtc_scn, geom->irtc); + } +} + +static INLINE void +embree_geometry_setup_filter_function + (struct s3d_session* session, struct geometry* geom) +{ + ASSERT(session && geom && geom->irtc != RTC_INVALID_GEOMETRY_ID); + ASSERT(geom->type == GEOM_MESH); + + if(!geom->data.mesh->filter.func) { + rtcSetIntersectionFilterFunction(session->rtc_scn, geom->irtc, NULL); + } else { + rtcSetIntersectionFilterFunction(session->rtc_scn, geom->irtc, filter_wrapper); + rtcSetUserData(session->rtc_scn, geom->irtc, &geom->data.mesh->filter); + } +} + +static INLINE void +embree_geometry_setup_transform + (struct s3d_session* session, struct geometry* geom) +{ + ASSERT(session && geom && geom->irtc != RTC_INVALID_GEOMETRY_ID); + ASSERT(geom->type == GEOM_INSTANCE); + rtcSetTransform + (session->rtc_scn, + geom->irtc, + RTC_MATRIX_COLUMN_MAJOR, + geom->data.instance->transform); +} + + +static INLINE res_T +session_setup_embree(struct s3d_session* session) +{ + struct htable_geom_iterator it, end; + int rtc_outdated = session->rtc_delete_geometry; + res_T res = RES_OK; + ASSERT(session); + + htable_shape_begin(&session->cached_geoms, &it); + htable_shape_end(&session->cached_geoms, &end); + + while(!htable_geom_iterator_eq(&it, &end)) { + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + + htable_geom_iterator_next(&it); + + /* Define whether or not the embree scene is outdated */ + if(geom->embree_outdated_mask) rtc_outdated = 1; + + /* Register the embree geometry */ + res = embree_geometry_register(session, geom); + if(res != RES_OK) goto error; + + /* Flush the embree geometry states */ + if((geom->embree_outdated_mask & EMBREE_VERTICES) != 0) + embree_geometry_setup_positions(session, geom); + if((geom->embree_outdated_mask & EMBREE_INDICES) != 0) + embree_geometry_setup_indices(session, geom); + if((geom->embree_outdated_mask & EMBREE_ENABLE) != 0) + embree_geometry_setup_enable_state(session, geom); + if((geom->embree_outdated_mask & EMBREE_FILTER_FUNCTION) != 0) + embree_geometry_setup_filter_function(session, geom); + if((geom->embree_outdated_mask & EMBREE_TRANSFORM) != 0) + embree_embree_geometry_setup_transform(session, geom); + + geom->embree_outdated_mask = 0; + } + + /* Commit the embree changes */ + if(rtc_outdated) { + rtcCommit(session->rtc_session); + session->rtc_delete_geometry = 0; + } + +exit: + return res; +error: + darray_geom_clear(&session->embree2geoms); + goto exit; +} + +static res_T +session_register_mesh + (struct s3d_session* session, + struct s3d_shape* shape) +{ + struct geometry** pgeom = NULL; + struct geometry* geom = NULL; + size_t iattr; + unsigned shape_id; + + res_T res = RES_OK; + ASSERT(session && shape && shape->type == GEOM_MESH); + + /* Retrieve the cached geometry */ + S3D(shape_get_id(shape, &shape_id)); + pgeom = htable_geom_find(&session->cached_geoms, &shape_id); + if(pgeom) { + geom = *pgeom; + } else { + res = geometry_create(session->scn->dev, &geom); + if(res != RES_OK) goto error; + res = mesh_create(session->scn->dev, &geom->data.mesh); + if(res != RES_OK) goto error; + geom->type = GEOM_MESH; + res = htable_geom_set(&session->cached_geoms, &shape_id, &geom); + if(res != RES_OK) goto error; + geom->name = shape->id.index; + } + + /* Discard the geometry that is not geometrically valid */ + if(!shape->data.mesh->indices || !shape->data.mesh->attribs[S3D_POSITION]) { + if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { + rtcDeleteGeometry(session->rtc_scn, geom->irtc); + geom->irtc = RTC_INVALID_GEOMETRY_ID; + } + mesh_clear(geom->data.mesh); + goto exit; + } + + /* Get a reference onto the shape mesh indices */ + if(geom->data.mesh->indices != shape->data.mesh->indices) { + geom->embree_outdated_mask |= EMBREE_INDICES; + if(geom->data.mesh->indices) { /* Release the previous index buffer */ + index_buffer_ref_put(geom->data.mesh->indices); + geom->data.mesh->indices = NULL; + } + ASSERT(shape->data.mesh->indices); + index_buffer_ref_get(shape->data.mesh->indices); + geom->data.mesh->indices = shape->data.mesh->indices; + } + + /* Get a reference onto the shape mesh attribs */ + FOR_EACH(iattr, 0, S3D_ATTRIBS_COUNT__) { + geom->embree_outdated_mask |= EMBREE_VERTICES; + if(geom->data.mesh->attribs[iattr] == shape->data.mesh->attribs[iattr]) + continue; + + if(geom->data.mesh->attribs[iattr]) { /* Release the previous buffer */ + vertex_buffer_ref_put(geom->data.mesh->attribs[iattr]); + geom->data.mesh->attribs[iattr] = NULL; + } + if(!shape->data.mesh->attribs[iattr]) + continue; + + vertex_buffer_ref_get(shape->data.mesh->attribs[iattr]); + geom->data.mesh->attribs[iattr] = shape->data.mesh->attribs[iattr]; + geom->data.mesh->attribs_type[iattr] = shape->data.mesh->attribs_type[iattr]; + } + + /* Update the enable flag */ + if(geom->is_enabled != shape->is_enabled) { + geom->is_enabled = shape->is_enabled; + geom->embree_outdated_mask |= EMBREE_ENABLE; + } + + /* Update the filter function */ + if(geom->data.mesh->filter.func != shape->data.mesh->filter.func + || geom->data.mesh->filter.data != shape->data.mesh->filter.data) { + geom->data.mesh->filter = shape->data.mesh->filter; + geom->embree_outdated_mask |= EMBREE_FILTER_FUNCTION; + } + + if(geom->irtc != RTC_INVALID_GEOMETRY_ID) { + struct index_buffer* shape_ids = shape->data.mesh->indices; + struct index_buffer* geom_ids = geom->data.mesh->indices; + struct vertex_buffer* shape_verts = shape->data.mesh->attribs[S3D_POSITION]; + struct vertex_buffer* geom_verts = geom->data.mesh->attribs[S3D_POSITION]; + const size_t shape_nids = darray_u32_size_get(&shape_ids->data); + const size_t geom_nids = darray_u32_size_get(&geom_ids->data); + const size_t shape_nverts = darray_float_size_get(&shape_verts->data); + const size_t geom_nverts = darray_float_size_get(&geom_verts->data); + + /* The shape mesh was resize => the Embree geometry is no more valid */ + if(shape_nids != geom_nids || shape_nverts != geom_nverts) { + rtcDeleteGeometry(session->rtc_scn, geom->irtc); + geom->irtc = RTC_INVALID_GEOMETRY_ID; + } + } + + geom->flip_surface = shape->flip_surface; + +exit: + return res; +error: + goto exit; +} + +static res_T +session_register_instance + (struct s3d_session* session, + struct s3d_shape* shape, + const int session_mask) +{ + struct geometry** pgeom = NULL; + struct geometry* geom = NULL; + struct s3d_session* instance_session = NULL; + unsigned shape_id; + res_T res = RES_OK; + ASSERT(session && shape && shape->type == GEOM_INSTANCE); + + /* The instance cannot contain instances, i.e. one instancing level is + * supported */ + if(shape->data.instance->scene->instances_count != 0) { + res = RES_BAD_ARG; + goto error; + } + + /* Recursively create a session on the scene to instantiate */ + res = s3d_scene_create_session + (shape->data.instance->scene, session_mask, &instance_session); + if(res != RES_OK) goto error; + + S3D(shape_get_id(shape, &shape_id)); + pgeom = htable_geom_find(&session->cached_geoms, &shape_id); + /* Create the scene instance of the geometry if necessary */ + if(pgeom) { + geom = *pgeom; + } else { + res = geometry_create(session->scn->dev, &geom); + if(res != RES_OK) goto error; + geom->type = GEOM_INSTANCE; + res = instance_create(shape->data.instance->scene, &geom->data.instance); + if(res != RES_OK) goto error; + res = htable_geom_set(&session->cached_geoms, &shape_id, &geom); + if(res != RES_OK) goto error; + geom->name = shape->id.index; + } + ASSERT(geom->data.instance->scene == shape->data.instance->scene); + geom->data.instance->session = instance_session; + + /* Update the Embree instance transformation if necessary */ + if(!f33_eq(shape->data.instance->transform, geom->data.instance->transform) + || !f3_eq(shape->data.instance->transform+9, geom->data.instance->transform+9)) { + geom->embree_outdated_mask |= EMBREE_TRANSFORM; + f33_set(geom->data.instance->transform, shape->data.instance->transform); + f3_set(geom->data.instance->transform+9, shape->data.instance->transform+9); + } + + /* Update the enable flag */ + if(geom->is_enabled != shape->is_enabled) { + geom->is_enabled = shape->is_enabled; + geom->embree_outdated_mask |= EMBREE_TRANSFORM; + } + + geom->flip_surface = shape->flip_surface; + +exit: + return res; +error: + goto exit; +} + +static res_T +session_compute_cdf(struct s3d_session* session) +{ + struct htable_shape_iterator it, end; + size_t len; + float area = 0.f; + res_T res = RES_OK; + ASSERT(session); + ASSERT(darray_fltui_size_get(&session->cdf) == 0); + + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->cached_geoms, &end); + + while(!htable_geom_iterator_eq(&it, &end)) { + const unsigned* shape_id = htable_geom_iterator_key_get(&it); + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + struct fltui fltui; + + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + switch(geom->type) { + case GEOM_MESH: + res = mesh_compute_cdf(geom->data.mesh); + if(res != RES_OK) goto error; + len = darray_float_size_get(&geom->data.mesh->cdf); + if(len) { + area += darray_float_cdata_get(&geom->data.mesh->cdf)[len - 1]; + } + break; + case GEOM_INSTANCE: + res = scene_compute_cdf(geom->data.instance->scene); + if(res != RES_OK) goto error; + len = darray_fltui_size_get(&geom->data.instance->scene->cdf); + if(len) { + area += darray_fltui_cdata_get + (&geom->data.instance->scene->cdf)[len - 1].flt; + } + break; + default: FATAL("Unreachable code\n"); break; + } + fltui.ui = *shape_id; + fltui.flt = area; + if(len) { + res = darray_fltui_push_back(&session->cdf, &fltui); + if(res != RES_OK) goto error; + } + } +exit: + return res; +error: + darray_fltui_clear(&session->cdf); + goto exit; +} + +static res_T +session_compute_nprims_cdf + (struct s3d_session* session, + const char store_cdf) +{ + struct htable_shape_iterator it, end; + size_t len; + unsigned nprims; + res_T res = RES_OK; + ASSERT(session); + + darray_nprims_cdf_clear(&session->nprims_cdf); + + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->shapes, &end); + + nprims = 0; + while(!htable_geom_iterator_eq(&it, &end)) { + const unsigned* shape_id = htable_geom_iterator_key_get(&it); + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + geom->scene_prim_id_offset = nprims; + switch(geom->type) { + case GEOM_MESH: + len = mesh_get_ntris(geom->data.mesh); + nprims += (unsigned)len; + break; + case GEOM_INSTANCE: + res = scene_compute_nprims_cdf(geom->data.instance->scene, store_cdf); + if(res != RES_OK) goto error; + len = darray_nprims_cdf_size_get(&geom->data.instance->scene->nprims_cdf); + if(len) { + nprims += darray_nprims_cdf_cdata_get + (&geom->data.instance->scene->nprims_cdf)[len - 1].nprims; + } + break; + default: FATAL("Unreachable code\n"); break; + } + + cdf.nprims = nprims; + cdf.ishape = *shape_id; + if(store_cdf && len) { + res = darray_nprims_cdf_push_back(&scn->nprims_cdf, &cdf); + if(res != RES_OK) goto error; + } + } +exit: + return res; +error: + darray_nprims_cdf_clear(&scn->nprims_cdf); + goto exit; +} + +static void +session_compute_scene_aabb(struct s3d_session* session) +{ + struct htable_shape_iterator it, end; + float lower[3], upper[3]; + + ASSERT(session->lower[0] == FLT_MAX && session->upper[0] = -FLT_MAX); + ASSERT(session->lower[1] == FLT_MAX && session->upper[1] = -FLT_MAX); + ASSERT(session->lower[2] == FLT_MAX && session->upper[2] = -FLT_MAX); + + htable_shape_begin(&session->cached_geoms, &it); + htable_shape_end(&session->cached_geoms, &end); + + while(!htable_shape_iterator_eq(&it, &end)) { + struct instance* inst; + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + switch(geom->type) { + case GEOM_MESH: mesh_compute_aabb(geom->data.mesh, lower, upper); break; + case GEOM_INSTANCE: + inst = geom->data.instance; + scene_compute_aabb(inst->scene); + /* Transform local scene AABB in world space */ + f33_mulf3(lower, inst->transform, inst->scene->lower); + f33_mulf3(upper, inst->transform, inst->scene->upper); + f3_add(lower, inst->transform + 9, lower); + f3_add(upper, inst->transform + 9, upper); + break; + default: FATAL("Unreachable code\n"); break; + } + f3_min(session->lower, session->lower, lower); + f3_max(session->upper, session->upper, upper); + } +} + +static void +session_sync + (struct s3d_session* session, + const int mask) +{ + struct htable_shape_iterator it, end; + res_T res = RES_OK; + + ASSERT(session); + ASSERT((mask & (S3D_TRACE|S3D_SAMPLE|S3D_GET_PRIMITIVE)) != 0); + + /* Commit the scene shape to the session */ + htable_shape_begin(&session->scn->shapes, &it); + htable_shape_end(&session->scn->shapes, &end); + while(!htable_shape_iterator_eq(&it, &end)) { + struct s3d_shape** pshape = htable_shape_iterator_data_get(&it); + struct s3d_shape* shape = *pshape; + + switch(shape->type) { + case GEOM_INSTANCE: + res = session_register_instance(session, shape, mask); + break; + case GEOM_MESH: + res = session_register_mesh(session, shape); + break; + default: FATAL("Unreachable code\n"); break; + } + htable_shape_iterator_next(&it); + } + + session_compute_scene_aabb(session); + + /* Setup the scene for the S3D_TRACE session */ + if((mask & S3D_TRACE) != 0) { + res = session_setup_embree(session); + if(res != RES_OK) goto error; + } + /* Setup the scene for the S3D_SAMPLE session */ + if((mask & S3D_SAMPLE) != 0) { + res = session_compute_cdf(session); + if(res != RES_OK) goto error; + } + /* Setup the scene for the scene_primitive_id/S3D_GET_PRIMITIVE session */ + res = scene_compute_nprims_cdf(scn, (mask & S3D_GET_PRIMITIVE)!=0); + if(res != RES_OK) goto error; + + session->mask = mask; + +exit: + return res; +error: + goto exit; +} + +static res_T +session_create(struct s3d_scene* scn, struct s3d_session** out_session) +{ + struct s3d_session* session = NULL; + res_T res = RES_OK; + ASSERT(scn && out_session); + + if(!is_list_empty(&scn->sessions)) { + /* Retrieve an already allocated session */ + session = CONTAINER_OF(list_head(&scn->sessions), struct s3d_session, node); + list_del(&session->node); + ref_get(&session->ref); + } else { + const int rtc_mask = + RTC_SCENE_DYNAMIC + | RTC_SCENE_INCOHERENT + | RTC_SCENE_ROBUST + | RTC_INTERSECT1; + + session = (struct s3d_session*)MEM_CALLOC + (scn->dev->allocator, 1, sizeof(struct s3d_session)); + if(!session) { + res = RES_MEM_ERR; + goto error; + } + list_init(&session->node); + htable_geom_init(scn->dev->allocator, &session->cached_geoms); + darray_geom_init(scn->dev->allocator, &session->embree2geoms); + darray_fltui_init(scn->dev->allocator, &session->cdf); + darray_nprims_cdf_init(scn->dev->allocator, &session->nprims_cdf); + f3_splat(session->lower, FLT_MAX); + f3_splat(session->upper,-FLT_MAX); + ref_init(&session->ref); + + session->rtc_scn = rtcDeviceNewScene(scn->dev->rtc, rtc_mask); + if(!session->rtc_scn) { + res = RES_MEM_ERR; + goto error; + } + + CLBK_SETUP(&session->on_shape_detach_cb, on_shape_detach, session); + SIG_CONNECT_CLBK(&scn->sig_shape_detach, &session->on_shape_detach_cb); + } + S3D(scene_ref_get(scn)); + session->scn = scn; +exit: + *out_session = session; + return res; +error: + if(session) { + S3D(session_ref_put(session)); + session = NULL; + } + goto exit; +} + +static void +session_release(ref_T* ref) +{ + struct htable_geom_iterator it, end; + struct s3d_session* session = CONTAINER_OF(ref, struct s3d_session, ref); + struct s3d_scene* scn; + size_t i, n; + ASSERT(ref); + + scn = session->scn; + + /* Release the session of the instances */ + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->cached_geoms, &end); + while(!htable_geom_iterator_eq(&it, &end)) { + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + htable_geom_iterator_next(&it); + + if(geom->type == GEOM_INSTANCE) { + if(geom->data.instance->session) { + S3D(session_ref_put(geom->data.instance->session)); + geom->data.instance->session = NULL; + } + } + } + + /* Remove the geometry of the shapes detached while the session was active */ + n = darray_uint_size_get(&session->detached_shapes); + FOR_EACH(i, 0, n) { + const unsigned spape_id = darray_uint_cdata_get(&session->detached_shapes); + struct geomtry** pgeom = htable_geom_find(&session->cached_geoms, &shape_id); + struct geomtry* geom = *pgeom; + session_destroy_geometry(session, geom); + } + darray_uint_clear(&session->detached_shapes); + + /* Clear the session data structures excepted the cache of geometries that + * will be used to speed up the future session creation */ + darray_geom_clear(&session->embree2geoms); + darray_fltui_clear(&session->cdf); + darray_nprims_cdf_clear(&session->nprims_cdf); + darray_session_clear(&session->instances); + f3_splat(session->lower, FLT_MAX); + f3_splat(session->upper,-FLT_MAX); + session->mask = NULL; + + /* Do not physically release the memory space of the session. Add it to the + * available sessions pool of the scene */ + list_add(scn->sessions, &session->node); + S3D(scene_ref_put(scn)); +} + +/******************************************************************************* + * Exported functions + ******************************************************************************/ +res_T +s3d_session_create + (struct s3d_scene* scn, + const int session_mask, + struct s3d_session** out_session) +{ + struct s3d_session* session = NULL; + res_T res = RES_OK; + + if(!scn) { + res = RES_BAD_ARG; + goto error; + } + + if(!(session_mask & S3D_TRACE) + && !(session_mask & S3D_SAMPLE) + && !(session_mask & S3D_GET_PRIMITIVE)) { + log_error(scn->dev, "%s: no valid session is defined.\n", FUNC_NAME); + res = RES_BAD_ARG; + goto error; + } + + res = session_create(scn, &session); + if(res != RES_OK) goto error; + + res = session_sync(session, session_mask); + if(res != RES_OK) goto error; + +exit: + if(out_session) *out_session = session; + return res; +error: + if(session) { + S3D(session_ref_put(session)); + session = NULL; + } + goto exit; +} + +res_T +s3d_session_ref_get(struct s3d_session* session) +{ + if(!session) return RES_BAD_ARG; + ref_get(&session->ref); + return RES_OK; +} + +res_T +s3d_session_ref_put(struct s3d_session* session) +{ + if(!session) return RES_BAD_ARG; + ref_put(&session->ref, session_release); + return RES_OK; +} + +res_T +s3d_session_get_mask(struct s3d_session* session, int* mask) +{ + if(!session || !mask) return RES_BAD_ARG; + *mask = session->mask; + return RES_OK; +} + +res_T +s3d_session_trace_ray + (struct s3d_session* session, + const float org[3], + const float dir[3], + const float range[2], + void* ray_data, + struct s3d_hit* hit) +{ + struct ray_extended ray_ex; + if(!session || !org || !dir || !range || !hit) + return RES_BAD_ARG; + if(!f3_is_normalized(dir)) { + log_error(session->dev, + "%s: unnormalized ray direction {%g, %g, %g}.\n", + FUNC_NAME, SPLIT3(dir)); + return RES_BAD_ARG; + } + if((session->mask & S3D_TRACE) == 0) { + log_error(session->scn->dev, + "%s: no active S3D_TRACE session on the submitted session.\n", FUNC_NAME); + return RES_BAD_OP; + } + if(range[0] > range[1]) { /* Degenerated range <=> disabled ray */ + *hit = S3D_HIT_NULL; + return RES_OK; + } + + f3_set(ray_ex.org, org); + f3_set(ray_ex.dir, dir); + ray_ex.tnear = range[0]; + ray_ex.tfar = range[1]; + ray_ex.geomID = RTC_INVALID_GEOMETRY_ID; + ray_ex.primID = RTC_INVALID_GEOMETRY_ID; + ray_ex.instID = RTC_INVALID_GEOMETRY_ID; + ray_ex.mask = 0xFFFFFFFF; + ray_ex.time = 0.f; + ray_ex.session = session; + ray_ex.data = ray_data; + + rtcIntersect(session->rtc_scn, ray_ex); + + hit_setup(session, &ray_ex, hit); + + return RES_OK; +} + +res_T +s3d_session_trace_rays + (struct s3d_session* session, + 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(!session) 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_session_trace_ray(session, 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_session_sample + (struct s3d_session* session, + const float u, + const float v, + const float w, + struct s3d_primitive* primitive, /* sampled primitive */ + float st[2]) +{ + struct geometry** pgeom; + struct geometry* geom; + const struct fltui* fltui_begin, *fltui_end, *fltui_found; + const float* flt_begin, *flt_end, *flt_found; + unsigned ishape; + float f; + res_T res = RES_OK; + + if(!session || !primitive || !st) { + res = RES_BAD_ARG; + goto error; + } + /* Expecting canonic numbers */ + if(u < 0.f || u >= 1.f || v < 0.f || v >= 1.f || w < 0.f || w >= 1.f) { + log_error(session->scn->dev, + "%s: the submitted numbers are not canonical, i.e. they are not in [0, 1[.\n", + FUNC_NAME); + res = RES_BAD_ARG; + goto error; + } + if((session->mask & S3D_SAMPLE) == 0) { + log_error(session->scn->dev, + "%s: no active S3D_SAMPLE session on the submitted scene.\n", + FUNC_NAME); + res = RES_BAD_OP; + goto error; + } + + /* Find the sampled geometry */ + if(darray_fltui_size_get(&session->cdf) == 0) { + /* No geometry to sample */ + *primitive = S3D_PRIMITIVE_NULL; + goto exit; + } else if(darray_fltui_size_get(&session->cdf) == 1) { + ishape = darray_fltui_cdata_get(&session->cdf)[0].ui; + /* Map u to the CDF bounds */ + f = u * darray_fltui_cdata_get(&session->cdf)[0].flt; + } else { + fltui_begin = darray_fltui_cdata_get(&session->cdf); + fltui_end = fltui_begin + darray_fltui_size_get(&session->cdf); + f = u * fltui_end[-1].flt; /* Map u to the CDF bounds */ + fltui_found = std::lower_bound(fltui_begin, fltui_end, f); + ASSERT(fltui_found != fltui_end); + ishape = fltui_found->ui; + + /* Transform u to the geometry CDF bounds */ + if(fltui_found != fltui_begin) + f -= fltui_found[-1].flt; + } + pgeom = htable_geom_find(&session->cached_geoms, &ishape); + ASSERT(pgeom); + geom = *pgeom; + + if(geom->type == GEOM_MESH) { + primitive->inst__ = NULL; + primitive->inst_id = S3D_INVALID_ID; + primitive->scene_prim_id = 0; + } else { + /* Find the sampled instantiated geometry */ + ASSERT(geom->type == GEOM_INSTANCE); + primitive->inst__ = geom; + primitive->inst_id = geom->name; + primitive->scene_prim_id = geom->scene_prim_id_offset; + if(darray_fltui_size_get(&geom->data.instance->session->cdf) == 1) { + ishape = darray_fltui_cdata_get(&geom->data.instance->session->cdf)[0].ui; + } else { + fltui_begin = darray_fltui_cdata_get(&geom->data.instance->session->cdf); + fltui_end = fltui_begin + darray_fltui_size_get(&geom->data.instance->session->cdf); + fltui_found = std::lower_bound(fltui_begin, fltui_end, f); + ASSERT(fltui_found != fltui_end); + ishape = fltui_found->ui; + + /* Transform u to the geometry CDF bounds */ + if(fltui_found != fltui_begin) + f -= fltui_found[-1].flt; + } + pgeom = htable_geom_find(&geom->data.instance->session->cached_geoms, &ishape); + ASSERT(pgeom); + geom = *pgeom; + } + ASSERT(geom->type == GEOM_MESH); + + /* Find the sampled triangle */ + primitive->mesh__ = geom; + primitive->geom_id = geom->name; + primitive->scene_prim_id += geom->scene_prim_id_offset; + flt_begin = darray_float_cdata_get(&geom->data.mesh->cdf); + flt_end = flt_begin + darray_float_size_get(&geom->data.mesh->cdf); + flt_found = std::lower_bound(flt_begin, flt_end, f); + ASSERT(flt_found != flt_end); + + primitive->prim_id = (unsigned)(flt_found - flt_begin); + primitive->scene_prim_id += primitive->prim_id; + S3D(primitive_sample(primitive, v, w, st)); + +exit: + return res; +error: + goto exit; +} + +res_T +s3d_session_get_primitive + (struct s3d_session* session, + const unsigned iprim, + struct s3d_primitive* prim) +{ + struct geometry** pgeom; + struct geometry* geom; + const struct nprims_cdf* begin, *end, *found; + size_t nprims; + unsigned ishape; + size_t i; + res_T res = RES_OK; + + if(!session || !prim) { + res = RES_BAD_ARG; + goto error; + } + if((session->mask & S3D_GET_PRIMITIVE) == 0) { + log_error(session->scn->dev, + "%s: no active S3D_GET_PRIMITIVE session on the submitted scene.\n", + FUNC_NAME); + res = RES_BAD_OP; + goto error; + } + S3D(session_primitives_count(session, &nprims)); + if(iprim >= nprims) { + log_error(session->scn->dev, + "%s: the primitive index %u exceeds the number of scene primitives %u.\n", + FUNC_NAME, iprim, (unsigned)nprims); + res = RES_BAD_ARG; + goto error; + } + + i = iprim; + if(darray_nprims_cdf_size_get(&session->nprims_cdf) == 1) { + ishape = darray_nprims_cdf_cdata_get(&session->nprims_cdf)[0].ishape; + } else { + begin = darray_nprims_cdf_cdata_get(&session->nprims_cdf); + end = begin + darray_nprims_cdf_size_get(&session->nprims_cdf); + found = std::lower_bound(begin, end, i); + ASSERT(found != end); + ishape = found->ishape; + if(found != begin) { + ASSERT(i >= found[-1].nprims); + i -= found[-1].nprims; + } + } + pgeom = htable_geom_find(&session->cached_geoms, &ishape); + ASSERT(pgeom); + geom = *pgeom; + + if(geom->type == GEOM_MESH) { + prim->inst__ = NULL; + prim->inst_id = S3D_INVALID_ID; + prim->scene_prim_id = 0; + } else { + ASSERT(geom->type == GEOM_INSTANCE); + prim->inst__ = geom; + prim->inst_id = geom->name; + prim->scene_prim_id = geom->scene_prim_id_offset; + if(darray_nprims_cdf_size_get(&geom->data.instance->session->nprims_cdf)==1) { + ishape = darray_nprims_cdf_cdata_get + (&geom->data.instance->session->nprims_cdf)[0].ishape; + } else { + begin = darray_nprims_cdf_cdata_get + (&geom->data.instance->session->nprims_cdf); + end = begin + darray_nprims_cdf_size_get + (&geom->data.instance->session->nprims_cdf); + found = std::lower_bound(begin, end, i); + ASSERT(found != end); + ishape = found->ishape; + if(found != begin) { + ASSERT(i >= found[-1].nprims); + i -= found[-1].nprims; + } + } + pgeom = htable_geom_find(&geom->data.instance->session->cached_geoms, &ishape); + ASSERT(pgeom); + geom = *pgeom; + } + ASSERT(geom->type == GEOM_MESH); + ASSERT(i < mesh_get_ntris(geom->data.mesh)); + prim->mesh__ = geom; + prim->geom_id = geom->name; + prim->prim_id = (unsigned)i; + prim->scene_prim_id += geom->scene_prim_id_offset; + prim->scene_prim_id += prim->prim_id; + +exit: + return res; +error: + goto exit; +} + +res_T +s3d_session_primitives_count(struct s3d_session* session, size_t* prims_count) +{ + res_T res = RES_OK; + + if(!session || !prims_count) { + res = RES_BAD_ARG; + goto error; + } + if((session->mask & S3D_GET_PRIMITIVE) != 0) { + const size_t len = darray_nprims_cdf_size_get(&session->nprims_cdf); + if(!len) { + *prims_count = 0; + } else { + *prims_count = darray_nprims_cdf_cdata_get + (&session->nprims_cdf)[len - 1].nprims; + } + } else { + struct htable_geom_iterator it, end; + size_t inst_count; + + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->cached_geoms, &end); + *prims_count = 0; + while(!htable_shape_iterator_eq(&it, &end)) { + const unsigned* shape_id = htable_geom_iterator_key_get(&it); + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + switch(geom->type) { + case GEOM_MESH: + *prims_count += mesh_get_ntris(geom->data.mesh); + break; + case GEOM_INSTANCE: + S3D(session_primitives_count(geom->data.instance->session, &inst_count)); + *prims_count += inst_count; + break; + default: FATAL("Unreachable code\n"); break; + } + } + } +exit: + return res; +error: + goto exit; +} + +res_T +s3d_session_compute_area(struct s3d_session* session, float* out_area) +{ + float area; + res_T res = RES_OK; + + if(!session || !out_area) { + res = RES_BAD_ARG; + goto error; + } + if((session->mask & S3D_SAMPLE) != 0) { + /* Retrieve the overall scene area from the scene cumulative distribution + * function. Note that the CDF stores the cumulative triangle area + * multiplied by 2; the real scene area is thus the CDF upper bound / 2 */ + size_t len = darray_fltui_size_get(&session->cdf); + if(!len) { + area = 0.f; + } else { + area = darray_fltui_cdata_get(&session->cdf)[len - 1].flt * 0.5f; + } + } else { + struct htable_geom_iterator it, end; + float inst_area; + + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->cached_geoms, &end); + + area = 0.f; + while(!htable_shape_iterator_eq(&it, &end)) { + const unsigned* shape_id = htable_geom_iterator_key_get(&it); + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + switch(geom->type) { + case GEOM_MESH: + area += mesh_compute_area(geom->data.mesh); + break; + case GEOM_INSTANCE: + /* TODO take into account the instance scale factor */ + S3D(session_compute_area(geom->data.instance->session, &inst_area)); + area += inst_area; + break; + default: FATAL("Unreachable code\n"); break; + } + } + } + +exit: + if(out_area) *out_area = area; + return res; +error: + area = -1.f; + goto exit; +} + +res_T +s3d_session_compute_volume(struct s3d_session* session, float* out_volume) +{ + struct htable_geom_iterator it, end; + float volume; + res_T res = RES_OK; + + if(!session || !out_volume) { + res = RES_BAD_ARG; + goto error; + } + htable_geom_begin(&session->shapes, &it); + htable_geom_end(&session->shapes, &end); + + volume = 0.f; + while(!htable_shape_iterator_eq(&it, &end)) { + const unsigned* shape_id = htable_geom_iterator_key_get(&it); + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + + htable_geom_iterator_next(&it); + + if(!geom->is_enabled) continue; + + switch(geom->type) { + case GEOM_MESH: + volume += mesh_compute_volume(geom->data.mesh, geom->flip_surface); + break; + case GEOM_INSTANCE: + volume += instance_compute_volume(geom->data.instance, geom->flip_surface); + break; + default: FATAL("Unreachable code\n"); break; + } + } + + if(volume < 0.f) { + log_warning(session->scn->dev, +"%s:\n" +"\tthe volume is negative. The scene shapes might not represent closed 2D\n" +"\tmanifold volumes, or their surface normal might not point inward the volume.\n", + FUNC_NAME); + } + +exit: + if(out_volume) *out_volume = volume; + return res; +error: + volume = -1.f; + goto exit; +} + +res_T +s3d_session_get_aabb(struct s3d_session* session, float lower[3], float upper[3]) +{ + if(!session || !lower || !upper) return RES_BAD_ARG; + f3_set(lower, session->lower); + f3_set(upper, session->upper); + return RES_OK; +} + +/******************************************************************************* + * Local functions + ******************************************************************************/ +void +session_destroy(struct s3d_session* session) +{ + htable_geom_iterator it, end; + ASSERT(session && !is_list_empty(&session->node)/*Not in use*/); + ASSERT(session->mask == 0); + + /* Delete the cached geometries */ + htable_geom_begin(&session->cached_geoms, &it); + htable_geom_end(&session->cached_geoms, &end); + while(htable_geom_iterator_eq(&it, &end)) { + struct geometry** pgeom = htable_geom_iterator_data_get(&it); + struct geometry* geom = *pgeom; + session_destroy_geometry(session, geom); + htable_geom_iterator_next(&it); + } + + /* Delete the back-end scene */ + if(session->rtc_scn) rtcDeleteScene(session->rtc_scn); + + /* Release internal data structure */ + htable_geom_release(&session->cached_geoms); + darray_geom_release(&session->embree2geoms); + darray_fltui_release(&session->cdf); + darray_nprims_cdf_release(&session->nprims_cdf); + darray_uint_release(&session->detached_shapes); + + /* Remove the session from its pool */ + list_del(&session->node); + CLBK_DISCONNECT(&session->on_shape_detach_cb); + + /* Free the session memory space */ + MEM_RM(session->scn->allocator, session); +} + diff --git a/src/s3d_session_c.h b/src/s3d_session_c.h @@ -0,0 +1,111 @@ +/* Copyright (C) |Meso|Star> 2015-2016 (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. */ + +#ifndef S3D_SESSION_C_H +#define S3D_SESSION_C_H + +#include "s3d_backend.h" +#include "s3d_scene_c.h" + +#include <rsys/dynamic_array.h> +#include <rsys/dynamic_array_uint.h> +#include <rsys/hash_table.h> +#include <rsys/list.h> +#include <rsys/ref_count.h> + +/* Forward declarations */ +struct s3d_session; + +/* + * The geometry pointers must be initialized to NULL in order to define + * which pointers are valid or not + */ +static FINLINE void +geom_ptr_init__(struct mem_allocator* alloc, struct geometry** geom) +{ + (void)alloc; *geom = NULL; +} + +/* Generate the darray_geom dynamic array */ +#define DARRAY_NAME geom +#define DARRAY_DATA struct geometry* +#define DARRAY_FUNCTOR_INIT geom_ptr_init__ +#include <rsys/dynamic_array.h> + +/* Generate the htable_geom hash table */ +#define HTABLE_NAME geom +#define HTABLE_DATA struct geometry* +#define HTABLE_KEY unsigned /* Id of the shape */ +#include <rsys/hash_table.h> + +/* Generate the darray_fltui dynamic array */ +struct fltui { float flt; unsigned ui; }; +#define DARRAY_NAME fltui +#define DARRAY_DATA struct fltui +#include <rsys/dynamic_array.h> + +/* Generate the darray_geom_nprims array */ +struct nprims_cdf { unsigned nprims, ishape; }; +#define DARRAY_NAME nprims_cdf +#define DARRAY_DATA struct nprims_cdf +#include <rsys/dynamic_array.h> + +struct s3d_session { + struct list_node node; /* Attachment point to the scene sessions pool */ + + struct htable_geom cached_geoms; /* Cached shape geometries */ + struct darray_geom embree2geoms; /* Embree index to geometry */ + struct darray_fltui cdf; /* Unormalized CDF */ + struct darray_nprims_cdf nprims_cdf; + + /* Id of Shapes detached while the session is active */ + struct darray_uint detached_shapes; + + float lower[3], upper[3]; /* AABB of the scene */ + + /* Callback attached to the sig_shape_detach signal of scn */ + scene_shape_cb_T on_shape_detach_cb; + + int mask; /* Combinatin of enum s3d_session_flag */ + int rtc_delete_geometry; /* Define if Embree geometries were deleted */ + RTCScene rtc_scn; /* Embree scene */ + + struct ref_T ref; + struct s3d_scene* scn; +}; + +extern LOCAL_SYM void +session_destroy + (struct s3d_session* session); + +#endif /* S3D_SESSION_C_H */ +