26#include <unordered_set>
28using namespace helios;
40 directRayCount_default = 100;
41 diffuseRayCount_default = 1000;
43 diffuseFlux_default = -1.f;
45 minScatterEnergy_default = 0.1;
46 scatteringDepth_default = 0;
55 temperature_default = 300;
59 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/camera_spectral_library.xml").
string());
60 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/light_spectral_library.xml").
string());
61 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/soil_surface_spectral_library.xml").
string());
62 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/leaf_surface_spectral_library.xml").
string());
63 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/bark_surface_spectral_library.xml").
string());
64 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/fruit_surface_spectral_library.xml").
string());
65 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/solar_spectrum_ASTMG173.xml").
string());
66 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").
string());
67 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/DGK_DKK_colorboard.xml").
string());
71 backend->initialize();
74 std::string backend_name = backend->getBackendName();
75 std::cout <<
"Radiation model initialized with " << backend_name <<
" backend";
76 if (backend_name.find(
"Vulkan") != std::string::npos) {
77 std::cout <<
" - WARNING: radiation model may be slow depending on your GPU (NVIDIA+OptiX backend recommended)";
81 std::cout << std::endl;
90 directRayCount_default = 100;
91 diffuseRayCount_default = 1000;
92 diffuseFlux_default = -1.f;
93 minScatterEnergy_default = 0.1;
94 scatteringDepth_default = 0;
100 temperature_default = 300;
103 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/camera_spectral_library.xml").
string());
104 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/light_spectral_library.xml").
string());
105 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/soil_surface_spectral_library.xml").
string());
106 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/leaf_surface_spectral_library.xml").
string());
107 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/bark_surface_spectral_library.xml").
string());
108 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/fruit_surface_spectral_library.xml").
string());
109 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/solar_spectrum_ASTMG173.xml").
string());
110 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/Calibrite_ColorChecker_Classic_colorboard.xml").
string());
111 spectral_library_files.push_back(
helios::resolvePluginAsset(
"radiation",
"spectral_data/color_board/DGK_DKK_colorboard.xml").
string());
120 model.backend = std::move(backend);
126 static bool checked =
false;
127 static bool available =
false;
135 const char *no_gpu = std::getenv(
"HELIOS_NO_GPU");
136 if (no_gpu && std::string(no_gpu) !=
"0") {
152 message_flag =
false;
161 return backend->getBackendName();
168 if (strcmp(label,
"reflectivity") == 0 || strcmp(label,
"transmissivity") == 0) {
169 output_prim_data.emplace_back(label);
171 std::cout <<
"WARNING (RadiationModel::optionalOutputPrimitiveData): unknown output primitive data " << label << std::endl;
177 helios_runtime_error(
"ERROR (RadiationModel::setDirectRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
179 radiation_bands.at(label).directRayCount = N;
184 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRayCount): Cannot set ray count for band '" + label +
"' because it is not a valid band.");
186 radiation_bands.at(label).diffuseRayCount = N;
191 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationFlux): Cannot set flux value for band '" + label +
"' because it is not a valid band.");
193 radiation_bands.at(label).diffuseFlux = flux;
202 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseRadiationExtinctionCoeff): Cannot set diffuse extinction value for band '" + label +
"' because it is not a valid band.");
210 for (
int j = 0; j < N; j++) {
211 for (
int i = 0; i < N; i++) {
212 float theta = 0.5f *
M_PI / float(N) * (0.5f + float(i));
213 float phi = 2.f *
M_PI / float(N) * (0.5f + float(j));
218 if (psi <
M_PI / 180.f) {
219 fd = powf(
M_PI / 180.f, -K);
224 norm += fd * cosf(theta) * sinf(theta) *
M_PI / float(N * N);
229 radiation_bands.at(label).diffuseExtinction = K;
230 radiation_bands.at(label).diffusePeakDir = dir;
231 radiation_bands.at(label).diffuseDistNorm = 1.f / norm;
236 if (spectrum_integral < 0) {
237 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
238 }
else if (global_diffuse_spectrum.empty()) {
239 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
244 if (current_integral > 0) {
245 float scale_factor = spectrum_integral / current_integral;
246 for (
vec2 &wavelength: global_diffuse_spectrum) {
247 wavelength.y *= scale_factor;
252 for (
auto &band: radiation_bands) {
253 band.second.diffuse_spectrum = global_diffuse_spectrum;
256 radiativepropertiesneedupdate =
true;
261 if (spectrum_integral < 0) {
262 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Spectrum integral must be non-negative.");
263 }
else if (global_diffuse_spectrum.empty()) {
264 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Global diffuse spectrum has not been set. Call setDiffuseSpectrum() first.");
268 float current_integral =
integrateSpectrum(global_diffuse_spectrum, wavelength1, wavelength2);
269 if (current_integral > 0) {
270 float scale_factor = spectrum_integral / current_integral;
271 for (
vec2 &wavelength: global_diffuse_spectrum) {
272 wavelength.y *= scale_factor;
277 for (
auto &band: radiation_bands) {
278 band.second.diffuse_spectrum = global_diffuse_spectrum;
281 radiativepropertiesneedupdate =
true;
286 if (spectrum_integral < 0) {
287 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
289 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
290 }
else if (radiation_bands.at(band_label).diffuse_spectrum.empty()) {
291 std::cerr <<
"WARNING (RadiationModel::setDiffuseSpectrumIntegral): Diffuse spectral distribution has not been set for radiation band '" + band_label +
"'. Cannot set its integral." << std::endl;
295 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum);
297 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
298 wavelength.y *= spectrum_integral / current_integral;
301 radiativepropertiesneedupdate =
true;
306 if (spectrum_integral < 0) {
307 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Source integral must be non-negative.");
309 helios_runtime_error(
"ERROR (RadiationModel::setDiffuseSpectrumIntegral): Cannot set integral for band '" + band_label +
"' because it is not a valid band.");
312 float current_integral =
integrateSpectrum(radiation_bands.at(band_label).diffuse_spectrum, wavelength1, wavelength2);
314 for (
vec2 &wavelength: radiation_bands.at(band_label).diffuse_spectrum) {
315 wavelength.y *= spectrum_integral / current_integral;
318 radiativepropertiesneedupdate =
true;
323 if (radiation_bands.find(label) != radiation_bands.end()) {
324 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
328 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
331 if (!global_diffuse_spectrum.empty()) {
335 radiation_bands.emplace(label, band);
338 for (
auto &source: radiation_sources) {
339 source.source_fluxes[label] = -1.f;
342 radiativepropertiesneedupdate =
true;
347 if (radiation_bands.find(label) != radiation_bands.end()) {
348 std::cerr <<
"WARNING (RadiationModel::addRadiationBand): Radiation band " << label <<
" has already been added. Skipping this call to addRadiationBand()." << std::endl;
350 }
else if (wavelength1 > wavelength2) {
351 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The upper wavelength bound for a band must be greater than the lower bound.");
352 }
else if (wavelength2 - wavelength1 < 1) {
353 helios_runtime_error(
"ERROR (RadiationModel::addRadiationBand): The waveband range of a radiation band must be at least 1 nm.");
356 RadiationBand band(label, directRayCount_default, diffuseRayCount_default, diffuseFlux_default, scatteringDepth_default, minScatterEnergy_default);
361 if (!global_diffuse_spectrum.empty()) {
365 radiation_bands.emplace(label, band);
368 for (
auto &source: radiation_sources) {
369 source.source_fluxes[label] = -1.f;
372 radiativepropertiesneedupdate =
true;
378 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
381 vec2 waveBounds = radiation_bands.at(old_label).wavebandBounds;
389 helios_runtime_error(
"ERROR (RadiationModel::copyRadiationBand): Cannot copy band " + old_label +
" because it does not exist.");
393 band.
label = new_label;
396 radiation_bands.emplace(new_label, band);
399 for (
auto &source: radiation_sources) {
400 source.source_fluxes[new_label] = source.source_fluxes.at(old_label);
403 radiativepropertiesneedupdate =
true;
407 if (radiation_bands.find(label) == radiation_bands.end()) {
417 helios_runtime_error(
"ERROR (RadiationModel::disableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
420 radiation_bands.at(label).emissionFlag =
false;
426 helios_runtime_error(
"ERROR (RadiationModel::enableEmission): Cannot disable emission for band '" + label +
"' because it is not a valid band.");
429 radiation_bands.at(label).emissionFlag =
true;
446 float fp_round5(
float x) {
447 return std::round(x * 1e5f) * 1e-5f;
451bool RadiationModel::FluspectCacheKey::operator==(
const FluspectCacheKey &o)
const noexcept {
452 return biochem_label == o.biochem_label && excitation_step_nm == o.excitation_step_nm;
455std::size_t RadiationModel::FluspectCacheKeyHash::operator()(
const FluspectCacheKey &k)
const noexcept {
456 std::size_t h = std::hash<std::string>{}(k.biochem_label);
458 std::memcpy(&bits, &k.excitation_step_nm,
sizeof(bits));
459 h ^=
static_cast<std::size_t
>(bits) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
463void RadiationModel::ensureFluspectOptiparLoaded() {
464 if (fluspect_optipar_loaded) {
467 const std::filesystem::path p =
helios::resolveFilePath(
"plugins/radiation/spectral_data/fluspect_B_optipar.xml");
469 fluspect_optipar_loaded =
true;
480 std::string biochem_label;
485 FluspectCacheKey key{biochem_label, fp_round5(excitation_step_nm)};
486 auto it = fluspect_cache.find(key);
487 if (it != fluspect_cache.end()) {
494 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): primitive " + std::to_string(UUID) +
495 " has fluspect_spectrum = '" + biochem_label +
"' but that global data does not exist. "
496 "Either call LeafOptics::run() to author the biochemistry, or manually setGlobalData("
497 "\"" + biochem_label +
"\", std::vector<float>{Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe}).");
500 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): global data '" + biochem_label +
501 "' is not a float vector — must be std::vector<float> with 11 elements.");
503 std::vector<float> biochem_vec;
505 if (biochem_vec.size() != 11) {
506 helios_runtime_error(
"ERROR (RadiationModel::getOrComputeFluspectKernel): global data '" + biochem_label +
507 "' has " + std::to_string(biochem_vec.size()) +
" elements but must have exactly 11 "
508 "(Cab, Cca, Cw, Cdm, Cs, Cant, Cp, Cbc, N, V2Z, fqe).");
512 biochem.
Cab = biochem_vec[0];
513 biochem.
Cca = biochem_vec[1];
514 biochem.
Cw = biochem_vec[2];
515 biochem.
Cdm = biochem_vec[3];
516 biochem.
Cs = biochem_vec[4];
517 biochem.
Cant = biochem_vec[5];
518 biochem.
Cp = biochem_vec[6];
519 biochem.
Cbc = biochem_vec[7];
520 biochem.
N = biochem_vec[8];
521 biochem.
V2Z = biochem_vec[9];
528 ensureFluspectOptiparLoaded();
530 auto [inserted_it, _] = fluspect_cache.emplace(key, std::move(kernel));
531 return &inserted_it->second;
534RadiationModel::ExcitationSet &RadiationModel::ensureExcitationSet(
float bin_width_nm,
uint scattering_depth) {
535 const float key = fp_round5(bin_width_nm);
536 auto it = excitation_sets.find(key);
537 if (it != excitation_sets.end()) {
542 if (scattering_depth > it->second.scattering_depth) {
543 it->second.scattering_depth = scattering_depth;
544 for (
const auto &bname : it->second.band_labels) {
552 set.bin_width_nm = bin_width_nm;
553 set.scattering_depth = scattering_depth;
554 constexpr float ex_min = 400.f;
555 constexpr float ex_max = 750.f;
559 const int n_bins =
static_cast<int>(std::ceil((ex_max - ex_min) / bin_width_nm));
560 set.band_labels.reserve(n_bins);
561 set.band_min_nm.reserve(n_bins);
562 set.band_max_nm.reserve(n_bins);
563 for (
int i = 0; i < n_bins; ++i) {
564 float wmin = ex_min + i * bin_width_nm;
565 float wmax = std::min(ex_max, wmin + bin_width_nm);
566 std::ostringstream oss;
567 oss <<
"_SIF_exc_" << bin_width_nm <<
"_" << wmin <<
"_" << wmax;
568 const std::string label = oss.str();
582 for (
uint sid = 0; sid < radiation_sources.size(); ++sid) {
583 const auto &src = radiation_sources.at(sid);
584 if (!src.source_spectrum.empty()) {
590 set.band_labels.push_back(label);
591 set.band_min_nm.push_back(wmin);
592 set.band_max_nm.push_back(wmax);
594 auto [inserted_it, _] = excitation_sets.emplace(key, std::move(set));
595 return inserted_it->second;
598void RadiationModel::populateExcitationAPAR(ExcitationSet &exc) {
602 exc.apar_buffer.clear();
603 const std::vector<uint> all_UUIDs = context->
getAllUUIDs();
604 const size_t n_bands = exc.band_labels.size();
605 for (
uint UUID : all_UUIDs) {
610 std::vector<float> row(n_bands, 0.f);
611 for (
size_t b = 0; b < n_bands; ++b) {
612 const std::string prop =
"radiation_flux_" + exc.band_labels[b];
617 exc.apar_buffer.emplace(UUID, std::move(row));
620 for (
uint UUID : all_UUIDs) {
621 for (
const auto &band_label : exc.band_labels) {
622 const std::string prop =
"radiation_flux_" + band_label;
628 exc.populated =
true;
631void RadiationModel::runExcitationBands() {
639 for (
auto &kv : excitation_sets) {
640 ExcitationSet &exc = kv.second;
645 populateExcitationAPAR(exc);
649void RadiationModel::computeSIFEmission(
const std::string &emission_band) {
655 auto band_it = radiation_bands.find(emission_band);
656 if (band_it == radiation_bands.end()) {
657 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): band '" + emission_band +
"' does not exist.");
659 const float em_min = band_it->second.wavebandBounds.x;
660 const float em_max = band_it->second.wavebandBounds.y;
663 runExcitationBands();
665 auto &buf_top = sif_emission_buffer[emission_band];
666 auto &buf_bot = sif_emission_buffer_bottom[emission_band];
673 auto bw_it = sif_band_bin_width.find(emission_band);
674 if (bw_it == sif_band_bin_width.end()) {
675 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): band '" + emission_band +
"' is flagged as SIF but has no excitation bin width registered. This is an internal inconsistency.");
677 const float step = bw_it->second;
681 const ExcitationSet *matched =
nullptr;
682 for (
const auto &kv : excitation_sets) {
683 if (std::abs(kv.second.bin_width_nm - step) < 1e-5f) {
684 matched = &kv.second;
689 helios_runtime_error(
"ERROR (RadiationModel::computeSIFEmission): no excitation set found for bin width " + std::to_string(step) +
" nm (band '" + emission_band +
"').");
692 const std::vector<uint> all_UUIDs = context->
getAllUUIDs();
693 size_t n_applied = 0;
694 size_t n_skipped_no_biochem_has_etr = 0;
695 size_t n_skipped_has_biochem_no_etr = 0;
696 for (
uint UUID : all_UUIDs) {
700 if (has_biochem) ++n_skipped_has_biochem_no_etr;
706 if (has_etr) ++n_skipped_no_biochem_has_etr;
710 float J_over_Jmax = 0.f;
712 float T_leaf_K = 298.15f;
715 if (T_leaf_K <= 0.f) T_leaf_K = 298.15f;
717 const float Phi_F = calculateFluorescenceYield(J_over_Jmax, T_leaf_K);
725 std::string biochem_label;
728 std::vector<float> biochem_vec;
730 if (biochem_vec.size() >= 11) fqe = biochem_vec[10];
733 const float phi_F_scaled = Phi_F * fqe;
741 const auto &wle = kernel->
wle;
742 const auto &wlf = kernel->
wlf;
744 auto apar_it = matched->apar_buffer.find(UUID);
745 if (apar_it == matched->apar_buffer.end())
continue;
746 const std::vector<float> &apar_bands = apar_it->second;
752 std::vector<double> F_top(wlf.size(), 0.0);
753 std::vector<double> F_bot(wlf.size(), 0.0);
754 for (
size_t b = 0; b < matched->band_labels.size(); ++b) {
755 const float band_center = 0.5f * (matched->band_min_nm[b] + matched->band_max_nm[b]);
758 float best_delta = std::numeric_limits<float>::infinity();
759 for (
size_t j = 0; j < wle.size(); ++j) {
760 const float d = std::abs(wle[j] - band_center);
761 if (d < best_delta) {
767 const double apar = apar_bands[b];
768 if (apar == 0.0)
continue;
769 for (
size_t i = 0; i < wlf.size(); ++i) {
770 F_top[i] += apar * kernel->
Mf[i][j_best];
771 F_bot[i] += apar * kernel->
Mb[i][j_best];
777 for (
size_t i = 0; i < wlf.size(); ++i) {
778 F_top[i] *= phi_F_scaled;
779 F_bot[i] *= phi_F_scaled;
783 auto integrate = [&](
const std::vector<double> &F) ->
double {
785 for (
size_t i = 0; i + 1 < wlf.size(); ++i) {
786 const float w0 = wlf[i];
787 const float w1 = wlf[i + 1];
788 if (w1 < em_min)
continue;
789 if (w0 > em_max)
break;
791 const double lo = std::max<double>(w0, em_min);
792 const double hi = std::min<double>(w1, em_max);
793 if (hi <= lo)
continue;
796 const double frac_lo = (lo - w0) / (w1 - w0);
797 const double frac_hi = (hi - w0) / (w1 - w0);
798 const double F_lo = F[i] + frac_lo * (F[i + 1] - F[i]);
799 const double F_hi = F[i] + frac_hi * (F[i + 1] - F[i]);
800 total += 0.5 * (F_lo + F_hi) * (hi - lo);
805 const double emit_top = integrate(F_top);
806 const double emit_bot = integrate(F_bot);
807 buf_top[UUID] =
static_cast<float>(emit_top);
808 buf_bot[UUID] =
static_cast<float>(emit_bot);
812 if (n_applied == 0 && message_flag) {
813 if (n_skipped_has_biochem_no_etr == 0 && n_skipped_no_biochem_has_etr == 0) {
814 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
815 <<
"' will emit zero — no primitives have both 'fluspect_spectrum' (leaf biochemistry "
816 "label) and 'electron_transport_ratio' (J/Jmax) primitive data. Call "
817 "LeafOptics::run() to author leaf biochemistry and PhotosynthesisModel::run() "
818 "with optionalOutputPrimitiveData(\"electron_transport_ratio\") before runBand()."
820 }
else if (n_skipped_has_biochem_no_etr > 0 && n_skipped_no_biochem_has_etr == 0) {
821 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
822 <<
"' will emit zero — " << n_skipped_has_biochem_no_etr <<
" primitives have "
823 "'fluspect_spectrum' but lack 'electron_transport_ratio'. Run PhotosynthesisModel::run() "
824 "with optionalOutputPrimitiveData(\"electron_transport_ratio\") before runBand()."
826 }
else if (n_skipped_no_biochem_has_etr > 0 && n_skipped_has_biochem_no_etr == 0) {
827 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
828 <<
"' will emit zero — " << n_skipped_no_biochem_has_etr <<
" primitives have "
829 "'electron_transport_ratio' but lack 'fluspect_spectrum'. Call LeafOptics::run() "
830 "to author leaf biochemistry for those primitives."
833 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): SIF-flagged band '" << emission_band
834 <<
"' will emit zero — " << n_skipped_has_biochem_no_etr <<
" primitives have "
835 "'fluspect_spectrum' but lack 'electron_transport_ratio', and "
836 << n_skipped_no_biochem_has_etr <<
" have 'electron_transport_ratio' but lack "
837 "'fluspect_spectrum'. No primitive has both required fields."
840 }
else if (n_applied > 0 && n_skipped_has_biochem_no_etr > 0 && message_flag) {
842 std::cerr <<
"WARNING (RadiationModel::computeSIFEmission): band '" << emission_band <<
"': "
843 << n_skipped_has_biochem_no_etr <<
" primitives with 'fluspect_spectrum' were silently "
844 "skipped because they lack 'electron_transport_ratio'. ("
845 << n_applied <<
" primitives emitted SIF normally.)"
854 if (emission_band_labels.empty()) {
855 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): emission_band_labels cannot be empty.");
857 for (
const auto &band : emission_band_labels) {
859 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): band '" + band +
"' does not exist. Add it with addRadiationBand() before calling addSIFCamera().");
864 sif_emission_bands.insert(band);
869 auto bw_it = sif_band_bin_width.find(band);
870 if (bw_it == sif_band_bin_width.end()) {
873 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): emission band '" + band +
"' is already bound to excitation_bin_width_nm=" + std::to_string(bw_it->second) +
874 " by a prior SIF camera, but this camera's excitation_bin_width_nm=" + std::to_string(camera_properties.
excitation_bin_width_nm) +
875 ". Each SIF emission band can be bound to only one excitation resolution. "
876 "Either use a separate band per camera or match excitation_bin_width_nm.");
880 helios_runtime_error(
"ERROR (RadiationModel::addSIFCamera): excitation_bin_width_nm must be > 0.");
893 bool any_source_has_spectrum =
false;
894 for (
const auto &src : radiation_sources) {
895 if (!src.source_spectrum.empty()) {
896 any_source_has_spectrum =
true;
900 if (!any_source_has_spectrum) {
901 std::cerr <<
"WARNING (RadiationModel::addSIFCamera): Camera '" << camera_label
902 <<
"' added, but no radiation source has a spectrum set. Auto-generated excitation "
903 "bands will receive zero flux, so SIF emission from all leaves will be zero. "
904 "Call setSourceSpectrum(source_ID, \"solar_spectrum_direct_ASTMG173\") (or similar) "
905 "on at least one source before runBand()."
916 const size_t n_prims = all_UUIDs.size();
917 size_t n_with_biochem = 0;
918 for (
uint UUID : all_UUIDs) {
923 if (n_prims > 0 && n_with_biochem == 0) {
924 std::cerr <<
"WARNING (RadiationModel::addSIFCamera): Camera '" << camera_label
925 <<
"' added to a scene with " << n_prims <<
" primitives, but none have "
926 "'fluspect_spectrum' primitive data. Helios cannot identify fluorescing leaves "
927 "without a biochemistry label. Call LeafOptics::run(UUIDs, properties, \"my_label\") "
928 "to author leaf biochemistry, or manually "
929 "setGlobalData(\"fluspect_biochem_<label>\", std::vector<float>{Cab, Cca, Cw, Cdm, "
930 "Cs, Cant, Cp, Cbc, N, V2Z, fqe}) and setPrimitiveData(UUIDs, \"fluspect_spectrum\", "
931 "\"fluspect_biochem_<label>\")."
937 addRadiationCamera(camera_label, emission_band_labels, position, lookat, camera_properties, antialiasing_samples);
940 sif_cameras.insert(camera_label);
947 addSIFCamera(camera_label, emission_band_labels, position, position + dir, camera_properties, antialiasing_samples);
951 return sif_cameras.find(camera_label) != sif_cameras.end();
954float RadiationModel::calculateFluorescenceYield(
float J_over_Jmax,
float T_leaf_K) {
959 constexpr float kF = 0.05f;
961 const float T_C = T_leaf_K - 273.15f;
962 const float kD = std::max(0.03f * T_C + 0.0773f, 0.87f);
968 }
else if (x < 0.6f) {
969 kN = 2.0f * (x - 0.2f) / 0.4f;
971 kN = 2.0f + 4.0f * (x - 0.6f) / 0.4f;
974 constexpr float Phi_P_max = 0.85f;
976 const float Phi_P =
helios::clamp(J_over_Jmax * Phi_P_max, 0.f, 0.84f);
977 const float kP = (kF + kD + kN) * Phi_P / (1.f - Phi_P);
979 return kF / (kF + kD + kP + kN);
994 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): Invalid collimated source direction. Direction vector should not have length of zero.");
997 uint Nsources = radiation_sources.size() + 1;
998 if (Nsources > 256) {
999 helios_runtime_error(
"ERROR (RadiationModel::addCollimatedRadiationSource): A maximum of 256 radiation sources are allowed.");
1002 bool warn_multiple_suns =
false;
1003 for (
auto &source: radiation_sources) {
1004 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1005 warn_multiple_suns =
true;
1008 if (warn_multiple_suns) {
1009 std::cerr <<
"WARNING (RadiationModel::addCollimatedRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
1015 for (
const auto &band: radiation_bands) {
1019 radiation_sources.emplace_back(collimated_source);
1021 radiativepropertiesneedupdate =
true;
1023 return Nsources - 1;
1029 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): Spherical radiation source radius must be positive.");
1032 uint Nsources = radiation_sources.size() + 1;
1033 if (Nsources > 256) {
1034 helios_runtime_error(
"ERROR (RadiationModel::addSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
1040 for (
const auto &band: radiation_bands) {
1044 radiation_sources.emplace_back(sphere_source);
1046 uint sourceID = Nsources - 1;
1048 if (islightvisualizationenabled) {
1049 buildLightModelGeometry(sourceID);
1052 radiativepropertiesneedupdate =
true;
1067 uint Nsources = radiation_sources.size() + 1;
1068 if (Nsources > 256) {
1069 helios_runtime_error(
"ERROR (RadiationModel::addSunSphereRadiationSource): A maximum of 256 radiation sources are allowed.");
1072 bool warn_multiple_suns =
false;
1073 for (
auto &source: radiation_sources) {
1074 if (source.source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1075 warn_multiple_suns =
true;
1078 if (warn_multiple_suns) {
1079 std::cerr <<
"WARNING (RadiationModel::addSunSphereRadiationSource): Multiple sun sources have been added to the radiation model. This may lead to unintended behavior." << std::endl;
1082 RadiationSource sphere_source(150e9 * sun_direction / sun_direction.
magnitude(), 150e9, 2.f * 695.5e6, sigma * powf(5700, 4) / 1288.437f);
1085 for (
const auto &band: radiation_bands) {
1089 radiation_sources.emplace_back(sphere_source);
1091 radiativepropertiesneedupdate =
true;
1093 return Nsources - 1;
1098 if (size.
x <= 0 || size.
y <= 0) {
1099 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): Radiation source size must be positive.");
1102 uint Nsources = radiation_sources.size() + 1;
1103 if (Nsources > 256) {
1104 helios_runtime_error(
"ERROR (RadiationModel::addRectangleRadiationSource): A maximum of 256 radiation sources are allowed.");
1110 for (
const auto &band: radiation_bands) {
1114 radiation_sources.emplace_back(rectangle_source);
1116 uint sourceID = Nsources - 1;
1118 if (islightvisualizationenabled) {
1119 buildLightModelGeometry(sourceID);
1122 radiativepropertiesneedupdate =
true;
1130 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): Disk radiation source radius must be positive.");
1133 uint Nsources = radiation_sources.size() + 1;
1134 if (Nsources > 256) {
1135 helios_runtime_error(
"ERROR (RadiationModel::addDiskRadiationSource): A maximum of 256 radiation sources are allowed.");
1141 for (
const auto &band: radiation_bands) {
1145 radiation_sources.emplace_back(disk_source);
1147 uint sourceID = Nsources - 1;
1149 if (islightvisualizationenabled) {
1150 buildLightModelGeometry(sourceID);
1153 radiativepropertiesneedupdate =
true;
1160 if (sourceID >= radiation_sources.size()) {
1161 helios_runtime_error(
"ERROR (RadiationModel::deleteRadiationSource): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1164 radiation_sources.erase(radiation_sources.begin() + sourceID);
1166 radiativepropertiesneedupdate =
true;
1171 if (source_ID >= radiation_sources.size()) {
1172 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1173 }
else if (source_integral < 0) {
1174 helios_runtime_error(
"ERROR (RadiationModel::setSourceIntegral): Source integral must be non-negative.");
1177 float current_integral =
integrateSpectrum(radiation_sources.at(source_ID).source_spectrum);
1179 for (
vec2 &wavelength: radiation_sources.at(source_ID).source_spectrum) {
1180 wavelength.y *= source_integral / current_integral;
1186 if (source_ID >= radiation_sources.size()) {
1187 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1188 }
else if (source_integral < 0) {
1189 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrumIntegral): Source integral must be non-negative.");
1190 }
else if (radiation_sources.at(source_ID).source_spectrum.empty()) {
1191 std::cout <<
"WARNING (RadiationModel::setSourceSpectrumIntegral): Spectral distribution has not been set for radiation source. Cannot set its integral." << std::endl;
1200 wavelength.y *= source_integral / old_integral;
1207 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Cannot add set source flux for band '" + label +
"' because it is not a valid band.");
1208 }
else if (source_ID >= radiation_sources.size()) {
1209 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1210 }
else if (flux < 0) {
1211 helios_runtime_error(
"ERROR (RadiationModel::setSourceFlux): Source flux must be non-negative.");
1214 radiation_sources.at(source_ID).source_fluxes[label] = flux * radiation_sources.at(source_ID).source_flux_scaling_factor;
1218 for (
auto ID: source_ID) {
1226 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get source flux for band '" + label +
"' because it is not a valid band.");
1227 }
else if (source_ID >= radiation_sources.size()) {
1228 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources have been created.");
1229 }
else if (radiation_sources.at(source_ID).source_fluxes.find(label) == radiation_sources.at(source_ID).source_fluxes.end()) {
1230 helios_runtime_error(
"ERROR (RadiationModel::getSourceFlux): Cannot get flux for source #" + std::to_string(source_ID) +
" because radiative band '" + label +
"' does not exist.");
1236 vec2 wavebounds = radiation_bands.at(label).wavebandBounds;
1250 if (source_ID >= radiation_sources.size()) {
1251 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
1255 for (
auto s = 0; s < spectrum.size(); s++) {
1257 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
1258 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Source spectral data validation failed. Wavelengths must increase monotonically.");
1261 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
1262 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.");
1265 if (spectrum.at(s).y < 0) {
1266 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.");
1270 radiation_sources.at(source_ID).source_spectrum = spectrum;
1272 radiativepropertiesneedupdate =
true;
1276 for (
auto ID: source_ID) {
1283 if (source_ID >= radiation_sources.size()) {
1284 helios_runtime_error(
"ERROR (RadiationModel::setSourceSpectrum): Cannot add radiation spectra for this source because it is not a valid radiation source ID.\n");
1287 std::vector<vec2> spectrum = loadSpectralData(spectrum_label);
1289 radiation_sources.at(source_ID).source_spectrum = spectrum;
1290 radiation_sources.at(source_ID).source_spectrum_label = spectrum_label;
1291 radiation_sources.at(source_ID).source_spectrum_version = context->
getGlobalDataVersion(spectrum_label.c_str());
1293 radiativepropertiesneedupdate =
true;
1297 for (
auto ID: source_ID) {
1304 std::vector<vec2> spectrum;
1307 if (spectrum_label ==
"ASTMG173") {
1308 spectrum = loadSpectralData(
"solar_spectrum_diffuse_ASTMG173");
1309 global_diffuse_spectrum_label =
"solar_spectrum_diffuse_ASTMG173";
1311 spectrum = loadSpectralData(spectrum_label);
1312 global_diffuse_spectrum_label = spectrum_label;
1316 global_diffuse_spectrum = spectrum;
1317 global_diffuse_spectrum_version = context->
getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
1320 for (
auto &band_pair: radiation_bands) {
1321 band_pair.second.diffuse_spectrum = spectrum;
1324 radiativepropertiesneedupdate =
true;
1330 helios_runtime_error(
"ERROR (RadiationModel::getDiffuseFlux): Cannot get diffuse flux for band '" + band_label +
"' because it is not a valid band.");
1349 if (!spectrum.empty()) {
1352 wavebounds =
make_vec2(spectrum.front().x, spectrum.back().x);
1361 islightvisualizationenabled =
true;
1364 for (
int s = 0; s < radiation_sources.size(); s++) {
1365 buildLightModelGeometry(s);
1370 islightvisualizationenabled =
false;
1371 for (
auto &UUIDs: source_model_UUIDs) {
1377 iscameravisualizationenabled =
true;
1380 for (
auto &cam: cameras) {
1381 buildCameraModelGeometry(cam.first);
1386 iscameravisualizationenabled =
false;
1387 for (
auto &UUIDs: camera_model_UUIDs) {
1392void RadiationModel::buildLightModelGeometry(
uint sourceID) {
1394 assert(sourceID < radiation_sources.size());
1397 if (source.
source_type == RADIATION_SOURCE_TYPE_SPHERE) {
1398 source_model_UUIDs[sourceID] = context->
loadOBJ(
"SphereLightSource.obj",
true);
1399 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1400 source_model_UUIDs[sourceID] = context->
loadOBJ(
"SphereLightSource.obj",
true);
1401 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_DISK) {
1402 source_model_UUIDs[sourceID] = context->
loadOBJ(
"DiskLightSource.obj",
true);
1404 std::vector<uint> UUIDs_arrow = context->
loadOBJ(
"Arrow.obj",
true);
1405 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
1407 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_RECTANGLE) {
1408 source_model_UUIDs[sourceID] = context->
loadOBJ(
"RectangularLightSource.obj",
true);
1410 std::vector<uint> UUIDs_arrow = context->
loadOBJ(
"Arrow.obj",
true);
1411 source_model_UUIDs.at(sourceID).insert(source_model_UUIDs.at(sourceID).begin(), UUIDs_arrow.begin(), UUIDs_arrow.end());
1417 if (source.
source_type == RADIATION_SOURCE_TYPE_SPHERE) {
1420 }
else if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
1427 context->
translatePrimitive(source_model_UUIDs.at(sourceID), center + sunvec * radius);
1438void RadiationModel::buildCameraModelGeometry(
const std::string &cameralabel) {
1440 assert(cameras.find(cameralabel) != cameras.end());
1444 vec3 viewvec = camera.lookat - camera.position;
1447 camera_model_UUIDs[cameralabel] = context->
loadOBJ(
"Camera.obj",
true);
1457void RadiationModel::updateLightModelPosition(
uint sourceID,
const helios::vec3 &delta_position) {
1459 assert(sourceID < radiation_sources.size());
1463 if (source.
source_type != RADIATION_SOURCE_TYPE_SPHERE && source.
source_type != RADIATION_SOURCE_TYPE_DISK && source.
source_type != RADIATION_SOURCE_TYPE_RECTANGLE) {
1470void RadiationModel::updateCameraModelPosition(
const std::string &cameralabel) {
1472 assert(cameras.find(cameralabel) != cameras.end());
1475 buildCameraModelGeometry(cameralabel);
1480 if (source_ID >= radiation_sources.size()) {
1481 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1482 }
else if (object_spectrum.size() < 2) {
1483 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1484 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1485 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1488 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1491 int iend = (int) object_spectrum.size() - 1;
1492 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
1494 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
1497 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
1505 for (
auto i = istart; i < iend; i++) {
1507 float x0 = object_spectrum.at(i).x;
1508 float Esource0 =
interp1(source_spectrum, object_spectrum.at(i).x);
1509 float Eobject0 = object_spectrum.at(i).y;
1511 float x1 = object_spectrum.at(i + 1).x;
1512 float Eobject1 = object_spectrum.at(i + 1).y;
1513 float Esource1 =
interp1(source_spectrum, object_spectrum.at(i + 1).x);
1515 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
1516 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1524 if (object_spectrum.size() < 2) {
1525 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1526 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1527 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1531 int iend = (int) object_spectrum.size() - 1;
1532 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
1534 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
1537 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
1544 for (
auto i = istart; i < iend; i++) {
1545 float E0 = object_spectrum.at(i).y;
1546 float x0 = object_spectrum.at(i).x;
1547 float E1 = object_spectrum.at(i + 1).y;
1548 float x1 = object_spectrum.at(i + 1).x;
1549 E += (E0 + E1) * (x1 - x0) * 0.5f;
1556 float wavelength1 = object_spectrum.at(0).x;
1557 float wavelength2 = object_spectrum.at(object_spectrum.size() - 1).x;
1564 if (source_ID >= radiation_sources.size()) {
1565 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1566 }
else if (object_spectrum.size() < 2) {
1567 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1570 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
1574 for (
auto i = 1; i < object_spectrum.size(); i++) {
1576 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
1579 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
1582 float x1 = object_spectrum.at(i).x;
1583 float Eobject1 = object_spectrum.at(i).y;
1584 float Esource1 =
interp1(source_spectrum, x1);
1585 float Ecamera1 =
interp1(camera_spectrum, x1);
1588 float x0 = object_spectrum.at(i - 1).x;
1589 float Eobject0 = object_spectrum.at(i - 1).y;
1590 float Esource0 =
interp1(source_spectrum, x0);
1591 float Ecamera0 =
interp1(camera_spectrum, x0);
1593 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
1594 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
1603 if (object_spectrum.size() < 2) {
1604 helios_runtime_error(
"ERROR (RadiationModel::integrateSpectrum): Radiation spectrum must have at least 2 wavelengths.");
1609 for (
auto i = 1; i < object_spectrum.size(); i++) {
1611 if (object_spectrum.at(i).x <= camera_spectrum.front().x) {
1614 if (object_spectrum.at(i).x > camera_spectrum.back().x) {
1618 float x1 = object_spectrum.at(i).x;
1619 float Eobject1 = object_spectrum.at(i).y;
1620 float Ecamera1 =
interp1(camera_spectrum, x1);
1623 float x0 = object_spectrum.at(i - 1).x;
1624 float Eobject0 = object_spectrum.at(i - 1).y;
1625 float Ecamera0 =
interp1(camera_spectrum, x0);
1627 E += 0.5f * ((Eobject1 * Ecamera1) + (Eobject0 * Ecamera0)) * (x1 - x0);
1628 Etot += 0.5f * (Ecamera1 + Ecamera0) * (x1 - x0);
1636 if (source_ID >= radiation_sources.size()) {
1637 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Radiation spectrum was not set for source ID. Make sure to set its spectrum using setSourceSpectrum() function.");
1638 }
else if (wavelength1 > wavelength2 || wavelength1 == wavelength2) {
1639 helios_runtime_error(
"ERROR (RadiationModel::integrateSourceSpectrum): Lower wavelength bound must be less than the upper wavelength bound.");
1642 return integrateSpectrum(radiation_sources.at(source_ID).source_spectrum, wavelength1, wavelength2);
1647 std::vector<helios::vec2> spectrum = loadSpectralData(existing_global_data_label);
1650 s.y *= scale_factor;
1653 context->
setGlobalData(new_global_data_label.c_str(), spectrum);
1658 std::vector<vec2> spectrum = loadSpectralData(global_data_label);
1660 for (
vec2 &s: spectrum) {
1661 s.y *= scale_factor;
1664 context->
setGlobalData(global_data_label.c_str(), spectrum);
1669 scaleSpectrum(existing_global_data_label, new_global_data_label, context->
randu(minimum_scale_factor, maximum_scale_factor));
1673void RadiationModel::blendSpectra(
const std::string &new_spectrum_label,
const std::vector<std::string> &spectrum_labels,
const std::vector<float> &weights)
const {
1675 if (spectrum_labels.size() != weights.size()) {
1676 helios_runtime_error(
"ERROR (RadiationModel::blendSpectra): number of spectra and weights must be equal");
1677 }
else if (fabsf(
sum(weights) - 1.f) > 1e-5f) {
1681 std::vector<vec2> new_spectrum;
1682 uint spectrum_size = 0;
1684 std::vector<std::vector<vec2>> spectrum(spectrum_labels.size());
1686 uint lambda_start = 0;
1687 uint lambda_end = 0;
1688 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1690 spectrum.at(i) = loadSpectralData(spectrum_labels.at(i));
1693 lambda_start = spectrum.at(i).front().x;
1694 lambda_end = spectrum.at(i).back().x;
1696 if (spectrum.at(i).front().x > lambda_start) {
1697 lambda_start = spectrum.at(i).front().x;
1699 if (spectrum.at(i).back().x < lambda_end) {
1700 lambda_end = spectrum.at(i).back().x;
1705 spectrum_size = lambda_end - lambda_start + 1;
1706 new_spectrum.resize(spectrum_size);
1707 for (
uint j = 0; j < spectrum_size; j++) {
1708 new_spectrum.at(j) =
make_vec2(lambda_start + j, 0);
1712 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1713 for (
uint j = 0; j < spectrum.at(i).size(); j++) {
1715 if (spectrum.at(i).at(j).x >= lambda_start) {
1717 spectrum.at(i).erase(spectrum.at(i).begin(), spectrum.at(i).begin() + j);
1725 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1726 for (
int j = spectrum.at(i).size() - 1; j <= 0; j--) {
1728 if (spectrum.at(i).at(j).x <= lambda_end) {
1729 if (j < spectrum.at(i).size() - 1) {
1730 spectrum.at(i).erase(spectrum.at(i).begin() + j + 1, spectrum.at(i).end());
1737 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1738 for (
uint j = 0; j < spectrum_size; j++) {
1739 assert(new_spectrum.at(j).x == spectrum.at(i).at(j).x);
1740 new_spectrum.at(j).y += weights.at(i) * spectrum.at(i).at(j).y;
1744 context->
setGlobalData(new_spectrum_label.c_str(), new_spectrum);
1749 std::vector<float> weights;
1750 weights.resize(spectrum_labels.size());
1751 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1752 weights.at(i) = context->
randu();
1754 float sum_weights =
sum(weights);
1755 for (
uint i = 0; i < spectrum_labels.size(); i++) {
1756 weights.at(i) /= sum_weights;
1759 blendSpectra(new_spectrum_label, spectrum_labels, weights);
1763 const std::string &primitive_data_radprop_label) {
1766 if (spectra.size() != values.size()) {
1767 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' vector (size=" + std::to_string(spectra.size()) +
") and 'values' vector (size=" + std::to_string(values.size()) +
1768 ") must have the same length.");
1772 if (spectra.empty()) {
1773 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'spectra' and 'values' vectors cannot be empty.");
1777 if (primitive_UUIDs.empty()) {
1778 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_UUIDs' vector cannot be empty.");
1782 if (primitive_data_query_label.empty()) {
1783 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_query_label' cannot be empty.");
1786 if (primitive_data_radprop_label.empty()) {
1787 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromPrimitiveData): The 'primitive_data_radprop_label' cannot be empty.");
1791 SpectrumInterpolationConfig *existing_config =
nullptr;
1792 for (
auto &config: spectrum_interpolation_configs) {
1793 if (config.query_data_label == primitive_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1794 existing_config = &config;
1799 if (existing_config !=
nullptr) {
1801 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1803 if (spectra_match) {
1805 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1808 existing_config->spectra_labels = spectra;
1809 existing_config->mapping_values = values;
1810 existing_config->primitive_UUIDs.clear();
1811 existing_config->primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1815 SpectrumInterpolationConfig config;
1816 config.primitive_UUIDs.insert(primitive_UUIDs.begin(), primitive_UUIDs.end());
1817 config.spectra_labels = spectra;
1818 config.mapping_values = values;
1819 config.query_data_label = primitive_data_query_label;
1820 config.target_data_label = primitive_data_radprop_label;
1822 spectrum_interpolation_configs.push_back(config);
1827 const std::string &primitive_data_radprop_label) {
1830 if (spectra.size() != values.size()) {
1831 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.");
1835 if (spectra.empty()) {
1836 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'spectra' and 'values' vectors cannot be empty.");
1840 if (object_IDs.empty()) {
1841 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_IDs' vector cannot be empty.");
1845 if (object_data_query_label.empty()) {
1846 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'object_data_query_label' cannot be empty.");
1849 if (primitive_data_radprop_label.empty()) {
1850 helios_runtime_error(
"ERROR (RadiationModel::interpolateSpectrumFromObjectData): The 'primitive_data_radprop_label' cannot be empty.");
1854 SpectrumInterpolationConfig *existing_config =
nullptr;
1855 for (
auto &config: spectrum_interpolation_configs) {
1856 if (config.query_data_label == object_data_query_label && config.target_data_label == primitive_data_radprop_label) {
1857 existing_config = &config;
1862 if (existing_config !=
nullptr) {
1864 bool spectra_match = (existing_config->spectra_labels == spectra && existing_config->mapping_values == values);
1866 if (spectra_match) {
1868 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1871 existing_config->spectra_labels = spectra;
1872 existing_config->mapping_values = values;
1873 existing_config->object_IDs.clear();
1874 existing_config->object_IDs.insert(object_IDs.begin(), object_IDs.end());
1878 SpectrumInterpolationConfig config;
1879 config.object_IDs.insert(object_IDs.begin(), object_IDs.end());
1880 config.spectra_labels = spectra;
1881 config.mapping_values = values;
1882 config.query_data_label = object_data_query_label;
1883 config.target_data_label = primitive_data_radprop_label;
1885 spectrum_interpolation_configs.push_back(config);
1891 if (source_ID >= radiation_sources.size()) {
1892 helios_runtime_error(
"ERROR (RadiationModel::setSourcePosition): Source ID out of bounds. Only " + std::to_string(radiation_sources.size() - 1) +
" radiation sources.");
1895 vec3 old_position = radiation_sources.at(source_ID).source_position;
1897 if (radiation_sources.at(source_ID).source_type == RADIATION_SOURCE_TYPE_COLLIMATED) {
1898 radiation_sources.at(source_ID).source_position = position / position.
magnitude();
1900 radiation_sources.at(source_ID).source_position = position * radiation_sources.at(source_ID).source_position_scaling_factor;
1903 if (islightvisualizationenabled) {
1904 updateLightModelPosition(source_ID, radiation_sources.at(source_ID).source_position - old_position);
1913 if (source_ID >= radiation_sources.size()) {
1916 return radiation_sources.at(source_ID).source_position;
1922 helios_runtime_error(
"ERROR (RadiationModel::setScatteringDepth): Cannot set scattering depth for band '" + label +
"' because it is not a valid band.");
1924 radiation_bands.at(label).scatteringDepth = depth;
1930 helios_runtime_error(
"ERROR (setMinScatterEnergy): Cannot set minimum scattering energy for band '" + label +
"' because it is not a valid band.");
1932 radiation_bands.at(label).minScatterEnergy = energy;
1937 if (boundary ==
"x") {
1939 periodic_flag.
x = 1;
1941 }
else if (boundary ==
"y") {
1943 periodic_flag.
y = 1;
1945 }
else if (boundary ==
"xy") {
1947 periodic_flag.
x = 1;
1948 periodic_flag.
y = 1;
1952 std::cout <<
"WARNING (RadiationModel::enforcePeriodicBoundary()): unknown boundary of '" << boundary <<
"'. Possible choices are x, y, or xy." << std::endl;
1964 std::cout <<
"Updating geometry in radiation transport model..." << std::flush;
1968 buildGeometryData(UUIDs);
1975 backend->updateGeometry(geometry_data);
1976 backend->buildAccelerationStructure();
1978 radiativepropertiesneedupdate =
true;
1979 isgeometryinitialized =
true;
1982 std::cout <<
"done." << std::endl;
1986void RadiationModel::updateRadiativeProperties() {
1999 std::cout <<
"Updating radiative properties..." << std::flush;
2002 uint Nbands = radiation_bands.size();
2003 uint Nsources = radiation_sources.size();
2004 uint Ncameras = cameras.size();
2005 size_t Nobjects = primitiveID.size();
2006 size_t Nprimitives = context_UUIDs.size();
2008 scattering_iterations_needed.clear();
2009 for (
auto &band: radiation_bands) {
2010 scattering_iterations_needed[band.first] =
false;
2016 std::vector<std::string> band_labels;
2017 for (
auto &band: radiation_bands) {
2018 band_labels.push_back(band.first);
2027 size_t mat_size = (size_t)Nsources * Nprimitives * Nbands;
2028 material_data.
reflectivity.assign(mat_size, rho_default);
2032 size_t cam_size = (size_t)Nsources * Nprimitives * Nbands * Ncameras;
2044 std::vector<std::vector<std::vector<helios::vec2>>> camera_response_unique;
2045 camera_response_unique.resize(Ncameras);
2048 for (
const auto &camera: cameras) {
2050 camera_response_unique.at(cam).resize(Nbands);
2052 for (
uint b = 0; b < Nbands; b++) {
2054 if (camera.second.band_spectral_response.find(band_labels.at(b)) == camera.second.band_spectral_response.end()) {
2058 std::string camera_response = camera.second.band_spectral_response.at(band_labels.at(b));
2060 if (!camera_response.empty()) {
2063 if (camera_response !=
"uniform") {
2064 warnings.
addWarning(
"missing_camera_response",
"Camera spectral response \"" + camera_response +
"\" does not exist. Assuming a uniform spectral response.");
2066 }
else if (context->
getGlobalDataType(camera_response.c_str()) == helios::HELIOS_TYPE_VEC2) {
2068 std::vector<helios::vec2> data = loadSpectralData(camera_response.c_str());
2070 camera_response_unique.at(cam).at(b) = data;
2072 }
else if (context->
getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(camera_response.c_str()) != helios::HELIOS_TYPE_STRING) {
2073 camera_response.clear();
2074 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;
2083 std::unordered_map<std::string, float> spectral_integration_cache;
2087 std::unordered_map<std::string, float> temp_spectral_cache;
2091 auto createCacheKey = [](
const std::string &spectrum_label,
uint source_id,
uint band_id,
uint camera_id,
const std::string &type) -> std::string {
2092 return spectrum_label +
"_" + std::to_string(source_id) +
"_" + std::to_string(band_id) +
"_" + std::to_string(camera_id) +
"_" + type;
2096 auto getCachedValue = [&](
const std::string &cache_key,
bool &found) ->
float {
2097 float result = 0.0f;
2105 auto cache_it = spectral_integration_cache.find(cache_key);
2106 if (cache_it != spectral_integration_cache.end()) {
2108 result = cache_it->second;
2117 auto setCachedValue = [&](
const std::string &cache_key,
float value) {
2122 spectral_integration_cache[cache_key] = value;
2129 auto cachedInterp1 = [&](
const std::vector<helios::vec2> &spectrum,
float wavelength,
const std::string &spectrum_id) ->
float {
2131 std::string cache_key =
"interp_" + spectrum_id +
"_" + std::to_string(wavelength);
2134 float cached_result = getCachedValue(cache_key, found);
2136 return cached_result;
2140 float result =
interp1(spectrum, wavelength);
2141 setCachedValue(cache_key, result);
2146 auto cachedIntegrateSpectrumWithSource = [&](
uint source_ID,
const std::vector<helios::vec2> &object_spectrum,
float wavelength1,
float wavelength2,
const std::string &object_spectrum_id) ->
float {
2147 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2 || wavelength1 >= wavelength2) {
2151 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
2152 std::string source_id =
"source_" + std::to_string(source_ID);
2155 int iend = (int) object_spectrum.size() - 1;
2156 for (
auto i = 0; i < object_spectrum.size() - 1; i++) {
2157 if (object_spectrum.at(i).x <= wavelength1 && object_spectrum.at(i + 1).x > wavelength1) {
2160 if (object_spectrum.at(i).x <= wavelength2 && object_spectrum.at(i + 1).x > wavelength2) {
2168 for (
auto i = istart; i < iend; i++) {
2169 float x0 = object_spectrum.at(i).x;
2170 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
2171 float Eobject0 = object_spectrum.at(i).y;
2173 float x1 = object_spectrum.at(i + 1).x;
2174 float Eobject1 = object_spectrum.at(i + 1).y;
2175 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
2177 E += 0.5f * (Eobject0 * Esource0 + Eobject1 * Esource1) * (x1 - x0);
2178 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
2181 return (Etot != 0.0f) ? E / Etot : 0.0f;
2185 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,
2186 const std::string &object_spectrum_id) ->
float {
2187 if (source_ID >= radiation_sources.size() || object_spectrum.size() < 2) {
2191 std::vector<helios::vec2> source_spectrum = radiation_sources.at(source_ID).source_spectrum;
2192 std::string source_id =
"source_" + std::to_string(source_ID);
2193 std::string camera_id =
"camera_" + std::to_string(camera_index) +
"_band_" + std::to_string(band_index);
2197 for (
auto i = 1; i < object_spectrum.size(); i++) {
2198 if (object_spectrum.at(i).x <= source_spectrum.front().x || object_spectrum.at(i).x <= camera_spectrum.front().x) {
2201 if (object_spectrum.at(i).x > source_spectrum.back().x || object_spectrum.at(i).x > camera_spectrum.back().x) {
2205 float x1 = object_spectrum.at(i).x;
2206 float Eobject1 = object_spectrum.at(i).y;
2207 float Esource1 = cachedInterp1(source_spectrum, x1, source_id);
2208 float Ecamera1 = cachedInterp1(camera_spectrum, x1, camera_id);
2210 float x0 = object_spectrum.at(i - 1).x;
2211 float Eobject0 = object_spectrum.at(i - 1).y;
2212 float Esource0 = cachedInterp1(source_spectrum, x0, source_id);
2213 float Ecamera0 = cachedInterp1(camera_spectrum, x0, camera_id);
2215 E += 0.5f * ((Eobject1 * Esource1 * Ecamera1) + (Eobject0 * Ecamera0 * Esource0)) * (x1 - x0);
2216 Etot += 0.5f * (Esource1 + Esource0) * (x1 - x0);
2219 return (Etot != 0.0f) ? E / Etot : 0.0f;
2223 for (
const auto &config: spectrum_interpolation_configs) {
2225 for (
const auto &spectrum_label: config.spectra_labels) {
2227 helios_runtime_error(
"ERROR (RadiationModel::updateRadiativeProperties): Spectral interpolation config references global data '" + spectrum_label +
"' which does not exist.");
2229 if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2) {
2230 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>).");
2234 for (
uint uuid: config.primitive_UUIDs) {
2243 if (context->
getPrimitiveDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
2244 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.");
2249 context->
getPrimitiveData(uuid, config.query_data_label.c_str(), query_value);
2252 size_t nearest_idx = 0;
2253 float min_distance = std::abs(query_value - config.mapping_values[0]);
2254 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
2255 float distance = std::abs(query_value - config.mapping_values[i]);
2256 if (distance < min_distance) {
2257 min_distance = distance;
2263 context->
setPrimitiveData(uuid, config.target_data_label.c_str(), config.spectra_labels[nearest_idx]);
2268 for (
uint objID: config.object_IDs) {
2277 if (context->
getObjectDataType(config.query_data_label.c_str()) != helios::HELIOS_TYPE_FLOAT) {
2278 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.");
2283 context->
getObjectData(objID, config.query_data_label.c_str(), query_value);
2286 size_t nearest_idx = 0;
2287 float min_distance = std::abs(query_value - config.mapping_values.at(0));
2288 for (
size_t i = 1; i < config.mapping_values.size(); i++) {
2289 float distance = std::abs(query_value - config.mapping_values.at(i));
2290 if (distance < min_distance) {
2291 min_distance = distance;
2298 context->
setPrimitiveData(prim_uuids, config.target_data_label.c_str(), config.spectra_labels.at(nearest_idx));
2306 std::map<std::string, std::vector<helios::vec2>> surface_spectra_rho;
2307 std::map<std::string, std::vector<helios::vec2>> surface_spectra_tau;
2308 for (
size_t u = 0; u < Nprimitives; u++) {
2310 uint UUID = context_UUIDs.at(u);
2314 std::string spectrum_label;
2318 if (surface_spectra_rho.find(spectrum_label) == surface_spectra_rho.end()) {
2320 if (!spectrum_label.empty()) {
2321 warnings.
addWarning(
"missing_reflectivity_spectrum",
"Primitive spectral reflectivity \"" + spectrum_label +
"\" does not exist. Using default reflectivity of 0.");
2323 std::vector<helios::vec2> data;
2324 surface_spectra_rho.emplace(spectrum_label, data);
2325 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
2327 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
2328 surface_spectra_rho.emplace(spectrum_label, data);
2330 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
2331 spectrum_label.clear();
2332 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;
2340 std::string spectrum_label;
2341 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
2344 if (surface_spectra_tau.find(spectrum_label) == surface_spectra_tau.end()) {
2346 if (!spectrum_label.empty()) {
2347 warnings.
addWarning(
"missing_transmissivity_spectrum",
"Primitive spectral transmissivity \"" + spectrum_label +
"\" does not exist. Using default transmissivity of 0.");
2349 std::vector<helios::vec2> data;
2350 surface_spectra_tau.emplace(spectrum_label, data);
2351 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) == HELIOS_TYPE_VEC2) {
2353 std::vector<helios::vec2> data = loadSpectralData(spectrum_label.c_str());
2354 surface_spectra_tau.emplace(spectrum_label, data);
2356 }
else if (context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_VEC2 && context->
getGlobalDataType(spectrum_label.c_str()) != helios::HELIOS_TYPE_STRING) {
2357 spectrum_label.clear();
2358 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;
2366 std::map<std::string, std::vector<std::vector<float>>> rho_unique;
2367 std::map<std::string, std::vector<std::vector<float>>> tau_unique;
2369 std::map<std::string, std::vector<std::vector<std::vector<float>>>> rho_cam_unique;
2370 std::map<std::string, std::vector<std::vector<std::vector<float>>>> tau_cam_unique;
2372 std::vector<std::vector<float>> empty;
2373 empty.resize(Nbands);
2374 for (
uint b = 0; b < Nbands; b++) {
2375 empty.at(b).resize(Nsources, 0);
2377 std::vector<std::vector<std::vector<float>>> empty_cam;
2379 empty_cam.resize(Nbands);
2380 for (
uint b = 0; b < Nbands; b++) {
2381 empty_cam.at(b).resize(Nsources);
2382 for (
uint s = 0; s < Nsources; s++) {
2383 empty_cam.at(b).at(s).resize(Ncameras, 0);
2389 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_rho_vector(surface_spectra_rho.begin(), surface_spectra_rho.end());
2392 for (
const auto &spectrum: spectra_rho_vector) {
2393 rho_unique[spectrum.first] = empty;
2395 rho_cam_unique[spectrum.first] = empty_cam;
2401#pragma omp parallel for schedule(dynamic)
2403 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_rho_vector.size(); spectrum_idx++) {
2404 const auto &spectrum = spectra_rho_vector[spectrum_idx];
2406 for (
uint b = 0; b < Nbands; b++) {
2407 std::string band = band_labels.at(b);
2409 for (
uint s = 0; s < Nsources; s++) {
2412 auto band_it = radiation_bands.find(band);
2413 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
2414 if (!radiation_sources.at(s).source_spectrum.empty()) {
2415 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_source");
2417 float cached_result = getCachedValue(cache_key, found);
2419 rho_unique[spectrum.first][b][s] = cached_result;
2421 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
2422 setCachedValue(cache_key, result);
2423 rho_unique[spectrum.first][b][s] = result;
2427 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"rho_no_source");
2429 float cached_result = getCachedValue(cache_key, found);
2431 rho_unique[spectrum.first][b][s] = cached_result;
2433 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);
2434 setCachedValue(cache_key, result);
2435 rho_unique[spectrum.first][b][s] = result;
2441 rho_unique[spectrum.first][b][s] = rho_default;
2447 float rho_cam_sum_for_averaging = 0.f;
2448 for (
const auto &camera: cameras) {
2450 if (camera_response_unique.at(cam).at(b).empty()) {
2451 rho_cam_unique[spectrum.first][b][s][cam] = rho_unique[spectrum.first][b][s];
2455 if (!spectrum.second.empty()) {
2456 if (!radiation_sources.at(s).source_spectrum.empty()) {
2457 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_source");
2459 float cached_result = getCachedValue(cache_key, found);
2461 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2462 rho_cam_sum_for_averaging += cached_result;
2464 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
2465 setCachedValue(cache_key, result);
2466 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2467 rho_cam_sum_for_averaging += result;
2470 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"rho_cam_no_source");
2472 float cached_result = getCachedValue(cache_key, found);
2474 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2475 rho_cam_sum_for_averaging += cached_result;
2477 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
2478 setCachedValue(cache_key, result);
2479 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2480 rho_cam_sum_for_averaging += result;
2484 rho_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = rho_default;
2494 if (rho_unique[spectrum.first][b][s] == rho_default && rho_cam_sum_for_averaging > 0 && cam > 0) {
2495 rho_unique[spectrum.first][b][s] = rho_cam_sum_for_averaging / float(cam);
2503 std::vector<std::pair<std::string, std::vector<helios::vec2>>> spectra_tau_vector(surface_spectra_tau.begin(), surface_spectra_tau.end());
2506 for (
const auto &spectrum: spectra_tau_vector) {
2507 tau_unique[spectrum.first] = empty;
2509 tau_cam_unique[spectrum.first] = empty_cam;
2515#pragma omp parallel for schedule(dynamic)
2517 for (
int spectrum_idx = 0; spectrum_idx < (int) spectra_tau_vector.size(); spectrum_idx++) {
2518 const auto &spectrum = spectra_tau_vector[spectrum_idx];
2520 for (
uint b = 0; b < Nbands; b++) {
2521 std::string band = band_labels.at(b);
2523 for (
uint s = 0; s < Nsources; s++) {
2526 auto band_it = radiation_bands.find(band);
2527 if (band_it != radiation_bands.end() && band_it->second.wavebandBounds.x != 0 && band_it->second.wavebandBounds.y != 0 && !spectrum.second.empty()) {
2528 if (!radiation_sources.at(s).source_spectrum.empty()) {
2529 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_source");
2531 float cached_result = getCachedValue(cache_key, found);
2533 tau_unique[spectrum.first][b][s] = cached_result;
2535 float result = cachedIntegrateSpectrumWithSource(s, spectrum.second, band_it->second.wavebandBounds.x, band_it->second.wavebandBounds.y, spectrum.first);
2536 setCachedValue(cache_key, result);
2537 tau_unique[spectrum.first][b][s] = result;
2540 std::string cache_key = createCacheKey(spectrum.first, s, b, 0,
"tau_no_source");
2542 float cached_result = getCachedValue(cache_key, found);
2544 tau_unique[spectrum.first][b][s] = cached_result;
2546 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);
2547 setCachedValue(cache_key, result);
2548 tau_unique[spectrum.first][b][s] = result;
2552 tau_unique[spectrum.first][b][s] = tau_default;
2558 for (
const auto &camera: cameras) {
2560 if (camera_response_unique.at(cam).at(b).empty()) {
2562 tau_cam_unique[spectrum.first][b][s][cam] = tau_unique[spectrum.first][b][s];
2567 if (!spectrum.second.empty()) {
2568 if (!radiation_sources.at(s).source_spectrum.empty()) {
2569 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_source");
2571 float cached_result = getCachedValue(cache_key, found);
2573 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2575 float result = cachedIntegrateSpectrumWithSourceAndCamera(s, spectrum.second, camera_response_unique.at(cam).at(b), cam, b, spectrum.first);
2576 setCachedValue(cache_key, result);
2577 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2580 std::string cache_key = createCacheKey(spectrum.first, s, b, cam,
"tau_cam_no_source");
2582 float cached_result = getCachedValue(cache_key, found);
2584 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = cached_result;
2586 float result =
integrateSpectrum(spectrum.second, camera_response_unique.at(cam).at(b));
2587 setCachedValue(cache_key, result);
2588 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = result;
2592 tau_cam_unique.at(spectrum.first).at(b).at(s).at(cam) = tau_default;
2603 for (
size_t u = 0; u < Nprimitives; u++) {
2605 uint UUID = context_UUIDs.at(u);
2609 if (type == helios::PRIMITIVE_TYPE_VOXEL) {
2616 std::string spectrum_label;
2624 for (
const auto &band: band_labels) {
2627 prop =
"reflectivity_" + band;
2629 float rho_s = rho_default;
2634 for (
uint s = 0; s < Nsources; s++) {
2635 float &rho_val = material_data.
reflectivity[mat_idx(s, u, b)];
2638 if (rho_s != rho_default || spectrum_label.empty() || !context->
doesGlobalDataExist(spectrum_label.c_str()) || rho_unique.find(spectrum_label) == rho_unique.end()) {
2643 for (
uint cam = 0; cam < Ncameras; cam++) {
2650 rho_val = rho_unique.at(spectrum_label).at(b).at(s);
2653 for (
uint cam = 0; cam < Ncameras; cam++) {
2654 material_data.
reflectivity_cam[cam_idx(s, u, b, cam)] = rho_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2661 warnings.
addWarning(
"reflectivity_negative_clamped",
"Reflectivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2662 }
else if (rho_val > 1.f) {
2664 warnings.
addWarning(
"reflectivity_exceeded_clamped",
"Reflectivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2667 scattering_iterations_needed.at(band) =
true;
2669 for (
auto &odata: output_prim_data) {
2670 if (odata ==
"reflectivity") {
2671 context->
setPrimitiveData(UUID, (
"reflectivity_" + std::to_string(s) +
"_" + band).c_str(), rho_val);
2681 spectrum_label.resize(0);
2684 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
2689 for (
const auto &band: band_labels) {
2692 prop =
"transmissivity_" + band;
2694 float tau_s = tau_default;
2699 for (
uint s = 0; s < Nsources; s++) {
2703 if (tau_s != tau_default || spectrum_label.empty() || !context->
doesGlobalDataExist(spectrum_label.c_str()) || tau_unique.find(spectrum_label) == tau_unique.end()) {
2708 for (
uint cam = 0; cam < Ncameras; cam++) {
2714 tau_val = tau_unique.at(spectrum_label).at(b).at(s);
2717 for (
uint cam = 0; cam < Ncameras; cam++) {
2718 material_data.
transmissivity_cam[cam_idx(s, u, b, cam)] = tau_cam_unique.at(spectrum_label).at(b).at(s).at(cam);
2725 warnings.
addWarning(
"transmissivity_negative_clamped",
"Transmissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2726 }
else if (tau_val > 1.f) {
2728 warnings.
addWarning(
"transmissivity_exceeded_clamped",
"Transmissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2731 scattering_iterations_needed.at(band) =
true;
2733 for (
auto &odata: output_prim_data) {
2734 if (odata ==
"transmissivity") {
2735 context->
setPrimitiveData(UUID, (
"transmissivity_" + std::to_string(s) +
"_" + band).c_str(), tau_val);
2745 for (
const auto &band: band_labels) {
2747 prop =
"emissivity_" + band;
2757 warnings.
addWarning(
"emissivity_negative_clamped",
"Emissivity cannot be less than 0. Clamping to 0 for band " + band +
".");
2758 }
else if (eps > 1.f) {
2760 warnings.
addWarning(
"emissivity_exceeded_clamped",
"Emissivity cannot be greater than 1. Clamping to 1 for band " + band +
".");
2763 scattering_iterations_needed.at(band) =
true;
2768 const bool is_sif_band = sif_emission_bands.count(band) > 0;
2770 for (
uint s = 0; s < Nsources; s++) {
2771 float &rho_val = material_data.
reflectivity[mat_idx(s, u, b)];
2781 if (tau_val + rho_val > 1.f) {
2782 helios_runtime_error(
"ERROR (RadiationModel): reflectivity and transmissivity must sum to less than or equal to 1 to ensure energy conservation. Band " + band +
", Primitive #" + std::to_string(UUID) +
2783 ": tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
".");
2785 }
else if (radiation_bands.at(band).emissionFlag) {
2786 if (eps != 1.f && rho_val == 0 && tau_val == 0) {
2787 rho_val = 1.f - eps;
2788 }
else if (eps + tau_val + rho_val != 1.f && eps > 0.f) {
2789 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=" +
2790 std::to_string(eps) +
", tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
". It is also possible that you forgot to disable emission for this band.");
2791 }
else if (radiation_bands.at(band).scatteringDepth == 0 && eps != 1.f) {
2796 }
else if (tau_val + rho_val > 1.f) {
2797 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) +
2798 ", tau=" + std::to_string(tau_val) +
", rho=" + std::to_string(rho_val) +
". It is also possible that you forgot to disable emission for this band.");
2810 bool specular_exponent_specified =
false;
2811 bool specular_scale_specified =
false;
2813 for (
size_t u = 0; u < Nprimitives; u++) {
2814 uint UUID = context_UUIDs.at(u);
2819 specular_exponent_specified =
true;
2826 specular_scale_specified =
true;
2832 if (specular_exponent_specified) {
2833 if (specular_scale_specified) {
2834 specular_reflection_mode = 2;
2836 specular_reflection_mode = 1;
2839 specular_reflection_mode = 0;
2842 backend->updateMaterials(material_data);
2844 radiativepropertiesneedupdate =
false;
2847 std::cout <<
"done\n";
2851 warnings.
report(std::cerr);
2854std::vector<float> RadiationModel::updateAtmosphericSkyModel(
const std::vector<std::string> &band_labels,
const RadiationCamera &camera) {
2859 size_t Nbands_launch = band_labels.size();
2860 std::vector<float> sky_base_radiances(Nbands_launch, 0.0f);
2864 bool has_atmospheric_data =
2867 if (!has_atmospheric_data) {
2869 return sky_base_radiances;
2874 float pressure_Pa = 101325.f;
2875 float temperature_K = 300.f;
2876 float humidity_rel = 0.5f;
2877 float turbidity = 0.02f;
2880 context->
getGlobalData(
"atmosphere_pressure_Pa", pressure_Pa);
2883 context->
getGlobalData(
"atmosphere_temperature_K", temperature_K);
2886 context->
getGlobalData(
"atmosphere_humidity_rel", humidity_rel);
2893 int prague_valid = 0;
2900 if (!radiation_sources.empty()) {
2901 sun_dir = radiation_sources[0].source_position;
2902 sun_dir.normalize();
2906 std::vector<helios::vec4> sky_params(Nbands_launch);
2909 bool use_prague_fallback = (prague_valid != 1);
2910 if (use_prague_fallback) {
2912 std::cerr <<
"WARNING (RadiationModel::updateAtmosphericSkyModel): "
2913 <<
"Prague sky model data not available in Context. "
2914 <<
"Using simple Rayleigh sky fallback. "
2915 <<
"Call SolarPosition::updatePragueSkyModel() for accurate sky radiance." << std::endl;
2919 std::vector<float> wavelengths;
2920 std::vector<float> L_zenith_spectrum;
2921 std::vector<float> circ_str_spectrum;
2922 std::vector<float> circ_width_spectrum;
2923 std::vector<float> horiz_bright_spectrum;
2924 std::vector<float> norm_spectrum;
2926 if (use_prague_fallback) {
2929 const int n_wavelengths = 40;
2930 wavelengths.resize(n_wavelengths);
2931 L_zenith_spectrum.resize(n_wavelengths);
2932 circ_str_spectrum.resize(n_wavelengths);
2933 circ_width_spectrum.resize(n_wavelengths);
2934 horiz_bright_spectrum.resize(n_wavelengths);
2935 norm_spectrum.resize(n_wavelengths);
2937 const float L_base = 0.4f;
2938 const float lambda_ref = 550.0f;
2940 for (
int i = 0; i < n_wavelengths; ++i) {
2941 float lambda = 360.0f + i * 10.0f;
2942 wavelengths[i] = lambda;
2945 float rayleigh_factor = std::pow(lambda_ref / lambda, 4.0f);
2946 L_zenith_spectrum[i] = L_base * rayleigh_factor;
2949 circ_str_spectrum[i] = 0.5f;
2950 circ_width_spectrum[i] = 20.0f;
2951 horiz_bright_spectrum[i] = 1.8f;
2952 norm_spectrum[i] = 0.7f;
2956 std::vector<float> spectral_params;
2957 context->
getGlobalData(
"prague_sky_spectral_params", spectral_params);
2959 const int params_per_wavelength = 6;
2960 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
2963 wavelengths.resize(n_wavelengths);
2964 L_zenith_spectrum.resize(n_wavelengths);
2965 circ_str_spectrum.resize(n_wavelengths);
2966 circ_width_spectrum.resize(n_wavelengths);
2967 horiz_bright_spectrum.resize(n_wavelengths);
2968 norm_spectrum.resize(n_wavelengths);
2970 for (
int i = 0; i < n_wavelengths; ++i) {
2971 int base = i * params_per_wavelength;
2972 wavelengths[i] = spectral_params[base + 0];
2973 L_zenith_spectrum[i] = spectral_params[base + 1];
2974 circ_str_spectrum[i] = spectral_params[base + 2];
2975 circ_width_spectrum[i] = spectral_params[base + 3];
2976 horiz_bright_spectrum[i] = spectral_params[base + 4];
2977 norm_spectrum[i] = spectral_params[base + 5];
2982 for (
size_t b = 0; b < Nbands_launch; b++) {
2983 const std::string &band_label = band_labels[b];
2984 if (radiation_bands.find(band_label) == radiation_bands.end()) {
2996 std::string spectral_response_label =
"uniform";
2997 if (camera.band_spectral_response.find(band_label) != camera.band_spectral_response.end()) {
2998 spectral_response_label = camera.band_spectral_response.at(band_label);
2999 if (spectral_response_label.empty() ||
trim_whitespace(spectral_response_label).empty()) {
3000 spectral_response_label =
"uniform";
3005 std::vector<helios::vec2> camera_response;
3007 if (spectral_response_label ==
"uniform") {
3010 if (wavelength_range.
x <= 0.f || wavelength_range.
y <= 0.f) {
3011 bool bounds_inferred =
false;
3013 if (band_label ==
"red" || band_label ==
"R") {
3015 bounds_inferred =
true;
3016 }
else if (band_label ==
"green" || band_label ==
"G") {
3018 bounds_inferred =
true;
3019 }
else if (band_label ==
"blue" || band_label ==
"B") {
3021 bounds_inferred =
true;
3024 if (!bounds_inferred) {
3029 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera '" + camera.label +
"' band '" + band_label +
"' has uniform spectral response but no wavelength bounds set.");
3038 camera_response = loadSpectralData(spectral_response_label);
3040 if (camera_response.empty()) {
3041 helios_runtime_error(
"ERROR (RadiationModel::updateAtmosphericSkyModel): Camera spectral response '" + spectral_response_label +
"' not found for camera '" + camera.label +
"' band '" + band_label +
"'.");
3047 float integrated_L_zenith = integrateOverResponse(wavelengths, L_zenith_spectrum, camera_response);
3051 float integrated_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, camera_response);
3052 float integrated_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, camera_response);
3053 float integrated_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, camera_response);
3056 float integrated_norm = computeAngularNormalization(integrated_circ_str, integrated_circ_width, integrated_horiz_bright);
3061 float base_radiance_for_gpu = integrated_L_zenith / std::max(integrated_norm, 0.1f);
3063 sky_base_radiances[b] = base_radiance_for_gpu;
3064 sky_params[b] =
helios::make_vec4(integrated_circ_str, integrated_circ_width, integrated_horiz_bright, integrated_norm);
3068 return sky_base_radiances;
3071void RadiationModel::updatePragueParametersForGeneralDiffuse(
const std::vector<std::string> &band_labels) {
3077 int prague_valid = 0;
3084 std::vector<float> spectral_params;
3085 context->
getGlobalData(
"prague_sky_spectral_params", spectral_params);
3088 const int params_per_wavelength = 6;
3089 const int n_wavelengths = spectral_params.size() / params_per_wavelength;
3091 std::vector<float> wavelengths(n_wavelengths);
3092 std::vector<float> L_zenith_spectrum(n_wavelengths);
3093 std::vector<float> circ_str_spectrum(n_wavelengths);
3094 std::vector<float> circ_width_spectrum(n_wavelengths);
3095 std::vector<float> horiz_bright_spectrum(n_wavelengths);
3096 std::vector<float> norm_spectrum(n_wavelengths);
3098 for (
int i = 0; i < n_wavelengths; ++i) {
3099 int base = i * params_per_wavelength;
3100 wavelengths[i] = spectral_params[base + 0];
3101 L_zenith_spectrum[i] = spectral_params[base + 1];
3102 circ_str_spectrum[i] = spectral_params[base + 2];
3103 circ_width_spectrum[i] = spectral_params[base + 3];
3104 horiz_bright_spectrum[i] = spectral_params[base + 4];
3105 norm_spectrum[i] = spectral_params[base + 5];
3110 context->
getGlobalData(
"prague_sky_sun_direction", sun_dir);
3113 for (
const auto &label: band_labels) {
3123 if (band_spectrum.empty()) {
3127 if (lambda_min > 0 && lambda_max > lambda_min) {
3128 band_spectrum = {{lambda_min, 1.0f}, {lambda_max, 1.0f}};
3132 if (band_spectrum.empty()) {
3138 float int_circ_str = weightedAverageOverResponse(wavelengths, circ_str_spectrum, L_zenith_spectrum, band_spectrum);
3139 float int_circ_width = weightedAverageOverResponse(wavelengths, circ_width_spectrum, L_zenith_spectrum, band_spectrum);
3140 float int_horiz_bright = weightedAverageOverResponse(wavelengths, horiz_bright_spectrum, L_zenith_spectrum, band_spectrum);
3143 float int_norm = computeAngularNormalization(int_circ_str, int_circ_width, int_horiz_bright);
3151float RadiationModel::integrateOverResponse(
const std::vector<float> &wavelengths,
const std::vector<float> &values,
const std::vector<helios::vec2> &camera_response)
const {
3153 if (wavelengths.empty() || camera_response.empty()) {
3159 double integrated_radiance = 0.0;
3162 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
3163 float lambda1 = camera_response[i].x;
3164 float lambda2 = camera_response[i + 1].x;
3167 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
3171 float r1 = camera_response[i].y;
3172 float r2 = camera_response[i + 1].y;
3178 if (lambda1 <= wavelengths.front()) {
3179 v1 = values.front();
3180 }
else if (lambda1 >= wavelengths.back()) {
3183 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3184 size_t idx = std::distance(wavelengths.begin(), it);
3187 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3188 v1 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
3192 if (lambda2 <= wavelengths.front()) {
3193 v2 = values.front();
3194 }
else if (lambda2 >= wavelengths.back()) {
3197 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3198 size_t idx = std::distance(wavelengths.begin(), it);
3201 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3202 v2 = values[idx - 1] + t * (values[idx] - values[idx - 1]);
3205 float dlambda = lambda2 - lambda1;
3209 integrated_radiance += 0.5 * (v1 * r1 + v2 * r2) * dlambda;
3213 return static_cast<float>(integrated_radiance);
3216float 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 {
3218 if (wavelengths.empty() || camera_response.empty()) {
3224 double weighted_sum = 0.0;
3225 double total_weight = 0.0;
3227 for (
size_t i = 0; i < camera_response.size() - 1; ++i) {
3228 float lambda1 = camera_response[i].x;
3229 float lambda2 = camera_response[i + 1].x;
3231 if (lambda2 < wavelengths.front() || lambda1 > wavelengths.back()) {
3235 float r1 = camera_response[i].y;
3236 float r2 = camera_response[i + 1].y;
3240 if (lambda1 <= wavelengths.front()) {
3241 p1 = param_values.front();
3242 }
else if (lambda1 >= wavelengths.back()) {
3243 p1 = param_values.back();
3245 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3246 size_t idx = std::distance(wavelengths.begin(), it);
3249 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3250 p1 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
3253 if (lambda2 <= wavelengths.front()) {
3254 p2 = param_values.front();
3255 }
else if (lambda2 >= wavelengths.back()) {
3256 p2 = param_values.back();
3258 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3259 size_t idx = std::distance(wavelengths.begin(), it);
3262 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3263 p2 = param_values[idx - 1] + t * (param_values[idx] - param_values[idx - 1]);
3268 if (lambda1 <= wavelengths.front()) {
3269 w1 = weight_values.front();
3270 }
else if (lambda1 >= wavelengths.back()) {
3271 w1 = weight_values.back();
3273 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda1);
3274 size_t idx = std::distance(wavelengths.begin(), it);
3277 float t = (lambda1 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3278 w1 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
3281 if (lambda2 <= wavelengths.front()) {
3282 w2 = weight_values.front();
3283 }
else if (lambda2 >= wavelengths.back()) {
3284 w2 = weight_values.back();
3286 auto it = std::lower_bound(wavelengths.begin(), wavelengths.end(), lambda2);
3287 size_t idx = std::distance(wavelengths.begin(), it);
3290 float t = (lambda2 - wavelengths[idx - 1]) / (wavelengths[idx] - wavelengths[idx - 1]);
3291 w2 = weight_values[idx - 1] + t * (weight_values[idx] - weight_values[idx - 1]);
3294 float dlambda = lambda2 - lambda1;
3297 weighted_sum += 0.5 * (p1 * w1 * r1 + p2 * w2 * r2) * dlambda;
3298 total_weight += 0.5 * (w1 * r1 + w2 * r2) * dlambda;
3302 if (total_weight > 1e-10) {
3303 return static_cast<float>(weighted_sum / total_weight);
3308float RadiationModel::computeAngularNormalization(
float circ_str,
float circ_width,
float horiz_bright)
const {
3311 float integral = 0.0f;
3316 for (
int j = 0; j < N; ++j) {
3317 for (
int i = 0; i < N; ++i) {
3318 float theta = 0.5f * float(
M_PI) * (i + 0.5f) / N;
3319 float phi = 2.0f * float(
M_PI) * (j + 0.5f) / N;
3324 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));
3325 float gamma = std::acos(cos_gamma) * 180.0f / float(
M_PI);
3328 float cos_theta = std::max(0.0f, dir.
z);
3329 float horizon_term = 1.0f + (horiz_bright - 1.0f) * (1.0f - cos_theta);
3330 float circ_term = 1.0f + circ_str * std::exp(-gamma / circ_width);
3332 float pattern = circ_term * horizon_term;
3335 integral += pattern * std::cos(theta) * std::sin(theta) * (float(
M_PI) / (2.0f * N)) * (2.0f * float(
M_PI) / N);
3339 return 1.0f / std::max(integral, 1e-10f);
3342std::vector<helios::vec2> RadiationModel::loadSpectralData(
const std::string &global_data_label)
const {
3344 std::vector<helios::vec2> spectrum;
3349 bool data_found =
false;
3350 for (
const auto &file: spectral_library_files) {
3352 context->
loadXML(file.c_str(),
true);
3359 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' could not be found.");
3363 if (context->
getGlobalDataType(global_data_label.c_str()) != HELIOS_TYPE_VEC2) {
3364 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is not of type HELIOS_TYPE_VEC2.");
3367 context->
getGlobalData(global_data_label.c_str(), spectrum);
3370 if (spectrum.empty()) {
3371 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Global data for spectrum '" + global_data_label +
"' is empty.");
3373 for (
auto s = 0; s < spectrum.size(); s++) {
3375 if (s > 0 && spectrum.at(s).x <= spectrum.at(s - 1).x) {
3376 helios_runtime_error(
"ERROR (RadiationModel::loadSpectralData): Source spectral data validation failed. Wavelengths must increase monotonically.");
3379 if (spectrum.at(s).x < 0 || spectrum.at(s).x > 100000) {
3380 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.");
3383 if (spectrum.at(s).y < 0) {
3384 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.");
3392 std::vector<std::string> labels{label};
3404 if (radiativepropertiesneedupdate) {
3405 for (
auto &kv : excitation_sets) {
3406 kv.second.populated =
false;
3425 std::vector<std::string> effective_label = label;
3426 std::vector<ExcitationSet *> piggybacked_sets;
3427 if (!excitation_sets.empty()) {
3428 bool label_has_sif_emission =
false;
3429 bool label_has_excitation_band =
false;
3430 for (
const auto &b : label) {
3431 if (sif_emission_bands.count(b) > 0) label_has_sif_emission =
true;
3432 if (b.size() >= 9 && b.compare(0, 9,
"_SIF_exc_") == 0) label_has_excitation_band =
true;
3434 if (!label_has_sif_emission && !label_has_excitation_band) {
3435 for (
auto &kv : excitation_sets) {
3436 ExcitationSet &exc = kv.second;
3437 if (exc.populated)
continue;
3438 for (
const auto &eb : exc.band_labels) {
3439 effective_label.push_back(eb);
3441 piggybacked_sets.push_back(&exc);
3448 std::vector<std::string> band_labels;
3449 for (
auto &band: radiation_bands) {
3450 if (std::find(effective_label.begin(), effective_label.end(), band.first) != effective_label.end()) {
3451 band_labels.push_back(band.first);
3457 std::cerr <<
"WARNING (RadiationModel::runBand): No geometry was added to the context. There is nothing to simulate...exiting." << std::endl;
3462 if (!isgeometryinitialized) {
3467 for (
const std::string &band: label) {
3469 helios_runtime_error(
"ERROR (RadiationModel::runBand): Cannot run band " + band +
" because it is not a valid band. Use addRadiationBand() function to add the band.");
3474 if (radiation_sources.empty()) {
3491 bool dispatch_has_sif_band =
false;
3492 for (
const auto &b : band_labels) {
3493 if (sif_emission_bands.count(b) > 0) {
3494 dispatch_has_sif_band =
true;
3498 if (dispatch_has_sif_band) {
3508 for (
const auto &b : band_labels) {
3509 if (sif_emission_bands.count(b) > 0) {
3510 auto &band = radiation_bands.at(b);
3512 sif_warn.
addWarning(
"sif_emission_reenabled",
3513 "Band '" + b +
"' is bound to a SIF camera but has emission disabled. "
3514 "Re-enabling emission for this dispatch — SIF cameras require emission "
3515 "enabled so that Fluspect-B source flux is traced through the emission "
3516 "loop. Remove the disableEmission(\"" + b +
"\") call to silence this warning.");
3521 sif_warn.
report(std::cerr);
3529 runExcitationBands();
3530 for (
const auto &b : band_labels) {
3531 if (sif_emission_bands.count(b) > 0) {
3532 computeSIFEmission(b);
3538 for (
auto &source: radiation_sources) {
3539 if (!source.source_spectrum_label.empty() && source.source_spectrum_label !=
"none") {
3540 uint64_t current_version = context->
getGlobalDataVersion(source.source_spectrum_label.c_str());
3543 source.
source_spectrum = loadSpectralData(source.source_spectrum_label);
3545 radiativepropertiesneedupdate =
true;
3551 if (!global_diffuse_spectrum_label.empty() && global_diffuse_spectrum_label !=
"none") {
3552 uint64_t current_version = context->
getGlobalDataVersion(global_diffuse_spectrum_label.c_str());
3553 if (current_version != global_diffuse_spectrum_version) {
3555 global_diffuse_spectrum = loadSpectralData(global_diffuse_spectrum_label);
3556 global_diffuse_spectrum_version = current_version;
3558 for (
auto &band_pair: radiation_bands) {
3559 band_pair.second.diffuse_spectrum = global_diffuse_spectrum;
3561 radiativepropertiesneedupdate =
true;
3565 if (radiativepropertiesneedupdate) {
3567 updateRadiativeProperties();
3571 buildMaterialData();
3572 backend->updateMaterials(material_data);
3577 backend->updateSources(source_data);
3580 size_t Nbands_launch = band_labels.size();
3581 size_t Nbands_global = radiation_bands.size();
3584 std::vector<char> band_launch_flag(Nbands_global);
3586 for (
auto &band: radiation_bands) {
3587 if (std::find(band_labels.begin(), band_labels.end(), band.first) != band_labels.end()) {
3588 band_launch_flag.at(bb) = 1;
3594 size_t Nobjects = primitiveID.size();
3595 size_t Nprimitives = context_UUIDs.size();
3596 uint Nsources = radiation_sources.size();
3597 uint Ncameras = cameras.size();
3603 std::vector<uint> scattering_depth(Nbands_launch);
3604 bool scatteringenabled =
false;
3605 for (
auto b = 0; b < Nbands_launch; b++) {
3606 scattering_depth.at(b) = radiation_bands.at(band_labels.at(b)).scatteringDepth;
3607 if (scattering_depth.at(b) > 0) {
3608 scatteringenabled =
true;
3618 for (
int b = 0; b < Nbands_launch; b++) {
3619 const std::string &bname = band_labels.at(b);
3620 const bool is_sif_excitation_band = bname.size() >= 9 && bname.compare(0, 9,
"_SIF_exc_") == 0;
3621 if (scattering_depth.at(b) == 0 && scattering_iterations_needed.at(bname) && !is_sif_excitation_band) {
3622 std::cout <<
"WARNING (RadiationModel::runBand): Surface radiative properties for band " << bname
3623 <<
" 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;
3628 std::vector<float> diffuse_flux(Nbands_launch);
3629 bool diffuseenabled =
false;
3630 for (
auto b = 0; b < Nbands_launch; b++) {
3632 if (diffuse_flux.at(b) > 0.f) {
3633 diffuseenabled =
true;
3639 std::vector<float> camera_sky_radiance(Nbands_launch, 0.0f);
3643 if (diffuseenabled) {
3644 updatePragueParametersForGeneralDiffuse(band_labels);
3648 std::vector<float> diffuse_extinction(Nbands_launch, 0);
3649 if (diffuseenabled) {
3650 for (
auto b = 0; b < Nbands_launch; b++) {
3651 diffuse_extinction.at(b) = radiation_bands.at(band_labels.at(b)).diffuseExtinction;
3657 std::vector<float> diffuse_dist_norm(Nbands_launch, 0);
3658 if (diffuseenabled) {
3659 for (
auto b = 0; b < Nbands_launch; b++) {
3660 diffuse_dist_norm.at(b) = radiation_bands.at(band_labels.at(b)).diffuseDistNorm;
3666 std::vector<helios::vec3> diffuse_peak_dir(Nbands_launch);
3667 if (diffuseenabled) {
3668 for (
auto b = 0; b < Nbands_launch; b++) {
3669 helios::vec3 peak_dir = radiation_bands.at(band_labels.at(b)).diffusePeakDir;
3677 std::vector<helios::vec4> prague_params(Nbands_launch);
3678 if (diffuseenabled) {
3679 for (
auto b = 0; b < Nbands_launch; b++) {
3680 const auto ¶ms = radiation_bands.at(band_labels.at(b)).diffusePragueParams;
3681 prague_params.at(b) =
helios::make_vec4(params.x, params.y, params.z, params.w);
3687 bool emissionenabled =
false;
3688 for (
auto b = 0; b < Nbands_launch; b++) {
3689 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
3690 emissionenabled =
true;
3695 size_t directRayCount = 0;
3696 for (
const auto &band: label) {
3697 if (radiation_bands.at(band).directRayCount > directRayCount) {
3698 directRayCount = radiation_bands.at(band).directRayCount;
3703 size_t diffuseRayCount = 0;
3704 for (
const auto &band: label) {
3705 if (radiation_bands.at(band).diffuseRayCount > diffuseRayCount) {
3706 diffuseRayCount = radiation_bands.at(band).diffuseRayCount;
3711 size_t scatteringDepth = 0;
3712 for (
const auto &band: label) {
3713 if (radiation_bands.at(band).scatteringDepth > scatteringDepth) {
3714 scatteringDepth = radiation_bands.at(band).scatteringDepth;
3719 backend->zeroRadiationBuffers(Nbands_launch);
3721 std::vector<float> TBS_top, TBS_bottom;
3722 TBS_top.resize(Nbands_launch * Nprimitives, 0);
3723 TBS_bottom = TBS_top;
3725 std::map<std::string, std::vector<std::vector<float>>> radiation_in_camera;
3727 size_t maxRays = 1024 * 1024 * 1024;
3733 bool rundirect =
false;
3734 for (
uint s = 0; s < Nsources; s++) {
3735 for (
uint b = 0; b < Nbands_launch; b++) {
3743 if (Nsources > 0 && rundirect) {
3747 std::vector<std::vector<float>> fluxes;
3748 fluxes.resize(Nsources);
3749 std::vector<helios::vec3> positions(Nsources);
3750 std::vector<helios::vec2> widths(Nsources);
3751 std::vector<helios::vec3> rotations(Nsources);
3752 std::vector<uint> types(Nsources);
3755 for (
const auto &source: radiation_sources) {
3757 fluxes.at(s).resize(Nbands_launch);
3759 for (
auto b = 0; b < label.size(); b++) {
3772 backend->uploadSourceFluxes(
flatten(fluxes));
3780 std::vector<float> source_fluxes_cam;
3781 source_fluxes_cam.resize(Nsources * Nbands_launch * Ncameras, 1.0f);
3783 for (
uint s = 0; s < Nsources; s++) {
3787 for (
const auto &camera: cameras) {
3788 for (
uint b = 0; b < Nbands_launch; b++) {
3789 std::string band_label = band_labels.at(b);
3792 float weight = 1.0f;
3795 if (camera.second.band_spectral_response.find(band_label) != camera.second.band_spectral_response.end()) {
3796 std::string response_label = camera.second.band_spectral_response.at(band_label);
3802 std::vector<helios::vec2> camera_response;
3803 context->
getGlobalData(response_label.c_str(), camera_response);
3806 helios::vec2 wavelength_range = radiation_bands.at(band_label).wavebandBounds;
3809 if (wavelength_range.
x == 0 && wavelength_range.
y == 0) {
3810 wavelength_range.
x = fmax(source.
source_spectrum.front().x, camera_response.front().x);
3811 wavelength_range.
y = fmin(source.
source_spectrum.back().x, camera_response.back().x);
3820 source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam] = weight;
3827 for (
uint s = 0; s < Nsources; s++) {
3828 source_data[s].fluxes_cam.clear();
3829 for (
uint b = 0; b < Nbands_launch; b++) {
3830 for (
uint cam = 0; cam < Ncameras; cam++) {
3831 source_data[s].fluxes_cam.push_back(source_fluxes_cam[s * Nbands_launch * Ncameras + b * Ncameras + cam]);
3835 backend->updateSources(source_data);
3841 std::cout <<
"Performing primary direct radiation ray trace for bands ";
3842 for (
const auto &band: label) {
3843 std::cout << band <<
", ";
3845 std::cout <<
"..." << std::flush;
3852 params.rays_per_primitive = directRayCount;
3853 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
3854 params.num_bands_global = Nbands_global;
3855 params.num_bands_launch = Nbands_launch;
3856 params.specular_reflection_enabled = specular_reflection_mode;
3859 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
3864 if (Ncameras > 0 && scatteringenabled) {
3865 backend->zeroCameraScatterBuffers(Nbands_launch);
3868 backend->launchDirectRays(params);
3871 std::cout <<
"done." << std::endl;
3879 std::vector<float> flux_top, flux_bottom;
3880 flux_top.resize(Nbands_launch * Nprimitives, 0);
3881 flux_bottom = flux_top;
3884 std::vector<float> scatter_top_cam;
3885 std::vector<float> scatter_bottom_cam;
3887 scatter_top_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3888 scatter_bottom_cam.resize(Nprimitives * Nbands_launch, 0.0f);
3891 if (scatteringenabled && rundirect) {
3894 backend->getRadiationResults(scatter_results);
3905 backend->zeroCameraScatterBuffers(Nbands_launch);
3912 for (
size_t i = 0; i < Nprimitives; i++) {
3913 uint UUID = context_UUIDs.at(i);
3916 if (twosided == 0) {
3917 for (
size_t b = 0; b < Nbands_launch; b++) {
3918 size_t ind = rad_indexer(i, b);
3919 float total = flux_top[ind] + flux_bottom[ind];
3920 flux_top[ind] = total;
3921 flux_bottom[ind] = total;
3927 backend->uploadRadiationOut(flux_top, flux_bottom);
3928 backend->zeroScatterBuffers();
3933 if (emissionenabled || diffuseenabled) {
3936 if (emissionenabled) {
3938 float eps, temperature;
3943 for (
auto b = 0; b < Nbands_launch; b++) {
3945 if (radiation_bands.at(band_labels.at(b)).emissionFlag) {
3946 std::string prop =
"emissivity_" + band_labels.at(b);
3949 auto sif_band_it = sif_emission_buffer.find(band_labels.at(b));
3950 auto sif_band_bot_it = sif_emission_buffer_bottom.find(band_labels.at(b));
3951 const bool have_sif_band = (sif_band_it != sif_emission_buffer.end());
3952 const bool have_sif_band_bot = (sif_band_bot_it != sif_emission_buffer_bottom.end());
3953 for (
size_t u = 0; u < Nprimitives; u++) {
3955 size_t ind = emission_indexer(u, b);
3956 uint p = context_UUIDs.at(u);
3958 float sif_bottom_flux = 0.f;
3959 bool used_sif =
false;
3960 if (have_sif_band) {
3961 auto sif_uuid_it = sif_band_it->second.find(p);
3962 if (sif_uuid_it != sif_band_it->second.end()) {
3963 out_top = sif_uuid_it->second;
3967 if (used_sif && have_sif_band_bot) {
3968 auto sif_bot_it = sif_band_bot_it->second.find(p);
3969 if (sif_bot_it != sif_band_bot_it->second.end()) {
3970 sif_bottom_flux = sif_bot_it->second;
3977 if (have_sif_band) {
3985 if (scattering_depth.at(b) == 0 && eps != 1.f) {
3990 if (temperature < 0) {
3991 temperature = temperature_default;
3994 temperature = temperature_default;
3996 out_top = sigma * eps * pow(temperature, 4);
3999 flux_top.at(ind) += out_top;
4001 scatter_top_cam[ind] += out_top;
4005 if (twosided_flag != 0) {
4009 flux_bottom.at(ind) += sif_bottom_flux;
4011 scatter_bottom_cam[ind] += sif_bottom_flux;
4014 flux_bottom.at(ind) += flux_top.at(ind);
4016 scatter_bottom_cam[ind] += out_top;
4028 backend->uploadCameraScatterBuffers(scatter_top_cam, scatter_bottom_cam);
4034 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4035 uint rays_per_primitive = n * n;
4038 std::cout <<
"Performing primary diffuse radiation ray trace for bands ";
4039 for (
const auto &band: label) {
4040 std::cout << band <<
" ";
4042 std::cout <<
"..." << std::flush;
4051 params.rays_per_primitive = rays_per_primitive;
4052 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4053 params.current_band = 0;
4054 params.num_bands_global = Nbands_global;
4055 params.num_bands_launch = Nbands_launch;
4056 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
4058 params.scattering_iteration = 0;
4059 params.max_scatters = scatteringDepth;
4068 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
4069 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
4070 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
4077 backend->launchDiffuseRays(params);
4081 backend->launchDiffuseRays(params);
4086 backend->getRadiationResults(primary_results);
4092 backend->zeroCameraScatterBuffers(Nbands_launch);
4096 std::cout <<
"done." << std::endl;
4103 if (scatteringenabled && (emissionenabled || diffuseenabled) && !rundirect) {
4104 backend->copyScatterToRadiation();
4105 backend->zeroScatterBuffers();
4108 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
4110 for (
auto b = 0; b < Nbands_launch; b++) {
4111 diffuse_flux.at(b) = 0.f;
4115 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4116 uint rays_per_primitive = n * n;
4120 std::vector<char> scatter_band_flags = band_launch_flag;
4122 for (s = 0; s < scatteringDepth; s++) {
4124 std::cout <<
"Performing scattering ray trace (iteration " << s + 1 <<
" of " << scatteringDepth <<
")..." << std::flush;
4128 int active_bands = 0;
4129 for (
uint b_global = 0; b_global < Nbands_global; b_global++) {
4131 if (scatter_band_flags.at(b_global) == 0) {
4136 const std::string &bname = band_labels.at(b);
4137 uint depth = radiation_bands.at(bname).scatteringDepth;
4138 if (s + 1 > depth) {
4145 const bool is_sif_excitation_band = bname.size() >= 9 && bname.compare(0, 9,
"_SIF_exc_") == 0;
4146 if (message_flag && !is_sif_excitation_band) {
4147 std::cout <<
"Skipping band " << bname <<
" for scattering launch " << s + 1 << std::endl;
4149 scatter_band_flags.at(b_global) = 0;
4158 if (s > 0 || (emissionenabled && rundirect)) {
4159 backend->copyScatterToRadiation();
4161 backend->zeroScatterBuffers();
4165 backend->getRadiationResults(scatter_results);
4174 params.rays_per_primitive = rays_per_primitive;
4175 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4176 params.current_band = 0;
4177 params.num_bands_global = Nbands_global;
4178 params.num_bands_launch = Nbands_launch;
4179 std::vector<bool> band_flags(scatter_band_flags.begin(), scatter_band_flags.end());
4181 params.scattering_iteration = s;
4182 params.max_scatters = scatteringDepth;
4189 std::vector<helios::vec3> peak_dirs(diffuse_peak_dir.size());
4190 for (
size_t i = 0; i < diffuse_peak_dir.size(); i++) {
4191 peak_dirs[i] =
helios::make_vec3(diffuse_peak_dir[i].x, diffuse_peak_dir[i].y, diffuse_peak_dir[i].z);
4202 backend->launchDiffuseRays(params);
4206 backend->launchDiffuseRays(params);
4211 backend->getRadiationResults(post_launch);
4217 backend->zeroCameraScatterBuffers(Nbands_launch);
4221 std::cout <<
"\r \r" << std::flush;
4226 std::cout <<
"Performing scattering ray trace...done." << std::endl;
4236 if (Ncameras > 0 && scatteringenabled) {
4237 backend->uploadRadiationOut(scatter_top_cam, scatter_bottom_cam);
4242 vec3 sun_dir(0, 0, 1);
4243 std::vector<float> solar_radiances(Nbands_launch, 0.0f);
4244 bool has_sun_source =
false;
4246 for (
size_t s = 0; s < radiation_sources.size(); s++) {
4248 if (source.
source_type == RADIATION_SOURCE_TYPE_COLLIMATED || source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4252 has_sun_source =
true;
4255 for (
size_t b = 0; b < Nbands_launch; b++) {
4258 if (source.
source_type == RADIATION_SOURCE_TYPE_SUN_SPHERE) {
4261 solar_radiances[b] = flux /
M_PI;
4265 const float solar_solid_angle = 6.74e-5f;
4266 solar_radiances[b] = flux / solar_solid_angle;
4273 if (scatteringenabled && (emissionenabled || diffuseenabled || rundirect)) {
4275 if (diffuseenabled) {
4276 for (
auto b = 0; b < Nbands_launch; b++) {
4281 size_t n = ceil(sqrt(
double(diffuseRayCount)));
4285 if (!cameras.empty() && prague_params.size() == Nbands_launch) {
4287 std::vector<float> sky_for_backend = updateAtmosphericSkyModel(band_labels, cameras.begin()->second);
4290 backend->updateSkyModel(prague_params, sky_for_backend, sun_dir, solar_radiances,
4291 has_sun_source ? 0.999989f : 0.0f
4296 for (
auto &camera: cameras) {
4304 bool camera_in_dispatch =
false;
4305 for (
const auto &camera_band: camera.second.band_labels) {
4306 if (std::find(band_labels.begin(), band_labels.end(), camera_band) != band_labels.end()) {
4307 camera_in_dispatch =
true;
4311 if (!camera_in_dispatch) {
4317 if (camera.second.antialiasing_samples > maxRays) {
4318 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) +
4319 "). Reduce antialiasing samples.");
4323 std::vector<CameraTile> tiles = computeCameraTiles(camera.second, maxRays);
4325 if (message_flag && tiles.size() > 1) {
4326 std::cout <<
"Camera '" << camera.second.label <<
"' requires " << tiles.size() <<
" tiles" << std::endl;
4332 std::vector<float> cam_weights(Nsources * Nbands_launch, 1.0f);
4333 for (
uint s = 0; s < Nsources; s++) {
4334 for (
uint b = 0; b < Nbands_launch; b++) {
4335 if (!source_data[s].fluxes_cam.empty() && source_data[s].fluxes_cam.size() == Nbands_launch * Ncameras) {
4336 cam_weights[s * Nbands_launch + b] = source_data[s].fluxes_cam[b * Ncameras + cam];
4340 backend->uploadSourceFluxesCam(cam_weights);
4343 for (
size_t tile_idx = 0; tile_idx < tiles.size(); tile_idx++) {
4344 const auto &tile = tiles[tile_idx];
4347 helios::RayTracingLaunchParams params = buildCameraLaunchParams(camera.second, cam, camera.second.antialiasing_samples, tile.resolution, tile.offset);
4350 params.num_bands_launch = Nbands_launch;
4351 params.num_bands_global = Nbands_global;
4352 params.random_seed = std::chrono::system_clock::now().time_since_epoch().count();
4353 std::vector<bool> band_flags(band_launch_flag.begin(), band_launch_flag.end());
4358 if (tiles.size() == 1) {
4359 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"..." << std::flush;
4361 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << tiles.size() <<
")..." << std::flush;
4366 backend->launchCameraRays(params);
4369 if (tiles.size() > 1) {
4370 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
4372 std::cout <<
"done." << std::endl;
4377 if (message_flag && tiles.size() > 1) {
4378 std::cout <<
"Performing scattering radiation camera ray trace for camera " << camera.second.label <<
"...done." << std::endl;
4382 std::vector<float> radiation_camera;
4383 std::vector<uint> dummy_labels;
4384 std::vector<float> dummy_depths;
4385 backend->getCameraResults(radiation_camera, dummy_labels, dummy_depths, cam, camera.second.resolution);
4388 std::string camera_label = camera.second.label;
4390 for (
auto b = 0; b < Nbands_launch; b++) {
4392 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.
x * camera.second.resolution.
y);
4394 std::string data_label =
"camera_" + camera_label +
"_" + band_labels.at(b);
4396 for (
auto p = 0; p < camera.second.resolution.
x * camera.second.resolution.
y; p++) {
4397 camera.second.pixel_data.at(band_labels.at(b)).at(p) = radiation_camera.at(p * Nbands_launch + b);
4400 context->
setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
4407 pixel_label_camera.antialiasing_samples = 1;
4408 std::vector<CameraTile> pixel_tiles = computeCameraTiles(pixel_label_camera, maxRays);
4411 backend->zeroCameraPixelBuffers(camera.second.resolution);
4414 for (
size_t tile_idx = 0; tile_idx < pixel_tiles.size(); tile_idx++) {
4415 const auto &tile = pixel_tiles[tile_idx];
4420 tile.resolution, tile.offset);
4424 if (pixel_tiles.size() == 1) {
4425 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"..." << std::flush;
4427 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
" (tile " << (tile_idx + 1) <<
" of " << pixel_tiles.size() <<
")..." << std::flush;
4432 backend->launchPixelLabelRays(params);
4435 if (pixel_tiles.size() > 1) {
4436 std::cout <<
"\r" << std::string(120,
' ') <<
"\r" << std::flush;
4438 std::cout <<
"done." << std::endl;
4443 if (message_flag && pixel_tiles.size() > 1) {
4444 std::cout <<
"Performing camera pixel labeling ray trace for camera " << camera.second.label <<
"...done." << std::endl;
4448 std::vector<float> dummy_pixel_data;
4449 backend->getCameraResults(dummy_pixel_data, camera.second.pixel_label_UUID, camera.second.pixel_depth, cam, camera.second.resolution);
4456 std::string data_label =
"camera_" + camera_label +
"_pixel_UUID";
4457 context->
setGlobalData(data_label.c_str(), camera.second.pixel_label_UUID);
4459 data_label =
"camera_" + camera_label +
"_pixel_depth";
4460 context->
setGlobalData(data_label.c_str(), camera.second.pixel_depth);
4466 for (
auto &camera: cameras) {
4467 for (
auto b = 0; b < Nbands_launch; b++) {
4468 camera.second.pixel_data[band_labels.at(b)].resize(camera.second.resolution.
x * camera.second.resolution.
y);
4470 std::string data_label =
"camera_" + camera.second.label +
"_" + band_labels.at(b);
4472 for (
auto p = 0; p < camera.second.resolution.
x * camera.second.resolution.
y; p++) {
4473 camera.second.pixel_data.at(band_labels.at(b)).at(p) = 0.f;
4475 context->
setGlobalData(data_label.c_str(), camera.second.pixel_data.at(band_labels.at(b)));
4482 for (
auto &camera: cameras) {
4487 for (
auto &camera: cameras) {
4495 backend->getRadiationResults(results);
4497 std::vector<float> radiation_flux_data = results.
radiation_in;
4503 std::vector<uint> UUIDs_context_all = context->
getAllUUIDs();
4508 for (
auto b = 0; b < Nbands_launch; b++) {
4510 std::string prop =
"radiation_flux_" + band_labels.at(b);
4511 std::vector<float>
R(Nprimitives);
4512 for (
size_t u = 0; u < Nprimitives; u++) {
4514 size_t ind = result_indexer(u, b);
4515 R.at(u) = radiation_flux_data.at(ind) + TBS_top.at(ind) + TBS_bottom.at(ind);
4519 if (UUIDs_context_all.size() != Nprimitives) {
4520 for (
uint UUID: UUIDs_context_all) {
4532 for (ExcitationSet *exc : piggybacked_sets) {
4533 populateExcitationAPAR(*exc);
4540 backend->getRadiationResults(results);
4543 for (
size_t i = 0; i < results.
sky_energy.size(); i++) {
4551 std::vector<float> total_flux;
4554 for (
const auto &band: radiation_bands) {
4556 std::string label = band.first;
4558 for (
size_t u = 0; u < context_UUIDs.size(); u++) {
4560 uint p = context_UUIDs.at(u);
4562 std::string str =
"radiation_flux_" + label;
4566 total_flux.at(u) +=
R;
4576 vec3 dir = view_direction;
4580 float total_area = 0;
4581 for (std::size_t u = 0; u < primitiveID.size(); u++) {
4583 uint UUID = context_UUIDs.at(primitiveID.at(u));
4588 Gtheta += fabsf(normal * dir) * area;
4593 return Gtheta / total_area;
4597 float average_delta_e) {
4599 std::ofstream file(file_path);
4600 if (!file.is_open()) {
4601 helios_runtime_error(
"ERROR (RadiationModel::exportColorCorrectionMatrixXML): Failed to open file for writing: " + file_path);
4605 std::string matrix_type =
"3x3";
4606 if (matrix.size() == 4 || (matrix.size() >= 3 && matrix[0].size() == 4)) {
4607 matrix_type =
"4x3";
4611 file <<
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
4612 file <<
"<!-- Camera Color Correction Matrix -->" << std::endl;
4613 file <<
"<!-- Source Image: " << source_image_path <<
" -->" << std::endl;
4614 file <<
"<!-- Camera Label: " << camera_label <<
" -->" << std::endl;
4615 file <<
"<!-- Colorboard Type: " << colorboard_type <<
" -->" << std::endl;
4616 if (average_delta_e >= 0.0f) {
4617 file <<
"<!-- Average Delta E: " << std::fixed << std::setprecision(2) << average_delta_e <<
" -->" << std::endl;
4619 file <<
"<!-- Matrix Type: " << matrix_type <<
" -->" << std::endl;
4620 file <<
"<!-- Generated: " << getCurrentDateTime() <<
" -->" << std::endl;
4623 file <<
"<helios>" << std::endl;
4624 file <<
" <ColorCorrectionMatrix camera_label=\"" << camera_label <<
"\" matrix_type=\"" << matrix_type <<
"\">" << std::endl;
4626 for (
size_t i = 0; i < matrix.size(); i++) {
4628 for (
size_t j = 0; j < matrix[i].size(); j++) {
4629 file << std::fixed << std::setprecision(6) << matrix[i][j];
4630 if (j < matrix[i].size() - 1) {
4634 file <<
"</row>" << std::endl;
4637 file <<
" </ColorCorrectionMatrix>" << std::endl;
4638 file <<
"</helios>" << std::endl;
4643std::string RadiationModel::getCurrentDateTime() {
4644 auto now = std::time(
nullptr);
4645 auto tm = *std::localtime(&now);
4646 std::stringstream ss;
4647 ss << std::put_time(&tm,
"%Y-%m-%d %H:%M:%S");
4653 std::ifstream file(file_path);
4654 if (!file.is_open()) {
4655 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Failed to open file for reading: " + file_path);
4658 std::vector<std::vector<float>> matrix;
4660 bool in_matrix =
false;
4661 std::string matrix_type =
"";
4663 while (std::getline(file, line)) {
4665 line.erase(0, line.find_first_not_of(
" \t"));
4666 line.erase(line.find_last_not_of(
" \t") + 1);
4669 if (line.find(
"<ColorCorrectionMatrix") != std::string::npos) {
4673 size_t camera_start = line.find(
"camera_label=\"");
4674 if (camera_start != std::string::npos) {
4676 size_t camera_end = line.find(
"\"", camera_start);
4677 if (camera_end != std::string::npos) {
4678 camera_label_out = line.substr(camera_start, camera_end - camera_start);
4683 size_t type_start = line.find(
"matrix_type=\"");
4684 if (type_start != std::string::npos) {
4686 size_t type_end = line.find(
"\"", type_start);
4687 if (type_end != std::string::npos) {
4688 matrix_type = line.substr(type_start, type_end - type_start);
4695 if (line.find(
"</ColorCorrectionMatrix>") != std::string::npos) {
4701 if (in_matrix && line.find(
"<row>") != std::string::npos && line.find(
"</row>") != std::string::npos) {
4703 size_t start = line.find(
"<row>") + 5;
4704 size_t end = line.find(
"</row>");
4705 std::string row_data = line.substr(start, end - start);
4708 std::vector<float> row;
4709 std::istringstream iss(row_data);
4711 while (iss >> value) {
4712 row.push_back(value);
4716 matrix.push_back(row);
4724 if (matrix.empty()) {
4725 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): No matrix data found in file: " + file_path);
4728 if (matrix.size() != 3) {
4729 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix size. Expected 3 rows, found " + std::to_string(matrix.size()) +
" rows in file: " + file_path);
4733 bool is_3x3 = (matrix[0].size() == 3 && matrix[1].size() == 3 && matrix[2].size() == 3);
4734 bool is_4x3 = (matrix[0].size() == 4 && matrix[1].size() == 4 && matrix[2].size() == 4);
4736 if (!is_3x3 && !is_4x3) {
4737 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Invalid matrix dimensions. All rows must have either 3 or 4 elements. File: " + file_path);
4741 if (!matrix_type.empty()) {
4742 if ((matrix_type ==
"3x3" && !is_3x3) || (matrix_type ==
"4x3" && !is_4x3)) {
4743 helios_runtime_error(
"ERROR (RadiationModel::loadColorCorrectionMatrixXML): Matrix type attribute ('" + matrix_type +
"') does not match actual matrix dimensions in file: " + file_path);
4750std::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,
4754 if (cameras.find(camera_label) == cameras.end()) {
4755 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
4759 std::string pixel_UUID_label =
"camera_" + camera_label +
"_pixel_UUID";
4761 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.");
4766 std::vector<std::string> colorboard_types;
4769 }
catch (
const std::exception &e) {
4770 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to detect colorboard types. " + std::string(e.what()));
4774 std::vector<CameraCalibration::LabColor> reference_lab_values;
4775 std::vector<std::string> colorboard_type_per_patch;
4777 for (
const auto &colorboard_type: colorboard_types) {
4778 std::vector<CameraCalibration::LabColor> current_reference_values;
4780 if (colorboard_type ==
"DGK") {
4782 }
else if (colorboard_type ==
"Calibrite") {
4784 }
else if (colorboard_type ==
"SpyderCHECKR") {
4787 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Unsupported colorboard type '" + colorboard_type +
"'.");
4791 reference_lab_values.insert(reference_lab_values.end(), current_reference_values.begin(), current_reference_values.end());
4794 for (
size_t i = 0; i < current_reference_values.size(); i++) {
4795 colorboard_type_per_patch.push_back(colorboard_type);
4800 std::vector<uint> pixel_UUIDs;
4801 context->
getGlobalData(pixel_UUID_label.c_str(), pixel_UUIDs);
4802 int2 camera_resolution = cameras.at(camera_label).resolution;
4805 std::map<int, std::vector<std::vector<bool>>> patch_masks;
4806 int global_patch_idx = 0;
4808 for (
const auto &colorboard_type: colorboard_types) {
4810 int num_patches = 0;
4811 if (colorboard_type ==
"DGK") {
4813 }
else if (colorboard_type ==
"Calibrite" || colorboard_type ==
"SpyderCHECKR") {
4818 for (
int local_patch_idx = 0; local_patch_idx < num_patches; local_patch_idx++) {
4819 std::vector<std::vector<bool>> mask(camera_resolution.
y, std::vector<bool>(camera_resolution.
x,
false));
4822 for (
int y = 0; y < camera_resolution.
y; y++) {
4823 for (
int x = 0; x < camera_resolution.
x; x++) {
4824 int pixel_index = y * camera_resolution.
x + x;
4825 uint pixel_UUID = pixel_UUIDs[pixel_index];
4827 if (pixel_UUID > 0) {
4831 std::string colorboard_data_label =
"colorboard_" + colorboard_type;
4834 context->
getPrimitiveData(pixel_UUID, colorboard_data_label.c_str(), patch_id);
4836 if ((
int) patch_id == local_patch_idx) {
4844 patch_masks[global_patch_idx] = mask;
4851 std::vector<float> red_data, green_data, blue_data;
4854 auto &camera_bands = cameras.at(camera_label).band_labels;
4855 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
4856 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
4858 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
4859 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
4861 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
4862 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
4866 red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
4867 green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
4868 blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
4871 float max_r = *std::max_element(red_data.begin(), red_data.end());
4872 float max_g = *std::max_element(green_data.begin(), green_data.end());
4873 float max_b = *std::max_element(blue_data.begin(), blue_data.end());
4876 float scale_factor = 1.0f;
4877 if (max_r > 1.0f || max_g > 1.0f || max_b > 1.0f) {
4878 scale_factor = 1.0f / std::max({max_r, max_g, max_b});
4880 for (
size_t i = 0; i < red_data.size(); i++) {
4881 red_data[i] *= scale_factor;
4882 green_data[i] *= scale_factor;
4883 blue_data[i] *= scale_factor;
4887 std::vector<helios::vec3> measured_rgb_values;
4888 int visible_patches = 0;
4890 for (
const auto &[patch_idx, mask]: patch_masks) {
4891 float sum_r = 0.0f, sum_g = 0.0f, sum_b = 0.0f;
4892 int pixel_count = 0;
4895 for (
int y = 0; y < camera_resolution.
y; y++) {
4896 for (
int x = 0; x < camera_resolution.
x; x++) {
4898 int pixel_index = y * camera_resolution.
x + x;
4899 sum_r += red_data[pixel_index];
4900 sum_g += green_data[pixel_index];
4901 sum_b += blue_data[pixel_index];
4907 if (pixel_count > 10) {
4909 measured_rgb_values.push_back(avg_rgb);
4914 measured_rgb_values.push_back(
make_vec3(0, 0, 0));
4919 std::vector<CameraCalibration::LabColor> measured_lab_values;
4920 for (
const auto &rgb: measured_rgb_values) {
4921 if (rgb.magnitude() > 0) {
4922 measured_lab_values.push_back(calibration.
rgbToLab(rgb));
4927 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}};
4930 std::string algorithm_name;
4931 switch (algorithm) {
4933 algorithm_name =
"Diagonal scaling (white balance only)";
4936 algorithm_name =
"3x3 matrix with auto-fallback to diagonal";
4939 algorithm_name =
"3x3 matrix (forced)";
4943 if (measured_lab_values.size() >= 6 && reference_lab_values.size() >= 6) {
4945 std::vector<helios::vec3> target_rgb;
4947 for (
size_t i = 0; i < reference_lab_values.size(); i++) {
4950 target_rgb.push_back(ref_rgb);
4958 std::vector<helios::vec3> valid_measured, valid_target;
4959 std::vector<float> patch_weights;
4961 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), target_rgb.size()); i++) {
4962 if (measured_rgb_values[i].magnitude() > 0.01f) {
4963 valid_measured.push_back(measured_rgb_values[i]);
4964 valid_target.push_back(target_rgb[i]);
4967 float weight = 1.0f;
4970 if (i >= 18 && i <= 23) {
4980 else if (i == 14 || i == 13 || i == 12)
4984 else if (i == 3 || i == 10)
4989 float luminance = 0.299f * measured_rgb.
x + 0.587f * measured_rgb.
y + 0.114f * measured_rgb.
z;
4992 if (luminance > 0.6f)
4994 else if (luminance < 0.2f)
5003 patch_weights.push_back(weight);
5011 bool matrix_valid =
true;
5014 std::vector<float> lambda_values = {0.01f, 0.05f, 0.1f, 0.15f, 0.2f};
5015 int lambda_attempt = 0;
5017 while (lambda_attempt < lambda_values.size()) {
5018 float lambda = lambda_values[lambda_attempt];
5019 matrix_valid =
true;
5021 for (
int row = 0; row < 3; row++) {
5023 float ATA[3][3] = {{0}};
5026 for (
size_t i = 0; i < valid_measured.size(); i++) {
5027 float weight = patch_weights[i];
5029 float target_val = (row == 0) ? valid_target[i].x : (row == 1) ? valid_target[i].y : valid_target[i].z;
5032 ATA[0][0] += weight * m.
x * m.
x;
5033 ATA[0][1] += weight * m.
x * m.
y;
5034 ATA[0][2] += weight * m.
x * m.
z;
5035 ATA[1][0] += weight * m.
y * m.
x;
5036 ATA[1][1] += weight * m.
y * m.
y;
5037 ATA[1][2] += weight * m.
y * m.
z;
5038 ATA[2][0] += weight * m.
z * m.
x;
5039 ATA[2][1] += weight * m.
z * m.
y;
5040 ATA[2][2] += weight * m.
z * m.
z;
5042 ATb[0] += weight * m.
x * target_val;
5043 ATb[1] += weight * m.
y * target_val;
5044 ATb[2] += weight * m.
z * target_val;
5050 float diag_reg = lambda * 2.0f;
5051 float offdiag_reg = lambda * 0.5f;
5053 ATA[0][0] += diag_reg;
5054 ATA[1][1] += diag_reg;
5055 ATA[2][2] += diag_reg;
5058 ATA[0][1] += offdiag_reg;
5059 ATA[1][0] += offdiag_reg;
5060 ATA[0][2] += offdiag_reg;
5061 ATA[2][0] += offdiag_reg;
5062 ATA[1][2] += offdiag_reg;
5063 ATA[2][1] += offdiag_reg;
5066 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]);
5068 if (fabs(det) < 1e-3f) {
5070 matrix_valid =
false;
5075 float inv_det = 1.0f / det;
5076 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]));
5078 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]));
5080 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]));
5085 bool elements_reasonable =
true;
5086 for (
int i = 0; i < 3; i++) {
5087 for (
int j = 0; j < 3; j++) {
5088 if (fabs(correction_matrix[i][j]) > 5.0f) {
5090 elements_reasonable =
false;
5095 if (!elements_reasonable)
5098 matrix_valid = elements_reasonable;
5108 if (!matrix_valid) {
5112 float total_weight = 0.0f;
5115 for (
size_t i = 0; i < valid_measured.size(); i++) {
5116 float weight = patch_weights[i];
5121 if (measured.
x > 0.01f && measured.
y > 0.01f && measured.
z > 0.01f) {
5124 weighted_correction.
x += weight * channel_correction.
x;
5125 weighted_correction.
y += weight * channel_correction.
y;
5126 weighted_correction.
z += weight * channel_correction.
z;
5127 total_weight += weight;
5131 if (total_weight > 0.1f) {
5133 correction_matrix[0][0] = weighted_correction.
x / total_weight;
5134 correction_matrix[1][1] = weighted_correction.
y / total_weight;
5135 correction_matrix[2][2] = weighted_correction.
z / total_weight;
5138 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5139 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5140 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5146 correction_matrix = {{1.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}};
5150 if (valid_measured.size() > 0 && patch_weights.size() == valid_measured.size()) {
5151 float total_weight = 0.0f;
5156 for (
size_t i = 0; i < valid_measured.size(); i++) {
5157 float weight = patch_weights[i];
5158 weighted_measured_avg = weighted_measured_avg + weight * valid_measured[i];
5159 weighted_target_avg = weighted_target_avg + weight * valid_target[i];
5160 total_weight += weight;
5163 if (total_weight > 0) {
5164 weighted_measured_avg = weighted_measured_avg / total_weight;
5165 weighted_target_avg = weighted_target_avg / total_weight;
5167 if (weighted_measured_avg.
x > 0.05f && weighted_measured_avg.
y > 0.05f && weighted_measured_avg.
z > 0.05f) {
5168 correction_matrix[0][0] = weighted_target_avg.
x / weighted_measured_avg.
x;
5169 correction_matrix[1][1] = weighted_target_avg.
y / weighted_measured_avg.
y;
5170 correction_matrix[2][2] = weighted_target_avg.
z / weighted_measured_avg.
z;
5173 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5174 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5175 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5179 size_t white_idx = 18;
5180 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
5181 helios::vec3 measured_white = measured_rgb_values[white_idx];
5183 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5184 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
5185 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
5186 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
5193 size_t white_idx = 18;
5194 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size()) {
5195 helios::vec3 measured_white = measured_rgb_values[white_idx];
5197 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5198 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, target_white.
x / measured_white.
x));
5199 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, target_white.
y / measured_white.
y));
5200 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, target_white.
z / measured_white.
z));
5207 size_t white_idx = 18;
5208 if (white_idx < measured_rgb_values.size() && white_idx < target_rgb.size() && measured_rgb_values[white_idx].magnitude() > 0) {
5210 helios::vec3 measured_white = measured_rgb_values[white_idx];
5213 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5214 correction_matrix[0][0] = target_white.
x / measured_white.
x;
5215 correction_matrix[1][1] = target_white.
y / measured_white.
y;
5216 correction_matrix[2][2] = target_white.
z / measured_white.
z;
5219 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5220 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5221 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5225 std::cout <<
"Insufficient valid patches (" << valid_measured.size() <<
" available), using identity matrix" << std::endl;
5229 size_t white_idx = 18;
5230 if (white_idx < measured_rgb_values.size() && white_idx < reference_lab_values.size() && measured_rgb_values[white_idx].magnitude() > 0) {
5234 helios::vec3 measured_white = measured_rgb_values[white_idx];
5236 if (measured_white.
x > 0.05f && measured_white.
y > 0.05f && measured_white.
z > 0.05f) {
5237 correction_matrix[0][0] = target_white.
x / measured_white.
x;
5238 correction_matrix[1][1] = target_white.
y / measured_white.
y;
5239 correction_matrix[2][2] = target_white.
z / measured_white.
z;
5242 correction_matrix[0][0] = std::max(0.5f, std::min(2.0f, correction_matrix[0][0]));
5243 correction_matrix[1][1] = std::max(0.5f, std::min(2.0f, correction_matrix[1][1]));
5244 correction_matrix[2][2] = std::max(0.5f, std::min(2.0f, correction_matrix[2][2]));
5248 std::cout <<
"Insufficient patches for correction (" << measured_lab_values.size() <<
" available), using identity matrix" << std::endl;
5252 if (print_quality_report) {
5253 std::cout <<
"\n========== COLOR CALIBRATION QUALITY REPORT ==========" << std::endl;
5254 std::cout <<
"Colorboard types: ";
5255 for (
size_t i = 0; i < colorboard_types.size(); i++) {
5256 std::cout << colorboard_types[i];
5257 if (i < colorboard_types.size() - 1) {
5261 std::cout << std::endl;
5262 std::cout <<
"Number of patches analyzed: " << visible_patches << std::endl;
5263 std::cout <<
"Algorithm used: " << algorithm_name << std::endl;
5266 bool is_diagonal_only =
true;
5267 for (
int i = 0; i < 3; i++) {
5268 for (
int j = 0; j < 3; j++) {
5269 if (i != j && fabs(correction_matrix[i][j]) > 1e-6f) {
5270 is_diagonal_only =
false;
5274 if (!is_diagonal_only)
5278 if (is_diagonal_only) {
5279 std::cout <<
"Color correction factors applied: R=" << correction_matrix[0][0] <<
", G=" << correction_matrix[1][1] <<
", B=" << correction_matrix[2][2] << std::endl;
5280 std::cout <<
"Matrix type: Diagonal (white balance only)" << std::endl;
5282 std::cout <<
"Full 3x3 color correction matrix applied:" << std::endl;
5283 for (
int i = 0; i < 3; i++) {
5284 std::cout <<
"[" << std::fixed << std::setprecision(4);
5285 for (
int j = 0; j < 3; j++) {
5286 std::cout << std::setw(8) << correction_matrix[i][j];
5290 std::cout <<
"]" << std::endl;
5292 std::cout <<
"Matrix type: Full 3x3 (corrects color casts and chromatic errors)" << std::endl;
5295 float det = correction_matrix[0][0] * (correction_matrix[1][1] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][1]) -
5296 correction_matrix[0][1] * (correction_matrix[1][0] * correction_matrix[2][2] - correction_matrix[1][2] * correction_matrix[2][0]) +
5297 correction_matrix[0][2] * (correction_matrix[1][0] * correction_matrix[2][1] - correction_matrix[1][1] * correction_matrix[2][0]);
5299 std::cout <<
"Matrix determinant: " << std::scientific << std::setprecision(3) << det << std::endl;
5300 if (fabs(det) > 0.1f) {
5301 std::cout <<
"Matrix conditioning: Good (well-conditioned)" << std::endl;
5302 }
else if (fabs(det) > 0.01f) {
5303 std::cout <<
"Matrix conditioning: Fair (moderately conditioned)" << std::endl;
5305 std::cout <<
"Matrix conditioning: Poor (ill-conditioned)" << std::endl;
5307 std::cout << std::fixed;
5311 double total_delta_e = 0.0;
5312 int valid_patches = 0;
5314 std::cout <<
"\nPer-patch analysis (after correction):" << std::endl;
5315 std::cout <<
"Patch | Corrected RGB | Reference RGB | Delta E " << std::endl;
5316 std::cout <<
"------|--------------------|--------------------|---------" << std::endl;
5318 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
5319 if (measured_rgb_values[i].magnitude() > 0) {
5322 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
5323 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
5324 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
5340 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
5342 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 <<
") | ";
5345 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;
5347 total_delta_e += delta_E;
5353 double mean_delta_e = total_delta_e / valid_patches;
5354 std::cout <<
"\n========== OVERALL CALIBRATION QUALITY ==========" << std::endl;
5355 std::cout <<
"Mean Delta E: " << std::fixed << std::setprecision(2) << mean_delta_e << std::endl;
5357 std::cout <<
"======================================================\n" << std::endl;
5361 std::vector<helios::RGBcolor> corrected_pixels;
5362 corrected_pixels.resize(red_data.size());
5365 for (
int j = 0; j < camera_resolution.
y; j++) {
5366 for (
int i = 0; i < camera_resolution.
x; i++) {
5368 int source_index = j * camera_resolution.
x + i;
5369 float r = red_data[source_index];
5370 float g = green_data[source_index];
5371 float b = blue_data[source_index];
5374 float corrected_r = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
5375 float corrected_g = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
5376 float corrected_b = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
5385 uint ii = camera_resolution.
x - i - 1;
5386 uint jj = camera_resolution.
y - j - 1;
5387 uint dest_index = jj * camera_resolution.
x + ii;
5389 corrected_pixels[dest_index] =
make_RGBcolor(corrected_r, corrected_g, corrected_b);
5394 std::string output_path = output_file_path;
5395 if (output_path.empty()) {
5396 output_path =
"auto_calibrated_" + camera_label +
".jpg";
5400 helios::writeJPEG(output_path, camera_resolution.
x, camera_resolution.
y, corrected_pixels);
5401 std::cout <<
"Wrote corrected image to: " << output_path << std::endl;
5402 }
catch (
const std::exception &e) {
5403 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to write corrected image. " + std::string(e.what()));
5407 if (!ccm_export_file_path.empty()) {
5410 double total_delta_e = 0.0;
5411 int valid_patches = 0;
5413 for (
size_t i = 0; i < std::min(measured_rgb_values.size(), reference_lab_values.size()); i++) {
5414 if (measured_rgb_values[i].magnitude() > 0) {
5417 float corrected_r = correction_matrix[0][0] * measured_rgb.
x + correction_matrix[0][1] * measured_rgb.
y + correction_matrix[0][2] * measured_rgb.
z;
5418 float corrected_g = correction_matrix[1][0] * measured_rgb.
x + correction_matrix[1][1] * measured_rgb.
y + correction_matrix[1][2] * measured_rgb.
z;
5419 float corrected_b = correction_matrix[2][0] * measured_rgb.
x + correction_matrix[2][1] * measured_rgb.
y + correction_matrix[2][2] * measured_rgb.
z;
5428 double delta_E = calibration.
deltaE2000(corrected_lab, reference_lab);
5429 total_delta_e += delta_E;
5434 double mean_delta_e = (valid_patches > 0) ? (total_delta_e / valid_patches) : -1.0;
5438 std::string colorboard_types_str;
5439 for (
size_t i = 0; i < colorboard_types.size(); i++) {
5440 colorboard_types_str += colorboard_types[i];
5441 if (i < colorboard_types.size() - 1) {
5442 colorboard_types_str +=
", ";
5447 std::cout <<
"Exported color correction matrix to: " << ccm_export_file_path << std::endl;
5448 }
catch (
const std::exception &e) {
5449 helios_runtime_error(
"ERROR (RadiationModel::autoCalibrateCameraImage): Failed to export CCM to XML. " + std::string(e.what()));
5459 if (cameras.find(camera_label) == cameras.end()) {
5460 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Camera '" + camera_label +
"' does not exist. Make sure the camera was added to the radiation model.");
5464 auto &camera_bands = cameras.at(camera_label).band_labels;
5465 if (std::find(camera_bands.begin(), camera_bands.end(), red_band_label) == camera_bands.end()) {
5466 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Red band '" + red_band_label +
"' not found in camera '" + camera_label +
"'.");
5468 if (std::find(camera_bands.begin(), camera_bands.end(), green_band_label) == camera_bands.end()) {
5469 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Green band '" + green_band_label +
"' not found in camera '" + camera_label +
"'.");
5471 if (std::find(camera_bands.begin(), camera_bands.end(), blue_band_label) == camera_bands.end()) {
5472 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Blue band '" + blue_band_label +
"' not found in camera '" + camera_label +
"'.");
5476 std::string loaded_camera_label;
5477 std::vector<std::vector<float>> correction_matrix;
5480 }
catch (
const std::exception &e) {
5481 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Failed to load CCM from XML file. " + std::string(e.what()));
5485 if (correction_matrix.size() != 3) {
5486 helios_runtime_error(
"ERROR (RadiationModel::applyCameraColorCorrectionMatrix): Invalid matrix dimensions. Expected 3x3 or 4x3 matrix, got " + std::to_string(correction_matrix.size()) +
" rows.");
5489 bool is_3x3 = (correction_matrix[0].size() == 3);
5490 bool is_4x3 = (correction_matrix[0].size() == 4);
5492 if (!is_3x3 && !is_4x3) {
5493 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()) +
5498 std::vector<float> &red_data = cameras.at(camera_label).pixel_data.at(red_band_label);
5499 std::vector<float> &green_data = cameras.at(camera_label).pixel_data.at(green_band_label);
5500 std::vector<float> &blue_data = cameras.at(camera_label).pixel_data.at(blue_band_label);
5502 int2 camera_resolution = cameras.at(camera_label).resolution;
5503 size_t pixel_count = red_data.size();
5506 for (
size_t i = 0; i < pixel_count; i++) {
5507 float r = red_data[i];
5508 float g = green_data[i];
5509 float b = blue_data[i];
5514 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b;
5515 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b;
5516 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b;
5519 red_data[i] = correction_matrix[0][0] * r + correction_matrix[0][1] * g + correction_matrix[0][2] * b + correction_matrix[0][3];
5520 green_data[i] = correction_matrix[1][0] * r + correction_matrix[1][1] * g + correction_matrix[1][2] * b + correction_matrix[1][3];
5521 blue_data[i] = correction_matrix[2][0] * r + correction_matrix[2][1] * g + correction_matrix[2][2] * b + correction_matrix[2][3];
5526 std::cout <<
"Applied color correction matrix from '" << ccm_file_path <<
"' to camera '" << camera_label <<
"'" << std::endl;
5527 std::cout <<
"Matrix type: " << (is_3x3 ?
"3x3" :
"4x3") <<
", processed " << pixel_count <<
" pixels" << std::endl;
5532 if (cameras.find(camera_label) == cameras.end()) {
5533 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Camera '" + camera_label +
"' does not exist.");
5536 auto &camera_pixel_data = cameras.at(camera_label).pixel_data;
5537 if (camera_pixel_data.find(band_label) == camera_pixel_data.end()) {
5538 helios_runtime_error(
"ERROR (RadiationModel::getCameraPixelData): Band '" + band_label +
"' does not exist in camera '" + camera_label +
"'.");
5541 return camera_pixel_data.at(band_label);
5545 if (cameras.find(camera_label) == cameras.end()) {
5546 helios_runtime_error(
"ERROR (RadiationModel::setCameraPixelData): Camera '" + camera_label +
"' does not exist.");
5549 cameras.at(camera_label).pixel_data[band_label] = pixel_data;
5554void RadiationModel::queryBackendGPUMemory()
const {
5556 backend->queryGPUMemory();
5558 std::cout <<
"Backend not initialized - cannot query GPU memory." << std::endl;
5568 params.camera_position = camera.position;
5573 params.camera_focal_length = camera.focal_length;
5574 params.camera_lens_diameter = camera.lens_diameter;
5575 params.camera_fov_aspect = camera.FOV_aspect_ratio;
5578 params.camera_resolution = tile_resolution;
5579 params.camera_resolution_full = camera.resolution;
5580 params.camera_pixel_offset = tile_offset;
5581 params.antialiasing_samples = antialiasing_samples;
5582 params.camera_id = camera_id;
5585 float effective_HFOV = camera.HFOV_degrees / camera.camera_zoom;
5586 params.camera_HFOV = effective_HFOV *
M_PI / 180.0f;
5587 params.camera_viewplane_length = 0.5f / tanf(0.5f * effective_HFOV *
M_PI / 180.f);
5590 float HFOV_rad = effective_HFOV *
M_PI / 180.f;
5591 float VFOV_rad = HFOV_rad / camera.FOV_aspect_ratio;
5592 float pixel_angle_h = HFOV_rad / float(camera.resolution.
x);
5593 float pixel_angle_v = VFOV_rad / float(camera.resolution.
y);
5594 params.camera_pixel_solid_angle = pixel_angle_h * pixel_angle_v;
5597 params.scattering_iteration = 0;
5600 params.specular_reflection_enabled = specular_reflection_mode;
5605std::vector<CameraTile> RadiationModel::computeCameraTiles(
const RadiationCamera &camera,
size_t maxRays) {
5607 std::vector<CameraTile> tiles;
5609 size_t total_rays = size_t(camera.antialiasing_samples) * size_t(camera.resolution.
x) * size_t(camera.resolution.
y);
5612 if (total_rays <= maxRays) {
5618 size_t rays_per_row = size_t(camera.antialiasing_samples) * size_t(camera.resolution.
x);
5619 size_t max_rows_per_tile = floor(
float(maxRays) /
float(rays_per_row));
5621 if (max_rows_per_tile == 0) {
5623 size_t max_pixels_per_tile = floor(
float(maxRays) /
float(camera.antialiasing_samples));
5625 float aspect = float(camera.resolution.
x) / float(camera.resolution.
y);
5626 size_t tile_width = round(sqrt(max_pixels_per_tile * aspect));
5627 size_t tile_height = floor(
float(max_pixels_per_tile) /
float(tile_width));
5629 tile_width = std::min(tile_width,
size_t(camera.resolution.
x));
5630 tile_height = std::min(tile_height,
size_t(camera.resolution.
y));
5632 int Ntiles_x = ceil(
float(camera.resolution.
x) /
float(tile_width));
5633 int Ntiles_y = ceil(
float(camera.resolution.
y) /
float(tile_height));
5635 for (
int ty = 0; ty < Ntiles_y; ty++) {
5636 for (
int tx = 0; tx < Ntiles_x; tx++) {
5637 size_t offset_x = tx * tile_width;
5638 size_t offset_y = ty * tile_height;
5639 size_t width_this = std::min(tile_width, camera.resolution.
x - offset_x);
5640 size_t height_this = std::min(tile_height, camera.resolution.
y - offset_y);
5647 size_t rows_per_tile = std::min(max_rows_per_tile,
size_t(camera.resolution.
y));
5648 int Ntiles = ceil(
float(camera.resolution.
y) /
float(rows_per_tile));
5650 for (
int t = 0; t < Ntiles; t++) {
5651 size_t offset_y = t * rows_per_tile;
5652 size_t height_this = std::min(rows_per_tile, camera.resolution.
y - offset_y);
5661void RadiationModel::buildGeometryData(
const std::vector<uint> &UUIDs) {
5666 std::vector<uint> valid_UUIDs;
5667 for (
uint UUID: UUIDs) {
5673 if ((area == 0 || std::isnan(area)) && context->
getObjectType(parentID) != helios::OBJECT_TYPE_TILE) {
5676 valid_UUIDs.push_back(UUID);
5679 if (valid_UUIDs.empty()) {
5686 std::vector<uint> primitive_UUIDs_ordered;
5687 std::unordered_set<uint> valid_set(valid_UUIDs.begin(), valid_UUIDs.end());
5689 for (
uint objID: objID_all) {
5694 std::sort(prim_UUIDs.begin(), prim_UUIDs.end());
5696 for (
uint UUID: prim_UUIDs) {
5698 primitive_UUIDs_ordered.push_back(UUID);
5703 size_t Nprimitives = primitive_UUIDs_ordered.size();
5716 geometry_data.
object_IDs.resize(Nprimitives);
5741 uint current_objID = 0;
5742 uint last_parentID = 99999;
5744 std::vector<uint> primitiveID_indices;
5746 for (
size_t u = 0; u < Nprimitives; u++) {
5747 uint UUID = primitive_UUIDs_ordered[u];
5750 if (last_parentID != parentID || parentID == 0 || context->
getObjectType(parentID) == helios::OBJECT_TYPE_TILE) {
5751 primitiveID_indices.push_back(u);
5752 last_parentID = parentID;
5755 last_parentID = parentID;
5758 geometry_data.
object_IDs[u] = current_objID - 1;
5761 size_t Nobjects = primitiveID_indices.size();
5764 primitiveID = primitiveID_indices;
5768 std::vector<uint> primitiveID_for_backend(Nprimitives);
5769 for (
size_t i = 0; i < Nprimitives; i++) {
5770 primitiveID_for_backend[i] = primitive_UUIDs_ordered[i];
5777 size_t patch_idx = 0, tri_idx = 0, disk_idx = 0, voxel_idx = 0, bbox_idx = 0;
5781 for (
size_t prim_idx = 0; prim_idx < Nprimitives; prim_idx++) {
5782 uint UUID = primitive_UUIDs_ordered[prim_idx];
5795 if (parentID > 0 && context->
getObjectType(parentID) == helios::OBJECT_TYPE_TILE) {
5805 for (
const auto &v: verts) {
5813 }
else if (type == helios::PRIMITIVE_TYPE_PATCH) {
5820 for (
const auto &v: verts) {
5831 }
else if (type == helios::PRIMITIVE_TYPE_TRIANGLE) {
5838 for (
const auto &v: verts) {
5846 }
else if (type == helios::PRIMITIVE_TYPE_VOXEL) {
5853 for (
const auto &v: verts) {
5875 vec2 xbounds, ybounds, zbounds;
5879 if (periodic_flag.
x == 1 || periodic_flag.
y == 1) {
5880 if (!cameras.empty()) {
5881 for (
auto &camera: cameras) {
5882 vec3 camerapos = camera.second.position;
5883 if (camerapos.
x < xbounds.
x || camerapos.
x > xbounds.
y || camerapos.
y < ybounds.
x || camerapos.
y > ybounds.
y) {
5884 std::cout <<
"WARNING (RadiationModel::buildGeometryData): camera position is outside of the domain bounding box. Disabling periodic boundary conditions." << std::endl;
5885 periodic_flag.
x = 0;
5886 periodic_flag.
y = 0;
5890 if (camerapos.
z < zbounds.
x) {
5891 zbounds.
x = camerapos.
z;
5893 if (camerapos.
z > zbounds.
y) {
5894 zbounds.
y = camerapos.
z;
5911 uint bbox_UUID_base = max_UUID + 1;
5914 if (periodic_flag.
x == 1) {
5920 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5928 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5932 if (periodic_flag.
y == 1) {
5938 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5946 geometry_data.
bboxes.
UUIDs.push_back(bbox_UUID_base + bbox_idx);
5978 uint bbox_max_UUID = max_UUID;
5994 for (
size_t i = 0; i < geometry_data.
bbox_count; i++) {
6002void RadiationModel::buildTextureData() {
6010 geometry_data.
uv_data.clear();
6014 geometry_data.
mask_IDs.resize(Nobjects, -1);
6015 geometry_data.
uv_IDs.clear();
6016 geometry_data.
uv_IDs.resize(Nobjects, -1);
6019 std::map<std::string, int> texture_to_mask_idx;
6021 for (
size_t prim_idx = 0; prim_idx < Nobjects; prim_idx++) {
6026 if (texture_file.empty()) {
6037 auto cache_it = texture_to_mask_idx.find(texture_file);
6038 if (cache_it != texture_to_mask_idx.end()) {
6040 mask_idx = cache_it->second;
6046 mask_idx =
static_cast<int>(geometry_data.
mask_sizes.size());
6047 texture_to_mask_idx[texture_file] = mask_idx;
6051 for (
int y = 0; y < tex_size.
y; y++) {
6052 for (
int x = 0; x < tex_size.
x; x++) {
6053 geometry_data.
mask_data.push_back((*trans_data)[y][x]);
6056 geometry_data.
mask_sizes.push_back(tex_size);
6059 geometry_data.
mask_IDs[prim_idx] = mask_idx;
6065 geometry_data.
uv_IDs[prim_idx] =
static_cast<int>(prim_idx);
6066 for (
const auto &uv: uvs) {
6067 geometry_data.
uv_data.push_back(uv);
6070 size_t start_idx = geometry_data.
uv_data.size() - uvs.size();
6071 while (geometry_data.
uv_data.size() - start_idx < 4) {
6072 geometry_data.
uv_data.push_back(uvs.back());
6079size_t RadiationModel::testBuildGeometryData() {
6084void RadiationModel::buildUUIDMapping() {
6088 uuid_to_position.clear();
6089 position_to_uuid.clear();
6095 uuid_to_position[UUID] = i;
6096 position_to_uuid.push_back(UUID);
6104static void validateAndCorrectMaterialProperties(
float &rho,
float &tau,
float eps,
bool emission_enabled,
uint scattering_depth,
const std::string &band_label,
uint UUID,
bool is_sif_band =
false,
6110 if (rho < 0.f || rho > 1.f) {
6112 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.");
6114 rho = std::max(0.f, std::min(1.f, rho));
6117 if (tau < 0.f || tau > 1.f) {
6119 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.");
6121 tau = std::max(0.f, std::min(1.f, tau));
6130 if (rho + tau > 1.f) {
6131 helios_runtime_error(std::string(
"ERROR (RadiationModel): reflectivity and transmissivity must sum to less than or equal to 1 to ensure energy conservation. Band ") + band_label +
", Primitive #" +
6132 std::to_string(UUID) +
": tau=" + std::to_string(tau) +
", rho=" + std::to_string(rho) +
".");
6138 if (emission_enabled) {
6140 if (scattering_depth == 0 && eps != 1.f) {
6141 if (warnings && (rho != 0.f || tau != 0.f)) {
6142 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));
6148 else if (eps != 1.f && rho == 0 && tau == 0) {
6151 }
else if (std::abs(eps + rho + tau - 1.f) > 1e-5f && eps > 0.f) {
6153 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) +
6154 ": 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.");
6158 if (rho + tau > 1.f) {
6159 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) +
6160 ": 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.");
6165void RadiationModel::buildMaterialData() {
6172 size_t Nbands = radiation_bands.size();
6173 size_t Nsources = radiation_sources.size();
6182 size_t total_size = Nsources * Nbands * Nprims;
6192 std::map<std::string, std::vector<helios::vec2>> unique_rho_spectra;
6193 std::map<std::string, std::vector<helios::vec2>> unique_tau_spectra;
6195 for (
size_t p = 0; p < Nprims; p++) {
6200 std::string spectrum_label;
6202 if (unique_rho_spectra.find(spectrum_label) == unique_rho_spectra.end()) {
6205 unique_rho_spectra[spectrum_label] = loadSpectralData(spectrum_label);
6212 std::string spectrum_label;
6213 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
6214 if (unique_tau_spectra.find(spectrum_label) == unique_tau_spectra.end()) {
6217 unique_tau_spectra[spectrum_label] = loadSpectralData(spectrum_label);
6225 for (
const auto &band_pair: radiation_bands) {
6226 std::string band_label = band_pair.second.label;
6228 for (
size_t s = 0; s < Nsources; s++) {
6229 for (
size_t p = 0; p < Nprims; p++) {
6234 size_t idx = mat_indexer(s, p, b_idx);
6237 float rho = rho_default;
6241 std::string spectrum_label;
6245 if (unique_rho_spectra.find(spectrum_label) != unique_rho_spectra.end()) {
6246 const std::vector<helios::vec2> &spectrum = unique_rho_spectra.at(spectrum_label);
6249 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
6254 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
6256 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
6257 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
6261 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
6262 if (!radiation_sources[s].source_spectrum.empty()) {
6274 std::string rho_label =
"reflectivity_" + band_label;
6281 float tau = tau_default;
6285 std::string spectrum_label;
6286 context->
getPrimitiveData(UUID,
"transmissivity_spectrum", spectrum_label);
6289 if (unique_tau_spectra.find(spectrum_label) != unique_tau_spectra.end()) {
6290 const std::vector<helios::vec2> &spectrum = unique_tau_spectra.at(spectrum_label);
6293 helios::vec2 wavebounds = band_pair.second.wavebandBounds;
6298 bool needs_spectral_integration = (band_pair.second.scatteringDepth > 0);
6300 if (needs_spectral_integration && wavebounds.
x == 0 && wavebounds.
y == 0) {
6301 helios_runtime_error(
"ERROR (RadiationModel::buildMaterialData): Band '" + band_label +
"' has no wavelength bounds - required for spectral integration");
6305 if (wavebounds.
x != 0 || wavebounds.
y != 0) {
6306 if (!radiation_sources[s].source_spectrum.empty()) {
6318 std::string tau_label =
"transmissivity_" + band_label;
6325 float eps = eps_default;
6326 std::string eps_label =
"emissivity_" + band_label;
6335 const bool is_sif_band = sif_emission_bands.count(band_label) > 0;
6336 validateAndCorrectMaterialProperties(rho, tau, eps, band.
emissionFlag, band.
scatteringDepth, band_label, UUID, is_sif_band, &warnings);
6350 bool specular_exponent_specified =
false;
6351 bool specular_scale_specified =
false;
6353 for (
size_t p = 0; p < Nprims; p++) {
6359 specular_exponent_specified =
true;
6366 specular_scale_specified =
true;
6372 if (specular_exponent_specified) {
6373 if (specular_scale_specified) {
6374 specular_reflection_mode = 2;
6376 specular_reflection_mode = 1;
6379 specular_reflection_mode = 0;
6386void RadiationModel::buildSourceData() {
6389 source_data.clear();
6390 source_data.reserve(radiation_sources.size());
6392 for (
size_t s = 0; s < radiation_sources.size(); s++) {
6393 const auto &src = radiation_sources[s];
6395 backend_src.
position = src.source_position;
6396 backend_src.
rotation = src.source_rotation;
6397 backend_src.
width = src.source_width;
6398 backend_src.
type = src.source_type;
6401 backend_src.
fluxes.clear();
6403 for (
const auto &band_pair: radiation_bands) {
6404 std::string band_label = band_pair.second.label;
6407 backend_src.
fluxes.push_back(flux);
6411 source_data.push_back(backend_src);
6417 return backend.get();
6421 return geometry_data;
6425 return material_data;
6428std::vector<helios::RayTracingSource> &RadiationModel::getSourceData() {
6433void RadiationModel::testBuildAllBackendData() {
6435 buildMaterialData();