commit d0fa71ccb625f4547d66ff9904b2b06a42f6bd3c
parent d6c941783611937ccfca5e95634a9d9431f48bda
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Thu, 29 Sep 2016 13:59:15 +0200
BugFix: rewrite management of hits on receivers
The previous code was based on the false assumption that hits were filtered by
increasing distance. As a consequence, hits past an occlusion point could
have been accepted as valid.
The new code adds the ability to process properly superposed receivers.
Diffstat:
5 files changed, 210 insertions(+), 71 deletions(-)
diff --git a/src/ssol_atmosphere.c b/src/ssol_atmosphere.c
@@ -16,6 +16,7 @@
#include "ssol.h"
#include "ssol_atmosphere_c.h"
#include "ssol_device_c.h"
+#include "ssol_spectrum_c.h"
#include <rsys/rsys.h>
#include <rsys/mem_allocator.h>
@@ -43,10 +44,31 @@ atmosphere_release(ref_T* ref)
SSOL(device_ref_put(dev));
}
-
/*******************************************************************************
* Exported ssol_atmosphere functions
******************************************************************************/
+double
+compute_atmosphere_attenuation
+ (const struct ssol_atmosphere* atmosphere,
+ const double distance,
+ const double wavelength)
+{
+ double ka;
+ const struct ssol_spectrum* spectrum;
+ if (!atmosphere)
+ return 1;
+
+ ASSERT(distance >= 0 && wavelength >= 0);
+ switch (atmosphere->type) {
+ case ATMOS_UNIFORM:
+ spectrum = atmosphere->data.uniform.spectrum;
+ CHECK(spectrum_interpolate(spectrum, wavelength, &ka), RES_OK);
+ break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ return exp(-ka * distance);
+}
+
res_T
ssol_atmosphere_create_uniform
(struct ssol_device* dev,
diff --git a/src/ssol_atmosphere_c.h b/src/ssol_atmosphere_c.h
@@ -41,4 +41,10 @@ struct ssol_atmosphere {
ref_T ref;
};
+double
+compute_atmosphere_attenuation
+ (const struct ssol_atmosphere* atmosphere,
+ const double distance,
+ const double wavelength);
+
#endif /* SSOL_ATMOSPHERE_C_H */
diff --git a/src/ssol_scene.c b/src/ssol_scene.c
@@ -335,7 +335,7 @@ hit_filter_function
void* realisation,
void* filter_data)
{
- const struct ssol_instance* inst;
+ struct ssol_instance* inst;
const struct ssol_shape* shape;
const struct str* receiver_name;
struct realisation* rs = realisation;
@@ -351,17 +351,9 @@ hit_filter_function
/* these components have been set */
ASSERT_NAN(seg->dir, 3);
ASSERT_NAN(seg->org, 3);
- ASSERT_NAN(&seg->tmin, 1);
ASSERT(seg->self_instance);
ASSERT(seg->self_front != NON_BOOL);
- /* need to reject identical hits on triangle's edges */
- if (hit->distance <= seg->tmin) {
- ASSERT(hit->distance == seg->tmin);
- return 1;
- }
- seg->tmin = hit->distance;
-
inst = *htable_instance_find(&rs->data.scene->instances_rt, &hit->prim.inst_id);
shape = inst->object->shape;
seg->on_punched = (shape->type == SHAPE_PUNCHED);
@@ -419,18 +411,19 @@ hit_filter_function
}
/* Check if the hit surface is a receiver that registers hit data */
- /* sun segments must not be registered */
+ /* impacts on receivers before primary mirros are not registered */
if (!seg->sun_segment && !str_is_empty(receiver_name)) {
- fprintf(rs->data.out_stream,
- "Receiver '%s': %u %u %g %g (%g:%g:%g) (%g:%g:%g) (%g:%g)\n",
- str_cget(receiver_name),
- (unsigned) rs->rs_id,
- (unsigned) rs->s_idx,
- rs->wavelength,
- seg->weight,
- SPLIT3(seg->hit_pos),
- SPLIT3(seg->dir),
- SPLIT2(hit->uv));
+ struct receiver_record candidate;
+ d3_set(candidate.dir, seg->dir);
+ candidate.hit_distance = seg->hit_distance;
+ d3_set(candidate.hit_normal, seg->hit_normal);
+ d3_set(candidate.hit_pos, seg->hit_pos);
+ candidate.instance = inst;
+ candidate.receiver_name = str_cget(receiver_name);
+ candidate.uv[0] = seg->hit.uv[0];
+ candidate.uv[1] = seg->hit.uv[1];
+ darray_receiver_record_push_back(
+ &rs->data.receiver_record_candidates, &candidate);
}
/* register success mask */
diff --git a/src/ssol_solver.c b/src/ssol_solver.c
@@ -57,6 +57,55 @@ solstice_trace_ray(struct realisation* rs)
/* the filter function recomputes intersections on quadrics and sets seg */
}
+static int
+cmp_candidates(const void* _c1, const void* _c2)
+{
+ const struct receiver_record* c1 = _c1;
+ const struct receiver_record* c2 = _c2;
+ const double d1 = c1->hit_distance;
+ const double d2 = c2->hit_distance;
+ return (d1 > d2) - (d1 < d2);
+}
+
+static FINLINE res_T
+check_scene(const struct ssol_scene* scene) {
+ ASSERT(scene);
+ if (!scene->sun) {
+ log_error(scene->dev, "%s: no sun attached.\n", FUNC_NAME);
+ return RES_BAD_ARG;
+ }
+
+ if (!scene->sun->spectrum) {
+ log_error(scene->dev, "%s: sun's spectrum undefined.\n", FUNC_NAME);
+ return RES_BAD_ARG;
+ }
+
+ if (scene->sun->dni <= 0) {
+ log_error(scene->dev, "%s: sun's DNI undefined.\n", FUNC_NAME);
+ return RES_BAD_ARG;
+ }
+
+ if (scene->atmosphere) {
+ switch (scene->atmosphere->type) {
+ case ATMOS_UNIFORM: {
+ char ok;
+ CHECK(spectrum_includes(
+ scene->atmosphere->data.uniform.spectrum,
+ scene->sun->spectrum,
+ &ok),
+ RES_OK);
+ if (!ok) {
+ log_error(scene->dev, "%s: sun/atmosphere spectra mismatch.\n", FUNC_NAME);
+ return RES_BAD_ARG;
+ }
+ break;
+ }
+ default: FATAL("Unreachable code\n"); break;
+ }
+ }
+ return RES_OK;
+}
+
/*******************************************************************************
* Local functions
******************************************************************************/
@@ -130,6 +179,10 @@ release_solver_data(struct solver_data* data)
if (data->sun_dir_ran) CHECK(ranst_sun_dir_ref_put(data->sun_dir_ran), RES_OK);
if (data->sun_wl_ran) CHECK(ranst_sun_wl_ref_put(data->sun_wl_ran), RES_OK);
if (data->brdfs) brdf_composite_ref_put(data->brdfs);
+ if (data->receiver_record_candidates.data)
+ darray_receiver_record_release(&data->receiver_record_candidates);
+ if (data->instances_ptr.data)
+ darray_instances_ptr_release(&data->instances_ptr);
*data = SOLVER_DATA_NULL;
}
@@ -194,6 +247,7 @@ current_segment(struct realisation* rs)
static void
check_fst_segment(const struct segment* seg)
{
+ (void) seg;
ASSERT(seg);
ASSERT_NAN(seg->dir, 3);
/* hit is not checked and can be used only for debugging purpose */
@@ -204,7 +258,6 @@ check_fst_segment(const struct segment* seg)
ASSERT_NAN(seg->hit_pos, 3);
ASSERT(seg->on_punched != NON_BOOL);
ASSERT_NAN(seg->org, 3);
- ASSERT_NAN(&seg->tmin, 1);
ASSERT(seg->weight > 0);
}
@@ -246,7 +299,9 @@ setup_next_segment(struct realisation* rs)
seg->self_instance = prev->hit_instance;
seg->self_front = prev->hit_front;
seg->sun_segment = 0;
- seg->tmin = -1; /* any valid tmin allowed */
+
+ /* reset candidates */
+ darray_receiver_record_clear(&rs->data.receiver_record_candidates);
d3_set(seg->org, prev->hit_pos);
@@ -259,7 +314,11 @@ setup_next_segment(struct realisation* rs)
seg->weight = prev->weight
* brdf_composite_sample(
- data->brdfs, data->rng, prev->dir, data->fragment.Ns, seg->dir);
+ data->brdfs, data->rng, prev->dir, data->fragment.Ns, seg->dir);
+ if (rs->s_idx > 1) {
+ seg->weight *= compute_atmosphere_attenuation(
+ rs->data.scene->atmosphere, prev->hit_distance, rs->wavelength);
+ }
return res;
}
@@ -282,7 +341,6 @@ reset_segment(struct segment* seg)
seg->self_instance = NULL;
seg->self_front = NON_BOOL;
seg->weight = NAN;
- seg->tmin = NAN;
#else
seg->hit = S3D_HIT_NULL;
#endif
@@ -312,6 +370,7 @@ reset_starting_point(struct starting_point* start)
static void
check_starting_point(const struct starting_point* start)
{
+ (void) start;
ASSERT(start);
ASSERT(start->cos_sun > 0); /* normal is flipped facing in_dir */
ASSERT(start->front_exposed != NON_BOOL);
@@ -360,6 +419,9 @@ init_realisation
rs->data.scene = scene;
rs->data.rng = rng;
rs->data.out_stream = out;
+ darray_receiver_record_init(
+ scene->dev->allocator, &rs->data.receiver_record_candidates);
+ darray_instances_ptr_init(scene->dev->allocator, &rs->data.instances_ptr);
/* create 2 s3d_scene_view for raytracing and sampling */
res = set_views(&rs->data);
if (res != RES_OK) goto error;
@@ -525,7 +587,6 @@ receive_sunlight(struct realisation* rs)
seg->self_instance = start->instance;
seg->self_front = start->front_exposed;
seg->sun_segment = 1;
- seg->tmin = -1; /* any valid tmin allowed */
/* search for occlusions from starting point */
ASSERT(rs->s_idx == 0); /* sun segment */
@@ -590,32 +651,94 @@ receive_sunlight(struct realisation* rs)
}
static void
+filter_receiver_hit_candidates(struct realisation* rs)
+{
+ struct receiver_record* candidates;
+ struct receiver_record* candidates_end;
+ struct segment* seg;
+ struct darray_instances_ptr* inst_array;
+ size_t candidates_count;
+ double tmax, prev_distance;
+
+ ASSERT(rs);
+ candidates_count = rs->data.receiver_record_candidates.size;
+ if (!candidates_count)
+ return;
+ candidates = rs->data.receiver_record_candidates.data;
+ inst_array = &rs->data.instances_ptr;
+
+ /* sort candidates by distance */
+ if (candidates_count > 1) {
+ qsort(candidates,
+ candidates_count, sizeof(struct receiver_record), cmp_candidates);
+ }
+ /* filter duplicates and candidates past the actual hit distance */
+ seg = current_segment(rs);
+ tmax = seg->hit_distance;
+ prev_distance = -1;
+ darray_instances_ptr_clear(inst_array);
+ for (candidates_end = candidates + candidates_count;
+ candidates->hit_distance <= tmax && candidates < candidates_end;
+ candidates++)
+ {
+ if (candidates->hit_distance == prev_distance) {
+ int i = 0, is_duplicate = 0;
+ const struct ssol_instance** ptr
+ = darray_instances_ptr_data_get(inst_array);
+ while (!is_duplicate && i < darray_instances_ptr_size_get(inst_array)) {
+ if (*ptr == candidates->instance)
+ is_duplicate = 1;
+ ptr++;
+ i++;
+ }
+ /* more than one candidate with same distance
+ can be duplicates (duplicate = same distance, same instance) */
+ if (is_duplicate) continue;
+ darray_instances_ptr_push_back(inst_array, &candidates->instance);
+ }
+ else {
+ prev_distance = candidates->hit_distance;
+ darray_instances_ptr_clear(inst_array);
+ darray_instances_ptr_push_back(inst_array, &candidates->instance);
+ }
+ /* output valid receiver hits */
+ fprintf(rs->data.out_stream,
+ "Receiver '%s': %u %u %g %g (%g:%g:%g) (%g:%g:%g) (%g:%g:%g) (%g:%g)\n",
+ candidates->receiver_name,
+ (unsigned) rs->rs_id,
+ (unsigned) rs->s_idx,
+ rs->wavelength,
+ /* take amosphere into account with the correct distance */
+ seg->weight * compute_atmosphere_attenuation(
+ rs->data.scene->atmosphere, candidates->hit_distance, rs->wavelength),
+ SPLIT3(candidates->hit_pos),
+ SPLIT3(candidates->dir),
+ SPLIT3(candidates->hit_normal),
+ SPLIT2(candidates->uv));
+ }
+}
+
+static void
propagate(struct realisation* rs)
{
- struct segment* seg = current_segment(rs);
+ struct segment* seg;
+
+ ASSERT(rs);
+ seg = current_segment(rs);
/* check if the ray hits something */
solstice_trace_ray(rs);
+
+ /* post process possible hits on receivers */
+ filter_receiver_hit_candidates(rs);
+
if (S3D_HIT_NONE(&seg->hit)) {
rs->end = TERM_MISSING;
return;
}
-
- if (rs->data.scene->atmosphere) {
- double ka;
- const struct ssol_spectrum* spectrum;
- switch (rs->data.scene->atmosphere->type) {
- case ATMOS_UNIFORM:
- spectrum = rs->data.scene->atmosphere->data.uniform.spectrum;
- CHECK(spectrum_interpolate(spectrum, rs->wavelength, &ka), RES_OK);
- break;
- default: FATAL("Unreachable code\n"); break;
- }
- seg->weight *= exp(-ka * seg->hit_distance);
- }
+ check_segment(seg);
/* fill fragment and loop */
- check_segment(seg);
surface_fragment_setup(&rs->data.fragment, seg->hit_pos, seg->dir,
seg->hit_normal, &seg->hit.prim, seg->hit.uv);
}
@@ -637,35 +760,8 @@ ssol_solve
if (!scene || !rng || !output || !realisations_count)
return RES_BAD_ARG;
- if (!scene->sun) {
- log_error(scene->dev, "%s: no sun attached.\n", FUNC_NAME);
- return RES_BAD_ARG;
- }
-
- if (!scene->sun->spectrum) {
- log_error(scene->dev, "%s: sun's spectrum undefined.\n", FUNC_NAME);
- return RES_BAD_ARG;
- }
-
- if (scene->sun->dni <= 0) {
- log_error(scene->dev, "%s: sun's DNI undefined.\n", FUNC_NAME);
- return RES_BAD_ARG;
- }
-
- if (scene->atmosphere) {
- switch (scene->atmosphere->type) {
- case ATMOS_UNIFORM: {
- char ok;
- CHECK(spectrum_includes(scene->atmosphere->data.uniform.spectrum, scene->sun->spectrum, &ok), RES_OK);
- if (!ok) {
- log_error(scene->dev, "%s: sun/atmosphere spectra mismatch.\n", FUNC_NAME);
- return RES_BAD_ARG;
- }
- break;
- }
- default: FATAL("Unreachable code\n"); break;
- }
- }
+ res = check_scene(scene);
+ if (res != RES_OK) return res;
/* init realisation */
res = init_realisation(scene, rng, output, &rs);
diff --git a/src/ssol_solver_c.h b/src/ssol_solver_c.h
@@ -17,6 +17,7 @@
#define SSOL_SOLVER_C_H
#include "ssol_material_c.h"
+#include "ssol_instance_c.h"
#include "ssol_c.h"
#include <rsys/ref_count.h>
@@ -37,6 +38,11 @@ struct ranst_sun_wl;
#define DARRAY_DATA struct s3d_shape*
#include <rsys/dynamic_array.h>
+#include <rsys/dynamic_array.h>
+#define DARRAY_DATA struct ssol_instance*
+#define DARRAY_NAME instances_ptr
+#include <rsys/dynamic_array.h>
+
enum realisation_termination {
TERM_NONE,
TERM_SUCCESS,
@@ -67,7 +73,6 @@ struct segment {
double hit_pos[3];
double hit_normal[3]; /* possibly reversed to face the incoming dir */
double hit_distance;
- float tmin; /* used to reject duplicate hits in raytracing */
char hit_front; /* is the ending point of the segment on the front face? */
char self_front; /* was the starting point of the segment on the front face? */
char on_punched; /* is the hit on a punched shape? */
@@ -89,11 +94,26 @@ struct starting_point {
char on_punched; /* is the point on a punched shape? */
};
+struct receiver_record {
+ struct ssol_instance* instance;
+ const char* receiver_name;
+ double dir[4];
+ double hit_pos[3];
+ float uv[2];
+ double hit_normal[3]; /* face the incoming dir */
+ double hit_distance;
+};
+
#include <rsys/dynamic_array.h>
#define DARRAY_DATA struct segment
#define DARRAY_NAME segment
#include <rsys/dynamic_array.h>
+#include <rsys/dynamic_array.h>
+#define DARRAY_DATA struct receiver_record
+#define DARRAY_NAME receiver_record
+#include <rsys/dynamic_array.h>
+
struct solver_data {
struct ssol_scene* scene;
struct ssp_rng* rng;
@@ -108,6 +128,8 @@ struct solver_data {
/* Tmp data used for propagation */
struct brdf_composite* brdfs;
struct surface_fragment fragment;
+ struct darray_receiver_record receiver_record_candidates;
+ struct darray_instances_ptr instances_ptr;
/* total area of the surface sampled as starting point candidate */
float sampled_area;
};