commit 61e48b579498c3f048e501097af849847f799889
parent 135e3ce32d4f72f490520b23edebbef1f56dda6c
Author: Christophe Coustet <christophe.coustet@meso-star.com>
Date: Tue, 20 Sep 2016 18:50:31 +0200
Add atmosphere models in API
First step is to allow uniform atmospheres, described by an absorbtion
spectra.
Diffstat:
12 files changed, 447 insertions(+), 5 deletions(-)
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
@@ -51,6 +51,7 @@ set(VERSION_PATCH 0)
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
set(SSOL_FILES_SRC
+ ssol_atmosphere.c
ssol_brdf.c
ssol_brdf_composite.c
ssol_brdf_reflection.c
@@ -71,6 +72,7 @@ set(SSOL_FILES_INC_API
ssol.h)
set(SSOL_FILES_INC
+ ssol_atmosphere_c.h
ssol_brdf.h
ssol_brdf_composite.h
ssol_brdf_reflection.h
@@ -142,6 +144,7 @@ if(NOT NO_TEST)
register_test(${_name} ${_name})
endfunction()
+ new_test(test_ssol_atmosphere)
new_test(test_ssol_device)
new_test(test_ssol_image)
new_test(test_ssol_material)
diff --git a/src/ssol.h b/src/ssol.h
@@ -46,13 +46,13 @@ struct mem_allocator;
struct ssp_rng;
/* Opaque Solstice solver types */
+struct ssol_atmosphere;
struct ssol_device;
struct ssol_image;
struct ssol_material;
struct ssol_object;
struct ssol_instance;
struct ssol_scene;
-struct ssol_quadric;
struct ssol_shape;
struct ssol_spectrum;
struct ssol_sun;
@@ -307,6 +307,16 @@ ssol_scene_detach_sun
(struct ssol_scene* scn,
struct ssol_sun* sun);
+SSOL_API res_T
+ssol_scene_attach_atmosphere
+ (struct ssol_scene* scn,
+ struct ssol_atmosphere* atm);
+
+SSOL_API res_T
+ssol_scene_detach_atmosphere
+ (struct ssol_scene* scn,
+ struct ssol_atmosphere* atm);
+
/*******************************************************************************
* Shape API - Define a geometry that can be generated from a quadric equation
* or from a triangular mesh.
@@ -523,6 +533,29 @@ ssol_sun_set_buie_param
const double param); /* In ]0, 1[ */
/*******************************************************************************
+ * Atmosphere API - Describe an atmosphere model.
+ ******************************************************************************/
+/* The atmosphere describes absorbtion along the light paths */
+SSOL_API res_T
+ssol_atmosphere_create_uniform
+ (struct ssol_device* dev,
+ struct ssol_atmosphere** atmosphere);
+
+SSOL_API res_T
+ssol_atmosphere_ref_get
+ (struct ssol_atmosphere* atmosphere);
+
+SSOL_API res_T
+ssol_atmosphere_ref_put
+ (struct ssol_atmosphere* atmosphere);
+
+/* List of per wavelength power of the sun */
+SSOL_API res_T
+ssol_atmosphere_set_uniform_absorbtion
+ (struct ssol_atmosphere* atmosphere,
+ struct ssol_spectrum* spectrum);
+
+/*******************************************************************************
* Miscellaneous functions
******************************************************************************/
SSOL_API res_T
diff --git a/src/ssol_atmosphere.c b/src/ssol_atmosphere.c
@@ -0,0 +1,120 @@
+/* Copyright (C) CNRS 2016
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "ssol.h"
+#include "ssol_atmosphere_c.h"
+#include "ssol_device_c.h"
+
+#include <rsys/rsys.h>
+#include <rsys/mem_allocator.h>
+#include <rsys/ref_count.h>
+
+/*******************************************************************************
+* Helper functions
+******************************************************************************/
+static void
+atmosphere_release(ref_T* ref)
+{
+ struct ssol_device* dev;
+ struct ssol_atmosphere* atmosphere = CONTAINER_OF(ref, struct ssol_atmosphere, ref);
+ ASSERT(ref);
+ dev = atmosphere->dev;
+ ASSERT(dev && dev->allocator);
+ switch (atmosphere->type) {
+ case ATMOS_UNIFORM:
+ if (atmosphere->data.uniform.spectrum)
+ SSOL(spectrum_ref_put(atmosphere->data.uniform.spectrum));
+ break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ MEM_RM(dev->allocator, atmosphere);
+ SSOL(device_ref_put(dev));
+}
+
+
+/*******************************************************************************
+* Exported ssol_atmosphere functions
+******************************************************************************/
+res_T
+ssol_atmosphere_create_uniform
+ (struct ssol_device* dev,
+ struct ssol_atmosphere** out_atmosphere)
+{
+ struct ssol_atmosphere* atmosphere = NULL;
+ res_T res = RES_OK;
+ if (!dev || !out_atmosphere) {
+ return RES_BAD_ARG;
+ }
+
+ atmosphere = (struct ssol_atmosphere*)MEM_CALLOC
+ (dev->allocator, 1, sizeof(struct ssol_atmosphere));
+ if (!atmosphere) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ SSOL(device_ref_get(dev));
+ atmosphere->dev = dev;
+ atmosphere->type = ATMOS_UNIFORM;
+ ref_init(&atmosphere->ref);
+
+exit:
+ if (out_atmosphere) *out_atmosphere = atmosphere;
+ return res;
+error:
+ if (atmosphere) {
+ SSOL(atmosphere_ref_put(atmosphere));
+ atmosphere = NULL;
+ }
+ goto exit;
+}
+
+res_T
+ssol_atmosphere_ref_get
+ (struct ssol_atmosphere* atmosphere)
+{
+ if (!atmosphere)
+ return RES_BAD_ARG;
+ ref_get(&atmosphere->ref);
+ return RES_OK;
+}
+
+res_T
+ssol_atmosphere_ref_put
+ (struct ssol_atmosphere* atmosphere)
+{
+ if (!atmosphere)
+ return RES_BAD_ARG;
+ ref_put(&atmosphere->ref, atmosphere_release);
+ return RES_OK;
+}
+
+res_T
+ssol_atmosphere_set_uniform_absorbtion
+ (struct ssol_atmosphere* atmosphere,
+ struct ssol_spectrum* spectrum)
+{
+ struct atm_uniform* uni;
+ if (!atmosphere || !spectrum || atmosphere->type != ATMOS_UNIFORM)
+ return RES_BAD_ARG;
+ uni = &atmosphere->data.uniform;
+ if (spectrum == uni->spectrum) /* no change */
+ return RES_OK;
+ if (uni->spectrum)
+ SSOL(spectrum_ref_put(uni->spectrum));
+ SSOL(spectrum_ref_get(spectrum));
+ uni->spectrum = spectrum;
+ return RES_OK;
+}
+\ No newline at end of file
diff --git a/src/ssol_atmosphere_c.h b/src/ssol_atmosphere_c.h
@@ -0,0 +1,44 @@
+/* Copyright (C) CNRS 2016
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SSOL_ATMOSPHERE_C_H
+#define SSOL_ATMOSPHERE_C_H
+
+#include <rsys/ref_count.h>
+#include <rsys/list.h>
+
+struct ssol_scene;
+
+enum atmosphere_type {
+ ATMOS_UNIFORM,
+ ATMOS_TYPES_COUNT__
+};
+
+struct atm_uniform {
+ struct ssol_spectrum* spectrum;
+};
+
+struct ssol_atmosphere {
+ enum atmosphere_type type;
+ struct ssol_scene* scene_attachment;
+ union {
+ struct atm_uniform uniform;
+ } data;
+
+ struct ssol_device* dev;
+ ref_T ref;
+};
+
+#endif /* SSOL_ATMOSPHERE_C_H */
diff --git a/src/ssol_scene.c b/src/ssol_scene.c
@@ -15,6 +15,7 @@
#include "ssol.h"
#include "ssol_c.h"
+#include "ssol_atmosphere_c.h"
#include "ssol_scene_c.h"
#include "ssol_solver_c.h"
#include "ssol_sun_c.h"
@@ -43,7 +44,8 @@ scene_release(ref_T* ref)
SSOL(scene_clear(scene));
if(scene->scn_rt) S3D(scene_ref_put(scene->scn_rt));
if(scene->scn_samp) S3D(scene_ref_put(scene->scn_samp));
- if(scene->sun) SSOL(sun_ref_put(scene->sun));
+ if (scene->sun) SSOL(sun_ref_put(scene->sun));
+ if (scene->atmosphere) SSOL(atmosphere_ref_put(scene->atmosphere));
htable_instance_release(&scene->instances_rt);
htable_instance_release(&scene->instances_samp);
MEM_RM(dev->allocator, scene);
@@ -51,7 +53,7 @@ scene_release(ref_T* ref)
}
/*******************************************************************************
- * Exported ssol_image functions
+ * Exported ssol_scene functions
******************************************************************************/
res_T
ssol_scene_create
@@ -214,6 +216,34 @@ ssol_scene_detach_sun(struct ssol_scene* scene, struct ssol_sun* sun)
return RES_OK;
}
+
+res_T
+ssol_scene_attach_atmosphere(struct ssol_scene* scene, struct ssol_atmosphere* atm)
+{
+ if (!scene || !atm
+ || atm->scene_attachment /* Should detach this atmosphere first from its own scene */
+ || scene->atmosphere) /* Should detach previous atm first */
+ return RES_BAD_ARG;
+
+ SSOL(atmosphere_ref_get(atm));
+ scene->atmosphere = atm;
+ atm->scene_attachment = scene;
+ return RES_OK;
+}
+
+res_T
+ssol_scene_detach_atmosphere(struct ssol_scene* scene, struct ssol_atmosphere* atm)
+{
+ if (!scene || !atm || !scene->atmosphere || atm->scene_attachment != scene)
+ return RES_BAD_ARG;
+
+ ASSERT(atm == scene->atmosphere);
+ atm->scene_attachment = NULL;
+ scene->atmosphere = NULL;
+ SSOL(atmosphere_ref_put(atm));
+ return RES_OK;
+}
+
/*******************************************************************************
* Local functions
******************************************************************************/
@@ -276,7 +306,6 @@ hit_filter_function
struct realisation* rs = realisation;
struct segment* seg;
struct segment* prev;
- double dist;
(void) filter_data, (void) org, (void) dir;
ASSERT(rs);
@@ -323,7 +352,7 @@ hit_filter_function
d33_muld3(dir_local, tr, seg->dir);
/* recompute hit */
int valid = punched_shape_intersect_local(shape, org_local, dir_local,
- hit->distance, seg->hit_pos_local, seg->hit_normal, &dist);
+ hit->distance, seg->hit_pos_local, seg->hit_normal, &seg->hit_distance);
if (!valid) return 1;
/* transform point to world */
d33_muld3(seg->hit_pos, transform, seg->hit_pos_local);
@@ -337,6 +366,7 @@ hit_filter_function
d3_set_f3(seg->hit_normal, hit->normal);
/* use raytraced distance to fill hit_pos */
d3_add(seg->hit_pos, seg->org, d3_muld(seg->hit_pos, seg->dir, hit->distance));
+ seg->hit_distance = hit->distance;
break;
}
default: FATAL("Unreachable code.\n"); break;
diff --git a/src/ssol_scene_c.h b/src/ssol_scene_c.h
@@ -41,6 +41,7 @@ struct ssol_scene {
struct s3d_scene* scn_rt;
struct s3d_scene* scn_samp;
struct ssol_sun* sun;
+ struct ssol_atmosphere* atmosphere;
struct ssol_device* dev;
ref_T ref;
diff --git a/src/ssol_solver.c b/src/ssol_solver.c
@@ -15,6 +15,7 @@
#include "ssol.h"
#include "ssol_c.h"
+#include "ssol_atmosphere_c.h"
#include "ssol_solver_c.h"
#include "ssol_device_c.h"
#include "ssol_scene_c.h"
@@ -207,6 +208,7 @@ static void
check_segment(const struct segment* seg)
{
check_fst_segment(seg);
+ ASSERT_NAN(&seg->hit_distance, 1);
ASSERT(seg->self_instance);
NCHECK(seg->self_front, 99);
/* hit filter is supposed to work properly */
@@ -264,6 +266,7 @@ reset_segment(struct segment* seg)
#ifndef NDEBUG
d3_splat(seg->dir, NAN);
seg->hit = S3D_HIT_NULL;
+ seg->hit_distance = NAN;
seg->hit_front = 99;
seg->hit_instance = NULL;
seg->hit_material = NULL;
@@ -598,6 +601,19 @@ propagate(struct realisation* rs)
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->freq, &ka), RES_OK);
+ break;
+ default: FATAL("Unreachable code\n"); break;
+ }
+ seg->weight *= exp(-ka * seg->hit_distance);
+ }
+
/* fill fragment and loop */
check_segment(seg);
surface_fragment_setup(&rs->data.fragment, seg->hit_pos, seg->dir,
diff --git a/src/ssol_solver_c.h b/src/ssol_solver_c.h
@@ -67,6 +67,7 @@ struct segment {
double hit_pos[3];
double hit_pos_local[3]; /* in local coordinate, only set on punched shapes */
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? */
diff --git a/src/ssol_spectrum.c b/src/ssol_spectrum.c
@@ -21,6 +21,7 @@
#include <rsys/mem_allocator.h>
#include <rsys/ref_count.h>
#include <rsys/math.h>
+#include <rsys/algorithm.h>
/*******************************************************************************
* Helper functions
@@ -39,6 +40,91 @@ spectrum_release(ref_T* ref)
SSOL(device_ref_put(dev));
}
+static char
+spectrum_includes_point
+ (const struct ssol_spectrum* spectrum,
+ const double wavelenght)
+{
+ const double* data;
+ size_t sz;
+ ASSERT(spectrum && spectrum->frequencies.data && spectrum->intensities.data);
+ sz = spectrum->frequencies.size;
+ ASSERT(sz && sz == spectrum->intensities.size);
+ data = spectrum->frequencies.data;
+ return data[0] <= wavelenght && wavelenght <= data[sz - 1];
+}
+
+int
+eq_d(const void* key, const void* base)
+{
+ double k = *(double*) key;
+ double b = *(double*) base;
+ if (k > b) return +1;
+ if (k < b) return -1;
+ return 0;
+}
+
+/*******************************************************************************
+* Local ssol_spectrum functions
+******************************************************************************/
+res_T
+spectrum_includes
+ (const struct ssol_spectrum* reference,
+ const struct ssol_spectrum* tested,
+ char* include)
+{
+ const double* test_data;
+ size_t test_sz;
+ if(!reference || !tested || !include) {
+ return RES_BAD_ARG;
+ }
+
+ test_sz = tested->frequencies.size;
+ if(!reference->frequencies.size || !test_sz) {
+ return RES_BAD_ARG;
+ }
+
+ test_data = tested->frequencies.data;
+ *include = spectrum_includes_point(reference, test_data[0])
+ && spectrum_includes_point(reference, test_data[test_sz - 1]);
+
+ return RES_OK;
+}
+
+res_T
+spectrum_interpolate
+ (const struct ssol_spectrum* spectrum,
+ const double wavelenght,
+ double* intensity)
+{
+ double* next;
+ double* freqs;
+ double* ints;
+ double slope;
+ size_t idx_next, sz;
+ if (!spectrum
+ || !intensity
+ || !spectrum_includes_point(spectrum, wavelenght))
+ {
+ return RES_BAD_ARG;
+ }
+
+ sz = spectrum->frequencies.size;
+ freqs = spectrum->frequencies.data;
+ ints = spectrum->intensities.data;
+ next = search_lower_bound(&wavelenght, freqs, sz, sizeof(double), &eq_d);
+ ASSERT(next); /* cause spectrum_includes_point */
+ idx_next = next - freqs;
+ ASSERT(idx_next); /* cause spectrum_includes_point */
+ ASSERT(ints[idx_next] >= ints[idx_next - 1]);
+ ASSERT(freqs[idx_next] >= freqs[idx_next - 1]);
+
+ slope = (ints[idx_next] - ints[idx_next - 1]) / (freqs[idx_next] - freqs[idx_next - 1]);
+ *intensity = ints[idx_next - 1] + (wavelenght - freqs[idx_next - 1]) * slope;
+ ASSERT(*intensity > 0);
+ return RES_OK;
+}
+
/*******************************************************************************
* Exported ssol_spectrum functions
******************************************************************************/
diff --git a/src/ssol_spectrum_c.h b/src/ssol_spectrum_c.h
@@ -26,4 +26,16 @@ struct ssol_spectrum {
ref_T ref;
};
+extern LOCAL_SYM res_T
+spectrum_includes
+ (const struct ssol_spectrum* reference,
+ const struct ssol_spectrum* tested,
+ char* include);
+
+extern LOCAL_SYM res_T
+spectrum_interpolate
+ (const struct ssol_spectrum* spectrum,
+ const double wavelenght,
+ double* intensity);
+
#endif /* SSOL_SPECTRUM_C_H */
diff --git a/src/test_ssol_atmosphere.c b/src/test_ssol_atmosphere.c
@@ -0,0 +1,74 @@
+/* Copyright (C) CNRS 2016
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "ssol.h"
+#include "test_ssol_utils.h"
+
+#include <rsys/logger.h>
+
+int
+main(int argc, char** argv)
+{
+ struct logger logger;
+ struct mem_allocator allocator;
+ struct ssol_device* dev;
+ struct ssol_spectrum* spectrum;
+ struct ssol_spectrum* spectrum2;
+ struct ssol_atmosphere* atm;
+ (void) argc, (void) argv;
+
+ mem_init_proxy_allocator(&allocator, &mem_default_allocator);
+
+ CHECK(logger_init(&allocator, &logger), RES_OK);
+ logger_set_stream(&logger, LOG_OUTPUT, log_stream, NULL);
+ logger_set_stream(&logger, LOG_ERROR, log_stream, NULL);
+ logger_set_stream(&logger, LOG_WARNING, log_stream, NULL);
+
+ CHECK(ssol_device_create
+ (&logger, &allocator, SSOL_NTHREADS_DEFAULT, 0, &dev), RES_OK);
+
+ CHECK(ssol_spectrum_create(dev, &spectrum), RES_OK);
+ CHECK(ssol_spectrum_create(dev, &spectrum2), RES_OK);
+
+ CHECK(ssol_atmosphere_create_uniform(NULL, &atm), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_create_uniform(dev, NULL), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_create_uniform(dev, &atm), RES_OK);
+
+ CHECK(ssol_atmosphere_ref_get(NULL), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_ref_get(atm), RES_OK);
+
+ CHECK(ssol_atmosphere_ref_put(NULL), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_ref_put(atm), RES_OK);
+
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(NULL, spectrum), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(atm, NULL), RES_BAD_ARG);
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(atm, spectrum), RES_OK);
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(atm, spectrum2), RES_OK);
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(atm, spectrum2), RES_OK);
+
+ CHECK(ssol_atmosphere_ref_put(atm), RES_OK);
+
+ CHECK(ssol_spectrum_ref_put(spectrum), RES_OK);
+ CHECK(ssol_spectrum_ref_put(spectrum2), RES_OK);
+ CHECK(ssol_device_ref_put(dev), RES_OK);
+
+ logger_release(&logger);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHECK(mem_allocated_size(), 0);
+
+ return 0;
+}
diff --git a/src/test_ssol_scene.c b/src/test_ssol_scene.c
@@ -31,6 +31,10 @@ main(int argc, char** argv)
struct ssol_sun* sun;
struct ssol_sun* sun2;
struct ssol_scene* scene;
+ struct ssol_spectrum* spectrum;
+ struct ssol_atmosphere* atm;
+ double wavelengths[3] = { 10, 20, 30 };
+ double data[3] = { 1, 2.1, 1.5 };
double transform[12];
(void) argc, (void) argv;
@@ -82,6 +86,21 @@ main(int argc, char** argv)
CHECK(ssol_scene_detach_sun(scene, sun), RES_OK);
CHECK(ssol_scene_detach_sun(scene, sun), RES_BAD_ARG);
+ CHECK(ssol_spectrum_create(dev, &spectrum), RES_OK);
+ CHECK(ssol_spectrum_setup(spectrum, wavelengths, data, 3), RES_OK);
+ CHECK(ssol_atmosphere_create_uniform(dev, &atm), RES_OK);
+ CHECK(ssol_atmosphere_set_uniform_absorbtion(atm, spectrum), RES_OK);
+
+ CHECK(ssol_scene_attach_atmosphere(NULL, atm), RES_BAD_ARG);
+ CHECK(ssol_scene_attach_atmosphere(scene, NULL), RES_BAD_ARG);
+ CHECK(ssol_scene_attach_atmosphere(scene, atm), RES_OK);
+ CHECK(ssol_scene_attach_atmosphere(scene, atm), RES_BAD_ARG);
+
+ CHECK(ssol_scene_detach_atmosphere(NULL, atm), RES_BAD_ARG);
+ CHECK(ssol_scene_detach_atmosphere(scene, NULL), RES_BAD_ARG);
+ CHECK(ssol_scene_detach_atmosphere(scene, atm), RES_OK);
+ CHECK(ssol_scene_detach_atmosphere(scene, atm), RES_BAD_ARG);
+
CHECK(ssol_sun_create_directional(dev, &sun2), RES_OK);
CHECK(ssol_scene_detach_sun(scene, sun2), RES_BAD_ARG);
CHECK(ssol_sun_ref_put(sun2), RES_OK);
@@ -92,6 +111,8 @@ main(int argc, char** argv)
CHECK(ssol_object_ref_put(object), RES_OK);
CHECK(ssol_shape_ref_put(shape), RES_OK);
CHECK(ssol_sun_ref_put(sun), RES_OK);
+ CHECK(ssol_spectrum_ref_put(spectrum), RES_OK);
+ CHECK(ssol_atmosphere_ref_put(atm), RES_OK);
CHECK(ssol_material_ref_put(material), RES_OK);
CHECK(ssol_device_ref_put(dev), RES_OK);