htrdr

Solving radiative transfer in heterogeneous media
git clone git://git.meso-star.fr/htrdr.git
Log | Files | Refs | README | LICENSE

commit 75d1ec107171acaebbcfccab98da24218b2b1521
parent f8f35d5bf11c7697e87401c5d7a4723bca952c0b
Author: Vincent Forest <vincent.forest@meso-star.com>
Date:   Sat, 15 Sep 2018 14:01:07 +0200

Begin the support of atmosphere

Diffstat:
Msrc/htrdr_compute_radiance_sw.c | 20++++++++------------
Msrc/htrdr_sky.c | 613+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Msrc/htrdr_sky.h | 24+++++++++++++++++-------
3 files changed, 529 insertions(+), 128 deletions(-)

diff --git a/src/htrdr_compute_radiance_sw.c b/src/htrdr_compute_radiance_sw.c @@ -138,6 +138,7 @@ transmissivity_hit_filter void* context) { struct transmissivity_context* ctx = context; + int comp_mask = HTRDR_ALL_COMPONENTS; double vox_dst; /* Distance to traverse into the voxel */ double k_max; double k_min; @@ -146,9 +147,9 @@ transmissivity_hit_filter (void)range; k_min = htrdr_sky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTRDR_SVX_MIN, HTRDR_ALL_COMPONENTS, ctx->iband, ctx->iquad, &hit->voxel); + HTRDR_SVX_MIN, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); k_max = htrdr_sky_fetch_svx_voxel_property(ctx->sky, ctx->prop, - HTRDR_SVX_MAX, HTRDR_ALL_COMPONENTS, ctx->iband, ctx->iquad, &hit->voxel); + HTRDR_SVX_MAX, comp_mask, ctx->iband, ctx->iquad, &hit->voxel); ASSERT(k_min <= k_max); /* Compute the distance to traverse into the voxel */ @@ -185,7 +186,7 @@ transmissivity_hit_filter x[2] = org[2] + ctx->traversal_dst * dir[2]; k = htrdr_sky_fetch_raw_property(ctx->sky, ctx->prop, - HTRDR_ALL_COMPONENTS, ctx->iband, ctx->iquad, x, k_min, k_max); + comp_mask, ctx->iband, ctx->iquad, x, k_min, k_max); ASSERT(k >= k_min && k <= k_max); /* Handle the case that k_max is not *really* the max */ @@ -218,7 +219,6 @@ transmissivity { struct s3d_hit s3d_hit; struct svx_hit svx_hit; - struct svx_tree* svx_tree; struct transmissivity_context transmissivity_ctx = TRANSMISSION_CONTEXT_NULL; struct s3d_hit s3d_hit_prev = hit_prev ? *hit_prev : S3D_HIT_NULL; @@ -245,9 +245,8 @@ transmissivity transmissivity_ctx.prop = prop; /* Compute the transmissivity */ - svx_tree = htrdr_sky_get_svx_tree(htrdr->sky, iband, iquad); - SVX(tree_trace_ray(svx_tree, pos, dir, range, NULL, - transmissivity_hit_filter, &transmissivity_ctx, &svx_hit)); + HTRDR(sky_trace_ray(htrdr->sky, pos, dir, range, NULL, + transmissivity_hit_filter, &transmissivity_ctx, iband, iquad, &svx_hit)); if(SVX_HIT_NONE(&svx_hit)) { return transmissivity_ctx.Tmin ? exp(-transmissivity_ctx.Tmin) : 1.0; @@ -272,7 +271,6 @@ htrdr_compute_radiance_sw struct s3d_hit s3d_hit = S3D_HIT_NULL; struct svx_hit svx_hit = SVX_HIT_NULL; struct s3d_hit s3d_hit_prev = S3D_HIT_NULL; - struct svx_tree* svx_tree = NULL; struct ssf_phase* phase_hg = NULL; struct ssf_phase* phase_rayleigh = NULL; struct ssf_bsdf* bsdf = NULL; @@ -328,8 +326,6 @@ htrdr_compute_radiance_sw sun_solid_angle = htrdr_sun_get_solid_angle(htrdr->sun); L_sun = htrdr_sun_get_radiance(htrdr->sun, wlen); - svx_tree = htrdr_sky_get_svx_tree(htrdr->sky, iband, iquad); - d3_set(pos, pos_in); d3_set(dir, dir_in); @@ -374,8 +370,8 @@ htrdr_compute_radiance_sw /* Define if a scattering event occurs */ d2(range, 0, s3d_hit.distance); - SVX(tree_trace_ray(svx_tree, pos, dir, range, NULL, - scattering_hit_filter, &scattering_ctx, &svx_hit)); + HTRDR(sky_trace_ray(htrdr->sky, pos, dir, range, NULL, + scattering_hit_filter, &scattering_ctx, iband, iquad, &svx_hit)); /* No scattering and no surface reflection. Stop the radiative random walk */ if(S3D_HIT_NONE(&s3d_hit) && SVX_HIT_NONE(&svx_hit)) { diff --git a/src/htrdr_sky.c b/src/htrdr_sky.c @@ -57,9 +57,9 @@ struct split { #define DARRAY_DATA struct split #include <rsys/dynamic_array.h> -struct build_octree_context { +struct build_tree_context { const struct htrdr_sky* sky; - double vxsz[3]; /* Size of a SVX voxel */ + double vxsz[3]; double dst_max; /* Max traversal distance */ double tau_threshold; /* Threshold criteria for the merge process */ size_t iband; /* Index of the band that overlaps the CIE XYZ color space */ @@ -69,9 +69,9 @@ struct build_octree_context { struct htrdr_grid* grid; }; -#define BUILD_OCTREE_CONTEXT_NULL__ { NULL, {0,0,0}, 0, 0, 0, NULL } -static const struct build_octree_context BUILD_OCTREE_CONTEXT_NULL = - BUILD_OCTREE_CONTEXT_NULL__; +#define BUILD_TREE_CONTEXT_NULL__ { NULL, {0,0,0}, 0, 0, 0, NULL } +static const struct build_tree_context BUILD_TREE_CONTEXT_NULL = + BUILD_TREE_CONTEXT_NULL__; struct vertex { double x; @@ -115,8 +115,8 @@ struct sw_band_prop { double g_avg; }; -/* Encompass the hierarchical data structure of the cloud data and its - * associated descriptor. +/* Encompass the hierarchical data structure of the cloud/atmospheric data and + * its associated descriptor. * * For each SVX voxel, the data of the optical property are stored * linearly as N single precision floating point data, with N computed as @@ -137,9 +137,14 @@ struct cloud { struct svx_tree* octree; struct svx_tree_desc octree_desc; }; +struct atmosphere { + struct svx_tree* bitree; + struct svx_tree_desc bitree_desc; +}; struct htrdr_sky { struct cloud* clouds; /* Per sw_band cloud data structure */ + struct atmosphere* atmosphere; /* Per sw_band atmospheric data structure */ struct htrdr_sun* sun; /* Sun attached to the sky */ @@ -216,6 +221,39 @@ cloud_water_vapor_molar_fraction return rvt / (rvt + H2O_MOLAR_MASS/DRY_AIR_MOLAR_MASS); } +/* Smits intersection: "Efficiency issues for ray tracing" */ +static FINLINE void +ray_intersect_aabb + (const double org[3], + const double dir[3], + const double range[2], + const double low[3], + const double upp[3], + double hit_range[2]) +{ + double hit[2]; + int i; + ASSERT(org && dir && range && low && upp && hit_range); + ASSERT(low[0] < upp[0]); + ASSERT(low[1] < upp[1]); + ASSERT(low[2] < upp[2]); + + hit_range[0] = INF; + hit_range[1] =-INF; + hit[0] = range[0]; + hit[1] = range[1]; + FOR_EACH(i, 0, 3) { + double t_min = (low[i] - org[i]) / dir[i]; + double t_max = (upp[i] - org[i]) / dir[i]; + if(t_min > t_max) SWAP(double, t_min, t_max); + hit[0] = MMAX(t_min, hit[0]); + hit[1] = MMIN(t_max, hit[1]); + if(hit[0] > hit[1]) return; + } + hit_range[0] = hit[0]; + hit_range[1] = hit[1]; +} + static INLINE void octree_data_init (const struct htrdr_sky* sky, @@ -296,10 +334,48 @@ register_leaf } static void -vox_get_particle +clean_clouds(struct htrdr_sky* sky) +{ + size_t nbands; + size_t i; + ASSERT(sky); + + if(!sky->clouds) return; + + nbands = htrdr_sky_get_sw_spectral_bands_count(sky); + FOR_EACH(i, 0, nbands) { + if(sky->clouds[i].octree) { + SVX(tree_ref_put(sky->clouds[i].octree)); + sky->clouds[i].octree = NULL; + } + } + MEM_RM(sky->htrdr->allocator, sky->clouds); +} + +static void +clean_atmosphere(struct htrdr_sky* sky) +{ + size_t nbands; + size_t i; + ASSERT(sky); + + if(!sky->atmosphere) return; + + nbands = htrdr_sky_get_sw_spectral_bands_count(sky); + FOR_EACH(i, 0, nbands) { + if(sky->atmosphere[i].bitree) { + SVX(tree_ref_put(sky->atmosphere[i].bitree)); + sky->atmosphere[i].bitree = NULL; + } + } + MEM_RM(sky->htrdr->allocator, sky->atmosphere); +} + +static void +cloud_vox_get_particle (const size_t xyz[3], float vox[], - const struct build_octree_context* ctx) + const struct build_tree_context* ctx) { double rct; double ka, ks, kext; @@ -411,10 +487,10 @@ vox_get_particle } static void -vox_get_gas +cloud_vox_get_gas (const size_t xyz[3], float vox[], - const struct build_octree_context* ctx) + const struct build_tree_context* ctx) { struct htgop_layer layer; struct htgop_layer_sw_spectral_interval band; @@ -555,9 +631,9 @@ vox_get_gas } static void -vox_get(const size_t xyz[3], void* dst, void* context) +cloud_vox_get(const size_t xyz[3], void* dst, void* context) { - struct build_octree_context* ctx = context; + struct build_tree_context* ctx = context; ASSERT(context); if(ctx->grid) { /* Fetch voxel data from precomputed grid */ @@ -565,8 +641,8 @@ vox_get(const size_t xyz[3], void* dst, void* context) memcpy(dst, vox_data, NFLOATS_PER_VOXEL * sizeof(float)); } else { /* No precomputed grid. Compute the voxel data at runtime */ - vox_get_particle(xyz, dst, ctx); - vox_get_gas(xyz, dst, ctx); + cloud_vox_get_particle(xyz, dst, ctx); + cloud_vox_get_gas(xyz, dst, ctx); } } @@ -606,7 +682,7 @@ vox_merge_component } static void -vox_merge(void* dst, const void* voxels[], const size_t nvoxs, void* context) +cloud_vox_merge(void* dst, const void* voxels[], const size_t nvoxs, void* context) { ASSERT(dst && voxels && nvoxs); (void)context; @@ -619,7 +695,7 @@ vox_challenge_merge_component (const enum htrdr_sky_component comp, const float* voxels[], const size_t nvoxs, - struct build_octree_context* ctx) + struct build_tree_context* ctx) { float kext_min = FLT_MAX; float kext_max =-FLT_MAX; @@ -635,7 +711,7 @@ vox_challenge_merge_component } static int -vox_challenge_merge +cloud_vox_challenge_merge (const void* voxels[], const size_t nvoxs, void* ctx) { const float** voxs = (const float**)voxels; @@ -711,7 +787,7 @@ setup_cloud_grid struct htrdr_grid* grid = NULL; struct str path; struct str str; - struct build_octree_context ctx = BUILD_OCTREE_CONTEXT_NULL; + struct build_tree_context ctx = BUILD_TREE_CONTEXT_NULL; size_t sizeof_cell; size_t ncells; uint64_t mcode; @@ -738,7 +814,7 @@ setup_cloud_grid CHK(RES_OK == str_append(&path, str_cget(&str))); CHK(RES_OK == str_append(&path, ".grid")); CHK(RES_OK == str_append(&path, buf)); - + if(!force_update) { /* Try to open an already saved grid */ res = htrdr_grid_open(sky->htrdr, str_cget(&path), &grid); @@ -813,7 +889,7 @@ setup_cloud_grid if((xyz[2] = morton3D_decode_u21(mcode >> 0)) >= definition[2]) continue; /* Compute the voxel data */ - vox_get(xyz, htrdr_grid_at_mcode(grid, mcode), &ctx); + cloud_vox_get(xyz, htrdr_grid_at_mcode(grid, mcode), &ctx); /* Update the progress message */ n = (size_t)ATOMIC_INCR(&ncells_computed); @@ -851,7 +927,7 @@ setup_clouds const int force_cache_update) { struct svx_voxel_desc vox_desc = SVX_VOXEL_DESC_NULL; - struct build_octree_context ctx = BUILD_OCTREE_CONTEXT_NULL; + struct build_tree_context ctx = BUILD_TREE_CONTEXT_NULL; size_t nvoxs[3]; double low[3]; double upp[3]; @@ -928,11 +1004,17 @@ setup_clouds ctx.sky = sky; ctx.dst_max = sz[2]; ctx.tau_threshold = 0.1; + ctx.vxsz[0] = sky->htcp_desc.upper[0] - sky->htcp_desc.lower[0]; + ctx.vxsz[1] = sky->htcp_desc.upper[1] - sky->htcp_desc.lower[1]; + ctx.vxsz[2] = sky->htcp_desc.upper[2] - sky->htcp_desc.lower[2]; + ctx.vxsz[0] = ctx.vxsz[0] / (double)sky->htcp_desc.spatial_definition[0]; + ctx.vxsz[1] = ctx.vxsz[1] / (double)sky->htcp_desc.spatial_definition[1]; + ctx.vxsz[2] = ctx.vxsz[2] / (double)sky->htcp_desc.spatial_definition[2]; /* Setup the voxel descriptor */ - vox_desc.get = vox_get; - vox_desc.merge = vox_merge; - vox_desc.challenge_merge = vox_challenge_merge; + vox_desc.get = cloud_vox_get; + vox_desc.merge = cloud_vox_merge; + vox_desc.challenge_merge = cloud_vox_challenge_merge; vox_desc.context = &ctx; vox_desc.size = sizeof(float) * NFLOATS_PER_VOXEL; @@ -977,16 +1059,180 @@ exit: return res; error: if(ctx.grid) htrdr_grid_ref_put(ctx.grid); - if(sky->clouds) { - FOR_EACH(i, 0, nbands) { - if(sky->clouds[i].octree) { - SVX(tree_ref_put(sky->clouds[i].octree)); - sky->clouds[i].octree = NULL; - } + clean_clouds(sky); + darray_split_clear(&sky->svx2htcp_z); + goto exit; +} + +static void +atmosphere_vox_get(const size_t xyz[3], void* dst, void* context) +{ + float* vox = dst; + struct build_tree_context* ctx = context; + struct htgop_level level; + size_t layer_range[2]; + size_t nlevels; + double vox_low, vox_upp; + double ka_min, ks_min, kext_min; + double ka_max, ks_max, kext_max; + size_t ilayer; + size_t i; + ASSERT(xyz && dst && context); + + i = ctx->iband - ctx->sky->sw_bands_range[0]; + ASSERT(i <= ctx->sky->sw_bands_range[1]); + + + /* Compute the boundaries of the SVX voxel */ + HTGOP(get_level(ctx->sky->htgop, 0, &level)); + vox_low = (double)xyz[2] * ctx->vxsz[2] + level.height; + HTGOP(get_levels_count(ctx->sky->htgop, &nlevels)); + HTGOP(get_level(ctx->sky->htgop, nlevels-1, &level)); + vox_upp = MMIN(vox_low + ctx->vxsz[2], level.height); /* Handle numerical issues */ + + /* Define the atmospheric layers overlapped by the SVX voxel */ + HTGOP(position_to_layer_id(ctx->sky->htgop, vox_low, &layer_range[0])); + HTGOP(position_to_layer_id(ctx->sky->htgop, vox_upp, &layer_range[1])); + + ka_min = ks_min = kext_min = DBL_MAX; + ka_max = ks_max = kext_max =-DBL_MAX; + + /* For each atmospheric layer that overlaps the SVX voxel ... */ + FOR_EACH(ilayer, layer_range[0], layer_range[1]+1) { + struct htgop_layer layer; + struct htgop_layer_sw_spectral_interval band; + size_t iquad; + + HTGOP(get_layer(ctx->sky->htgop, ilayer, &layer)); + + /* ... retrieve the considered spectral interval */ + HTGOP(layer_get_sw_spectral_interval(&layer, ctx->iband, &band)); + + /* ... and update the radiative properties bound with the per quadrature + * point nominal values */ + FOR_EACH(iquad, 0, band.quadrature_length) { + ka_min = MMIN(ka_min, band.ka_nominal[iquad]); + ka_max = MMAX(ka_max, band.ka_nominal[iquad]); + ks_min = MMIN(ks_min, band.ks_nominal[iquad]); + ks_max = MMAX(ks_max, band.ks_nominal[iquad]); + kext_min = MMIN(kext_min, band.ka_nominal[iquad]+band.ks_nominal[iquad]); + kext_max = MMAX(kext_max, band.ka_nominal[iquad]+band.ks_nominal[iquad]); } - MEM_RM(sky->htrdr->allocator, sky->clouds); } - darray_split_clear(&sky->svx2htcp_z); + + /* Ensure that the single precision bounds include their double precision + * version. */ + if(ka_min != (float)ka_min) ka_min = nextafterf((float)ka_min,-FLT_MAX); + if(ka_max != (float)ka_max) ka_max = nextafterf((float)ka_max, FLT_MAX); + if(ks_min != (float)ks_min) ks_min = nextafterf((float)ks_min,-FLT_MAX); + if(ks_max != (float)ks_max) ks_max = nextafterf((float)ks_max, FLT_MAX); + if(kext_min != (float)kext_min) kext_min = nextafterf((float)kext_min,-FLT_MAX); + if(kext_max != (float)kext_max) kext_max = nextafterf((float)kext_max, FLT_MAX); + + /* Setup gas optical properties of the voxel */ + voxel_set(vox, HTRDR_GAS__, HTRDR_Ka, HTRDR_SVX_MIN, (float)ka_min); + voxel_set(vox, HTRDR_GAS__, HTRDR_Ka, HTRDR_SVX_MAX, (float)ka_max); + voxel_set(vox, HTRDR_GAS__, HTRDR_Ks, HTRDR_SVX_MIN, (float)ks_min); + voxel_set(vox, HTRDR_GAS__, HTRDR_Ks, HTRDR_SVX_MAX, (float)ks_max); + voxel_set(vox, HTRDR_GAS__, HTRDR_Kext, HTRDR_SVX_MIN, (float)kext_min); + voxel_set(vox, HTRDR_GAS__, HTRDR_Kext, HTRDR_SVX_MAX, (float)kext_max); +} + +static void +atmosphere_vox_merge + (void* dst, const void* voxels[], const size_t nvoxs, void* context) +{ + ASSERT(dst && voxels && nvoxs); + (void)context; + vox_merge_component(dst, HTRDR_GAS__, (const float**)voxels, nvoxs); +} + +static int +atmosphere_vox_challenge_merge + (const void* voxels[], const size_t nvoxs, void* ctx) +{ + const float** voxs = (const float**)voxels; + ASSERT(voxels); + return vox_challenge_merge_component(HTRDR_GAS__, voxs, nvoxs, ctx); +} + +static res_T +setup_atmosphere(struct htrdr_sky* sky) +{ + struct build_tree_context ctx = BUILD_TREE_CONTEXT_NULL; + struct htgop_level lvl_low, lvl_upp; + struct svx_voxel_desc vox_desc = SVX_VOXEL_DESC_NULL; + double low, upp; + size_t nlayers, nlevels; + size_t definition; + size_t nbands; + size_t i; + res_T res = RES_OK; + ASSERT(sky); + + HTGOP(get_layers_count(sky->htgop, &nlayers)); + HTGOP(get_levels_count(sky->htgop, &nlevels)); + HTGOP(get_level(sky->htgop, 0, &lvl_low)); + HTGOP(get_level(sky->htgop, nlevels-1, &lvl_upp)); + low = lvl_low.height; + upp = lvl_upp.height; + definition = nlayers*10; + + /* Setup the build context */ + ctx.sky = sky; + ctx.dst_max = upp - low; + ctx.tau_threshold = 0.1; + ctx.vxsz[0] = INF; + ctx.vxsz[1] = INF; + ctx.vxsz[2] = (upp-low)/(double)definition; + + /* Setup the voxel descriptor for the atmosphere. Note that in contrats with + * the clouds, the voxel contains only NFLOATS_PER_COMPONENT floats and not + * NFLOATS_PER_VOXEL. Indeed, the atmosphere has only 1 component: the gas. + * Anyway, we still rely on the memory layout of the cloud voxels excepted + * that we assume that the optical properties of the particles are never + * fetched. We thus have to ensure that the gas properties are arranged + * before the particles, i.e. HTRDR_GAS__ == 0 */ + vox_desc.get = atmosphere_vox_get; + vox_desc.merge = atmosphere_vox_merge; + vox_desc.challenge_merge = atmosphere_vox_challenge_merge; + vox_desc.context = &ctx; + vox_desc.size = sizeof(float) * NFLOATS_PER_COMPONENT; + STATIC_ASSERT(HTRDR_GAS__ == 0, Unexpected_enum_value); + + /* Create as many atmospheric data structure than considered SW spectral + * bands */ + nbands = htrdr_sky_get_sw_spectral_bands_count(sky); + sky->atmosphere = MEM_CALLOC + (sky->htrdr->allocator, nbands, sizeof(*sky->atmosphere)); + if(!sky->atmosphere) { + htrdr_log_err(sky->htrdr, + "could not create the list of per SW band atmospheric data structure.\n"); + res = RES_MEM_ERR; + goto error; + } + + FOR_EACH(i, 0, nbands) { + ctx.iband = i + sky->sw_bands_range[0]; + + /* Create the atmospheric binary tree */ + res = svx_bintree_create(sky->htrdr->svx, low, upp, definition, SVX_AXIS_Z, + &vox_desc, &sky->atmosphere[i].bitree); + if(res != RES_OK) { + htrdr_log_err(sky->htrdr, "could not create the binary tree of the " + "atmospheric properties for the band %lu.\n", (unsigned long)ctx.iband); + goto error; + } + + /* Fetch the binary tree descriptor for future use */ + SVX(tree_get_desc + (sky->atmosphere[i].bitree, &sky->atmosphere[i].bitree_desc)); + } + +exit: + return res; +error: + clean_atmosphere(sky); goto exit; } @@ -1076,17 +1322,8 @@ release_sky(ref_T* ref) if(sky->htgop) HTGOP(ref_put(sky->htgop)); if(sky->htmie) HTMIE(ref_put(sky->htmie)); if(sky->sw_bands) MEM_RM(sky->htrdr->allocator, sky->sw_bands); - if(sky->clouds) { - const size_t nbands = htrdr_sky_get_sw_spectral_bands_count(sky); - size_t i; - FOR_EACH(i, 0, nbands) { - if(sky->clouds[i].octree) { - SVX(tree_ref_put(sky->clouds[i].octree)); - sky->clouds[i].octree = NULL; - } - } - MEM_RM(sky->htrdr->allocator, sky->clouds); - } + clean_clouds(sky); + clean_atmosphere(sky); darray_split_release(&sky->svx2htcp_z); MEM_RM(sky->htrdr->allocator, sky); } @@ -1180,6 +1417,9 @@ htrdr_sky_create (sky, htcp_filename, htgop_filename, htmie_filename, force_upd); if(res != RES_OK) goto error; + res = setup_atmosphere(sky); + if(res != RES_OK) goto error; + exit: *out_sky = sky; return res; @@ -1234,7 +1474,10 @@ htrdr_sky_fetch_raw_property size_t ivox[3]; size_t i; const struct svx_tree_desc* cloud_desc; + const struct svx_tree_desc* atmosphere_desc; int comp_mask = components_mask; + int in_clouds; /* Defines if `pos' lies in the clouds */ + int in_atmosphere; /* Defines if `pos' lies in the atmosphere */ double k_particle = 0; double k_gas = 0; double k = 0; @@ -1245,30 +1488,50 @@ htrdr_sky_fetch_raw_property i = iband - sky->sw_bands_range[0]; cloud_desc = &sky->clouds[i].octree_desc; - - /* Is the position outside the clouds? */ - if(pos[0] < cloud_desc->lower[0] - || pos[1] < cloud_desc->lower[1] - || pos[2] < cloud_desc->lower[2] - || pos[0] > cloud_desc->upper[0] - || pos[1] > cloud_desc->upper[1] - || pos[2] > cloud_desc->upper[2]) { - comp_mask &= ~HTRDR_PARTICLES; /* No particle */ - } - - /* Compute the index of the voxel to fetch */ - ivox[0] = (size_t)((pos[0] - cloud_desc->lower[0])/sky->htcp_desc.vxsz_x); - ivox[1] = (size_t)((pos[1] - cloud_desc->lower[1])/sky->htcp_desc.vxsz_y); - if(!sky->htcp_desc.irregular_z) { - /* The voxels along the Z dimension have the same size */ - ivox[2] = (size_t)((pos[2] - cloud_desc->lower[2])/sky->htcp_desc.vxsz_z[0]); + atmosphere_desc = &sky->atmosphere[i].bitree_desc; + ASSERT(atmosphere_desc->frame[0] == SVX_AXIS_Z); + + /* Is the position inside the clouds? */ + in_clouds = + pos[0] >= cloud_desc->lower[0] + && pos[1] >= cloud_desc->lower[1] + && pos[2] >= cloud_desc->lower[2] + && pos[0] <= cloud_desc->upper[0] + && pos[1] <= cloud_desc->upper[1] + && pos[2] <= cloud_desc->upper[2]; + + in_clouds = 0; /* FIXME FIXME FIXME */ + + /* Is the position inside the atmosphere? */ + in_atmosphere = + pos[2] >= atmosphere_desc->lower[2] + && pos[2] <= atmosphere_desc->upper[2]; + + if(!in_clouds) { + /* Make invalid the voxel index */ + ivox[0] = SIZE_MAX; + ivox[1] = SIZE_MAX; + ivox[2] = SIZE_MAX; + /* Not in clouds => No particle */ + comp_mask &= ~HTRDR_PARTICLES; + /* Not in atmopshere => No gas */ + if(!in_atmosphere) comp_mask &= ~HTRDR_GAS; } else { - /* Irregular voxel size along the Z dimension. Compute the index of the Z - * position in the svx2htcp_z Look Up Table and use the LUT to define the - * voxel index into the HTCP descripptor */ - const struct split* splits = darray_split_cdata_get(&sky->svx2htcp_z); - const size_t ilut = (size_t)((pos[2] - cloud_desc->lower[2]) / sky->lut_cell_sz); - ivox[2] = splits[ilut].index + (pos[2] > splits[ilut].height); + /* Compute the index of the voxel to fetch */ + ivox[0] = (size_t)((pos[0] - cloud_desc->lower[0])/sky->htcp_desc.vxsz_x); + ivox[1] = (size_t)((pos[1] - cloud_desc->lower[1])/sky->htcp_desc.vxsz_y); + if(!sky->htcp_desc.irregular_z) { + /* The voxels along the Z dimension have the same size */ + ivox[2] = (size_t)((pos[2] - cloud_desc->lower[2])/sky->htcp_desc.vxsz_z[0]); + } else { + /* Irregular voxel size along the Z dimension. Compute the index of the Z + * position in the svx2htcp_z Look Up Table and use the LUT to define the + * voxel index into the HTCP descripptor */ + const struct split* splits = darray_split_cdata_get(&sky->svx2htcp_z); + const size_t ilut = (size_t) + ((pos[2] - cloud_desc->lower[2]) / sky->lut_cell_sz); + ivox[2] = splits[ilut].index + (pos[2] > splits[ilut].height); + } } if(comp_mask & HTRDR_PARTICLES) { @@ -1277,6 +1540,7 @@ htrdr_sky_fetch_raw_property double ql = 0; /* Droplet density In kg.m^-3 */ double Ca = 0; /* Massic absorption cross section in m^2.kg^-1 */ double Cs = 0; /* Massic scattering cross section in m^2.kg^-1 */ + ASSERT(in_clouds); /* Compute he dry air density */ rho_da = cloud_dry_air_density(&sky->htcp_desc, ivox); @@ -1295,29 +1559,49 @@ htrdr_sky_fetch_raw_property if(comp_mask & HTRDR_GAS) { struct htgop_layer layer; struct htgop_layer_sw_spectral_interval band; - struct htgop_layer_sw_spectral_interval_tab tab; - const double x_h2o = cloud_water_vapor_molar_fraction(&sky->htcp_desc, ivox); size_t ilayer = 0; + ASSERT(in_atmosphere); /* Retrieve the quadrature point into the spectral band of the layer into * which `pos' lies */ HTGOP(position_to_layer_id(sky->htgop, pos[2], &ilayer)); HTGOP(get_layer(sky->htgop, ilayer, &layer)); HTGOP(layer_get_sw_spectral_interval(&layer, iband, &band)); - HTGOP(layer_sw_spectral_interval_get_tab(&band, iquad, &tab)); - - /* Fetch the optical properties wrt x_h2o */ - switch(prop) { - case HTRDR_Ka: - HTGOP(layer_sw_spectral_interval_tab_fetch_ka(&tab, x_h2o, &k_gas)); - break; - case HTRDR_Ks: - HTGOP(layer_sw_spectral_interval_tab_fetch_ks(&tab, x_h2o, &k_gas)); - break; - case HTRDR_Kext: - HTGOP(layer_sw_spectral_interval_tab_fetch_kext(&tab, x_h2o, &k_gas)); - break; - default: FATAL("Unreachable code.\n"); break; + + if(!in_clouds) { + /* Pos is outside the clouds. Directly fetch the nominal optical + * properties */ + ASSERT(iquad < band.quadrature_length); + switch(prop) { + case HTRDR_Ka: k_gas = band.ka_nominal[iquad]; break; + case HTRDR_Ks: k_gas = band.ks_nominal[iquad]; break; + case HTRDR_Kext: + k_gas = band.ka_nominal[iquad] + band.ks_nominal[iquad]; + break; + default: FATAL("Unreachable code.\n"); break; + } + } else { + /* Pos is inside the clouds. Compute the water vapor molar fraction at + * the current voxel */ + const double x_h2o = cloud_water_vapor_molar_fraction(&sky->htcp_desc, ivox); + struct htgop_layer_sw_spectral_interval_tab tab; + + /* Retrieve the tabulated data for the quadrature point */ + HTGOP(layer_sw_spectral_interval_get_tab(&band, iquad, &tab)); + + /* Fetch the optical properties wrt x_h2o */ + switch(prop) { + case HTRDR_Ka: + HTGOP(layer_sw_spectral_interval_tab_fetch_ka(&tab, x_h2o, &k_gas)); + break; + case HTRDR_Ks: + HTGOP(layer_sw_spectral_interval_tab_fetch_ks(&tab, x_h2o, &k_gas)); + break; + case HTRDR_Kext: + HTGOP(layer_sw_spectral_interval_tab_fetch_kext(&tab, x_h2o, &k_gas)); + break; + default: FATAL("Unreachable code.\n"); break; + } } } @@ -1327,21 +1611,6 @@ htrdr_sky_fetch_raw_property return k; } -struct svx_tree* -htrdr_sky_get_svx_tree - (struct htrdr_sky* sky, - const size_t ispectral_band, - const size_t iquadrature_pt) -{ - size_t i; - ASSERT(sky); - ASSERT(ispectral_band >= sky->sw_bands_range[0]); - ASSERT(ispectral_band <= sky->sw_bands_range[1]); - (void)iquadrature_pt; - i = ispectral_band - sky->sw_bands_range[0]; - return sky->clouds[i].octree; -} - res_T htrdr_sky_dump_clouds_vtk (const struct htrdr_sky* sky, @@ -1434,6 +1703,8 @@ htrdr_sky_fetch_svx_property { struct svx_voxel voxel = SVX_VOXEL_NULL; size_t i; + int in_clouds; /* Defines if `pos' lies in the clouds */ + int in_atmosphere; /* Defines if `pos' lies in the atmosphere */ int comp_mask = components_mask; ASSERT(sky && pos); ASSERT(comp_mask & HTRDR_ALL_COMPONENTS); @@ -1442,17 +1713,38 @@ htrdr_sky_fetch_svx_property i = iband - sky->sw_bands_range[0]; - /* Is the position outside the clouds? */ - if(pos[0] < sky->clouds[i].octree_desc.lower[0] - || pos[1] < sky->clouds[i].octree_desc.lower[1] - || pos[2] < sky->clouds[i].octree_desc.lower[2] - || pos[0] > sky->clouds[i].octree_desc.upper[0] - || pos[1] > sky->clouds[i].octree_desc.upper[1] - || pos[2] > sky->clouds[i].octree_desc.upper[2]) { - comp_mask &= ~HTRDR_PARTICLES; /* No particle */ + /* Is the position inside the clouds? */ + in_clouds = + pos[0] >= sky->clouds[i].octree_desc.lower[0] + && pos[1] >= sky->clouds[i].octree_desc.lower[1] + && pos[2] >= sky->clouds[i].octree_desc.lower[2] + && pos[0] <= sky->clouds[i].octree_desc.upper[0] + && pos[1] <= sky->clouds[i].octree_desc.upper[1] + && pos[2] <= sky->clouds[i].octree_desc.upper[2]; + + in_clouds = 0; /* FIXME FIXME FIXME */ + + ASSERT(sky->atmosphere[i].bitree_desc.frame[0] = SVX_AXIS_Z); + in_atmosphere = + pos[2] >= sky->atmosphere[i].bitree_desc.lower[2] + && pos[2] <= sky->atmosphere[i].bitree_desc.upper[2]; + + if(!in_clouds) { /* Not in clouds => No particle */ + comp_mask &= ~HTRDR_PARTICLES; } + if(!in_atmosphere) { /* Not in atmosphere => No gas */ + comp_mask &= ~HTRDR_GAS; + } + + if(!in_clouds && !in_atmosphere) /* In vacuum */ + return 0; - SVX(tree_at(sky->clouds[i].octree, pos, NULL, NULL, &voxel)); + if(in_clouds) { + SVX(tree_at(sky->clouds[i].octree, pos, NULL, NULL, &voxel)); + } else { + ASSERT(in_atmosphere); + SVX(tree_at(sky->atmosphere[i].bitree, pos, NULL, NULL, &voxel)); + } return htrdr_sky_fetch_svx_voxel_property (sky, prop, op, comp_mask, iband, iquad, &voxel); } @@ -1469,15 +1761,23 @@ htrdr_sky_fetch_svx_voxel_property { double gas = 0; double par = 0; + int comp_mask = components_mask; ASSERT(sky && voxel); ASSERT((unsigned)prop < HTRDR_PROPERTIES_COUNT__); ASSERT((unsigned)op < HTRDR_SVX_OPS_COUNT__); (void)sky, (void)ispectral_band, (void)iquad; - if(components_mask & HTRDR_PARTICLES) { + /* Check if the voxel has infinite bounds/degenerated. In such case it is + * atmospheric voxel with only gas properties */ + if(IS_INF(voxel->upper[0]) || voxel->lower[0] > voxel->upper[0]) { + ASSERT(IS_INF(voxel->upper[1]) || voxel->lower[1] > voxel->upper[1]); + comp_mask &= ~HTRDR_PARTICLES; + } + + if(comp_mask & HTRDR_PARTICLES) { par = voxel_get(voxel->data, HTRDR_PARTICLES__, prop, op); } - if(components_mask & HTRDR_GAS) { + if(comp_mask & HTRDR_GAS) { gas = voxel_get(voxel->data, HTRDR_GAS__, prop, op); } /* Interval arithmetic to ensure that the returned Min/Max includes the @@ -1555,3 +1855,98 @@ htrdr_sky_sample_sw_spectral_data_CIE_1931_Z ispectral_band, iquadrature_pt); } +res_T +htrdr_sky_trace_ray + (struct htrdr_sky* sky, + const double org[3], + const double dir[3], /* Must be normalized */ + const double range[2], + const svx_hit_challenge_T challenge, /* NULL <=> Traversed up to the leaves */ + const svx_hit_filter_T filter, /* NULL <=> Stop RT at the 1st hit voxel */ + void* context, /* Data sent to the filter functor */ + const size_t ispectral_band, + const size_t iquadrature_pt, + struct svx_hit* hit) +{ + double cloud_range[2]; + struct svx_tree* clouds; + struct svx_tree* atmosphere; + size_t i; + res_T res = RES_OK; + ASSERT(sky); + ASSERT(ispectral_band >= sky->sw_bands_range[0]); + ASSERT(ispectral_band <= sky->sw_bands_range[1]); + (void)iquadrature_pt; + + /* Fetch the clouds/atmosphere corresponding to the submitted spectral data */ + i = ispectral_band - sky->sw_bands_range[0]; + clouds = sky->clouds[i].octree; + atmosphere = sky->atmosphere[i].bitree; + + /* Compute the ray range, intersecting the clouds AABB */ + ray_intersect_aabb(org, dir, range, sky->htcp_desc.lower, + sky->htcp_desc.upper, cloud_range); + + cloud_range[0] = DBL_MAX; + cloud_range[1] =-DBL_MAX; + + /* Reset the hit */ + *hit = SVX_HIT_NULL; + + if(cloud_range[0] > cloud_range[1]) { /* The ray does not traverse the clouds */ + res = svx_tree_trace_ray(atmosphere, org, dir, range, challenge, filter, + context, hit); + if(res != RES_OK) { + htrdr_log_err(sky->htrdr, + "%s: could not trace the ray in the atmosphere.\n", + FUNC_NAME); + goto error; + } + } else { /* The ray may traverse the clouds */ + double range_adjusted[2]; + + if(cloud_range[0] > range[0]) { /* The ray begins in the atmosphere */ + /* Trace a ray in the atmosphere from range[0] to cloud_range[0] */ + range_adjusted[0] = range[0]; + range_adjusted[1] = nextafter(cloud_range[0], -DBL_MAX); + res = svx_tree_trace_ray(atmosphere, org, dir, range_adjusted, challenge, + filter, context, hit); + if(res != RES_OK) { + htrdr_log_err(sky->htrdr, + "%s: could not to trace the part that begins in the atmosphere.\n", + FUNC_NAME); + goto error; + } + if(!SVX_HIT_NONE(hit)) goto exit; /* Collision */ + } + + /* Pursue ray traversal into the clouds */ + res = svx_tree_trace_ray(clouds, org, dir, cloud_range, challenge, filter, + context, hit); + if(res != RES_OK) { + htrdr_log_err(sky->htrdr, + "%s: could not trace the ray part that intersects the clouds.\n", + FUNC_NAME); + goto error; + } + if(!SVX_HIT_NONE(hit)) goto exit; /* Collision */ + + /* Pursue ray traversal into the atmosphere */ + range_adjusted[0] = nextafter(cloud_range[1], DBL_MAX); + range_adjusted[1] = range[1]; + res = svx_tree_trace_ray(atmosphere, org, dir, range_adjusted, challenge, + filter, context, hit); + if(res != RES_OK) { + htrdr_log_err(sky->htrdr, + "%s: could not trace the ray part that in the atmosphere.\n", FUNC_NAME); + goto error; + } + if(!SVX_HIT_NONE(hit)) goto exit; /* Collision */ + } + +exit: + return res; +error: + goto exit; +} + diff --git a/src/htrdr_sky.h b/src/htrdr_sky.h @@ -17,6 +17,7 @@ #define HTRDR_SKY_H #include <rsys/rsys.h> +#include <star/svx.h> /* Properties of the sky */ enum htrdr_sky_property { @@ -26,7 +27,9 @@ enum htrdr_sky_property { HTRDR_PROPERTIES_COUNT__ }; -enum htrdr_sky_component { /* FIXME */ +/* FIXME Maybe rename this constant to avoid the confusion with the + * htrdr_sky_component_flag enumerate */ +enum htrdr_sky_component { HTRDR_GAS__, HTRDR_PARTICLES__, HTRDR_COMPONENTS_COUNT__ @@ -87,12 +90,6 @@ htrdr_sky_fetch_raw_property const double k_min, /* For debug only */ const double k_max); /* For debug only */ -extern LOCAL_SYM struct svx_tree* -htrdr_sky_get_svx_tree - (struct htrdr_sky* sky, - const size_t ispectral_band, - const size_t iquadrature_pt); - extern LOCAL_SYM double htrdr_sky_fetch_svx_property (const struct htrdr_sky* sky, @@ -156,5 +153,18 @@ htrdr_sky_sample_sw_spectral_data_CIE_1931_Z size_t* ispectral_band, size_t* iquadrature_pt); +extern LOCAL_SYM res_T +htrdr_sky_trace_ray + (struct htrdr_sky* sky, + const double ray_origin[3], + const double ray_direction[3], /* Must be normalized */ + const double ray_range[2], + const svx_hit_challenge_T challenge, /* NULL <=> Traversed up to the leaves */ + const svx_hit_filter_T filter, /* NULL <=> Stop RT at the 1st hit voxel */ + void* context, /* Data sent to the filter functor */ + const size_t ispectral_band, + const size_t iquadrature_pt, + struct svx_hit* hit); + #endif /* HTRDR_SKY_H */