1.3.64
 
Loading...
Searching...
No Matches
PragueSkyModelInterface.cpp
Go to the documentation of this file.
1
19#include "PragueSkyModel.h"
20#include "global.h"
21#include <cmath>
22#include <algorithm>
23#include <sstream>
24
25using namespace helios;
26
27// Constructor
28PragueSkyModelInterface::PragueSkyModelInterface() : model(nullptr), initialized(false) {
29}
30
31// Destructor
33 // unique_ptr automatically cleans up
34}
35
36// Initialize the Prague Sky Model
37void PragueSkyModelInterface::initialize(const std::string &dataset_path) {
38 if (initialized) {
39 helios_runtime_error("ERROR (PragueSkyModelInterface::initialize): Model already initialized.");
40 }
41
42 // Create Prague model instance
43 model = std::make_unique<PragueSkyModel>();
44
45 // Try to initialize with dataset file
46 // Prague model throws exceptions, we need to catch and convert to helios_runtime_error
47 try {
48 model->initialize(dataset_path);
50 std::ostringstream oss;
51 oss << "ERROR (PragueSkyModelInterface::initialize): Prague dataset file not found: " << dataset_path << "\n"
52 << "Please ensure PragueSkyModelReduced.dat is present in plugins/radiation/spectral_data/prague_sky_model/\n"
53 << "The reduced dataset should be ~26 MB.";
54 helios_runtime_error(oss.str());
55 } catch (const PragueSkyModel::DatasetReadException &e) {
56 std::ostringstream oss;
57 oss << "ERROR (PragueSkyModelInterface::initialize): Failed to read Prague dataset: " << e.what();
58 helios_runtime_error(oss.str());
59 } catch (const std::exception &e) {
60 std::ostringstream oss;
61 oss << "ERROR (PragueSkyModelInterface::initialize): Unexpected error initializing Prague model: " << e.what();
62 helios_runtime_error(oss.str());
63 }
64
65 this->dataset_path = dataset_path;
66 initialized = true;
67}
68
69// Check if initialized
71 return initialized;
72}
73
74// Convert Helios sun direction to Prague elevation and azimuth
75void PragueSkyModelInterface::sunDirectionToAngles(const vec3 &sun_dir, double &elevation_deg, double &azimuth_deg) const {
76 // Helios: sun_dir is unit vector from origin toward sun
77 // Z component gives elevation: sun_dir.z = sin(elevation)
78 // X,Y components give azimuth
79
80 // Convert to spherical coordinates
81 SphericalCoord sun_sphere = cart2sphere(sun_dir);
82
83 // sun_sphere.zenith is angle from vertical (radians)
84 // Prague elevation is angle from horizontal (degrees)
85 double zenith_rad = sun_sphere.zenith;
86 elevation_deg = 90.0 - (zenith_rad * 180.0 / M_PI);
87
88 // sun_sphere.azimuth is in radians
89 // Prague expects azimuth from North, increasing clockwise
90 // Helios azimuth convention needs verification, but typically:
91 // 0 = +X axis, increasing counterclockwise when viewed from above
92 // Prague: 0 = North (+Y), increasing clockwise
93 // Conversion: prague_azimuth = 90 - helios_azimuth
94 double helios_azimuth_deg = sun_sphere.azimuth * 180.0 / M_PI;
95 azimuth_deg = 90.0 - helios_azimuth_deg;
96
97 // Normalize azimuth to [0, 360)
98 while (azimuth_deg < 0.0)
99 azimuth_deg += 360.0;
100 while (azimuth_deg >= 360.0)
101 azimuth_deg -= 360.0;
102}
103
104// Get sky radiance at single wavelength
105float PragueSkyModelInterface::getSkyRadiance(const vec3 &view_direction, const vec3 &sun_direction, float wavelength_nm, float visibility_km, float albedo) const {
106 if (!initialized) {
107 helios_runtime_error("ERROR (PragueSkyModelInterface::getSkyRadiance): Model not initialized. Call initialize() first.");
108 }
109
110 // Convert sun direction to elevation and azimuth
111 double solar_elevation_deg, solar_azimuth_deg;
112 sunDirectionToAngles(sun_direction, solar_elevation_deg, solar_azimuth_deg);
113
114 // Set default albedo if not provided
115 if (albedo < 0.0f) {
116 // Use default from reduced dataset (0.33 - typical vegetation)
117 albedo = 0.33f;
118 }
119
120 // Create Prague Vector3 for view direction
121 PragueSkyModel::Vector3 prague_view_dir(view_direction.x, view_direction.y, view_direction.z);
122
123 // Observer at ground level (altitude = 0)
124 PragueSkyModel::Vector3 view_point(0.0, 0.0, 0.0);
125
126 // Compute Prague parameters
128 try {
129 params = model->computeParameters(view_point, prague_view_dir,
130 solar_elevation_deg * M_PI / 180.0, // Convert to radians
131 solar_azimuth_deg * M_PI / 180.0, visibility_km, albedo);
132 } catch (const PragueSkyModel::NotInitializedException &e) {
133 helios_runtime_error("ERROR (PragueSkyModelInterface::getSkyRadiance): Prague model not initialized.");
134 } catch (const std::exception &e) {
135 std::ostringstream oss;
136 oss << "ERROR (PragueSkyModelInterface::getSkyRadiance): Error computing Prague parameters: " << e.what();
137 helios_runtime_error(oss.str());
138 }
139
140 // Query sky radiance at wavelength
141 double radiance_Wm2_sr_nm = 0.0;
142 try {
143 radiance_Wm2_sr_nm = model->skyRadiance(params, wavelength_nm);
144 } catch (const PragueSkyModel::NotInitializedException &e) {
145 helios_runtime_error("ERROR (PragueSkyModelInterface::getSkyRadiance): Prague model not initialized.");
146 } catch (const std::exception &e) {
147 std::ostringstream oss;
148 oss << "ERROR (PragueSkyModelInterface::getSkyRadiance): Error querying sky radiance: " << e.what();
149 helios_runtime_error(oss.str());
150 }
151
152 return static_cast<float>(radiance_Wm2_sr_nm);
153}
154
155// Compute integrated sky radiance over camera spectral response
156float PragueSkyModelInterface::computeIntegratedSkyRadiance(const vec3 &view_direction, const vec3 &sun_direction, float visibility_km, const std::vector<vec2> &camera_response, float albedo) const {
157 if (!initialized) {
158 helios_runtime_error("ERROR (PragueSkyModelInterface::computeIntegratedSkyRadiance): Model not initialized.");
159 }
160
161 if (camera_response.empty()) {
162 helios_runtime_error("ERROR (PragueSkyModelInterface::computeIntegratedSkyRadiance): Empty camera response.");
163 }
164
165 // Get wavelength range
166 float lambda_min = camera_response.front().x;
167 float lambda_max = camera_response.back().x;
168
169 if (lambda_min >= lambda_max) {
170 helios_runtime_error("ERROR (PragueSkyModelInterface::computeIntegratedSkyRadiance): Invalid wavelength range.");
171 }
172
173 // Determine number of wavelength samples for integration
174 // Use adaptive sampling: more samples for broader bands
175 float wavelength_span = lambda_max - lambda_min;
176 int n_samples = std::max(10, std::min(50, static_cast<int>(wavelength_span / 20.0f)));
177
178 float dlambda = wavelength_span / static_cast<float>(n_samples);
179
180 // Integrate: ∫ L(λ) × R(λ) dλ
181 // This gives the band-specific radiance weighted by camera spectral response
182 double integrated_radiance = 0.0;
183
184 for (int i = 0; i < n_samples; ++i) {
185 // Sample at midpoint of interval
186 float lambda = lambda_min + (i + 0.5f) * dlambda;
187
188 // Interpolate camera response at this wavelength
189 float response = interp1(camera_response, lambda);
190
191 if (response <= 0.0f) {
192 continue; // Skip wavelengths with zero response
193 }
194
195 // Query Prague model for sky radiance at this wavelength
196 float sky_rad = getSkyRadiance(view_direction, sun_direction, lambda, visibility_km, albedo);
197
198 // Integrate: ∫ L(λ) × R(λ) dλ
199 // L(λ) is in W/m²/sr/nm, R(λ) is unitless (0-1), dλ is in nm
200 // Result has units: W/m²/sr
201 integrated_radiance += sky_rad * response * dlambda;
202 }
203
204 return static_cast<float>(integrated_radiance);
205}
206
207// Convert turbidity to visibility
209 // Koschmieder formula: V = 3.912 / (β × (λ/500)^α)
210 // For 500 nm and typical conditions: V ≈ 3.9 / turbidity
211 // where turbidity is Ångström AOD at 500 nm
212
213 if (turbidity <= 0.0f) {
214 // Very clear conditions - return maximum visibility
215 return 131.8f;
216 }
217
218 float visibility_km = 3.9f / turbidity;
219
220 // Clamp to Prague dataset range [20, 131.8 km]
221 visibility_km = std::max(20.0f, std::min(131.8f, visibility_km));
222
223 return visibility_km;
224}
225
226// Get available data ranges
227void PragueSkyModelInterface::getAvailableRanges(float &min_wavelength_nm, float &max_wavelength_nm, float &min_visibility_km, float &max_visibility_km, float &min_elevation_deg, float &max_elevation_deg) const {
228 if (!initialized) {
229 helios_runtime_error("ERROR (PragueSkyModelInterface::getAvailableRanges): Model not initialized.");
230 }
231
232 try {
233 PragueSkyModel::AvailableData available = model->getAvailableData();
234
235 // Wavelength range
236 min_wavelength_nm = static_cast<float>(available.channelStart);
237 max_wavelength_nm = static_cast<float>(available.channelStart + available.channelWidth * (available.channels - 1));
238
239 // Visibility range
240 min_visibility_km = static_cast<float>(available.visibilityMin);
241 max_visibility_km = static_cast<float>(available.visibilityMax);
242
243 // Elevation range (convert from radians to degrees)
244 min_elevation_deg = static_cast<float>(available.elevationMin * 180.0 / M_PI);
245 max_elevation_deg = static_cast<float>(available.elevationMax * 180.0 / M_PI);
246
247 } catch (const PragueSkyModel::NotInitializedException &e) {
248 helios_runtime_error("ERROR (PragueSkyModelInterface::getAvailableRanges): Prague model not initialized.");
249 } catch (const std::exception &e) {
250 std::ostringstream oss;
251 oss << "ERROR (PragueSkyModelInterface::getAvailableRanges): Error querying available data: " << e.what();
252 helios_runtime_error(oss.str());
253 }
254}