24#include <unordered_set>
26using namespace helios;
38 directRayCount_default = 100;
39 diffuseRayCount_default = 1000;
41 diffuseFlux_default = -1.f;
43 minScatterEnergy_default = 0.1;
44 scatteringDepth_default = 0;
53 temperature_default = 300;
57 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/camera_spectral_library.xml").
string());
58 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/light_spectral_library.xml").
string());
59 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/soil_surface_spectral_library.xml").
string());
60 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/leaf_surface_spectral_library.xml").
string());
61 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/bark_surface_spectral_library.xml").
string());
62 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/fruit_surface_spectral_library.xml").
string());
63 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/solar_spectrum_ASTMG173.xml").
string());
64 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").
string());
65 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/DGK_DKK_colorboard.xml").
string());
69 backend->initialize();
86 if (strcmp(label,
"reflectivity") == 0 || strcmp(label,
"transmissivity") == 0) {
87 output_prim_data.emplace_back(label);
89 std::cout <<
"WARNING (RadiationModel::optionalOutputPrimitiveData): unknown output primitive data " << label << std::endl;
95 helios_runtime_error(
"ERROR (RadiationModel::setDirectRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
97 radiation_bands.at(label).directRayCount = N;
102 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
104 radiation_bands.at(label).diffuseRayCount = N;
109 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationFlux): Cannot set flux value for band '" + label +
"' because it is not a valid band.");
111 radiation_bands.at(label).diffuseFlux = flux;
120 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationExtinctionCoeff): Cannot set diffuse extinction value for band '" + label +
"' because it is not a valid band.");
128 for (
int j = 0; j < N; j++) {
129 for (
int i = 0; i < N; i++) {
130 float theta = 0.5f *
M_PI / float(N) * (0.5f + float(i));
131 float phi = 2.f *
M_PI / float(N) * (0.5f + float(j));
136 if (psi <
M_PI / 180.f) {
137 fd = powf(
M_PI / 180.f, -K);
142 norm += fd * cosf(theta) * sinf(theta) *
M_PI / float(N * N);
147 radiation_bands.at(label).diffuseExtinction = K;
148 radiation_bands.at(label).diffusePeakDir = dir;
149 radiation_bands.at(label).diffuseDistNorm = 1.f / norm;
154 if (spectrum_integral < 0) {
155 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
156 }
else if (global_diffuse_spectrum.empty()) {
157 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
162 if (current_integral > 0) {
163 float scale_factor = spectrum_integral / current_integral;
164 for (
vec2 &wavelength: global_diffuse_spectrum) {
165 wavelength.y *= scale_factor;
170 for (
auto &band: radiation_bands) {
171 band.second.diffuse_spectrum = global_diffuse_spectrum;
174 radiativepropertiesneedupdate =
true;
179 if (spectrum_integral < 0) {
180 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
181 }
else if (global_diffuse_spectrum.empty()) {
182 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
186 float current_integral =
integrateSpectrum(global_diffuse_spectrum, wavelength1, wavelength2);
187 if (current_integral > 0) {
188 float scale_factor = spectrum_integral / current_integral;
189 for (
vec2 &wavelength: global_diffuse_spectrum) {
190 wavelength.y *= scale_factor;
195 for (
auto &band: radiation_bands) {
196 band.second.diffuse_spectrum = global_diffuse_spectrum;
199 radiativepropertiesneedupdate =
true;
204 if (spectrum_integral < 0) {
205 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
207 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
208 }
else if (radiation_bands.at(band_label).diffuse_spectrum.empty()) {
209 std::cerr <<
"WARNING (RadiationModel::setDiffuseSpectrumIntegral): Diffuse spectral distribution has not been set for radiation band '" + band_label +
"'. Cannot set its integral." << std::endl;
213 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum);
215 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
216 wavelength.y *= spectrum_integral / current_integral;
219 radiativepropertiesneedupdate =
true;
224 if (spectrum_integral < 0) {
225 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
227 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
230 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum, wavelength1, wavelength2);
232 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
233 wavelength.y *= spectrum_integral / current_integral;
236 radiativepropertiesneedupdate =
true;
241 if (radiation_bands.find(label) != radiation_bands.end()) {
242 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
246 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
249 if (!global_diffuse_spectrum.empty()) {
253 radiation_bands.emplace(label, band);
256 for (
auto &source: radiation_sources) {
257 source.source_fluxes[label] = -1.f;
260 radiativepropertiesneedupdate =
true;
265 if (radiation_bands.find(label) != radiation_bands.end()) {
266 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
268 }
else if (wavelength1 > wavelength2) {
269 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The upper wavelength bound for a band must be greater than the lower bound.");
270 }
else if (wavelength2 - wavelength1 < 1) {
271 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The waveband range of a radiation band must be at least 1 nm.");
274 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
279 if (!global_diffuse_spectrum.empty()) {
283 radiation_bands.emplace(label, band);
286 for (
auto &source: radiation_sources) {
287 source.source_fluxes[label] = -1.f;
290 radiativepropertiesneedupdate =
true;
296 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
299 vec2 waveBounds = radiation_bands.at(old_label).wavebandBounds;
307 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
311 band.
label = new_label;
314 radiation_bands.emplace(new_label, band);
317 for (
auto &source: radiation_sources) {
318 source.source_fluxes[new_label] = source.source_fluxes.at(old_label);
321 radiativepropertiesneedupdate =
true;
325 if (radiation_bands.find(label) == radiation_bands.end()) {
335 helios_runtime_error(
"ERROR (RadiationModel::disableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
338 radiation_bands.at(label).emissionFlag =
false;
344 helios_runtime_error(
"ERROR (RadiationModel::enableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
347 radiation_bands.at(label).emissionFlag =
true;
362 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): Invalid collimated source direction. Direction vector should not have length of zero.");
365 uint Nsources = radiation_sources.size() + 1;
366 if (Nsources > 256) {
367 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): A maximum of 256 radiation sources are allowed.");
370 bool warn_multiple_suns =
false;
371 for (
auto &source: radiation_sources) {
372 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
373 warn_multiple_suns =
true;
376 if (warn_multiple_suns) {
377 std::cerr <<
"WARNING (RadiationModel::addCollimatedRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
383 for (
const auto &band: radiation_bands) {
387 radiation_sources.emplace_back(collimated_source);
389 radiativepropertiesneedupdate =
true;
397 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): Spherical radiation source radius must be positive.");
400 uint Nsources = radiation_sources.size() + 1;
401 if (Nsources > 256) {
402 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
408 for (
const auto &band: radiation_bands) {
412 radiation_sources.emplace_back(sphere_source);
414 uint sourceID = Nsources - 1;
416 if (islightvisualizationenabled) {
417 buildLightModelGeometry(sourceID);
420 radiativepropertiesneedupdate =
true;
435 uint Nsources = radiation_sources.size() + 1;
436 if (Nsources > 256) {
437 helios_runtime_error(
"ERROR (RadiationModel::addSunSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
440 bool warn_multiple_suns =
false;
441 for (
auto &source: radiation_sources) {
442 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
443 warn_multiple_suns =
true;
446 if (warn_multiple_suns) {
447 std::cerr <<
"WARNING (RadiationModel::addSunSphereRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
450 RadiationSource sphere_source(150e9 * sun_direction / sun_direction.
magnitude(), 150e9, 2.f * 695.5e6, sigma * powf(5700, 4) / 1288.437f);
453 for (
const auto &band: radiation_bands) {
457 radiation_sources.emplace_back(sphere_source);
459 radiativepropertiesneedupdate =
true;
466 if (size.
x <= 0 || size.
y <= 0) {
467 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): Radiation source size must be positive.");
470 uint Nsources = radiation_sources.size() + 1;
471 if (Nsources > 256) {
472 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): A maximum of 256 radiation sources are allowed.");
478 for (
const auto &band: radiation_bands) {
482 radiation_sources.emplace_back(rectangle_source);
484 uint sourceID = Nsources - 1;
486 if (islightvisualizationenabled) {
487 buildLightModelGeometry(sourceID);
490 radiativepropertiesneedupdate =
true;
498 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): Disk radiation source radius must be positive.");
501 uint Nsources = radiation_sources.size() + 1;
502 if (Nsources > 256) {
503 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): A maximum of 256 radiation sources are allowed.");
509 for (
const auto &band: radiation_bands) {
513 radiation_sources.emplace_back(disk_source);
515 uint sourceID = Nsources - 1;
517 if (islightvisualizationenabled) {
518 buildLightModelGeometry(sourceID);
521 radiativepropertiesneedupdate =
true;
528 if (sourceID >= radiation_sources.size()) {
529 helios_runtime_error(
"ERROR (RadiationModel::deleteRadiationSource): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
532 radiation_sources.erase(radiation_sources.begin() + sourceID);
534 radiativepropertiesneedupdate =
true;
539 if (source_ID >= radiation_sources.size()) {
540 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
541 }
else if (source_integral < 0) {
542 helios_runtime_error(
"ERROR (RadiationModel::setSourceIntegral): Source integral must be non-negative.");
545 float current_integral =
integrateSpectrum(radiation_sources.at(source_ID).source_spectrum);
547 for (
vec2 &wavelength: radiation_sources.at(source_ID).source_spectrum) {
548 wavelength.y *= source_integral / current_integral;
554 if (source_ID >= radiation_sources.size()) {
555 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
556 }
else if (source_integral < 0) {
557 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source integral must be non-negative.");
558 }
else if (radiation_sources.at(source_ID).source_spectrum.empty()) {
559 std::cout <<
"WARNING (RadiationModel::setSourceSpectrumIntegral): Spectral distribution has not been set for radiation source. Cannot set its integral." << std::endl;
568 wavelength.y *= source_integral / old_integral;
575 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Cannot add set source flux for band '" + label +
"' because it is not a valid band.");
576 }
else if (source_ID >= radiation_sources.size()) {
577 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
578 }
else if (flux < 0) {
579 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source flux must be non-negative.");
582 radiation_sources.at(source_ID).source_fluxes[label] = flux * radiation_sources.at(source_ID).source_flux_scaling_factor;
586 for (
auto ID: source_ID) {
594 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get source flux for band '" + label +
"' because it is not a valid band.");
595 }
else if (source_ID >= radiation_sources.size()) {
596 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
597 }
else if (radiation_sources.at(source_ID).source_fluxes.find(label) == radiation_sources.at(source_ID).source_fluxes.end()) {
598 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get flux for source #" + std::to_string(source_ID) +
" because radiative band '" + label +
"' does not exist.");
604 vec2 wavebounds = radiation_bands.at(label).wavebandBounds;
618 if (source_ID >= radiation_sources.size()) {
619 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
623 for (
auto s = 0; s < spectrum.size(); s++) {
625 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
626 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Wavelengths must increase monotonically.");
629 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
630 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Wavelength value of " + std::to_string(spectrum.at(s).x) +
" appears to be erroneous.");
633 if (spectrum.at(s).y < 0) {
634 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Flux value at wavelength of " + std::to_string(spectrum.at(s).x) +
" appears is negative.");
638 radiation_sources.at(source_ID).source_spectrum = spectrum;
640 radiativepropertiesneedupdate =
true;
644 for (
auto ID: source_ID) {
651 if (source_ID >= radiation_sources.size()) {
652 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
655 std::vector<vec2> spectrum = loadSpectralData(spectrum_label);
657 radiation_sources.at(source_ID).source_spectrum = spectrum;
658 radiation_sources.at(source_ID).source_spectrum_label = spectrum_label;
659 radiation_sources.at(source_ID).source_spectrum_version = context->
getGlobalDataVersion(spectrum_label.c_str());
661 radiativepropertiesneedupdate =
true;
665 for (
auto ID: source_ID) {
672 std::vector<vec2> spectrum;
675 if (spectrum_label ==
"ASTMG173") {
676 spectrum = loadSpectralData(
"solar_spectrum_diffuse_ASTMG173");
677 global_diffuse_spectrum_label =
"solar_spectrum_diffuse_ASTMG173";
679 spectrum = loadSpectralData(spectrum_label);
680 global_diffuse_spectrum_label = spectrum_label;
684 global_diffuse_spectrum = spectrum;
685 global_diffuse_spectrum_version = context->
getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
688 for (
auto &band_pair: radiation_bands) {
689 band_pair.second.diffuse_spectrum = spectrum;
692 radiativepropertiesneedupdate =
true;
698 helios_runtime_error(
"ERROR (RadiationModel::getDiffuseFlux): Cannot get diffuse flux for band '" + band_label +
"' because it is not a valid band.");
717 if (!spectrum.empty()) {
720 wavebounds =
make_vec2(spectrum.front().x, spectrum.back().x);
729 islightvisualizationenabled =
true;
732 for (
int s = 0; s < radiation_sources.size(); s++) {
733 buildLightModelGeometry(s);
738 islightvisualizationenabled =
false;
739 for (
auto &UUIDs: source_model_UUIDs) {
745 iscameravisualizationenabled =
true;
748 for (
auto &cam: cameras) {
749 buildCameraModelGeometry(cam.first);
754 iscameravisualizationenabled =
false;
755 for (
auto &UUIDs: camera_model_UUIDs) {
760void RadiationModel::buildLightModelGeometry(
uint sourceID) {
762 assert(sourceID < radiation_sources.size());
765 if (source.
source_type == RADIATION_SOURCE_TYPE_SPHERE) {
766 source_model_UUIDs[sourceID] = context->
loadOBJ(
"SphereLightSource.obj",
true);
767 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
768 source_model_UUIDs[sourceID] = context->
loadOBJ(
"SphereLightSource.obj",
true);
769 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_DISK) {
770 source_model_UUIDs[sourceID] = context->
loadOBJ(
"DiskLightSource.obj",
true);
772 std::vector<uint> UUIDs_arrow = context->
loadOBJ(
"Arrow.obj",
true);
773 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
775 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_RECTANGLE) {
776 source_model_UUIDs[sourceID] = context->
loadOBJ(
"RectangularLightSource.obj",
true);
778 std::vector<uint> UUIDs_arrow = context->
loadOBJ(
"Arrow.obj",
true);
779 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
785 if (source.
source_type == RADIATION_SOURCE_TYPE_SPHERE) {
788 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
795 context->
translatePrimitive(source_model_UUIDs.at(sourceID), center + sunvec * radius);
806void RadiationModel::buildCameraModelGeometry(
const std::string &cameralabel) {
808 assert(cameras.find(cameralabel) != cameras.end());
812 vec3 viewvec = camera.lookat - camera.position;
815 camera_model_UUIDs[cameralabel] = context->
loadOBJ(
"Camera.obj",
true);
825void RadiationModel::updateLightModelPosition(
uint sourceID,
const helios::vec3 &delta_position) {
827 assert(sourceID < radiation_sources.size());
831 if (source.
source_type != RADIATION_SOURCE_TYPE_SPHERE && source.
source_type != RADIATION_SOURCE_TYPE_DISK && source.
source_type != RADIATION_SOURCE_TYPE_RECTANGLE) {
838void RadiationModel::updateCameraModelPosition(
const std::string &cameralabel) {
840 assert(cameras.find(cameralabel) != cameras.end());
843 buildCameraModelGeometry(cameralabel);
848 if (source_ID >= radiation_sources.size()) {
849 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
850 }
else if (object_spectrum.size() < 2) {
851 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
852 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
853 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
856 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
859 int iend = (int) object_spectrum.size() - 1;
860 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
862 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
865 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
873 for (
auto i = istart; i < iend; i++) {
875 float x0 = object_spectrum.at(i).x;
876 float Esource0 =
interp1(source_spectrum, object_spectrum.at(i).x);
877 float Eobject0 = object_spectrum.at(i).y;
879 float x1 = object_spectrum.at(i + 1).x;
880 float Eobject1 = object_spectrum.at(i + 1).y;
881 float Esource1 =
interp1(source_spectrum, object_spectrum.at(i + 1).x);
883 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
884 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
892 if (object_spectrum.size() < 2) {
893 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
894 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
895 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
899 int iend = (int) object_spectrum.size() - 1;
900 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
902 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
905 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
912 for (
auto i = istart; i < iend; i++) {
913 float E0 = object_spectrum.at(i).y;
914 float x0 = object_spectrum.at(i).x;
915 float E1 = object_spectrum.at(i + 1).y;
916 float x1 = object_spectrum.at(i + 1).x;
917 E += (E0 + E1) * (x1 - x0) * 0.5f;
924 float wavelength1 = object_spectrum.at(0).x;
925 float wavelength2 = object_spectrum.at(object_spectrum.size() - 1).x;
932 if (source_ID >= radiation_sources.size()) {
933 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
934 }
else if (object_spectrum.size() < 2) {
935 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
938 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
942 for (
auto i = 1; i < object_spectrum.size(); i++) {
944 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
947 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
950 float x1 = object_spectrum.at(i).x;
951 float Eobject1 = object_spectrum.at(i).y;
952 float Esource1 =
interp1(source_spectrum, x1);
953 float Ecamera1 =
interp1(camera_spectrum, x1);
956 float x0 = object_spectrum.at(i - 1).x;
957 float Eobject0 = object_spectrum.at(i - 1).y;
958 float Esource0 =
interp1(source_spectrum, x0);
959 float Ecamera0 =
interp1(camera_spectrum, x0);
961 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
962 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
971 if (object_spectrum.size() < 2) {
972 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
977 for (
auto i = 1; i < object_spectrum.size(); i++) {
979 if (object_spectrum.at(i).x <= camera_spectrum.front().x) {
982 if (object_spectrum.at(i).x > camera_spectrum.back().x) {
986 float x1 = object_spectrum.at(i).x;
987 float Eobject1 = object_spectrum.at(i).y;
988 float Ecamera1 =
interp1(camera_spectrum, x1);
991 float x0 = object_spectrum.at(i - 1).x;
992 float Eobject0 = object_spectrum.at(i - 1).y;
993 float Ecamera0 =
interp1(camera_spectrum, x0);
995 E += 0.5f * ((Eobject1 * Ecamera1) + (Eobject0 * Ecamera0)) * (x1 - x0);
996 Etot += 0.5f * (Ecamera1 + Ecamera0) * (x1 - x0);
1004 if (source_ID >= radiation_sources.size()) {
1005 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1006 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1007 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1010 return integrateSpectrum(radiation_sources.at(source_ID).source_spectrum, wavelength1, wavelength2);
1015 std::vector<helios::vec2> spectrum = loadSpectralData(existing_global_data_label);
1018 s.y *= scale_factor;
1021 context->
setGlobalData(new_global_data_label.c_str(), spectrum);
1026 std::vector<vec2> spectrum = loadSpectralData(global_data_label);
1028 for (
vec2 &s: spectrum) {
1029 s.y *= scale_factor;
1032 context->
setGlobalData(global_data_label.c_str(), spectrum);
1037 scaleSpectrum(existing_global_data_label, new_global_data_label, context->
randu(minimum_scale_factor, maximum_scale_factor));
1041void RadiationModel::blendSpectra(
const std::string &new_spectrum_label,
const std::vector<std::string> &spectrum_labels,
const std::vector<float> &weights)
const {
1043 if (spectrum_labels.size() != weights.size()) {
1044 helios_runtime_error(
"ERROR (RadiationModel::blendSpectra): number of spectra and weights must be equal");
1045 }
else if (
sum(weights) != 1.f) {
1049 std::vector<vec2> new_spectrum;
1050 uint spectrum_size = 0;
1052 std::vector<std::vector<vec2>> spectrum(spectrum_labels.size());
1054 uint lambda_start = 0;
1055 uint lambda_end = 0;
1056 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1058 spectrum.at(i) = loadSpectralData(spectrum_labels.at(i));
1061 lambda_start = spectrum.at(i).front().x;
1062 lambda_end = spectrum.at(i).back().x;
1064 if (spectrum.at(i).front().x > lambda_start) {
1065 lambda_start = spectrum.at(i).front().x;
1067 if (spectrum.at(i).back().x < lambda_end) {
1068 lambda_end = spectrum.at(i).back().x;
1073 spectrum_size = lambda_end - lambda_start + 1;
1074 new_spectrum.resize(spectrum_size);
1075 for (
uint j = 0; j < spectrum_size; j++) {
1076 new_spectrum.at(j) =
make_vec2(lambda_start + j, 0);
1080 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1081 for (
uint j = 0; j < spectrum.at(i).size(); j++) {
1083 if (spectrum.at(i).at(j).x >= lambda_start) {
1085 spectrum.at(i).erase(spectrum.at(i).begin(), spectrum.at(i).begin() + j);
1093 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1094 for (
int j = spectrum.at(i).size() - 1; j <= 0; j--) {
1096 if (spectrum.at(i).at(j).x <= lambda_end) {
1097 if (j < spectrum.at(i).size() - 1) {
1098 spectrum.at(i).erase(spectrum.at(i).begin() + j + 1, spectrum.at(i).end());
1105 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1106 for (
uint j = 0; j < spectrum_size; j++) {
1107 assert(new_spectrum.at(j).x == spectrum.at(i).at(j).x);
1108 new_spectrum.at(j).y += weights.at(i) * spectrum.at(i).at(j).y;
1112 context->
setGlobalData(new_spectrum_label.c_str(), new_spectrum);
1117 std::vector<float> weights;
1118 weights.resize(spectrum_labels.size());
1119 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1120 weights.at(i) = context->
randu();
1122 float sum_weights =
sum(weights);
1123 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1124 weights.at(i) /= sum_weights;
1127 blendSpectra(new_spectrum_label, spectrum_labels, weights);
1131 const std::string &primitive_data_radprop_label) {
1134 if (spectra.size() != values.size()) {
1135 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' vector (size=" + std::to_string(spectra.size()) +
") and 'values' vector (size=" + std::to_string(values.size()) +
1136 ") must have the same length.");
1140 if (spectra.empty()) {
1141 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' and 'values' vectors cannot be empty.");
1145 if (primitive_UUIDs.empty()) {
1146 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_UUIDs' vector cannot be empty.");
1150 if (primitive_data_query_label.empty()) {
1151 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_query_label' cannot be empty.");
1154 if (primitive_data_radprop_label.empty()) {
1155 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_radprop_label' cannot be empty.");
1159 SpectrumInterpolationConfig *existing_config =
nullptr;
1160 for (
auto &config: spectrum_interpolation_configs) {
1161 if (config.query_data_label == primitive_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1162 existing_config = &config;
1167 if (existing_config !=
nullptr) {
1169 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1171 if (spectra_match) {
1173 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1176 existing_config->spectra_labels = spectra;
1177 existing_config->mapping_values = values;
1178 existing_config->primitive_UUIDs.clear();
1179 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1183 SpectrumInterpolationConfig config;
1184 config.primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1185 config.spectra_labels = spectra;
1186 config.mapping_values = values;
1187 config.query_data_label = primitive_data_query_label;
1188 config.target_data_label = primitive_data_radprop_label;
1190 spectrum_interpolation_configs.push_back(config);
1195 const std::string &primitive_data_radprop_label) {
1198 if (spectra.size() != values.size()) {
1199 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'spectra' vector (size=" + std::to_string(spectra.size()) +
") and 'values' vector (size=" + std::to_string(values.size()) +
") must have the same length.");
1203 if (spectra.empty()) {
1204 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'spectra' and 'values' vectors cannot be empty.");
1208 if (object_IDs.empty()) {
1209 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_IDs' vector cannot be empty.");
1213 if (object_data_query_label.empty()) {
1214 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_data_query_label' cannot be empty.");
1217 if (primitive_data_radprop_label.empty()) {
1218 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'primitive_data_radprop_label' cannot be empty.");
1222 SpectrumInterpolationConfig *existing_config =
nullptr;
1223 for (
auto &config: spectrum_interpolation_configs) {
1224 if (config.query_data_label == object_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1225 existing_config = &config;
1230 if (existing_config !=
nullptr) {
1232 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1234 if (spectra_match) {
1236 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1239 existing_config->spectra_labels = spectra;
1240 existing_config->mapping_values = values;
1241 existing_config->object_IDs.clear();
1242 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1246 SpectrumInterpolationConfig config;
1247 config.object_IDs.insert(object_IDs.begin(), object_IDs.end());
1248 config.spectra_labels = spectra;
1249 config.mapping_values = values;
1250 config.query_data_label = object_data_query_label;
1251 config.target_data_label = primitive_data_radprop_label;
1253 spectrum_interpolation_configs.push_back(config);
1259 if (source_ID >= radiation_sources.size()) {
1260 helios_runtime_error(
"ERROR (RadiationModel::setSourcePosition): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources.");
1263 vec3 old_position = radiation_sources.at(source_ID).source_position;
1265 if (radiation_sources.at(source_ID).source_type == RADIATION_SOURCE_TYPE_COLLIMATED) {
1266 radiation_sources.at(source_ID).source_position = position / position.
magnitude();
1268 radiation_sources.at(source_ID).source_position = position * radiation_sources.at(source_ID).source_position_scaling_factor;
1271 if (islightvisualizationenabled) {
1272 updateLightModelPosition(source_ID, radiation_sources.at(source_ID).source_position - old_position);
1281 if (source_ID >= radiation_sources.size()) {
1284 return radiation_sources.at(source_ID).source_position;
1290 helios_runtime_error(
"ERROR (RadiationModel::setScatteringDepth): Cannot set scattering depth for band '" + label +
"' because it is not a valid band.");
1292 radiation_bands.at(label).scatteringDepth = depth;
1298 helios_runtime_error(
"ERROR (setMinScatterEnergy): Cannot set minimum scattering energy for band '" + label +
"' because it is not a valid band.");
1300 radiation_bands.at(label).minScatterEnergy = energy;
1305 if (boundary ==
"x") {
1307 periodic_flag.
x = 1;
1309 }
else if (boundary ==
"y") {
1311 periodic_flag.
y = 1;
1313 }
else if (boundary ==
"xy") {
1315 periodic_flag.
x = 1;
1316 periodic_flag.
y = 1;
1320 std::cout <<
"WARNING (RadiationModel::enforcePeriodicBoundary()): unknown boundary of '" << boundary <<
"'. Possible choices are x, y, or xy." << std::endl;
1332 std::cout <<
"Updating geometry in radiation transport model..." << std::flush;
1336 buildGeometryData();
1343 backend->updateGeometry(geometry_data);
1344 backend->buildAccelerationStructure();
1346 radiativepropertiesneedupdate =
true;
1347 isgeometryinitialized =
true;
1350 std::cout <<
"done." << std::endl;
1354void RadiationModel::updateRadiativeProperties() {
1367 std::cout <<
"Updating radiative properties..." << std::flush;
1370 uint Nbands = radiation_bands.size();
1371 uint Nsources = radiation_sources.size();
1372 uint Ncameras = cameras.size();
1373 size_t Nobjects = primitiveID.size();
1374 size_t Nprimitives = context_UUIDs.size();
1376 scattering_iterations_needed.clear();
1377 for (
auto &band: radiation_bands) {
1378 scattering_iterations_needed[band.first] =
false;
1381 std::vector<std::vector<std::vector<float>>> rho, tau;
1382 std::vector<std::vector<std::vector<std::vector<float>>>> rho_cam, tau_cam;
1386 std::vector<std::string> band_labels;
1387 for (
auto &band: radiation_bands) {
1388 band_labels.push_back(band.first);
1391 rho.resize(Nsources);
1392 tau.resize(Nsources);
1393 for (
size_t s = 0; s < Nsources; s++) {
1394 rho.at(s).resize(Nprimitives);
1395 tau.at(s).resize(Nprimitives);
1396 for (
size_t p = 0; p < Nprimitives; p++) {
1397 rho.at(s).at(p).resize(Nbands);
1398 tau.at(s).at(p).resize(Nbands);
1402 rho_cam.resize(Nsources);
1403 tau_cam.resize(Nsources);
1404 for (
size_t s = 0; s < Nsources; s++) {
1405 rho_cam.at(s).resize(Nprimitives);
1406 tau_cam.at(s).resize(Nprimitives);
1407 for (
size_t p = 0; p < Nprimitives; p++) {
1408 rho_cam.at(s).at(p).resize(Nbands);
1409 tau_cam.at(s).at(p).resize(Nbands);
1410 for (
size_t b = 0; b < Nbands; b++) {
1411 rho_cam.at(s).at(p).at(b).resize(Ncameras);
1412 tau_cam.at(s).at(p).at(b).resize(Ncameras);
1419 std::vector<std::vector<std::vector<helios::vec2>>> camera_response_unique;
1420 camera_response_unique.resize(Ncameras);
1423 for (
const auto &camera: cameras) {
1425 camera_response_unique.at(cam).resize(Nbands);
1427 for (
uint b = 0; b < Nbands; b++) {
1429 if (camera.second.band_spectral_response.find(band_labels.at(b)) == camera.second.band_spectral_response.end()) {
1433 std::string camera_response = camera.second.band_spectral_response.at(band_labels.at(b));
1435 if (!camera_response.empty()) {
1438 if (camera_response !=
"uniform") {
1439 warnings.
addWarning(
"missing_camera_response",
"Camera spectral response \"" + camera_response +
"\" does not exist. Assuming a uniform spectral response.");
1441 }
else if (context->
getGlobalDataType(camera_response.c_str()) == helios::HELIOS_TYPE_VEC2) {
1443 std::vector<helios::vec2> data = loadSpectralData(camera_response.c_str());
1445 camera_response_unique.at(cam).at(b) = data;
1447 }
else if (context->
getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_STRING) {
1448 camera_response.clear();
1449 std::cout <<
"WARNING (RadiationModel::runBand): Camera spectral response \"" << camera_response <<
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral response..." << std::endl;
1458 std::unordered_map<std::string, float> spectral_integration_cache;
1462 std::unordered_map<std::string, float> temp_spectral_cache;
1466 auto createCacheKey = [](
const std::string &spectrum_label,
uint source_id,
uint band_id,
uint camera_id,
const std::string &type) -> std::string {
1467 return spectrum_label +
"_" + std::to_string(source_id) +
"_" + std::to_string(band_id) +
"_" + std::to_string(camera_id) +
"_" + type;
1471 auto getCachedValue = [&](
const std::string &cache_key,
bool &found) ->
float {
1472 float result = 0.0f;
1480 auto cache_it = spectral_integration_cache.find(cache_key);
1481 if (cache_it != spectral_integration_cache.end()) {
1483 result = cache_it->second;
1492 auto setCachedValue = [&](
const std::string &cache_key,
float value) {
1497 spectral_integration_cache[cache_key] = value;
1504 auto cachedInterp1 = [&](
const std::vector<helios::vec2> &spectrum,
float wavelength,
const std::string &spectrum_id) ->
float {
1506 std::string cache_key =
"interp_" + spectrum_id +
"_" + std::to_string(wavelength);
1509 float cached_result = getCachedValue(cache_key, found);
1511 return cached_result;
1515 float result =
interp1(spectrum, wavelength);
1516 setCachedValue(cache_key, result);
1521 auto cachedIntegrateSpectrumWithSource = [&](
uint source_ID,
const std::vector<helios::vec2> &object_spectrum,
float wavelength1,
float wavelength2,
const std::string &object_spectrum_id) ->
float {
1522 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2 || wavelength1 >= wavelength2) {
1526 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1527 std::string source_id =
"source_" + std::to_string(source_ID);
1530 int iend = (int) object_spectrum.size() - 1;
1531 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
1532 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
1535 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
1543 for (
auto i = istart; i < iend; i++) {
1544 float x0 = object_spectrum.at(i).x;
1545 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
1546 float Eobject0 = object_spectrum.at(i).y;
1548 float x1 = object_spectrum.at(i + 1).x;
1549 float Eobject1 = object_spectrum.at(i + 1).y;
1550 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
1552 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
1553 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1556 return (Etot != 0.0f) ? E / Etot : 0.0f;
1560 auto cachedIntegrateSpectrumWithSourceAndCamera = [&](
uint source_ID,
const std::vector<helios::vec2> &object_spectrum,
const std::vector<helios::vec2> &camera_spectrum,
uint camera_index,
uint band_index,
1561 const std::string &object_spectrum_id) ->
float {
1562 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2) {
1566 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1567 std::string source_id =
"source_" + std::to_string(source_ID);
1568 std::string camera_id =
"camera_" + std::to_string(camera_index) +
"_band_" + std::to_string(band_index);
1572 for (
auto i = 1; i < object_spectrum.size(); i++) {
1573 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
1576 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
1580 float x1 = object_spectrum.at(i).x;
1581 float Eobject1 = object_spectrum.at(i).y;
1582 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
1583 float Ecamera1 = cachedInterp1(camera_spectrum, x1, camera_id);
1585 float x0 = object_spectrum.at(i - 1).x;
1586 float Eobject0 = object_spectrum.at(i - 1).y;
1587 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
1588 float Ecamera0 = cachedInterp1(camera_spectrum, x0, camera_id);
1590 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
1591 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1594 return (Etot != 0.0f) ? E / Etot : 0.0f;
1598 for (
const auto &config: spectrum_interpolation_configs) {
1600 for (
const auto &spectrum_label: config.spectra_labels) {
1602 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Spectral interpolation config references global data '" + spectrum_label +
"' which does not exist.");
1604 if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2) {
1605 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Spectral interpolation config references global data '" + spectrum_label +
"' which must be of type HELIOS_TYPE_VEC2 (std::vector<helios::vec2>).");
1609 for (
uint uuid: config.primitive_UUIDs) {
1618 if (context->
getPrimitiveDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
1619 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Primitive data '" + config.query_data_label +
"' for UUID " + std::to_string(uuid) +
" must be of type HELIOS_TYPE_FLOAT for spectral interpolation.");
1624 context->
getPrimitiveData(uuid, config.query_data_label.c_str(), query_value);
1627 size_t nearest_idx = 0;
1628 float min_distance = std::abs(query_value - config.mapping_values[0]);
1629 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
1630 float distance = std::abs(query_value - config.mapping_values[i]);
1631 if (distance < min_distance) {
1632 min_distance = distance;
1638 context->
setPrimitiveData(uuid, config.target_data_label.c_str(), config.spectra_labels[nearest_idx]);
1643 for (
uint objID: config.object_IDs) {
1652 if (context->
getObjectDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
1653 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Object data '" + config.query_data_label +
"' for object ID " + std::to_string(objID) +
" must be of type HELIOS_TYPE_FLOAT for spectral interpolation.");
1658 context->
getObjectData(objID, config.query_data_label.c_str(), query_value);
1661 size_t nearest_idx = 0;
1662 float min_distance = std::abs(query_value - config.mapping_values.at(0));
1663 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
1664 float distance = std::abs(query_value - config.mapping_values.at(i));
1665 if (distance < min_distance) {
1666 min_distance = distance;
1673 context->
setPrimitiveData(prim_uuids, config.target_data_label.c_str(), config.spectra_labels.at(nearest_idx));
1681 std::map<std::string, std::vector<helios::vec2>> surface_spectra_rho;
1682 std::map<std::string, std::vector<helios::vec2>> surface_spectra_tau;
1683 for (
size_t u = 0; u < Nprimitives; u++) {
1685 uint UUID = context_UUIDs.at(u);
1689 std::string spectrum_label;
1693 if (surface_spectra_rho.find(spectrum_label) == surface_spectra_rho.end()) {
1695 if (!spectrum_label.empty()) {
1696 warnings.
addWarning(
"missing_reflectivity_spectrum",
"Primitive spectral reflectivity \"" + spectrum_label +
"\" does not exist. Using default reflectivity of 0.");
1698 std::vector<helios::vec2> data;
1699 surface_spectra_rho.emplace(spectrum_label, data);
1700 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
1702 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
1703 surface_spectra_rho.emplace(spectrum_label, data);
1705 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
1706 spectrum_label.clear();
1707 std::cout <<
"WARNING (RadiationModel::runBand): Object spectral reflectivity \"" << spectrum_label <<
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral distribution..." << std::flush;
1715 std::string spectrum_label;
1716 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
1719 if (surface_spectra_tau.find(spectrum_label) == surface_spectra_tau.end()) {
1721 if (!spectrum_label.empty()) {
1722 warnings.
addWarning(
"missing_transmissivity_spectrum",
"Primitive spectral transmissivity \"" + spectrum_label +
"\" does not exist. Using default transmissivity of 0.");
1724 std::vector<helios::vec2> data;
1725 surface_spectra_tau.emplace(spectrum_label, data);
1726 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
1728 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
1729 surface_spectra_tau.emplace(spectrum_label, data);
1731 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
1732 spectrum_label.clear();
1733 std::cout <<
"WARNING (RadiationModel::runBand): Object spectral transmissivity \"" << spectrum_label <<
"\" is not of type HELIOS_TYPE_VEC2 or HELIOS_TYPE_STRING. Assuming a uniform spectral distribution..." << std::flush;
1741 std::map<std::string, std::vector<std::vector<float>>> rho_unique;
1742 std::map<std::string, std::vector<std::vector<float>>> tau_unique;
1744 std::map<std::string, std::vector<std::vector<std::vector<float>>>> rho_cam_unique;
1745 std::map<std::string, std::vector<std::vector<std::vector<float>>>> tau_cam_unique;
1747 std::vector<std::vector<float>> empty;
1748 empty.resize(Nbands);
1749 for (
uint b = 0; b < Nbands; b++) {
1750 empty.at(b).resize(Nsources, 0);
1752 std::vector<std::vector<std::vector<float>>> empty_cam;
1754 empty_cam.resize(Nbands);
1755 for (
uint b = 0; b < Nbands; b++) {
1756 empty_cam.at(b).resize(Nsources);
1757 for (
uint s = 0; s < Nsources; s++) {
1758 empty_cam.at(b).at(s).resize(Ncameras, 0);
1764 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_rho_vector(surface_spectra_rho.begin(), surface_spectra_rho.end());
1767 for (
const auto &spectrum: spectra_rho_vector) {
1768 rho_unique[spectrum.first] = empty;
1770 rho_cam_unique[spectrum.first] = empty_cam;
1776#pragma omp parallel for schedule(dynamic)
1778 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_rho_vector.size(); spectrum_idx++) {
1779 const auto &spectrum = spectra_rho_vector[spectrum_idx];
1781 for (
uint b = 0; b < Nbands; b++) {
1782 std::string band = band_labels.at(b);
1784 for (
uint s = 0; s < Nsources; s++) {
1787 auto band_it = radiation_bands.find(band);
1788 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
1789 if (!radiation_sources.at(s).source_spectrum.empty()) {
1790 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_source");
1792 float cached_result = getCachedValue(cache_key, found);
1794 rho_unique[spectrum.first][b][s] = cached_result;
1796 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
1797 setCachedValue(cache_key, result);
1798 rho_unique[spectrum.first][b][s] = result;
1802 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_no_source");
1804 float cached_result = getCachedValue(cache_key, found);
1806 rho_unique[spectrum.first][b][s] = cached_result;
1808 float result =
integrateSpectrum(spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y) / (band_it->second.wavebandBounds.y - band_it->second.wavebandBounds.x);
1809 setCachedValue(cache_key, result);
1810 rho_unique[spectrum.first][b][s] = result;
1816 rho_unique[spectrum.first][b][s] = rho_default;
1822 float rho_cam_sum_for_averaging = 0.f;
1823 for (
const auto &camera: cameras) {
1825 if (camera_response_unique.at(cam).at(b).empty()) {
1826 rho_cam_unique[spectrum.first][b][s][cam] = rho_unique[spectrum.first][b][s];
1830 if (!spectrum.second.empty()) {
1831 if (!radiation_sources.at(s).source_spectrum.empty()) {
1832 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_source");
1834 float cached_result = getCachedValue(cache_key, found);
1836 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
1837 rho_cam_sum_for_averaging += cached_result;
1839 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
1840 setCachedValue(cache_key, result);
1841 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
1842 rho_cam_sum_for_averaging += result;
1845 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_no_source");
1847 float cached_result = getCachedValue(cache_key, found);
1849 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
1850 rho_cam_sum_for_averaging += cached_result;
1852 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
1853 setCachedValue(cache_key, result);
1854 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
1855 rho_cam_sum_for_averaging += result;
1859 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = rho_default;
1869 if (rho_unique[spectrum.first][b][s] == rho_default && rho_cam_sum_for_averaging > 0 && cam > 0) {
1870 rho_unique[spectrum.first][b][s] = rho_cam_sum_for_averaging / float(cam);
1878 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_tau_vector(surface_spectra_tau.begin(), surface_spectra_tau.end());
1881 for (
const auto &spectrum: spectra_tau_vector) {
1882 tau_unique[spectrum.first] = empty;
1884 tau_cam_unique[spectrum.first] = empty_cam;
1890#pragma omp parallel for schedule(dynamic)
1892 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_tau_vector.size(); spectrum_idx++) {
1893 const auto &spectrum = spectra_tau_vector[spectrum_idx];
1895 for (
uint b = 0; b < Nbands; b++) {
1896 std::string band = band_labels.at(b);
1898 for (
uint s = 0; s < Nsources; s++) {
1901 auto band_it = radiation_bands.find(band);
1902 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
1903 if (!radiation_sources.at(s).source_spectrum.empty()) {
1904 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_source");
1906 float cached_result = getCachedValue(cache_key, found);
1908 tau_unique[spectrum.first][b][s] = cached_result;
1910 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
1911 setCachedValue(cache_key, result);
1912 tau_unique[spectrum.first][b][s] = result;
1915 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_no_source");
1917 float cached_result = getCachedValue(cache_key, found);
1919 tau_unique[spectrum.first][b][s] = cached_result;
1921 float result =
integrateSpectrum(spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y) / (band_it->second.wavebandBounds.y - band_it->second.wavebandBounds.x);
1922 setCachedValue(cache_key, result);
1923 tau_unique[spectrum.first][b][s] = result;
1927 tau_unique[spectrum.first][b][s] = tau_default;
1933 for (
const auto &camera: cameras) {
1935 if (camera_response_unique.at(cam).at(b).empty()) {
1937 tau_cam_unique[spectrum.first][b][s][cam] = tau_unique[spectrum.first][b][s];
1942 if (!spectrum.second.empty()) {
1943 if (!radiation_sources.at(s).source_spectrum.empty()) {
1944 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_source");
1946 float cached_result = getCachedValue(cache_key, found);
1948 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
1950 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
1951 setCachedValue(cache_key, result);
1952 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
1955 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_no_source");
1957 float cached_result = getCachedValue(cache_key, found);
1959 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
1961 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
1962 setCachedValue(cache_key, result);
1963 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
1967 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = tau_default;
1978 for (
size_t u = 0; u < Nprimitives; u++) {
1980 uint UUID = context_UUIDs.at(u);
1984 if (type == helios::PRIMITIVE_TYPE_VOXEL) {
1991 std::string spectrum_label;
1999 for (
const auto &band: band_labels) {
2002 prop =
"reflectivity_" + band;
2004 float rho_s = rho_default;
2009 for (
uint s = 0; s < Nsources; s++) {
2011 if (rho_s != rho_default || spectrum_label.empty() || !context->
doesGlobalDataExist(spectrum_label.c_str()) || rho_unique.find(spectrum_label) == rho_unique.end()) {
2013 rho.at(s).at(u).at(b) = rho_s;
2016 for (
uint cam = 0; cam < Ncameras; cam++) {
2017 rho_cam.at(s).at(u).at(b).at(cam) = rho_s;
2023 rho.at(s).at(u).at(b) = rho_unique.at(spectrum_label).at(b).at(s);
2026 for (
uint cam = 0; cam < Ncameras; cam++) {
2027 rho_cam.at(s).at(u).at(b).at(cam) = rho_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2032 if (rho.at(s).at(u).at(b) < 0) {
2033 rho.at(s).at(u).at(b) = 0.f;
2034 warnings.
addWarning(
"reflectivity_negative_clamped",
"Reflectivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2035 }
else if (rho.at(s).at(u).at(b) > 1.f) {
2036 rho.at(s).at(u).at(b) = 1.f;
2037 warnings.
addWarning(
"reflectivity_exceeded_clamped",
"Reflectivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2039 if (rho.at(s).at(u).at(b) != 0) {
2040 scattering_iterations_needed.at(band) =
true;
2042 for (
auto &odata: output_prim_data) {
2043 if (odata ==
"reflectivity") {
2044 context->
setPrimitiveData(UUID, (
"reflectivity_" + std::to_string(s) +
"_" + band).c_str(), rho.at(s).at(u).at(b));
2054 spectrum_label.resize(0);
2057 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
2062 for (
const auto &band: band_labels) {
2065 prop =
"transmissivity_" + band;
2067 float tau_s = tau_default;
2072 for (
uint s = 0; s < Nsources; s++) {
2074 if (tau_s != tau_default || spectrum_label.empty() || !context->
doesGlobalDataExist(spectrum_label.c_str()) || tau_unique.find(spectrum_label) == tau_unique.end()) {
2076 tau.at(s).at(u).at(b) = tau_s;
2079 for (
uint cam = 0; cam < Ncameras; cam++) {
2080 tau_cam.at(s).at(u).at(b).at(cam) = tau_s;
2085 tau.at(s).at(u).at(b) = tau_unique.at(spectrum_label).at(b).at(s);
2088 for (
uint cam = 0; cam < Ncameras; cam++) {
2089 tau_cam.at(s).at(u).at(b).at(cam) = tau_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2094 if (tau.at(s).at(u).at(b) < 0) {
2095 tau.at(s).at(u).at(b) = 0.f;
2096 warnings.
addWarning(
"transmissivity_negative_clamped",
"Transmissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2097 }
else if (tau.at(s).at(u).at(b) > 1.f) {
2098 tau.at(s).at(u).at(b) = 1.f;
2099 warnings.
addWarning(
"transmissivity_exceeded_clamped",
"Transmissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2101 if (tau.at(s).at(u).at(b) != 0) {
2102 scattering_iterations_needed.at(band) =
true;
2104 for (
auto &odata: output_prim_data) {
2105 if (odata ==
"transmissivity") {
2106 context->
setPrimitiveData(UUID, (
"transmissivity_" + std::to_string(s) +
"_" + band).c_str(), tau.at(s).at(u).at(b));
2116 for (
const auto &band: band_labels) {
2118 prop =
"emissivity_" + band;
2128 warnings.
addWarning(
"emissivity_negative_clamped",
"Emissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2129 }
else if (eps > 1.f) {
2131 warnings.
addWarning(
"emissivity_exceeded_clamped",
"Emissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2134 scattering_iterations_needed.at(band) =
true;
2139 for (
uint s = 0; s < Nsources; s++) {
2140 if (radiation_bands.at(band).emissionFlag) {
2141 if (eps != 1.f && rho.at(s).at(u).at(b) == 0 && tau.at(s).at(u).at(b) == 0) {
2142 rho.at(s).at(u).at(b) = 1.f - eps;
2143 }
else if (eps + tau.at(s).at(u).at(b) + rho.at(s).at(u).at(b) != 1.f && eps > 0.f) {
2144 helios_runtime_error(
"ERROR (RadiationModel): emissivity, transmissivity, and reflectivity must sum to 1 to ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
": eps=" +
2145 std::to_string(eps) +
", tau=" + std::to_string(tau.at(s).at(u).at(b)) +
", rho=" + std::to_string(rho.at(s).at(u).at(b)) +
". It is also possible that you forgot to disable emission for this band.");
2146 }
else if (radiation_bands.at(band).scatteringDepth == 0 && eps != 1.f) {
2148 rho.at(s).at(u).at(b) = 0.f;
2149 tau.at(s).at(u).at(b) = 0.f;
2151 }
else if (tau.at(s).at(u).at(b) + rho.at(s).at(u).at(b) > 1.f) {
2152 helios_runtime_error(
"ERROR (RadiationModel): transmissivity and reflectivity cannot sum to greater than 1 ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
": eps=" + std::to_string(eps) +
2153 ", tau=" + std::to_string(tau.at(s).at(u).at(b)) +
", rho=" + std::to_string(rho.at(s).at(u).at(b)) +
". It is also possible that you forgot to disable emission for this band.");
2161 std::vector<float> rho_flat =
flatten(rho);
2162 std::vector<float> tau_flat =
flatten(tau);
2163 std::vector<float> rho_cam_flat =
flatten(rho_cam);
2164 std::vector<float> tau_cam_flat =
flatten(tau_cam);
2168 material_data.
num_bands = radiation_bands.size();
2169 material_data.
num_sources = radiation_sources.size();
2180 bool specular_exponent_specified =
false;
2181 bool specular_scale_specified =
false;
2183 for (
size_t u = 0; u < Nprimitives; u++) {
2184 uint UUID = context_UUIDs.at(u);
2189 specular_exponent_specified =
true;
2196 specular_scale_specified =
true;
2202 if (specular_exponent_specified) {
2203 if (specular_scale_specified) {
2204 specular_reflection_mode = 2;
2206 specular_reflection_mode = 1;
2209 specular_reflection_mode = 0;
2212 backend->updateMaterials(material_data);
2214 radiativepropertiesneedupdate =
false;
2217 std::cout <<
"done\n";
2221 warnings.
report(std::cerr);
2224std::vector<float> RadiationModel::updateAtmosphericSkyModel(
const std::vector<std::string> &band_labels,
const RadiationCamera &camera) {
2229 size_t Nbands_launch = band_labels.size();
2230 std::vector<float> sky_base_radiances(Nbands_launch, 0.0f);
2234 bool has_atmospheric_data =
2237 if (!has_atmospheric_data) {
2239 return sky_base_radiances;
2244 float pressure_Pa = 101325.f;
2245 float temperature_K = 300.f;
2246 float humidity_rel = 0.5f;
2247 float turbidity = 0.02f;
2250 context->
getGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
2253 context->
getGlobalData(
"atmosphere_temperature_K", temperature_K);
2256 context->
getGlobalData(
"atmosphere_humidity_rel", humidity_rel);
2263 int prague_valid = 0;
2270 if (!radiation_sources.empty()) {
2271 sun_dir = radiation_sources[0].source_position;
2272 sun_dir.normalize();
2276 std::vector<helios::vec4> sky_params(Nbands_launch);
2279 bool use_prague_fallback = (prague_valid != 1);
2280 if (use_prague_fallback) {
2282 std::cerr <<
"WARNING (RadiationModel::updateAtmosphericSkyModel): "
2283 <<
"Prague sky model data not available in Context. "
2284 <<
"Using simple Rayleigh sky fallback. "
2285 <<
"Call SolarPosition::updatePragueSkyModel() for accurate sky radiance." << std::endl;
2289 std::vector<float> wavelengths;
2290 std::vector<float> L_zenith_spectrum;
2291 std::vector<float> circ_str_spectrum;
2292 std::vector<float> circ_width_spectrum;
2293 std::vector<float> horiz_bright_spectrum;
2294 std::vector<float> norm_spectrum;
2296 if (use_prague_fallback) {
2299 const int n_wavelengths = 40;
2300 wavelengths.resize(n_wavelengths);
2301 L_zenith_spectrum.resize(n_wavelengths);
2302 circ_str_spectrum.resize(n_wavelengths);
2303 circ_width_spectrum.resize(n_wavelengths);
2304 horiz_bright_spectrum.resize(n_wavelengths);
2305 norm_spectrum.resize(n_wavelengths);
2307 const float L_base = 0.4f;
2308 const float lambda_ref = 550.0f;
2310 for (
int i = 0; i < n_wavelengths; ++i) {
2311 float lambda = 360.0f + i * 10.0f;
2312 wavelengths[i] = lambda;
2315 float rayleigh_factor = std::pow(lambda_ref / lambda, 4.0f);
2316 L_zenith_spectrum[i] = L_base * rayleigh_factor;
2319 circ_str_spectrum[i] = 0.5f;
2320 circ_width_spectrum[i] = 20.0f;
2321 horiz_bright_spectrum[i] = 1.8f;
2322 norm_spectrum[i] = 0.7f;
2326 std::vector<float> spectral_params;
2327 context->
getGlobalData(
"prague_sky_spectral_params", spectral_params);
2329 const int params_per_wavelength = 6;
2330 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
2333 wavelengths.resize(n_wavelengths);
2334 L_zenith_spectrum.resize(n_wavelengths);
2335 circ_str_spectrum.resize(n_wavelengths);
2336 circ_width_spectrum.resize(n_wavelengths);
2337 horiz_bright_spectrum.resize(n_wavelengths);
2338 norm_spectrum.resize(n_wavelengths);
2340 for (
int i = 0; i < n_wavelengths; ++i) {
2341 int base = i * params_per_wavelength;
2342 wavelengths[i] = spectral_params[base + 0];
2343 L_zenith_spectrum[i] = spectral_params[base + 1];
2344 circ_str_spectrum[i] = spectral_params[base + 2];
2345 circ_width_spectrum[i] = spectral_params[base + 3];
2346 horiz_bright_spectrum[i] = spectral_params[base + 4];
2347 norm_spectrum[i] = spectral_params[base + 5];
2352 for (
size_t b = 0; b < Nbands_launch; b++) {
2353 const std::string &band_label = band_labels[b];
2354 if (radiation_bands.find(band_label) == radiation_bands.end()) {
2366 std::string spectral_response_label =
"uniform";
2367 if (camera.band_spectral_response.find(band_label) != camera.band_spectral_response.end()) {
2368 spectral_response_label = camera.band_spectral_response.at(band_label);
2369 if (spectral_response_label.empty() ||
trim_whitespace(spectral_response_label).empty()) {
2370 spectral_response_label =
"uniform";
2375 std::vector<helios::vec2> camera_response;
2377 if (spectral_response_label ==
"uniform") {
2380 if (wavelength_range.
x <= 0.f || wavelength_range.
y <= 0.f) {
2381 bool bounds_inferred =
false;
2383 if (band_label ==
"red" || band_label ==
"R") {
2385 bounds_inferred =
true;
2386 }
else if (band_label ==
"green" || band_label ==
"G") {
2388 bounds_inferred =
true;
2389 }
else if (band_label ==
"blue" || band_label ==
"B") {
2391 bounds_inferred =
true;
2394 if (!bounds_inferred) {
2399 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera '" + camera.label +
"' band '" + band_label +
"' has uniform spectral response but no wavelength bounds set.");
2408 camera_response = loadSpectralData(spectral_response_label);
2410 if (camera_response.empty()) {
2411 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera spectral response '" + spectral_response_label +
"' not found for camera '" + camera.label +
"' band '" + band_label +
"'.");
2417 float integrated_L_zenith = integrateOverResponse(wavelengths, L_zenith_spectrum, camera_response);
2421 float integrated_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, camera_response);
2422 float integrated_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, camera_response);
2423 float integrated_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, camera_response);
2426 float integrated_norm = computeAngularNormalization(integrated_circ_str, integrated_circ_width, integrated_horiz_bright);
2431 float base_radiance_for_gpu = integrated_L_zenith / std::max(integrated_norm, 0.1f);
2433 sky_base_radiances[b] = base_radiance_for_gpu;
2434 sky_params[b] =
helios::make_vec4(integrated_circ_str, integrated_circ_width, integrated_horiz_bright, integrated_norm);
2438 return sky_base_radiances;
2441void RadiationModel::updatePragueParametersForGeneralDiffuse(
const std::vector<std::string> &band_labels) {
2447 int prague_valid = 0;
2454 std::vector<float> spectral_params;
2455 context->
getGlobalData(
"prague_sky_spectral_params", spectral_params);
2458 const int params_per_wavelength = 6;
2459 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
2461 std::vector<float> wavelengths(n_wavelengths);
2462 std::vector<float> L_zenith_spectrum(n_wavelengths);
2463 std::vector<float> circ_str_spectrum(n_wavelengths);
2464 std::vector<float> circ_width_spectrum(n_wavelengths);
2465 std::vector<float> horiz_bright_spectrum(n_wavelengths);
2466 std::vector<float> norm_spectrum(n_wavelengths);
2468 for (
int i = 0; i < n_wavelengths; ++i) {
2469 int base = i * params_per_wavelength;
2470 wavelengths[i] = spectral_params[base + 0];
2471 L_zenith_spectrum[i] = spectral_params[base + 1];
2472 circ_str_spectrum[i] = spectral_params[base + 2];
2473 circ_width_spectrum[i] = spectral_params[base + 3];
2474 horiz_bright_spectrum[i] = spectral_params[base + 4];
2475 norm_spectrum[i] = spectral_params[base + 5];
2480 context->
getGlobalData(
"prague_sky_sun_direction", sun_dir);
2483 for (
const auto &label: band_labels) {
2493 if (band_spectrum.empty()) {
2497 if (lambda_min > 0 && lambda_max > lambda_min) {
2498 band_spectrum = {{lambda_min, 1.0f}, {lambda_max, 1.0f}};
2502 if (band_spectrum.empty()) {
2508 float int_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, band_spectrum);
2509 float int_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, band_spectrum);
2510 float int_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, band_spectrum);
2513 float int_norm = computeAngularNormalization(int_circ_str, int_circ_width, int_horiz_bright);
2521float RadiationModel::integrateOverResponse(
const std::vector<float> &wavelengths,
const std::vector<float> &values,
const std::vector<helios::vec2> &camera_response)
const {
2523 if (wavelengths.empty() || camera_response.empty()) {
2529 double integrated_radiance = 0.0;
2532 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
2533 float lambda1 = camera_response[i].x;
2534 float lambda2 = camera_response[i + 1].x;
2537 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
2541 float r1 = camera_response[i].y;
2542 float r2 = camera_response[i + 1].y;
2548 if (lambda1 <= wavelengths.front()) {
2549 v1 = values.front();
2550 }
else if (lambda1 >= wavelengths.back()) {
2553 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
2554 size_t idx = std::distance(wavelengths.begin(), it);
2557 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2558 v1 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
2562 if (lambda2 <= wavelengths.front()) {
2563 v2 = values.front();
2564 }
else if (lambda2 >= wavelengths.back()) {
2567 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
2568 size_t idx = std::distance(wavelengths.begin(), it);
2571 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2572 v2 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
2575 float dlambda = lambda2 - lambda1;
2579 integrated_radiance += 0.5 * (v1 * r1 + v2 * r2) * dlambda;
2583 return static_cast<float>(integrated_radiance);
2586float RadiationModel::weightedAverageOverResponse(
const std::vector<float> &wavelengths,
const std::vector<float> ¶m_values,
const std::vector<float> &weight_values,
const std::vector<helios::vec2> &camera_response)
const {
2588 if (wavelengths.empty() || camera_response.empty()) {
2594 double weighted_sum = 0.0;
2595 double total_weight = 0.0;
2597 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
2598 float lambda1 = camera_response[i].x;
2599 float lambda2 = camera_response[i + 1].x;
2601 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
2605 float r1 = camera_response[i].y;
2606 float r2 = camera_response[i + 1].y;
2610 if (lambda1 <= wavelengths.front()) {
2611 p1 = param_values.front();
2612 }
else if (lambda1 >= wavelengths.back()) {
2613 p1 = param_values.back();
2615 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
2616 size_t idx = std::distance(wavelengths.begin(), it);
2619 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2620 p1 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
2623 if (lambda2 <= wavelengths.front()) {
2624 p2 = param_values.front();
2625 }
else if (lambda2 >= wavelengths.back()) {
2626 p2 = param_values.back();
2628 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
2629 size_t idx = std::distance(wavelengths.begin(), it);
2632 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2633 p2 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
2638 if (lambda1 <= wavelengths.front()) {
2639 w1 = weight_values.front();
2640 }
else if (lambda1 >= wavelengths.back()) {
2641 w1 = weight_values.back();
2643 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
2644 size_t idx = std::distance(wavelengths.begin(), it);
2647 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2648 w1 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
2651 if (lambda2 <= wavelengths.front()) {
2652 w2 = weight_values.front();
2653 }
else if (lambda2 >= wavelengths.back()) {
2654 w2 = weight_values.back();
2656 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
2657 size_t idx = std::distance(wavelengths.begin(), it);
2660 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
2661 w2 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
2664 float dlambda = lambda2 - lambda1;
2667 weighted_sum += 0.5 * (p1 * w1 * r1 + p2 * w2 * r2) * dlambda;
2668 total_weight += 0.5 * (w1 * r1 + w2 * r2) * dlambda;
2672 if (total_weight > 1e-10) {
2673 return static_cast<float>(weighted_sum / total_weight);
2678float RadiationModel::computeAngularNormalization(
float circ_str,
float circ_width,
float horiz_bright)
const {
2681 float integral = 0.0f;
2686 for (
int j = 0; j < N; ++j) {
2687 for (
int i = 0; i < N; ++i) {
2688 float theta = 0.5f * float(
M_PI) * (i + 0.5f) / N;
2689 float phi = 2.0f * float(
M_PI) * (j + 0.5f) / N;
2694 float cos_gamma = std::max(-1.0f, std::min(1.0f, dir.
x * sun_dir.
x + dir.
y * sun_dir.
y + dir.
z * sun_dir.
z));
2695 float gamma = std::acos(cos_gamma) * 180.0f / float(
M_PI);
2698 float cos_theta = std::max(0.0f, dir.
z);
2699 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
2700 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
2702 float pattern = circ_term * horizon_term;
2705 integral += pattern * std::cos(theta) * std::sin(theta) * (float(
M_PI) / (2.0f * N)) * (2.0f * float(
M_PI) / N);
2709 return 1.0f / std::max(integral, 1e-10f);
2712std::vector<helios::vec2> RadiationModel::loadSpectralData(
const std::string &global_data_label)
const {
2714 std::vector<helios::vec2> spectrum;
2719 bool data_found =
false;
2720 for (
const auto &file: spectral_library_files) {
2722 context->
loadXML(file.c_str(),
true);
2729 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' could not be found.");
2733 if (context->
getGlobalDataType(global_data_label.c_str()) != HELIOS_TYPE_VEC2) {
2734 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is not of type HELIOS_TYPE_VEC2.");
2737 context->
getGlobalData(global_data_label.c_str(), spectrum);
2740 if (spectrum.empty()) {
2741 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is empty.");
2743 for (
auto s = 0; s < spectrum.size(); s++) {
2745 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
2746 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Wavelengths must increase monotonically.");
2749 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
2750 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Wavelength value of " + std::to_string(spectrum.at(s).x) +
" appears to be erroneous.");
2753 if (spectrum.at(s).y < 0) {
2754 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Flux value at wavelength of " + std::to_string(spectrum.at(s).x) +
" appears is negative.");
2762 std::vector<std::string> labels{label};
2772 std::vector<std::string> band_labels;
2773 for (
auto &band: radiation_bands) {
2774 if (std::find(label.begin(), label.end(), band.first) != label.end()) {
2775 band_labels.push_back(band.first);
2781 std::cerr <<
"WARNING (RadiationModel::runBand): No geometry was added to the context. There is nothing to simulate...exiting." << std::endl;
2786 if (!isgeometryinitialized) {
2791 for (
const std::string &band: label) {
2793 helios_runtime_error(
"ERROR (RadiationModel::runBand): Cannot run band " + band +
" because it is not a valid band. Use addRadiationBand() function to add the band.");
2798 if (radiation_sources.empty()) {
2803 for (
auto &source: radiation_sources) {
2804 if (!source.source_spectrum_label.empty() && source.source_spectrum_label !=
"none") {
2805 uint64_t current_version = context->
getGlobalDataVersion(source.source_spectrum_label.c_str());
2808 source.
source_spectrum = loadSpectralData(source.source_spectrum_label);
2810 radiativepropertiesneedupdate =
true;
2816 if (!global_diffuse_spectrum_label.empty() && global_diffuse_spectrum_label !=
"none") {
2817 uint64_t current_version = context->
getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
2818 if (current_version != global_diffuse_spectrum_version) {
2820 global_diffuse_spectrum = loadSpectralData(global_diffuse_spectrum_label);
2821 global_diffuse_spectrum_version = current_version;
2823 for (
auto &band_pair: radiation_bands) {
2824 band_pair.second.diffuse_spectrum = global_diffuse_spectrum;
2826 radiativepropertiesneedupdate =
true;
2830 if (radiativepropertiesneedupdate) {
2832 updateRadiativeProperties();
2836 buildMaterialData();
2837 backend->updateMaterials(material_data);
2842 backend->updateSources(source_data);
2845 size_t Nbands_launch = band_labels.size();
2846 size_t Nbands_global = radiation_bands.size();
2849 std::vector<char> band_launch_flag(Nbands_global);
2851 for (
auto &band: radiation_bands) {
2852 if (std::find(band_labels.begin(), band_labels.end(), band.first) != band_labels.end()) {
2853 band_launch_flag.at(bb) = 1;
2859 size_t Nobjects = primitiveID.size();
2860 size_t Nprimitives = context_UUIDs.size();
2861 uint Nsources = radiation_sources.size();
2862 uint Ncameras = cameras.size();
2868 std::vector<uint> scattering_depth(Nbands_launch);
2869 bool scatteringenabled =
false;
2870 for (
auto b = 0; b < Nbands_launch; b++) {
2871 scattering_depth.at(b) = radiation_bands.at(band_labels.at(b)).scatteringDepth;
2872 if (scattering_depth.at(b) > 0) {
2873 scatteringenabled =
true;
2878 for (
int b = 0; b < Nbands_launch; b++) {
2879 if (scattering_depth.at(b) == 0 && scattering_iterations_needed.at(band_labels.at(b))) {
2880 std::cout <<
"WARNING (RadiationModel::runBand): Surface radiative properties for band " << band_labels.at(b)
2881 <<
" are set to non-default values, but scattering iterations are disabled. Surface radiative properties will be ignored unless scattering depth is non-zero." << std::endl;
2886 std::vector<float> diffuse_flux(Nbands_launch);
2887 bool diffuseenabled =
false;
2888 for (
auto b = 0; b < Nbands_launch; b++) {
2890 if (diffuse_flux.at(b) > 0.f) {
2891 diffuseenabled =
true;
2897 std::vector<float> camera_sky_radiance(Nbands_launch, 0.0f);
2901 if (diffuseenabled) {
2902 updatePragueParametersForGeneralDiffuse(band_labels);
2906 std::vector<float> diffuse_extinction(Nbands_launch, 0);
2907 if (diffuseenabled) {
2908 for (
auto b = 0; b < Nbands_launch; b++) {
2909 diffuse_extinction.at(b) = radiation_bands.at(band_labels.at(b)).diffuseExtinction;
2915 std::vector<float> diffuse_dist_norm(Nbands_launch, 0);
2916 if (diffuseenabled) {
2917 for (
auto b = 0; b < Nbands_launch; b++) {
2918 diffuse_dist_norm.at(b) = radiation_bands.at(band_labels.at(b)).diffuseDistNorm;
2924 std::vector<helios::vec3> diffuse_peak_dir(Nbands_launch);
2925 if (diffuseenabled) {
2926 for (
auto b = 0; b < Nbands_launch; b++) {
2927 helios::vec3 peak_dir = radiation_bands.at(band_labels.at(b)).diffusePeakDir;
2935 std::vector<helios::vec4> prague_params(Nbands_launch);
2936 if (diffuseenabled) {
2937 for (
auto b = 0; b < Nbands_launch; b++) {
2938 const auto ¶ms = radiation_bands.at(band_labels.at(b)).diffusePragueParams;
2939 prague_params.at(b) =
helios::make_vec4(params.x, params.y, params.z, params.w);
2945 bool emissionenabled =
false;
2946 for (
auto b = 0; b < Nbands_launch; b++) {
2947 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
2948 emissionenabled =
true;
2953 size_t directRayCount = 0;
2954 for (
const auto &band: label) {
2955 if (radiation_bands.at(band).directRayCount > directRayCount) {
2956 directRayCount = radiation_bands.at(band).directRayCount;
2961 size_t diffuseRayCount = 0;
2962 for (
const auto &band: label) {
2963 if (radiation_bands.at(band).diffuseRayCount > diffuseRayCount) {
2964 diffuseRayCount = radiation_bands.at(band).diffuseRayCount;
2969 size_t scatteringDepth = 0;
2970 for (
const auto &band: label) {
2971 if (radiation_bands.at(band).scatteringDepth > scatteringDepth) {
2972 scatteringDepth = radiation_bands.at(band).scatteringDepth;
2977 backend->zeroRadiationBuffers(Nbands_launch);
2979 std::vector<float> TBS_top, TBS_bottom;
2980 TBS_top.resize(Nbands_launch * Nprimitives, 0);
2981 TBS_bottom = TBS_top;
2983 std::map<std::string, std::vector<std::vector<float>>> radiation_in_camera;
2985 size_t maxRays = 1024 * 1024 * 1024;
2991 bool rundirect =
false;
2992 for (
uint s = 0; s < Nsources; s++) {
2993 for (
uint b = 0; b < Nbands_launch; b++) {
3001 if (Nsources > 0 && rundirect) {
3005 std::vector<std::vector<float>> fluxes;
3006 fluxes.resize(Nsources);
3007 std::vector<helios::vec3> positions(Nsources);
3008 std::vector<helios::vec2> widths(Nsources);
3009 std::vector<helios::vec3> rotations(Nsources);
3010 std::vector<uint> types(Nsources);
3013 for (
const auto &source: radiation_sources) {
3015 fluxes.at(s).resize(Nbands_launch);
3017 for (
auto b = 0; b < label.size(); b++) {
3030 backend->uploadSourceFluxes(
flatten(fluxes));
3038 std::vector<float> source_fluxes_cam;
3039 source_fluxes_cam.resize(Nsources * Nbands_launch * Ncameras, 1.0f);
3041 for (
uint s = 0; s < Nsources; s++) {
3045 for (
const auto &camera: cameras) {
3046 for (
uint b = 0; b < Nbands_launch; b++) {
3047 std::string band_label = band_labels.at(b);
3050 float weight = 1.0f;
3053 if (camera.second.band_spectral_response.find(band_label) != camera.second.band_spectral_response.end()) {
3054 std::string response_label = camera.second.band_spectral_response.at(band_label);
3060 std::vector<helios::vec2> camera_response;
3061 context->
getGlobalData(response_label.c_str(), camera_response);
3064 helios::vec2 wavelength_range = radiation_bands.at(band_label).wavebandBounds;
3067 if (wavelength_range.
x == 0 && wavelength_range.
y == 0) {
3068 wavelength_range.
x = fmax(source.
source_spectrum.front().x, camera_response.front().x);
3069 wavelength_range.
y = fmin(source.
source_spectrum.back().x, camera_response.back().x);
3078 source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam] = weight;
3085 for (
uint s = 0; s < Nsources; s++) {
3086 source_data[s].fluxes_cam.clear();
3087 for (
uint b = 0; b < Nbands_launch; b++) {
3088 for (
uint cam = 0; cam < Ncameras; cam++) {
3089 source_data[s].fluxes_cam.push_back(source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam]);
3093 backend->updateSources(source_data);
3099 std::cout <<
"Performing primary direct radiation ray trace for bands ";
3100 for (
const auto &band: label) {
3101 std::cout << band <<
", ";
3103 std::cout <<
"..." << std::flush;
3111 params.
random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3117 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
3120 backend->launchDirectRays(params);
3123 std::cout <<
"done." << std::endl;
3131 std::vector<float> flux_top, flux_bottom;
3132 flux_top.resize(Nbands_launch * Nprimitives, 0);
3133 flux_bottom = flux_top;
3136 std::vector<float> scatter_top_cam;
3137 std::vector<float> scatter_bottom_cam;
3139 scatter_top_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3140 scatter_bottom_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3143 if (scatteringenabled && rundirect) {
3146 backend->getRadiationResults(scatter_results);
3157 backend->zeroCameraScatterBuffers(Nbands_launch);
3164 for (
size_t i = 0; i < Nprimitives; i++) {
3165 uint UUID = context_UUIDs.at(i);
3168 if (twosided == 0) {
3169 for (
size_t b = 0; b < Nbands_launch; b++) {
3170 size_t ind = rad_indexer(i, b);
3171 float total = flux_top[ind] + flux_bottom[ind];
3172 flux_top[ind] = total;
3173 flux_bottom[ind] = total;
3179 backend->uploadRadiationOut(flux_top, flux_bottom);
3180 backend->zeroScatterBuffers();
3185 if (emissionenabled || diffuseenabled) {
3188 if (emissionenabled) {
3190 float eps, temperature;
3195 for (
auto b = 0; b < Nbands_launch; b++) {
3197 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
3198 std::string prop =
"emissivity_" + band_labels.at(b);
3199 for (
size_t u = 0; u < Nprimitives; u++) {
3201 size_t ind = emission_indexer(u, b);
3202 uint p = context_UUIDs.at(u);
3208 if (scattering_depth.at(b) == 0 && eps != 1.f) {
3213 if (temperature < 0) {
3214 temperature = temperature_default;
3217 temperature = temperature_default;
3219 float out_top = sigma * eps * pow(temperature, 4);
3220 flux_top.at(ind) += out_top;
3222 scatter_top_cam[ind] += out_top;
3226 if (twosided_flag != 0) {
3227 flux_bottom.at(ind) += flux_top.at(ind);
3229 scatter_bottom_cam[ind] += out_top;
3240 backend->uploadCameraScatterBuffers(scatter_top_cam, scatter_bottom_cam);
3246 size_t n = ceil(sqrt(
double(diffuseRayCount)));
3247 uint rays_per_primitive = n * n;
3249 size_t maxPrims = floor(
float(maxRays) /
float(rays_per_primitive));
3251 int Nlaunches = ceil(rays_per_primitive * Nprimitives /
float(maxRays));
3253 size_t prims_per_launch = fmin(Nprimitives, maxPrims);
3255 for (
uint launch = 0; launch < Nlaunches; launch++) {
3257 size_t prims_this_launch;
3258 if ((launch + 1) * prims_per_launch > Nprimitives) {
3259 prims_this_launch = Nprimitives - launch * prims_per_launch;
3261 prims_this_launch = prims_per_launch;
3265 std::cout <<
"Performing primary diffuse radiation ray trace for bands ";
3266 for (
const auto &band: label) {
3267 std::cout << band <<
" ";
3269 std::cout <<
" (batch " << launch + 1 <<
" of " << Nlaunches <<
")..." << std::flush;
3277 params.
random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3281 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
3293 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
3294 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
3295 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
3301 backend->launchDiffuseRays(params);
3305 backend->launchDiffuseRays(params);
3310 backend->getRadiationResults(primary_results);
3316 backend->zeroCameraScatterBuffers(Nbands_launch);
3320 std::cout <<
"\r \r" << std::flush;
3325 std::cout <<
"Performing primary diffuse radiation ray trace for bands ";
3326 for (
const auto &band: label) {
3327 std::cout << band <<
", ";
3329 std::cout <<
"...done." << std::endl;
3336 if (scatteringenabled && (emissionenabled || diffuseenabled) && !rundirect) {
3337 backend->copyScatterToRadiation();
3338 backend->zeroScatterBuffers();
3341 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
3343 for (
auto b = 0; b < Nbands_launch; b++) {
3344 diffuse_flux.at(b) = 0.f;
3348 size_t n = ceil(sqrt(
double(diffuseRayCount)));
3349 uint rays_per_primitive = n * n;
3351 size_t maxPrims = floor(
float(maxRays) /
float(rays_per_primitive));
3353 int Nlaunches = ceil(n * n * Nobjects /
float(maxRays));
3355 size_t prims_per_launch = fmin(Nobjects, maxPrims);
3359 std::vector<char> scatter_band_flags = band_launch_flag;
3361 for (s = 0; s < scatteringDepth; s++) {
3363 std::cout <<
"Performing scattering ray trace (iteration " << s + 1 <<
" of " << scatteringDepth <<
")..." << std::flush;
3367 int active_bands = 0;
3368 for (
uint b_global = 0; b_global < Nbands_global; b_global++) {
3370 if (scatter_band_flags.at(b_global) == 0) {
3375 uint depth = radiation_bands.at(band_labels.at(b)).scatteringDepth;
3376 if (s + 1 > depth) {
3378 std::cout <<
"Skipping band " << band_labels.at(b) <<
" for scattering launch " << s + 1 << std::flush;
3380 scatter_band_flags.at(b_global) = 0;
3389 if (s > 0 || (emissionenabled && rundirect)) {
3390 backend->copyScatterToRadiation();
3392 backend->zeroScatterBuffers();
3396 backend->getRadiationResults(scatter_results);
3400 for (
uint launch = 0; launch < Nlaunches; launch++) {
3402 size_t prims_this_launch;
3403 if ((launch + 1) * prims_per_launch > Nprimitives) {
3404 prims_this_launch = Nprimitives - launch * prims_per_launch;
3406 prims_this_launch = prims_per_launch;
3414 params.
random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3418 std::vector<bool> band_flags(scatter_band_flags.begin(), scatter_band_flags.end());
3428 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
3429 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
3430 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
3440 backend->launchDiffuseRays(params);
3445 backend->launchDiffuseRays(params);
3451 backend->getRadiationResults(post_launch);
3457 backend->zeroCameraScatterBuffers(Nbands_launch);
3461 std::cout <<
"\r \r" << std::flush;
3466 std::cout <<
"Performing scattering ray trace...done." << std::endl;
3476 if (Ncameras > 0 && scatteringenabled) {
3477 backend->uploadRadiationOut(scatter_top_cam, scatter_bottom_cam);
3482 vec3 sun_dir(0, 0, 1);
3483 std::vector<float> solar_radiances(Nbands_launch, 0.0f);
3484 bool has_sun_source =
false;
3486 for (
size_t s = 0; s < radiation_sources.size(); s++) {
3488 if (source.
source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
3492 has_sun_source =
true;
3495 for (
size_t b = 0; b < Nbands_launch; b++) {
3498 if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
3501 solar_radiances[b] = flux /
M_PI;
3505 const float solar_solid_angle = 6.74e-5f;
3506 solar_radiances[b] = flux / solar_solid_angle;
3513 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
3515 if (diffuseenabled) {
3516 for (
auto b = 0; b < Nbands_launch; b++) {
3521 size_t n = ceil(sqrt(
double(diffuseRayCount)));
3525 if (!cameras.empty() && prague_params.size() == Nbands_launch) {
3527 std::vector<float> sky_for_backend = updateAtmosphericSkyModel(band_labels, cameras.begin()->second);
3530 backend->updateSkyModel(prague_params, sky_for_backend, sun_dir, solar_radiances,
3531 has_sun_source ? 0.999989f : 0.0f
3536 for (
auto &camera: cameras) {
3540 if (camera.second.antialiasing_samples > maxRays) {
3541 helios_runtime_error(
"ERROR (runBand): Camera '" + camera.second.label +
"' antialiasing samples (" + std::to_string(camera.second.antialiasing_samples) +
") exceeds OptiX maximum launch size (" + std::to_string(maxRays) +
3542 "). Reduce antialiasing samples.");
3546 std::vector<CameraTile> tiles = computeCameraTiles(camera.second, maxRays);
3548 if (message_flag && tiles.size() > 1) {
3549 std::cout <<
"Camera '" << camera.second.label <<
"' requires " << tiles.size() <<
" tiles" << std::endl;
3553 for (
size_t tile_idx = 0; tile_idx < tiles.size(); tile_idx++) {
3554 const auto &tile = tiles[tile_idx];
3557 helios::RayTracingLaunchParams params = buildCameraLaunchParams(camera.second, cam, camera.second.antialiasing_samples, tile.resolution, tile.offset);
3562 params.
random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3563 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
3568 if (tiles.size() == 1) {
3569 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"..." << std::flush;
3571 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << tiles.size() <<
")..." << std::flush;
3576 backend->launchCameraRays(params);
3579 if (tiles.size() > 1) {
3580 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
3582 std::cout <<
"done." << std::endl;
3587 if (message_flag && tiles.size() > 1) {
3588 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"...done." << std::endl;
3592 std::vector<float> radiation_camera;
3593 std::vector<uint> dummy_labels;
3594 std::vector<float> dummy_depths;
3595 backend->getCameraResults(radiation_camera, dummy_labels, dummy_depths, cam, camera.second.resolution);
3598 std::string camera_label = camera.second.label;
3600 for (
auto b = 0; b < Nbands_launch; b++) {
3602 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.
x * camera.second.resolution.
y);
3604 std::string data_label =
"camera_" + camera_label +
"_" + band_labels.at(b);
3606 for (
auto p = 0; p < camera.second.resolution.
x * camera.second.resolution.
y; p++) {
3607 camera.second.pixel_data.at(band_labels.at(b)).at(p) = radiation_camera.at(p * Nbands_launch + b);
3610 context->
setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
3617 pixel_label_camera.antialiasing_samples = 1;
3618 std::vector<CameraTile> pixel_tiles = computeCameraTiles(pixel_label_camera, maxRays);
3621 backend->zeroCameraPixelBuffers(camera.second.resolution);
3624 for (
size_t tile_idx = 0; tile_idx < pixel_tiles.size(); tile_idx++) {
3625 const auto &tile = pixel_tiles[tile_idx];
3630 tile.resolution, tile.offset);
3634 if (pixel_tiles.size() == 1) {
3635 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"..." << std::flush;
3637 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << pixel_tiles.size() <<
")..." << std::flush;
3642 backend->launchPixelLabelRays(params);
3645 if (pixel_tiles.size() > 1) {
3646 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
3648 std::cout <<
"done." << std::endl;
3653 if (message_flag && pixel_tiles.size() > 1) {
3654 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"...done." << std::endl;
3658 std::vector<float> dummy_pixel_data;
3659 backend->getCameraResults(dummy_pixel_data, camera.second.pixel_label_UUID, camera.second.pixel_depth, cam, camera.second.resolution);
3663 for (
uint ID = 0; ID < camera.second.pixel_label_UUID.size(); ID++) {
3664 if (camera.second.pixel_label_UUID.at(ID) > 0) {
3665 uint position = camera.second.pixel_label_UUID.at(ID) - 1;
3668 if (position >= context_UUIDs.size()) {
3670 uint bbox_index = position - context_UUIDs.size();
3672 camera.second.pixel_label_UUID.at(ID) = bbox_UUID + 1;
3675 camera.second.pixel_label_UUID.at(ID) = context_UUIDs.at(position) + 1;
3681 std::string data_label =
"camera_" + camera_label +
"_pixel_UUID";
3682 context->
setGlobalData(data_label.c_str(), camera.second.pixel_label_UUID);
3684 data_label =
"camera_" + camera_label +
"_pixel_depth";
3685 context->
setGlobalData(data_label.c_str(), camera.second.pixel_depth);
3691 for (
auto &camera: cameras) {
3692 for (
auto b = 0; b < Nbands_launch; b++) {
3693 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.
x * camera.second.resolution.
y);
3695 std::string data_label =
"camera_" + camera.second.label +
"_" + band_labels.at(b);
3697 for (
auto p = 0; p < camera.second.resolution.
x * camera.second.resolution.
y; p++) {
3698 camera.second.pixel_data.at(band_labels.at(b)).at(p) = 0.f;
3700 context->
setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
3707 for (
auto &camera: cameras) {
3712 for (
auto &camera: cameras) {
3720 backend->getRadiationResults(results);
3722 std::vector<float> radiation_flux_data = results.
radiation_in;
3728 std::vector<uint> UUIDs_context_all = context->
getAllUUIDs();
3733 for (
auto b = 0; b < Nbands_launch; b++) {
3735 std::string prop =
"radiation_flux_" + band_labels.at(b);
3736 std::vector<float>
R(Nprimitives);
3737 for (
size_t u = 0; u < Nprimitives; u++) {
3739 size_t ind = result_indexer(u, b);
3740 R.at(u) = radiation_flux_data.at(ind) + TBS_top.at(ind) + TBS_bottom.at(ind);
3744 if (UUIDs_context_all.size() != Nprimitives) {
3745 for (
uint UUID: UUIDs_context_all) {
3757 backend->getRadiationResults(results);
3760 for (
size_t i = 0; i < results.
sky_energy.size(); i++) {
3768 std::vector<float> total_flux;
3771 for (
const auto &band: radiation_bands) {
3773 std::string label = band.first;
3775 for (
size_t u = 0; u < context_UUIDs.size(); u++) {
3777 uint p = context_UUIDs.at(u);
3779 std::string str =
"radiation_flux_" + label;
3783 total_flux.at(u) +=
R;
3793 vec3 dir = view_direction;
3797 float total_area = 0;
3798 for (std::size_t u = 0; u < primitiveID.size(); u++) {
3800 uint UUID = context_UUIDs.at(primitiveID.at(u));
3805 Gtheta += fabsf(normal * dir) * area;
3810 return Gtheta / total_area;
3814 float average_delta_e) {
3816 std::ofstream file(file_path);
3817 if (!file.is_open()) {
3818 helios_runtime_error(
"ERROR (RadiationModel::exportColorCorrectionMatrixXML): Failed to open file for writing: " + file_path);
3822 std::string matrix_type =
"3x3";
3823 if (matrix.size() == 4 || (matrix.size() >= 3 && matrix[0].size() == 4)) {
3824 matrix_type =
"4x3";
3828 file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
3829 file <<
"<!-- Camera Color Correction Matrix -->" << std::endl;
3830 file <<
"<!-- Source Image: " << source_image_path <<
" -->" << std::endl;
3831 file <<
"<!-- Camera Label: " << camera_label <<
" -->" << std::endl;
3832 file <<
"<!-- Colorboard Type: " << colorboard_type <<
" -->" << std::endl;
3833 if (average_delta_e >= 0.0f) {
3834 file <<
"<!-- Average Delta E: " << std::fixed << std::setprecision(2) << average_delta_e <<
" -->" << std::endl;
3836 file <<
"<!-- Matrix Type: " << matrix_type <<
" -->" << std::endl;
3837 file <<
"<!-- Generated: " << getCurrentDateTime() <<
" -->" << std::endl;
3840 file <<
"<helios>" << std::endl;
3841 file <<
" <ColorCorrectionMatrix camera_label=\"" << camera_label <<
"\" matrix_type=\"" << matrix_type <<
"\">" << std::endl;
3843 for (
size_t i = 0; i < matrix.size(); i++) {
3845 for (
size_t j = 0; j < matrix[i].size(); j++) {
3846 file << std::fixed << std::setprecision(6) << matrix[i][j];
3847 if (j < matrix[i].size() - 1) {
3851 file <<
"</row>" << std::endl;
3854 file <<
" </ColorCorrectionMatrix>" << std::endl;
3855 file <<
"</helios>" << std::endl;
3860std::string RadiationModel::getCurrentDateTime() {
3861 auto now = std::time(
nullptr);
3862 auto tm = *std::localtime(&now);
3863 std::stringstream ss;
3864 ss << std::put_time(&tm,
"%Y-%m-%d %H:%M:%S");
3870 std::ifstream file(file_path);
3871 if (!file.is_open()) {
3872 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Failed to open file for reading: " + file_path);
3875 std::vector<std::vector<float>> matrix;
3877 bool in_matrix =
false;
3878 std::string matrix_type =
"";
3880 while (std::getline(file, line)) {
3882 line.erase(0, line.find_first_not_of(
" \t"));
3883 line.erase(line.find_last_not_of(
" \t") + 1);
3886 if (line.find(
"<ColorCorrectionMatrix") != std::string::npos) {
3890 size_t camera_start = line.find(
"camera_label=\"");
3891 if (camera_start != std::string::npos) {
3893 size_t camera_end = line.find(
"\"", camera_start);
3894 if (camera_end != std::string::npos) {
3895 camera_label_out = line.substr(camera_start, camera_end - camera_start);
3900 size_t type_start = line.find(
"matrix_type=\"");
3901 if (type_start != std::string::npos) {
3903 size_t type_end = line.find(
"\"", type_start);
3904 if (type_end != std::string::npos) {
3905 matrix_type = line.substr(type_start, type_end - type_start);
3912 if (line.find(
"</ColorCorrectionMatrix>") != std::string::npos) {
3918 if (in_matrix && line.find(
"<row>") != std::string::npos && line.find(
"</row>") != std::string::npos) {
3920 size_t start = line.find(
"<row>") + 5;
3921 size_t end = line.find(
"</row>");
3922 std::string row_data = line.substr(start, end - start);
3925 std::vector<float> row;
3926 std::istringstream iss(row_data);
3928 while (iss >> value) {
3929 row.push_back(value);
3933 matrix.push_back(row);
3941 if (matrix.empty()) {
3942 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): No matrix data found in file: " + file_path);
3945 if (matrix.size() != 3) {
3946 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix size. Expected 3 rows, found " + std::to_string(matrix.size()) +
" rows in file: " + file_path);
3950 bool is_3x3 = (matrix[0].size() == 3 && matrix[1].size() == 3 && matrix[2].size() == 3);
3951 bool is_4x3 = (matrix[0].size() == 4 && matrix[1].size() == 4 && matrix[2].size() == 4);
3953 if (!is_3x3 && !is_4x3) {
3954 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix dimensions. All rows must have either 3 or 4 elements. File: " + file_path);
3958 if (!matrix_type.empty()) {
3959 if ((matrix_type ==
"3x3" && !is_3x3) || (matrix_type ==
"4x3" && !is_4x3)) {
3960 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Matrix type attribute ('" + matrix_type +
"') does not match actual matrix dimensions in file: " + file_path);
3967std::string
RadiationModel::autoCalibrateCameraImage(
const std::string &camera_label,
const std::string &red_band_label,
const std::string &green_band_label,
const std::string &blue_band_label,
const std::string &output_file_path,
3971 if (cameras.find(camera_label) == cameras.end()) {
3972 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
3976 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
3978 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Camera pixel UUID data '" + pixel_UUID_label +
"' does not exist for camera '" + camera_label +
"'. Make sure the radiation model has been run.");
3983 std::vector<std::string> colorboard_types;
3986 }
catch (
const std::exception &e) {
3987 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to detect colorboard types. " + std::string(e.what()));
3991 std::vector<CameraCalibration::LabColor> reference_lab_values;
3992 std::vector<std::string> colorboard_type_per_patch;
3994 for (
const auto &colorboard_type: colorboard_types) {
3995 std::vector<CameraCalibration::LabColor> current_reference_values;
3997 if (colorboard_type ==
"DGK") {
3999 }
else if (colorboard_type ==
"Calibrite") {
4001 }
else if (colorboard_type ==
"SpyderCHECKR") {
4004 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Unsupported colorboard type '" + colorboard_type +
"'.");
4008 reference_lab_values.insert(reference_lab_values.end(), current_reference_values.begin(), current_reference_values.end());
4011 for (
size_t i = 0; i < current_reference_values.size(); i++) {
4012 colorboard_type_per_patch.push_back(colorboard_type);
4017 std::vector<uint> pixel_UUIDs;
4018 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
4019 int2 camera_resolution = cameras.at(camera_label).resolution;
4022 std::map<int, std::vector<std::vector<bool>>> patch_masks;
4023 int global_patch_idx = 0;
4025 for (
const auto &colorboard_type: colorboard_types) {
4027 int num_patches = 0;
4028 if (colorboard_type ==
"DGK") {
4030 }
else if (colorboard_type ==
"Calibrite" || colorboard_type ==
"SpyderCHECKR") {
4035 for (
int local_patch_idx = 0; local_patch_idx < num_patches; local_patch_idx++) {
4036 std::vector<std::vector<bool>> mask(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
4039 for (
int y = 0; y < camera_resolution.
y; y++) {
4040 for (
int x = 0; x < camera_resolution.
x; x++) {
4041 int pixel_index = y * camera_resolution.
x + x;
4042 uint pixel_UUID = pixel_UUIDs[pixel_index];
4044 if (pixel_UUID > 0) {
4048 std::string colorboard_data_label =
"colorboard_" + colorboard_type;
4051 context->
getPrimitiveData(pixel_UUID, colorboard_data_label.c_str(), patch_id);
4053 if ((
int) patch_id == local_patch_idx) {
4061 patch_masks[global_patch_idx] = mask;
4068 std::vector<float> red_data, green_data, blue_data;
4071 auto &camera_bands = cameras.at(camera_label).band_labels;
4072 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
4073 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
4075 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
4076 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
4078 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
4079 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
4083 red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
4084 green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
4085 blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
4088 float max_r = *std::max_element(red_data.begin(), red_data.end());
4089 float max_g = *std::max_element(green_data.begin(), green_data.end());
4090 float max_b = *std::max_element(blue_data.begin(), blue_data.end());
4093 float scale_factor = 1.0f;
4094 if (max_r > 1.0f || max_g > 1.0f || max_b > 1.0f) {
4095 scale_factor = 1.0f / std::max({max_r, max_g, max_b});
4097 for (
size_t i = 0; i < red_data.size(); i++) {
4098 red_data[i] *= scale_factor;
4099 green_data[i] *= scale_factor;
4100 blue_data[i] *= scale_factor;
4104 std::vector<helios::vec3> measured_rgb_values;
4105 int visible_patches = 0;
4107 for (
const auto &[patch_idx, mask]: patch_masks) {
4108 float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f;
4109 int pixel_count = 0;
4112 for (
int y = 0; y < camera_resolution.
y; y++) {
4113 for (
int x = 0; x < camera_resolution.
x; x++) {
4115 int pixel_index = y * camera_resolution.
x + x;
4116 sum_r += red_data[pixel_index];
4117 sum_g += green_data[pixel_index];
4118 sum_b += blue_data[pixel_index];
4124 if (pixel_count > 10) {
4126 measured_rgb_values.push_back(avg_rgb);
4131 measured_rgb_values.push_back(
make_vec3(0, 0, 0));
4136 std::vector<CameraCalibration::LabColor> measured_lab_values;
4137 for (
const auto &rgb: measured_rgb_values) {
4138 if (rgb.magnitude() > 0) {
4139 measured_lab_values.push_back(calibration.
rgbToLab(rgb));
4144 std::vector<std::vector<float>> correction_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
4147 std::string algorithm_name;
4148 switch (algorithm) {
4150 algorithm_name =
"Diagonal scaling (white balance only)";
4153 algorithm_name =
"3x3 matrix with auto-fallback to diagonal";
4156 algorithm_name =
"3x3 matrix (forced)";
4160 if (measured_lab_values.size() >= 6 && reference_lab_values.size() >= 6) {
4162 std::vector<helios::vec3> target_rgb;
4164 for (
size_t i = 0; i < reference_lab_values.size(); i++) {
4167 target_rgb.push_back(ref_rgb);
4175 std::vector<helios::vec3> valid_measured, valid_target;
4176 std::vector<float> patch_weights;
4178 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), target_rgb.size()); i++) {
4179 if (measured_rgb_values[i].magnitude() > 0.01f) {
4180 valid_measured.push_back(measured_rgb_values[i]);
4181 valid_target.push_back(target_rgb[i]);
4184 float weight = 1.0f;
4187 if (i >= 18 && i <= 23) {
4197 else if (i == 14 || i == 13 || i == 12)
4201 else if (i == 3 || i == 10)
4206 float luminance = 0.299f * measured_rgb.
x + 0.587f * measured_rgb.
y + 0.114f * measured_rgb.
z;
4209 if (luminance > 0.6f)
4211 else if (luminance < 0.2f)
4220 patch_weights.push_back(weight);
4228 bool matrix_valid =
true;
4231 std::vector<float> lambda_values = {0.01f, 0.05f, 0.1f, 0.15f, 0.2f};
4232 int lambda_attempt = 0;
4234 while (lambda_attempt < lambda_values.size()) {
4235 float lambda = lambda_values[lambda_attempt];
4236 matrix_valid =
true;
4238 for (
int row = 0; row < 3; row++) {
4240 float ATA[3][3] = {{0}};
4243 for (
size_t i = 0; i < valid_measured.size(); i++) {
4244 float weight = patch_weights[i];
4246 float target_val = (row == 0) ? valid_target[i].x : (row == 1) ? valid_target[i].y : valid_target[i].z;
4249 ATA[0][0] += weight * m.
x * m.
x;
4250 ATA[0][1] += weight * m.
x * m.
y;
4251 ATA[0][2] += weight * m.
x * m.
z;
4252 ATA[1][0] += weight * m.
y * m.
x;
4253 ATA[1][1] += weight * m.
y * m.
y;
4254 ATA[1][2] += weight * m.
y * m.
z;
4255 ATA[2][0] += weight * m.
z * m.
x;
4256 ATA[2][1] += weight * m.
z * m.
y;
4257 ATA[2][2] += weight * m.
z * m.
z;
4259 ATb[0] += weight * m.
x * target_val;
4260 ATb[1] += weight * m.
y * target_val;
4261 ATb[2] += weight * m.
z * target_val;
4267 float diag_reg = lambda * 2.0f;
4268 float offdiag_reg = lambda * 0.5f;
4270 ATA[0][0] += diag_reg;
4271 ATA[1][1] += diag_reg;
4272 ATA[2][2] += diag_reg;
4275 ATA[0][1] += offdiag_reg;
4276 ATA[1][0] += offdiag_reg;
4277 ATA[0][2] += offdiag_reg;
4278 ATA[2][0] += offdiag_reg;
4279 ATA[1][2] += offdiag_reg;
4280 ATA[2][1] += offdiag_reg;
4283 float det = ATA[0][0] * (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) - ATA[0][1] * (ATA[1][0] * ATA[2][2] - ATA[1][2] * ATA[2][0]) + ATA[0][2] * (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]);
4285 if (fabs(det) < 1e-3f) {
4287 matrix_valid =
false;
4292 float inv_det = 1.0f / det;
4293 correction_matrix[row][0] = inv_det * (ATb[0] * (ATA[1][1] * ATA[2][2] - ATA[1][2] * ATA[2][1]) - ATb[1] * (ATA[0][1] * ATA[2][2] - ATA[0][2] * ATA[2][1]) + ATb[2] * (ATA[0][1] * ATA[1][2] - ATA[0][2] * ATA[1][1]));
4295 correction_matrix[row][1] = inv_det * (ATb[1] * (ATA[0][0] * ATA[2][2] - ATA[0][2] * ATA[2][0]) - ATb[0] * (ATA[1][0] * ATA[2][2] - ATA[1][2] * ATA[2][0]) + ATb[2] * (ATA[1][0] * ATA[0][2] - ATA[1][2] * ATA[0][0]));
4297 correction_matrix[row][2] = inv_det * (ATb[2] * (ATA[0][0] * ATA[1][1] - ATA[0][1] * ATA[1][0]) - ATb[0] * (ATA[1][0] * ATA[2][1] - ATA[1][1] * ATA[2][0]) + ATb[1] * (ATA[0][0] * ATA[2][1] - ATA[0][1] * ATA[2][0]));
4302 bool elements_reasonable =
true;
4303 for (
int i = 0; i < 3; i++) {
4304 for (
int j = 0; j < 3; j++) {
4305 if (fabs(correction_matrix[i][j]) > 5.0f) {
4307 elements_reasonable =
false;
4312 if (!elements_reasonable)
4315 matrix_valid = elements_reasonable;
4325 if (!matrix_valid) {
4329 float total_weight = 0.0f;
4332 for (
size_t i = 0; i < valid_measured.size(); i++) {
4333 float weight = patch_weights[i];
4338 if (measured.
x > 0.01f && measured.
y > 0.01f && measured.
z > 0.01f) {
4341 weighted_correction.
x += weight * channel_correction.
x;
4342 weighted_correction.
y += weight * channel_correction.
y;
4343 weighted_correction.
z += weight * channel_correction.
z;
4344 total_weight += weight;
4348 if (total_weight > 0.1f) {
4350 correction_matrix[0][0] = weighted_correction.
x / total_weight;
4351 correction_matrix[1][1] = weighted_correction.
y / total_weight;
4352 correction_matrix[2][2] = weighted_correction.
z / total_weight;
4355 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
4356 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
4357 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
4363 correction_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
4367 if (valid_measured.size() > 0 && patch_weights.size() == valid_measured.size()) {
4368 float total_weight = 0.0f;
4373 for (
size_t i = 0; i < valid_measured.size(); i++) {
4374 float weight = patch_weights[i];
4375 weighted_measured_avg = weighted_measured_avg + weight * valid_measured[i];
4376 weighted_target_avg = weighted_target_avg + weight * valid_target[i];
4377 total_weight += weight;
4380 if (total_weight > 0) {
4381 weighted_measured_avg = weighted_measured_avg / total_weight;
4382 weighted_target_avg = weighted_target_avg / total_weight;
4384 if (weighted_measured_avg.
x > 0.05f && weighted_measured_avg.
y > 0.05f && weighted_measured_avg.
z > 0.05f) {
4385 correction_matrix[0][0] = weighted_target_avg.
x / weighted_measured_avg.
x;
4386 correction_matrix[1][1] = weighted_target_avg.
y / weighted_measured_avg.
y;
4387 correction_matrix[2][2] = weighted_target_avg.
z / weighted_measured_avg.
z;
4390 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
4391 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
4392 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
4396 size_t white_idx = 18;
4397 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
4398 helios::vec3 measured_white = measured_rgb_values[white_idx];
4400 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
4401 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
4402 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
4403 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
4410 size_t white_idx = 18;
4411 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
4412 helios::vec3 measured_white = measured_rgb_values[white_idx];
4414 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
4415 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
4416 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
4417 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
4424 size_t white_idx = 18;
4425 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size() && measured_rgb_values[white_idx].magnitude() > 0) {
4427 helios::vec3 measured_white = measured_rgb_values[white_idx];
4430 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
4431 correction_matrix[0][0] = target_white.
x / measured_white.
x;
4432 correction_matrix[1][1] = target_white.
y / measured_white.
y;
4433 correction_matrix[2][2] = target_white.
z / measured_white.
z;
4436 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
4437 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
4438 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
4442 std::cout <<
"Insufficient valid patches (" << valid_measured.size() <<
" available), using identity matrix" << std::endl;
4446 size_t white_idx = 18;
4447 if (white_idx < measured_rgb_values.size() && white_idx < reference_lab_values.size() && measured_rgb_values[white_idx].magnitude() > 0) {
4451 helios::vec3 measured_white = measured_rgb_values[white_idx];
4453 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
4454 correction_matrix[0][0] = target_white.
x / measured_white.
x;
4455 correction_matrix[1][1] = target_white.
y / measured_white.
y;
4456 correction_matrix[2][2] = target_white.
z / measured_white.
z;
4459 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
4460 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
4461 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
4465 std::cout <<
"Insufficient patches for correction (" << measured_lab_values.size() <<
" available), using identity matrix" << std::endl;
4469 if (print_quality_report) {
4470 std::cout <<
"\n========== COLOR CALIBRATION QUALITY REPORT ==========" << std::endl;
4471 std::cout <<
"Colorboard types: ";
4472 for (
size_t i = 0; i < colorboard_types.size(); i++) {
4473 std::cout << colorboard_types[i];
4474 if (i < colorboard_types.size() - 1) {
4478 std::cout << std::endl;
4479 std::cout <<
"Number of patches analyzed: " << visible_patches << std::endl;
4480 std::cout <<
"Algorithm used: " << algorithm_name << std::endl;
4483 bool is_diagonal_only =
true;
4484 for (
int i = 0; i < 3; i++) {
4485 for (
int j = 0; j < 3; j++) {
4486 if (i != j && fabs(correction_matrix[i][j]) > 1e-6f) {
4487 is_diagonal_only =
false;
4491 if (!is_diagonal_only)
4495 if (is_diagonal_only) {
4496 std::cout <<
"Color correction factors applied: R=" << correction_matrix[0][0] <<
", G=" << correction_matrix[1][1] <<
", B=" << correction_matrix[2][2] << std::endl;
4497 std::cout <<
"Matrix type: Diagonal (white balance only)" << std::endl;
4499 std::cout <<
"Full 3x3 color correction matrix applied:" << std::endl;
4500 for (
int i = 0; i < 3; i++) {
4501 std::cout <<
"[" << std::fixed << std::setprecision(4);
4502 for (
int j = 0; j < 3; j++) {
4503 std::cout << std::setw(8) << correction_matrix[i][j];
4507 std::cout <<
"]" << std::endl;
4509 std::cout <<
"Matrix type: Full 3x3 (corrects color casts and chromatic errors)" << std::endl;
4512 float det = correction_matrix[0][0] * (correction_matrix[1][1] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][1]) -
4513 correction_matrix[0][1] * (correction_matrix[1][0] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][0]) +
4514 correction_matrix[0][2] * (correction_matrix[1][0] * correction_matrix[2][1] - correction_matrix[1][1] * correction_matrix[2][0]);
4516 std::cout <<
"Matrix determinant: " << std::scientific << std::setprecision(3) << det << std::endl;
4517 if (fabs(det) > 0.1f) {
4518 std::cout <<
"Matrix conditioning: Good (well-conditioned)" << std::endl;
4519 }
else if (fabs(det) > 0.01f) {
4520 std::cout <<
"Matrix conditioning: Fair (moderately conditioned)" << std::endl;
4522 std::cout <<
"Matrix conditioning: Poor (ill-conditioned)" << std::endl;
4524 std::cout << std::fixed;
4528 double total_delta_e = 0.0;
4529 int valid_patches = 0;
4531 std::cout <<
"\nPer-patch analysis (after correction):" << std::endl;
4532 std::cout <<
"Patch | Corrected RGB | Reference RGB | Delta E " << std::endl;
4533 std::cout <<
"------|--------------------|--------------------|---------" << std::endl;
4535 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
4536 if (measured_rgb_values[i].magnitude() > 0) {
4539 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
4540 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
4541 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
4557 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
4559 std::cout << std::setw(5) << i <<
" | " << std::fixed << std::setprecision(3) <<
"(" << std::setw(5) << corrected_rgb.
x <<
"," << std::setw(5) << corrected_rgb.
y <<
"," << std::setw(5) << corrected_rgb.
z <<
") | ";
4562 std::cout <<
"(" << std::setw(5) << ref_rgb.
x <<
"," << std::setw(5) << ref_rgb.
y <<
"," << std::setw(5) << ref_rgb.
z <<
") | " << std::setw(7) << delta_E << std::endl;
4564 total_delta_e += delta_E;
4570 double mean_delta_e = total_delta_e / valid_patches;
4571 std::cout <<
"\n========== OVERALL CALIBRATION QUALITY ==========" << std::endl;
4572 std::cout <<
"Mean Delta E: " << std::fixed << std::setprecision(2) << mean_delta_e << std::endl;
4574 std::cout <<
"======================================================\n" << std::endl;
4578 std::vector<helios::RGBcolor> corrected_pixels;
4579 corrected_pixels.resize(red_data.size());
4582 for (
int j = 0; j < camera_resolution.
y; j++) {
4583 for (
int i = 0; i < camera_resolution.
x; i++) {
4585 int source_index = j * camera_resolution.
x + i;
4586 float r = red_data[source_index];
4587 float g = green_data[source_index];
4588 float b = blue_data[source_index];
4591 float corrected_r = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
4592 float corrected_g = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
4593 float corrected_b = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
4602 uint ii = camera_resolution.
x - i - 1;
4603 uint jj = camera_resolution.
y - j - 1;
4604 uint dest_index = jj * camera_resolution.
x + ii;
4606 corrected_pixels[dest_index] =
make_RGBcolor(corrected_r, corrected_g, corrected_b);
4611 std::string output_path = output_file_path;
4612 if (output_path.empty()) {
4613 output_path =
"auto_calibrated_" + camera_label +
".jpg";
4617 helios::writeJPEG(output_path, camera_resolution.
x, camera_resolution.
y, corrected_pixels);
4618 std::cout <<
"Wrote corrected image to: " << output_path << std::endl;
4619 }
catch (
const std::exception &e) {
4620 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to write corrected image. " + std::string(e.what()));
4624 if (!ccm_export_file_path.empty()) {
4627 double total_delta_e = 0.0;
4628 int valid_patches = 0;
4630 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
4631 if (measured_rgb_values[i].magnitude() > 0) {
4634 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
4635 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
4636 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
4645 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
4646 total_delta_e += delta_E;
4651 double mean_delta_e = (valid_patches > 0) ? (total_delta_e / valid_patches) : -1.0;
4655 std::string colorboard_types_str;
4656 for (
size_t i = 0; i < colorboard_types.size(); i++) {
4657 colorboard_types_str += colorboard_types[i];
4658 if (i < colorboard_types.size() - 1) {
4659 colorboard_types_str +=
", ";
4664 std::cout <<
"Exported color correction matrix to: " << ccm_export_file_path << std::endl;
4665 }
catch (
const std::exception &e) {
4666 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to export CCM to XML. " + std::string(e.what()));
4676 if (cameras.find(camera_label) == cameras.end()) {
4677 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
4681 auto &camera_bands = cameras.at(camera_label).band_labels;
4682 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
4683 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
4685 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
4686 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
4688 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
4689 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
4693 std::string loaded_camera_label;
4694 std::vector<std::vector<float>> correction_matrix;
4697 }
catch (
const std::exception &e) {
4698 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Failed to load CCM from XML file. " + std::string(e.what()));
4702 if (correction_matrix.size() != 3) {
4703 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Invalid matrix dimensions. Expected 3x3 or 4x3 matrix, got " + std::to_string(correction_matrix.size()) +
" rows.");
4706 bool is_3x3 = (correction_matrix[0].size() == 3);
4707 bool is_4x3 = (correction_matrix[0].size() == 4);
4709 if (!is_3x3 && !is_4x3) {
4710 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Invalid matrix dimensions. Expected 3x3 or 4x3 matrix, got " + std::to_string(correction_matrix.size()) +
"x" + std::to_string(correction_matrix[0].size()) +
4715 std::vector<float> &red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
4716 std::vector<float> &green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
4717 std::vector<float> &blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
4719 int2 camera_resolution = cameras.at(camera_label).resolution;
4720 size_t pixel_count = red_data.size();
4723 for (
size_t i = 0; i < pixel_count; i++) {
4724 float r = red_data[i];
4725 float g = green_data[i];
4726 float b = blue_data[i];
4731 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
4732 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
4733 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
4736 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b + correction_matrix[0][3];
4737 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b + correction_matrix[1][3];
4738 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b + correction_matrix[2][3];
4743 std::cout <<
"Applied color correction matrix from '" << ccm_file_path <<
"' to camera '" << camera_label <<
"'" << std::endl;
4744 std::cout <<
"Matrix type: " << (is_3x3 ?
"3x3" :
"4x3") <<
", processed " << pixel_count <<
" pixels" << std::endl;
4749 if (cameras.find(camera_label) == cameras.end()) {
4750 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Camera '" + camera_label +
"' does not exist.");
4753 auto &camera_pixel_data = cameras.at(camera_label).pixel_data;
4754 if (camera_pixel_data.find(band_label) == camera_pixel_data.end()) {
4755 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Band '" + band_label +
"' does not exist in camera '" + camera_label +
"'.");
4758 return camera_pixel_data.at(band_label);
4762 if (cameras.find(camera_label) == cameras.end()) {
4763 helios_runtime_error(
"ERROR (RadiationModel::setCameraPixelData): Camera '" + camera_label +
"' does not exist.");
4766 cameras.at(camera_label).pixel_data[band_label] = pixel_data;
4773 backend->queryGPUMemory();
4775 std::cout <<
"Backend not initialized - cannot query GPU memory." << std::endl;
4802 float effective_HFOV = camera.HFOV_degrees / camera.camera_zoom;
4807 float HFOV_rad = effective_HFOV *
M_PI / 180.f;
4808 float VFOV_rad = HFOV_rad / camera.FOV_aspect_ratio;
4809 float pixel_angle_h = HFOV_rad / float(camera.resolution.
x);
4810 float pixel_angle_v = VFOV_rad / float(camera.resolution.
y);
4822std::vector<CameraTile> RadiationModel::computeCameraTiles(
const RadiationCamera &camera,
size_t maxRays) {
4824 std::vector<CameraTile> tiles;
4826 size_t total_rays = size_t(camera.antialiasing_samples) * size_t(camera.resolution.
x) * size_t(camera.resolution.
y);
4829 if (total_rays <= maxRays) {
4835 size_t rays_per_row = size_t(camera.antialiasing_samples) * size_t(camera.resolution.
x);
4836 size_t max_rows_per_tile = floor(
float(maxRays) /
float(rays_per_row));
4838 if (max_rows_per_tile == 0) {
4840 size_t max_pixels_per_tile = floor(
float(maxRays) /
float(camera.antialiasing_samples));
4842 float aspect = float(camera.resolution.
x) / float(camera.resolution.
y);
4843 size_t tile_width = round(sqrt(max_pixels_per_tile * aspect));
4844 size_t tile_height = floor(
float(max_pixels_per_tile) /
float(tile_width));
4846 tile_width = std::min(tile_width,
size_t(camera.resolution.
x));
4847 tile_height = std::min(tile_height,
size_t(camera.resolution.
y));
4849 int Ntiles_x = ceil(
float(camera.resolution.
x) /
float(tile_width));
4850 int Ntiles_y = ceil(
float(camera.resolution.
y) /
float(tile_height));
4852 for (
int ty = 0; ty < Ntiles_y; ty++) {
4853 for (
int tx = 0; tx < Ntiles_x; tx++) {
4854 size_t offset_x = tx * tile_width;
4855 size_t offset_y = ty * tile_height;
4856 size_t width_this = std::min(tile_width, camera.resolution.
x - offset_x);
4857 size_t height_this = std::min(tile_height, camera.resolution.
y - offset_y);
4864 size_t rows_per_tile = std::min(max_rows_per_tile,
size_t(camera.resolution.
y));
4865 int Ntiles = ceil(
float(camera.resolution.
y) /
float(rows_per_tile));
4867 for (
int t = 0; t < Ntiles; t++) {
4868 size_t offset_y = t * rows_per_tile;
4869 size_t height_this = std::min(rows_per_tile, camera.resolution.
y - offset_y);
4878void RadiationModel::buildGeometryData() {
4885 std::vector<uint> valid_UUIDs;
4886 for (
uint UUID: UUIDs) {
4892 if ((area == 0 || std::isnan(area)) && context->
getObjectType(parentID) != helios::OBJECT_TYPE_TILE) {
4895 valid_UUIDs.push_back(UUID);
4898 if (valid_UUIDs.empty()) {
4905 std::vector<uint> primitive_UUIDs_ordered;
4906 std::unordered_set<uint> valid_set(valid_UUIDs.begin(), valid_UUIDs.end());
4908 for (
uint objID: objID_all) {
4910 for (
uint UUID: prim_UUIDs) {
4912 primitive_UUIDs_ordered.push_back(UUID);
4917 size_t Nprimitives = primitive_UUIDs_ordered.size();
4930 geometry_data.
object_IDs.resize(Nprimitives);
4955 uint current_objID = 0;
4956 uint last_parentID = 99999;
4958 std::vector<uint> primitiveID_indices;
4960 for (
size_t u = 0; u < Nprimitives; u++) {
4961 uint UUID = primitive_UUIDs_ordered[u];
4964 if (last_parentID != parentID || parentID == 0 || context->
getObjectType(parentID) != helios::OBJECT_TYPE_TILE) {
4965 primitiveID_indices.push_back(u);
4966 last_parentID = parentID;
4969 last_parentID = parentID;
4972 geometry_data.
object_IDs[u] = current_objID - 1;
4975 size_t Nobjects = primitiveID_indices.size();
4978 primitiveID = primitiveID_indices;
4982 std::vector<uint> primitiveID_for_backend(Nprimitives);
4983 for (
size_t i = 0; i < Nprimitives; i++) {
4984 primitiveID_for_backend[i] = primitive_UUIDs_ordered[i];
4991 size_t patch_idx = 0, tri_idx = 0, disk_idx = 0, tile_idx = 0, voxel_idx = 0, bbox_idx = 0;
4995 std::set<uint> processed_tile_parents;
4999 for (
size_t prim_idx = 0; prim_idx < Nprimitives; prim_idx++) {
5000 uint UUID = primitive_UUIDs_ordered[prim_idx];
5013 if (parentID > 0 && context->
getObjectType(parentID) == helios::OBJECT_TYPE_TILE) {
5026 if (processed_tile_parents.find(parentID) == processed_tile_parents.end()) {
5027 processed_tile_parents.insert(parentID);
5030 for (
const auto &v: verts) {
5039 }
else if (type == helios::PRIMITIVE_TYPE_PATCH) {
5046 for (
const auto &v: verts) {
5057 }
else if (type == helios::PRIMITIVE_TYPE_TRIANGLE) {
5064 for (
const auto &v: verts) {
5072 }
else if (type == helios::PRIMITIVE_TYPE_VOXEL) {
5079 for (
const auto &v: verts) {
5101 vec2 xbounds, ybounds, zbounds;
5105 if (periodic_flag.
x == 1 || periodic_flag.
y == 1) {
5106 if (!cameras.empty()) {
5107 for (
auto &camera: cameras) {
5108 vec3 camerapos = camera.second.position;
5109 if (camerapos.
x < xbounds.
x || camerapos.
x > xbounds.
y || camerapos.
y < ybounds.
x || camerapos.
y > ybounds.
y) {
5110 std::cout <<
"WARNING (RadiationModel::buildGeometryData): camera position is outside of the domain bounding box. Disabling periodic boundary conditions." << std::endl;
5111 periodic_flag.
x = 0;
5112 periodic_flag.
y = 0;
5116 if (camerapos.
z < zbounds.
x) {
5117 zbounds.
x = camerapos.
z;
5119 if (camerapos.
z > zbounds.
y) {
5120 zbounds.
y = camerapos.
z;
5137 uint bbox_UUID_base = max_UUID + 1;
5140 if (periodic_flag.
x == 1) {
5146 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5154 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5158 if (periodic_flag.
y == 1) {
5164 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5172 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5196 for (
size_t i = 0; i < bbox_idx; i++) {
5227 uint bbox_max_UUID = max_UUID;
5243 for (
size_t i = 0; i < geometry_data.
bbox_count; i++) {
5251void RadiationModel::buildTextureData() {
5259 geometry_data.
uv_data.clear();
5263 geometry_data.
mask_IDs.resize(Nobjects, -1);
5264 geometry_data.
uv_IDs.clear();
5265 geometry_data.
uv_IDs.resize(Nobjects, -1);
5268 std::map<std::string, int> texture_to_mask_idx;
5270 for (
size_t prim_idx = 0; prim_idx < Nobjects; prim_idx++) {
5275 if (texture_file.empty()) {
5286 auto cache_it = texture_to_mask_idx.find(texture_file);
5287 if (cache_it != texture_to_mask_idx.end()) {
5289 mask_idx = cache_it->second;
5295 mask_idx =
static_cast<int>(geometry_data.
mask_sizes.size());
5296 texture_to_mask_idx[texture_file] = mask_idx;
5300 for (
int y = 0; y < tex_size.
y; y++) {
5301 for (
int x = 0; x < tex_size.
x; x++) {
5302 geometry_data.
mask_data.push_back((*trans_data)[y][x]);
5305 geometry_data.
mask_sizes.push_back(tex_size);
5308 geometry_data.
mask_IDs[prim_idx] = mask_idx;
5314 geometry_data.
uv_IDs[prim_idx] =
static_cast<int>(prim_idx);
5315 for (
const auto &uv: uvs) {
5316 geometry_data.
uv_data.push_back(uv);
5319 size_t start_idx = geometry_data.
uv_data.size() - uvs.size();
5320 while (geometry_data.
uv_data.size() - start_idx < 4) {
5321 geometry_data.
uv_data.push_back(uvs.back());
5329 buildGeometryData();
5333void RadiationModel::buildUUIDMapping() {
5337 uuid_to_position.clear();
5338 position_to_uuid.clear();
5344 uuid_to_position[UUID] = i;
5345 position_to_uuid.push_back(UUID);
5353static void validateAndCorrectMaterialProperties(
float &rho,
float &tau,
float eps,
bool emission_enabled,
uint scattering_depth,
const std::string &band_label,
uint UUID,
helios::WarningAggregator *warnings =
nullptr) {
5358 if (rho < 0.f || rho > 1.f) {
5360 warnings->
addWarning(
"material_property_clamping",
"Reflectivity out of range [0,1] for band " + band_label +
", primitive #" + std::to_string(UUID) +
": rho=" + std::to_string(rho) +
". Clamping to valid range.");
5362 rho = std::max(0.f, std::min(1.f, rho));
5365 if (tau < 0.f || tau > 1.f) {
5367 warnings->
addWarning(
"material_property_clamping",
"Transmissivity out of range [0,1] for band " + band_label +
", primitive #" + std::to_string(UUID) +
": tau=" + std::to_string(tau) +
". Clamping to valid range.");
5369 tau = std::max(0.f, std::min(1.f, tau));
5373 if (emission_enabled) {
5375 if (scattering_depth == 0 && eps != 1.f) {
5376 if (warnings && (rho != 0.f || tau != 0.f)) {
5377 warnings->
addWarning(
"blackbody_override",
"Band " + band_label +
" has emission with scatteringDepth=0, " +
"enforcing blackbody behavior (eps=1, rho=0, tau=0) for primitive #" + std::to_string(UUID));
5383 else if (eps != 1.f && rho == 0 && tau == 0) {
5386 }
else if (std::abs(eps + rho + tau - 1.f) > 1e-5f && eps > 0.f) {
5388 helios_runtime_error(std::string(
"ERROR (RadiationModel): emissivity, transmissivity, and reflectivity ") +
"must sum to 1 to ensure energy conservation. Band " + band_label +
", Primitive #" + std::to_string(UUID) +
5389 ": eps=" + std::to_string(eps) +
", tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
". It is also possible that you forgot to disable emission for this band.");
5393 if (rho + tau > 1.f) {
5394 helios_runtime_error(std::string(
"ERROR (RadiationModel): transmissivity and reflectivity cannot sum to ") +
"greater than 1 to ensure energy conservation. Band " + band_label +
", Primitive #" + std::to_string(UUID) +
5395 ": eps=" + std::to_string(eps) +
", tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
". It is also possible that you forgot to disable emission for this band.");
5400void RadiationModel::buildMaterialData() {
5407 size_t Nbands = radiation_bands.size();
5408 size_t Nsources = radiation_sources.size();
5417 size_t total_size = Nsources * Nbands * Nprims;
5427 std::map<std::string, std::vector<helios::vec2>> unique_rho_spectra;
5428 std::map<std::string, std::vector<helios::vec2>> unique_tau_spectra;
5430 for (
size_t p = 0; p < Nprims; p++) {
5435 std::string spectrum_label;
5437 if (unique_rho_spectra.find(spectrum_label) == unique_rho_spectra.end()) {
5440 unique_rho_spectra[spectrum_label] = loadSpectralData(spectrum_label);
5447 std::string spectrum_label;
5448 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
5449 if (unique_tau_spectra.find(spectrum_label) == unique_tau_spectra.end()) {
5452 unique_tau_spectra[spectrum_label] = loadSpectralData(spectrum_label);
5460 for (
const auto &band_pair: radiation_bands) {
5461 std::string band_label = band_pair.second.label;
5463 for (
size_t s = 0; s < Nsources; s++) {
5464 for (
size_t p = 0; p < Nprims; p++) {
5469 size_t idx = mat_indexer(s, p, b_idx);
5472 float rho = rho_default;
5476 std::string spectrum_label;
5480 if (unique_rho_spectra.find(spectrum_label) != unique_rho_spectra.end()) {
5481 const std::vector<helios::vec2> &spectrum = unique_rho_spectra.at(spectrum_label);
5484 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
5489 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
5491 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
5492 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
5496 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
5497 if (!radiation_sources[s].source_spectrum.empty()) {
5509 std::string rho_label =
"reflectivity_" + band_label;
5516 float tau = tau_default;
5520 std::string spectrum_label;
5521 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
5524 if (unique_tau_spectra.find(spectrum_label) != unique_tau_spectra.end()) {
5525 const std::vector<helios::vec2> &spectrum = unique_tau_spectra.at(spectrum_label);
5528 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
5533 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
5535 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
5536 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
5540 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
5541 if (!radiation_sources[s].source_spectrum.empty()) {
5553 std::string tau_label =
"transmissivity_" + band_label;
5560 float eps = eps_default;
5561 std::string eps_label =
"emissivity_" + band_label;
5582 bool specular_exponent_specified =
false;
5583 bool specular_scale_specified =
false;
5585 for (
size_t p = 0; p < Nprims; p++) {
5591 specular_exponent_specified =
true;
5598 specular_scale_specified =
true;
5604 if (specular_exponent_specified) {
5605 if (specular_scale_specified) {
5606 specular_reflection_mode = 2;
5608 specular_reflection_mode = 1;
5611 specular_reflection_mode = 0;
5618void RadiationModel::buildSourceData() {
5621 source_data.clear();
5622 source_data.reserve(radiation_sources.size());
5624 for (
size_t s = 0; s < radiation_sources.size(); s++) {
5625 const auto &src = radiation_sources[s];
5627 backend_src.
position = src.source_position;
5628 backend_src.
rotation = src.source_rotation;
5629 backend_src.
width = src.source_width;
5630 backend_src.
type = src.source_type;
5633 backend_src.
fluxes.clear();
5635 for (
const auto &band_pair: radiation_bands) {
5636 std::string band_label = band_pair.second.label;
5639 backend_src.
fluxes.push_back(flux);
5643 source_data.push_back(backend_src);
5649 return backend.get();
5653 return geometry_data;
5657 return material_data;
5666 buildGeometryData();
5667 buildMaterialData();