1.3.49
 
Loading...
Searching...
No Matches
VisualizerCore.cpp
Go to the documentation of this file.
1
16// OpenGL Includes
17#include <GL/glew.h>
18#include <GLFW/glfw3.h>
19
20// Freetype Libraries (rendering fonts)
21extern "C" {
22#include <ft2build.h>
23#include FT_FREETYPE_H
24}
25
26#include "Visualizer.h"
27
28using namespace helios;
29
30int read_JPEG_file(const char *filename, std::vector<unsigned char> &texture, uint &height, uint &width) {
31 std::vector<helios::RGBcolor> rgb_data;
32 helios::readJPEG(filename, width, height, rgb_data);
33
34 texture.clear();
35 texture.reserve(width * height * 4);
36
37 for (const auto &pixel: rgb_data) {
38 texture.push_back(static_cast<unsigned char>(pixel.r * 255.0f));
39 texture.push_back(static_cast<unsigned char>(pixel.g * 255.0f));
40 texture.push_back(static_cast<unsigned char>(pixel.b * 255.0f));
41 texture.push_back(255); // alpha channel - opaque
42 }
43
44 return 0;
45}
46
47int write_JPEG_file(const char *filename, uint width, uint height, bool print_messages) {
48 if (print_messages) {
49 std::cout << "writing JPEG image: " << filename << std::endl;
50 }
51
52 const size_t bsize = 3 * width * height;
53 std::vector<GLubyte> screen_shot_trans;
54 screen_shot_trans.resize(bsize);
55
56 // Read from front buffer since we may be in headless mode or have complex timing
57 // The front buffer should contain the most recent rendered content
58 constexpr GLenum read_buf = GL_FRONT;
59 glReadBuffer(read_buf);
60 glReadPixels(0, 0, scast<GLsizei>(width), scast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, &screen_shot_trans[0]);
61 glFinish();
62
63 // Convert to RGBcolor vector and use Context's writeJPEG
64 std::vector<helios::RGBcolor> rgb_data;
65 rgb_data.reserve(width * height);
66
67 for (size_t i = 0; i < width * height; i++) {
68 size_t byte_idx = i * 3;
69 rgb_data.emplace_back(screen_shot_trans[byte_idx] / 255.0f, screen_shot_trans[byte_idx + 1] / 255.0f, screen_shot_trans[byte_idx + 2] / 255.0f);
70 }
71
72 helios::writeJPEG(filename, width, height, rgb_data);
73 return 1;
74}
75
76int write_JPEG_file(const char *filename, uint width, uint height, const std::vector<helios::RGBcolor> &data, bool print_messages) {
77 if (print_messages) {
78 std::cout << "writing JPEG image: " << filename << std::endl;
79 }
80
81 helios::writeJPEG(filename, width, height, data);
82 return 1;
83}
84
85void read_png_file(const char *filename, std::vector<unsigned char> &texture, uint &height, uint &width) {
86 std::vector<helios::RGBAcolor> rgba_data;
87 helios::readPNG(filename, width, height, rgba_data);
88
89 texture.clear();
90 texture.reserve(width * height * 4);
91
92 for (const auto &pixel: rgba_data) {
93 texture.push_back(static_cast<unsigned char>(pixel.r * 255.0f));
94 texture.push_back(static_cast<unsigned char>(pixel.g * 255.0f));
95 texture.push_back(static_cast<unsigned char>(pixel.b * 255.0f));
96 texture.push_back(static_cast<unsigned char>(pixel.a * 255.0f));
97 }
98}
99
100Visualizer::Visualizer(uint Wdisplay) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray() {
101 initialize(Wdisplay, uint(std::round(Wdisplay * 0.8)), 16, true, false);
102}
103
104Visualizer::Visualizer(uint Wdisplay, uint Hdisplay) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray() {
105 initialize(Wdisplay, Hdisplay, 16, true, false);
106}
107
108Visualizer::Visualizer(uint Wdisplay, uint Hdisplay, int aliasing_samples) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray() {
109 initialize(Wdisplay, Hdisplay, aliasing_samples, true, false);
110}
111
112Visualizer::Visualizer(uint Wdisplay, uint Hdisplay, int aliasing_samples, bool window_decorations, bool headless) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray() {
113 initialize(Wdisplay, Hdisplay, aliasing_samples, window_decorations, headless);
114}
115
116void Visualizer::openWindow() {
117 // Open a window and create its OpenGL context
118 GLFWwindow *_window = glfwCreateWindow(Wdisplay, Hdisplay, "Helios 3D Simulation", nullptr, nullptr);
119 if (_window == nullptr) {
120 std::string errorsrtring;
121 errorsrtring.append("ERROR(Visualizer): Failed to initialize graphics.\n");
122 errorsrtring.append("Common causes for this error:\n");
123 errorsrtring.append("-- OSX\n - Is XQuartz installed (xquartz.org) and configured as the default X11 window handler? When running the visualizer, XQuartz should automatically open and appear in the dock, indicating it is working.\n");
124 errorsrtring.append("-- Linux\n - Are you running this program remotely via SSH? Remote X11 graphics along with OpenGL are not natively supported. Installing and using VirtualGL is a good solution for this (virtualgl.org).\n");
125 helios_runtime_error(errorsrtring);
126 }
127 glfwMakeContextCurrent(_window);
128
129 // Associate this Visualizer instance with the GLFW window so that
130 // callbacks have access to it.
131 glfwSetWindowUserPointer(_window, this);
132
133 // Ensure we can capture the escape key being pressed below
134 glfwSetInputMode(_window, GLFW_STICKY_KEYS, GL_TRUE);
135
136 window = (void *) _window;
137
138 int window_width, window_height;
139 glfwGetWindowSize(_window, &window_width, &window_height);
140
141 int framebuffer_width, framebuffer_height;
142 glfwGetFramebufferSize(_window, &framebuffer_width, &framebuffer_height);
143
144 Wframebuffer = uint(framebuffer_width);
145 Hframebuffer = uint(framebuffer_height);
146
147 if (window_width < Wdisplay || window_height < Hdisplay) {
148 std::cerr << "WARNING (Visualizer): requested size of window is larger than the screen area." << std::endl;
149 Wdisplay = uint(window_width);
150 Hdisplay = uint(window_height);
151 }
152
153 glfwSetWindowSize(_window, window_width, window_height);
154
155 // Allow the window to freely resize so that entering full-screen
156 // results in the framebuffer matching the display resolution.
157 // This prevents the operating system from simply scaling the
158 // window contents, which can skew geometry.
159 glfwSetWindowAspectRatio(_window, GLFW_DONT_CARE, GLFW_DONT_CARE);
160
161 // Register callbacks so that window and framebuffer size changes
162 // properly update the internal dimensions used for rendering.
163 glfwSetWindowSizeCallback(_window, Visualizer::windowResizeCallback);
164 glfwSetFramebufferSizeCallback(_window, Visualizer::framebufferResizeCallback);
165
166 // Initialize GLEW
167 glewExperimental = GL_TRUE; // Needed in core profile
168 if (glewInit() != GLEW_OK) {
169 helios_runtime_error("ERROR (Visualizer): Failed to initialize GLEW.");
170 }
171
172 // NOTE: for some reason calling glewInit throws an error. Need to clear it to move on.
173 glGetError();
174}
175
176void Visualizer::initialize(uint window_width_pixels, uint window_height_pixels, int aliasing_samples, bool window_decorations, bool headless_mode) {
177 Wdisplay = window_width_pixels;
178 Hdisplay = window_height_pixels;
179
180 headless = headless_mode;
181
182 shadow_buffer_size = make_uint2(8192, 8192);
183
184 maximum_texture_size = make_uint2(2048, 2048);
185
186 texArray = 0;
187 texture_array_layers = 0;
188 textures_dirty = false;
189
190 message_flag = true;
191
192 frame_counter = 0;
193
194 camera_FOV = 45;
195
196 minimum_view_radius = 0.05f;
197
198 context = nullptr;
199 primitiveColorsNeedUpdate = false;
200
201 isWatermarkVisible = true;
202 watermark_ID = 0;
203
204 colorbar_flag = 0;
205
206 colorbar_min = 0.f;
207 colorbar_max = 0.f;
208
209 colorbar_title = "";
210 colorbar_fontsize = 12;
211 colorbar_fontcolor = RGB::black;
212
213 colorbar_position = make_vec3(0.65, 0.1, 0.1);
214 colorbar_size = make_vec2(0.15, 0.1);
215 colorbar_IDs.clear();
216
217 point_width = 1;
218
219 // Initialize point cloud culling settings
220 point_culling_enabled = true;
221 point_culling_threshold = 10000; // Enable culling for point clouds with 10K+ points
222 point_max_render_distance = 0; // Auto-calculated based on scene size
223 point_lod_factor = 10.0f; // Cull every 10th point in far regions
224
225 // Initialize performance metrics
226 points_total_count = 0;
227 points_rendered_count = 0;
228 last_culling_time_ms = 0;
229
230 // Initialize OpenGL context for both regular and headless modes
231 // Headless mode needs an offscreen context for geometry operations
232
233 // Initialise GLFW
234 if (!glfwInit()) {
235 helios_runtime_error("ERROR (Visualizer::initialize): Failed to initialize GLFW");
236 }
237
238 glfwWindowHint(GLFW_SAMPLES, std::max(0, aliasing_samples)); // antialiasing
239 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // We want OpenGL 3.3
240 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
241#if __APPLE__
242 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
243 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // We don't want the old OpenGL
244#endif
245
246 if (headless) {
247 // Create offscreen context for headless mode
248 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Ensure window is not visible
249 } else {
250 // Regular windowed mode
251 glfwWindowHint(GLFW_VISIBLE, 0); // Initially hidden, will show later if needed
252
253 if (!window_decorations) {
254 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
255 }
256 }
257
258 openWindow();
259
260 // Initialize GLEW - required for both headless and windowed modes
261 glewExperimental = GL_TRUE; // Needed in core profile
262 if (glewInit() != GLEW_OK) {
263 helios_runtime_error("ERROR (Visualizer::initialize): Failed to initialize GLEW");
264 }
265
266 // NOTE: for some reason calling glewInit throws an error. Need to clear it to move on.
267 glGetError();
268
269 // Check for OpenGL errors after GLEW initialization
270 if (!checkerrors()) {
271 helios_runtime_error("ERROR (Visualizer::initialize): OpenGL context initialization failed after GLEW setup. "
272 "This often occurs in headless CI environments without proper GPU drivers or display servers. "
273 "For headless operation, ensure proper virtual display or software rendering is configured.");
274 }
275
276 // Enable relevant parameters for both regular and headless modes
277
278 glEnable(GL_DEPTH_TEST); // Enable depth test
279 glDepthFunc(GL_LESS); // Accept fragment if it closer to the camera than the former one
280 // glEnable(GL_DEPTH_CLAMP);
281
282 if (aliasing_samples <= 0) {
283 glDisable(GL_MULTISAMPLE);
284 glDisable(GL_MULTISAMPLE_ARB);
285 }
286
287 if (aliasing_samples <= 1) {
288 glDisable(GL_POLYGON_SMOOTH);
289 } else {
290 glEnable(GL_POLYGON_SMOOTH);
291 }
292
293 // glEnable(GL_TEXTURE0);
294 // glEnable(GL_TEXTURE_2D_ARRAY);
295 // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT);
296 // glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT);
297
298 // Check for OpenGL errors after basic setup
299 if (!checkerrors()) {
300 helios_runtime_error("ERROR (Visualizer::initialize): OpenGL context setup failed during basic parameter configuration. "
301 "This typically indicates graphics driver incompatibility or missing OpenGL support in the execution environment.");
302 }
303
304 // glEnable(GL_TEXTURE1);
305 glEnable(GL_POLYGON_OFFSET_FILL);
306 glPolygonOffset(1.0f, 1.0f);
307 glDisable(GL_CULL_FACE);
308
309 // Check for OpenGL errors after advanced setup
310 if (!checkerrors()) {
311 helios_runtime_error("ERROR (Visualizer::initialize): OpenGL context setup failed during advanced parameter configuration. "
312 "Verify that the graphics environment supports the required OpenGL version and features.");
313 }
314
315 // Initialize VBO's and texture buffers
316 constexpr size_t Ntypes = GeometryHandler::all_geometry_types.size();
317 // per-vertex data
318 face_index_buffer.resize(Ntypes);
319 vertex_buffer.resize(Ntypes);
320 uv_buffer.resize(Ntypes);
321 glGenBuffers((GLsizei) face_index_buffer.size(), face_index_buffer.data());
322 glGenBuffers((GLsizei) vertex_buffer.size(), vertex_buffer.data());
323 glGenBuffers((GLsizei) uv_buffer.size(), uv_buffer.data());
324
325 // per-primitive data
326 color_buffer.resize(Ntypes);
327 color_texture_object.resize(Ntypes);
328 normal_buffer.resize(Ntypes);
329 normal_texture_object.resize(Ntypes);
330 texture_flag_buffer.resize(Ntypes);
331 texture_flag_texture_object.resize(Ntypes);
332 texture_ID_buffer.resize(Ntypes);
333 texture_ID_texture_object.resize(Ntypes);
334 coordinate_flag_buffer.resize(Ntypes);
335 coordinate_flag_texture_object.resize(Ntypes);
336 hidden_flag_buffer.resize(Ntypes);
337 hidden_flag_texture_object.resize(Ntypes);
338 glGenBuffers((GLsizei) color_buffer.size(), color_buffer.data());
339 glGenTextures((GLsizei) color_texture_object.size(), color_texture_object.data());
340 glGenBuffers((GLsizei) normal_buffer.size(), normal_buffer.data());
341 glGenTextures((GLsizei) normal_texture_object.size(), normal_texture_object.data());
342 glGenBuffers((GLsizei) texture_flag_buffer.size(), texture_flag_buffer.data());
343 glGenTextures((GLsizei) texture_flag_texture_object.size(), texture_flag_texture_object.data());
344 glGenBuffers((GLsizei) texture_ID_buffer.size(), texture_ID_buffer.data());
345 glGenTextures((GLsizei) texture_ID_texture_object.size(), texture_ID_texture_object.data());
346 glGenBuffers((GLsizei) coordinate_flag_buffer.size(), coordinate_flag_buffer.data());
347 glGenTextures((GLsizei) coordinate_flag_texture_object.size(), coordinate_flag_texture_object.data());
348 glGenBuffers((GLsizei) hidden_flag_buffer.size(), hidden_flag_buffer.data());
349 glGenTextures((GLsizei) hidden_flag_texture_object.size(), hidden_flag_texture_object.data());
350
351 glGenBuffers(1, &uv_rescale_buffer);
352 glGenTextures(1, &uv_rescale_texture_object);
353
354 // Check for OpenGL errors after buffer creation
355 if (!checkerrors()) {
356 helios_runtime_error("ERROR (Visualizer::initialize): OpenGL buffer creation failed. "
357 "This indicates insufficient graphics memory or unsupported buffer operations in the current OpenGL context.");
358 }
359
360 //~~~~~~~~~~~~~ Load the Shaders ~~~~~~~~~~~~~~~~~~~//
361
362 primaryShader.initialize("plugins/visualizer/shaders/primaryShader.vert", "plugins/visualizer/shaders/primaryShader.frag", this);
363 depthShader.initialize("plugins/visualizer/shaders/shadow.vert", "plugins/visualizer/shaders/shadow.frag", this);
364
365 // Check for OpenGL errors after shader initialization
366 if (!checkerrors()) {
367 helios_runtime_error("ERROR (Visualizer::initialize): Shader initialization failed. "
368 "Verify that shader files are accessible and the OpenGL context supports the required shading language version.");
369 }
370
371 primaryShader.useShader();
372
373 // Initialize frame buffer only for windowed mode
374 if (!headless) {
375 // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
376 glGenFramebuffers(1, &framebufferID);
377 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
378
379 // Depth texture. Slower than a depth buffer, but you can sample it later in your shader
380 glActiveTexture(GL_TEXTURE1);
381 glGenTextures(1, &depthTexture);
382 glBindTexture(GL_TEXTURE_2D, depthTexture);
383 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, shadow_buffer_size.x, shadow_buffer_size.y, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
384
385 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
386 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
387
388 // clamp to border so any lookup outside [0,1] returns 1.0 (no shadow)
389 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
390 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
391 GLfloat borderColor[4] = {1.0f, 1.0f, 1.0f, 1.0f};
392 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
393
394 // enable hardware depth comparison
395 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
396
397 if (!checkerrors()) {
398 helios_runtime_error("ERROR (Visualizer::initialize): OpenGL setup failed during texture configuration. "
399 "This may indicate graphics driver issues or insufficient OpenGL support.");
400 }
401
402 // restore default active texture for subsequent texture setup
403 glActiveTexture(GL_TEXTURE0);
404
405 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0);
406
407 glDrawBuffer(GL_NONE); // No color buffer is drawn to.
408
409 // Always check that our framebuffer is ok
410 int max_checks = 10000;
411 int checks = 0;
412 while (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE && checks < max_checks) {
413 checks++;
414 }
415 // Check framebuffer completeness instead of using assert
416 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
417 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
418 std::string error_message = "ERROR (Visualizer::initialize): Framebuffer is incomplete. Status: ";
419 switch (framebuffer_status) {
420 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
421 error_message += "GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT - Framebuffer attachment is incomplete";
422 break;
423 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
424 error_message += "GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT - No attachments";
425 break;
426 case GL_FRAMEBUFFER_UNSUPPORTED:
427 error_message += "GL_FRAMEBUFFER_UNSUPPORTED - Unsupported framebuffer format";
428 break;
429 default:
430 error_message += "Unknown framebuffer error code: " + std::to_string(framebuffer_status);
431 break;
432 }
433 error_message += ". This typically occurs in CI environments with limited graphics support or missing GPU drivers.";
434 helios_runtime_error(error_message);
435 }
436
437 // Finished OpenGL setup
438 // Check for OpenGL errors after framebuffer setup
439 if (!checkerrors()) {
440 helios_runtime_error("ERROR (Visualizer::initialize): Framebuffer setup failed. "
441 "This indicates issues with OpenGL framebuffer operations, often related to graphics driver limitations or insufficient resources.");
442 }
443 } else {
444 // Set framebuffer dimensions for headless mode (no framebuffer created)
445 Wframebuffer = Wdisplay;
446 Hframebuffer = Hdisplay;
447 }
448
449 // Initialize transformation matrices
450
451 perspectiveTransformationMatrix = glm::mat4(1.f);
452
453 customTransformationMatrix = glm::mat4(1.f);
454
455 // Default values
456
457 light_direction = make_vec3(1, 1, 1);
458 light_direction.normalize();
459 if (!headless) {
460 primaryShader.setLightDirection(light_direction);
461 }
462
463 primaryLightingModel.push_back(Visualizer::LIGHTING_NONE);
464
465 camera_lookat_center = make_vec3(0, 0, 0);
466 camera_eye_location = camera_lookat_center + sphere2cart(make_SphericalCoord(2.f, 90.f * PI_F / 180.f, 0));
467
468 backgroundColor = make_RGBcolor(0.8, 0.8, 0.8);
469
470 // colormaps
471
472 // HOT
473 std::vector<RGBcolor> ctable_c{{0.f, 0.f, 0.f}, {0.5f, 0.f, 0.5f}, {1.f, 0.f, 0.f}, {1.f, 0.5f, 0.f}, {1.f, 1.f, 0.f}};
474
475 std::vector<float> clocs_c{0.f, 0.25f, 0.5f, 0.75f, 1.f};
476
477 colormap_hot.set(ctable_c, clocs_c, 100, 0, 1);
478
479 // COOL
480 ctable_c = {RGB::cyan, RGB::magenta};
481
482 clocs_c = {0.f, 1.f};
483
484 colormap_cool.set(ctable_c, clocs_c, 100, 0, 1);
485
486 // LAVA
487 ctable_c = {{0.f, 0.05f, 0.05f}, {0.f, 0.6f, 0.6f}, {1.f, 1.f, 1.f}, {1.f, 0.f, 0.f}, {0.5f, 0.f, 0.f}};
488
489 clocs_c = {0.f, 0.4f, 0.5f, 0.6f, 1.f};
490
491 colormap_lava.set(ctable_c, clocs_c, 100, 0, 1);
492
493 // RAINBOW
494 ctable_c = {RGB::navy, RGB::cyan, RGB::yellow, make_RGBcolor(0.75f, 0.f, 0.f)};
495
496 clocs_c = {0, 0.3f, 0.7f, 1.f};
497
498 colormap_rainbow.set(ctable_c, clocs_c, 100, 0, 1);
499
500 // PARULA
501 ctable_c = {RGB::navy, make_RGBcolor(0, 0.6, 0.6), RGB::goldenrod, RGB::yellow};
502
503 clocs_c = {0, 0.4f, 0.7f, 1.f};
504
505 colormap_parula.set(ctable_c, clocs_c, 100, 0, 1);
506
507 // GRAY
508 ctable_c = {RGB::black, RGB::white};
509
510 clocs_c = {0.f, 1.f};
511
512 colormap_gray.set(ctable_c, clocs_c, 100, 0, 1);
513
514 colormap_current = colormap_hot;
515
516 if (!headless) {
517 glfwSetMouseButtonCallback((GLFWwindow *) window, mouseCallback);
518 glfwSetCursorPosCallback((GLFWwindow *) window, cursorCallback);
519 glfwSetScrollCallback((GLFWwindow *) window, scrollCallback);
520
521 // Check for OpenGL errors after callback setup
522 if (!checkerrors()) {
523 helios_runtime_error("ERROR (Visualizer::initialize): Final OpenGL setup failed during callback configuration. "
524 "The OpenGL context may be in an invalid state or missing required extensions.");
525 }
526 }
527}
528
530 if (!headless) {
531 glDeleteFramebuffers(1, &framebufferID);
532 glDeleteTextures(1, &depthTexture);
533
534 glDeleteBuffers((GLsizei) face_index_buffer.size(), face_index_buffer.data());
535 glDeleteBuffers((GLsizei) vertex_buffer.size(), vertex_buffer.data());
536 glDeleteBuffers((GLsizei) uv_buffer.size(), uv_buffer.data());
537
538 glDeleteBuffers((GLsizei) color_buffer.size(), color_buffer.data());
539 glDeleteTextures((GLsizei) color_texture_object.size(), color_texture_object.data());
540 glDeleteBuffers((GLsizei) normal_buffer.size(), normal_buffer.data());
541 glDeleteTextures((GLsizei) normal_texture_object.size(), normal_texture_object.data());
542 glDeleteBuffers((GLsizei) texture_flag_buffer.size(), texture_flag_buffer.data());
543 glDeleteTextures((GLsizei) texture_flag_texture_object.size(), texture_flag_texture_object.data());
544 glDeleteBuffers((GLsizei) texture_ID_buffer.size(), texture_ID_buffer.data());
545 glDeleteTextures((GLsizei) texture_ID_texture_object.size(), texture_ID_texture_object.data());
546 glDeleteBuffers((GLsizei) coordinate_flag_buffer.size(), coordinate_flag_buffer.data());
547 glDeleteTextures((GLsizei) coordinate_flag_texture_object.size(), coordinate_flag_texture_object.data());
548 glDeleteBuffers((GLsizei) hidden_flag_buffer.size(), hidden_flag_buffer.data());
549 glDeleteTextures((GLsizei) hidden_flag_texture_object.size(), hidden_flag_texture_object.data());
550
551 // Clean up texture array and UV rescaling resources
552 if (texArray != 0) {
553 glDeleteTextures(1, &texArray);
554 }
555 glDeleteBuffers(1, &uv_rescale_buffer);
556 glDeleteTextures(1, &uv_rescale_texture_object);
557
558 glfwDestroyWindow(scast<GLFWwindow *>(window));
559 glfwTerminate();
560 }
561}
562
564 message_flag = true;
565}
566
568 message_flag = false;
569}
570
571void Visualizer::setCameraPosition(const helios::vec3 &cameraPosition, const helios::vec3 &lookAt) {
572 camera_eye_location = cameraPosition;
573 camera_lookat_center = lookAt;
574}
575
577 camera_lookat_center = lookAt;
578 camera_eye_location = camera_lookat_center + sphere2cart(cameraAngle);
579}
580
581void Visualizer::setCameraFieldOfView(float angle_FOV) {
582 camera_FOV = angle_FOV;
583}
584
586 light_direction = direction / direction.magnitude();
587 primaryShader.setLightDirection(direction);
588}
589
591 for (auto &i: primaryLightingModel) {
592 i = lightingmodel;
593 }
594}
595
596void Visualizer::setLightIntensityFactor(float lightintensityfactor) {
597 lightintensity = lightintensityfactor;
598}
599
601 backgroundColor = color;
602}
603
605 isWatermarkVisible = false;
606 if (watermark_ID != 0) {
607 geometry_handler.deleteGeometry(watermark_ID);
608 watermark_ID = 0;
609 }
610}
611
613 isWatermarkVisible = true;
615}
616
617void Visualizer::updatePerspectiveTransformation(bool shadow) {
618 float dist = glm::distance(glm_vec3(camera_lookat_center), glm_vec3(camera_eye_location));
619 float nearPlane = std::max(0.1f, 0.05f * dist); // avoid 0
620 if (shadow) {
621 float farPlane = std::max(5.f * camera_eye_location.z, 2.0f * dist);
622 cameraProjectionMatrix = glm::perspective(glm::radians(camera_FOV), float(Wframebuffer) / float(Hframebuffer), nearPlane, farPlane);
623 } else {
624 cameraProjectionMatrix = glm::infinitePerspective(glm::radians(camera_FOV), float(Wframebuffer) / float(Hframebuffer), nearPlane);
625 }
626 cameraViewMatrix = glm::lookAt(glm_vec3(camera_eye_location), glm_vec3(camera_lookat_center), glm::vec3(0, 0, 1));
627
628 perspectiveTransformationMatrix = cameraProjectionMatrix * cameraViewMatrix;
629}
630
631void Visualizer::updateCustomTransformation(const glm::mat4 &matrix) {
632 customTransformationMatrix = matrix;
633}
634
636 colorbar_flag = 2;
637}
638
640 if (!colorbar_IDs.empty()) {
641 geometry_handler.deleteGeometry(colorbar_IDs);
642 colorbar_IDs.clear();
643 }
644 colorbar_flag = 1;
645}
646
648 if (position.x < 0 || position.x > 1 || position.y < 0 || position.y > 1 || position.z < -1 || position.z > 1) {
649 helios_runtime_error("ERROR (Visualizer::setColorbarPosition): position is out of range. Coordinates must be: 0<x<1, 0<y<1, -1<z<1.");
650 }
651 colorbar_position = position;
652}
653
655 if (size.x < 0 || size.x > 1 || size.y < 0 || size.y > 1) {
656 helios_runtime_error("ERROR (Visualizer::setColorbarSize): Size must be greater than 0 and less than the window size (i.e., 1).");
657 }
658 colorbar_size = size;
659}
660
661void Visualizer::setColorbarRange(float cmin, float cmax) {
662 if (message_flag && cmin > cmax) {
663 std::cerr << "WARNING (Visualizer::setColorbarRange): Maximum colorbar value must be greater than minimum value...Ignoring command." << std::endl;
664 return;
665 }
666 colorbar_min = cmin;
667 colorbar_max = cmax;
668}
669
670void Visualizer::setColorbarTicks(const std::vector<float> &ticks) {
671 // check that vector is not empty
672 if (ticks.empty()) {
673 helios_runtime_error("ERROR (Visualizer::setColorbarTicks): Colorbar ticks vector is empty.");
674 }
675
676 // Check that ticks are monotonically increasing
677 for (int i = 1; i < ticks.size(); i++) {
678 if (ticks.at(i) <= ticks.at(i - 1)) {
679 helios_runtime_error("ERROR (Visualizer::setColorbarTicks): Colorbar ticks must be monotonically increasing.");
680 }
681 }
682
683 // Check that ticks are within the range of colorbar values
684 for (int i = ticks.size() - 1; i >= 0; i--) {
685 if (ticks.at(i) < colorbar_min) {
686 colorbar_min = ticks.at(i);
687 }
688 }
689 for (float tick: ticks) {
690 if (tick > colorbar_max) {
691 colorbar_max = tick;
692 }
693 }
694
695 colorbar_ticks = ticks;
696}
697
698void Visualizer::setColorbarTitle(const char *title) {
699 colorbar_title = title;
700}
701
703 if (font_size <= 0) {
704 helios_runtime_error("ERROR (Visualizer::setColorbarFontSize): Font size must be greater than zero.");
705 }
706 colorbar_fontsize = font_size;
707}
708
710 colorbar_fontcolor = fontcolor;
711}
712
713void Visualizer::setColormap(Ctable colormap_name) {
714 if (colormap_name == COLORMAP_HOT) {
715 colormap_current = colormap_hot;
716 } else if (colormap_name == COLORMAP_COOL) {
717 colormap_current = colormap_cool;
718 } else if (colormap_name == COLORMAP_LAVA) {
719 colormap_current = colormap_lava;
720 } else if (colormap_name == COLORMAP_RAINBOW) {
721 colormap_current = colormap_rainbow;
722 } else if (colormap_name == COLORMAP_PARULA) {
723 colormap_current = colormap_parula;
724 } else if (colormap_name == COLORMAP_GRAY) {
725 colormap_current = colormap_gray;
726 } else if (colormap_name == COLORMAP_CUSTOM) {
727 helios_runtime_error("ERROR (Visualizer::setColormap): Setting a custom colormap requires calling setColormap with additional arguments defining the colormap.");
728 } else {
729 helios_runtime_error("ERROR (Visualizer::setColormap): Invalid colormap.");
730 }
731}
732
733void Visualizer::setColormap(const std::vector<RGBcolor> &colors, const std::vector<float> &divisions) {
734 if (colors.size() != divisions.size()) {
735 helios_runtime_error("ERROR (Visualizer::setColormap): The number of colors must be equal to the number of divisions.");
736 }
737
738 Colormap colormap_custom(colors, divisions, 100, 0, 1);
739
740 colormap_current = colormap_custom;
741}
742
744 return colormap_current;
745}
746
747glm::mat4 Visualizer::computeShadowDepthMVP() const {
748 glm::vec3 lightDir = -glm::normalize(glm_vec3(light_direction));
749
750 const float margin = 0.01;
751
752 // Get the eight corners of the camera frustum in world space (NDC cube corners → clip → view → world)
753
754 // NDC cube
755 static const std::array<glm::vec4, 8> ndcCorners = {glm::vec4(-1, -1, -1, 1), glm::vec4(+1, -1, -1, 1), glm::vec4(+1, +1, -1, 1), glm::vec4(-1, +1, -1, 1),
756 glm::vec4(-1, -1, +1, 1), glm::vec4(+1, -1, +1, 1), glm::vec4(+1, +1, +1, 1), glm::vec4(-1, +1, +1, 1)};
757
758 glm::mat4 invCam = glm::inverse(this->perspectiveTransformationMatrix);
759
760 std::array<glm::vec3, 8> frustumWs;
761 for (std::size_t i = 0; i < 8; i++) {
762 glm::vec4 ws = invCam * ndcCorners[i];
763 frustumWs[i] = glm::vec3(ws) / ws.w;
764 }
765
766 // Build a light-view matrix (orthographic, directional light) We choose an arbitrary but stable "up" vector.
767 glm::vec3 lightUp(0.0f, 1.0f, 0.0f);
768 if (glm::abs(glm::dot(lightUp, lightDir)) > 0.9f) // almost collinear
769 lightUp = glm::vec3(1, 0, 0);
770
771 // Position the "camera" that generates the shadow map so that every
772 // frustum corner is in front of it. We place it on the negative light
773 // direction, centered on the frustum’s centroid.
774 glm::vec3 centroid(0);
775 for (auto &c: frustumWs)
776 centroid += c;
777 centroid /= 8.0f;
778
779 glm::vec3 lightPos = centroid - lightDir * 100.0f; // 100 is arbitrary,
780 // we will tighten z
781 glm::mat4 lightView = glm::lookAt(lightPos, centroid, lightUp);
782
783 // Transform frustum corners to light space and find min/max extents
784 glm::vec3 minL(std::numeric_limits<float>::infinity());
785 glm::vec3 maxL(-std::numeric_limits<float>::infinity());
786
787 for (auto &c: frustumWs) {
788 glm::vec3 p = glm::vec3(lightView * glm::vec4(c, 1));
789 minL = glm::min(minL, p);
790 maxL = glm::max(maxL, p);
791 }
792
793 // Build orthographic projection that exactly fits the camera frustum and enlarge slightly to avoid clipping due to kernel offsets.
794 glm::vec3 extent = maxL - minL;
795 minL -= extent * margin;
796 maxL += extent * margin;
797
798 float zNear = -maxL.z; // light space points toward -z
799 float zFar = -minL.z;
800
801 glm::mat4 lightProj = glm::ortho(minL.x, maxL.x, minL.y, maxL.y, zNear, zFar);
802
803 // Transform into [0,1] texture space (bias matrix)
804 const glm::mat4 bias(0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f);
805
806 return bias * lightProj * lightView;
807}
808
809Visualizer::Texture::Texture(const std::string &texture_file, uint textureID, const helios::uint2 &maximum_texture_size, bool loadalphaonly) : texture_file(texture_file), glyph(), textureID(textureID) {
810#ifdef HELIOS_DEBUG
811 if (loadalphaonly) {
812 assert(validateTextureFile(texture_file, true));
813 } else {
814 assert(validateTextureFile(texture_file));
815 }
816#endif
817
818 //--- Load the Texture ----//
819
820 if (loadalphaonly) {
821 num_channels = 1;
822 } else {
823 num_channels = 4;
824 }
825
826 std::vector<unsigned char> image_data;
827
828 if (texture_file.substr(texture_file.find_last_of('.') + 1) == "png") {
829 read_png_file(texture_file.c_str(), image_data, texture_resolution.y, texture_resolution.x);
830 } else { // JPEG
831 read_JPEG_file(texture_file.c_str(), image_data, texture_resolution.y, texture_resolution.x);
832 }
833
834 texture_data = std::move(image_data);
835
836 // If the texture image is too large, resize it
837 if (texture_resolution.x > maximum_texture_size.x || texture_resolution.y > maximum_texture_size.y) {
838 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.x), std::min(texture_resolution.y, maximum_texture_size.y));
839 resizeTexture(new_texture_resolution);
840 }
841}
842
843Visualizer::Texture::Texture(const Glyph *glyph_ptr, uint textureID, const helios::uint2 &maximum_texture_size) : textureID(textureID) {
844 assert(glyph_ptr != nullptr);
845
846 glyph = *glyph_ptr;
847
848 texture_resolution = glyph_ptr->size;
849
850 // Texture only has 1 channel, and contains transparency data
851 texture_data.resize(texture_resolution.x * texture_resolution.y);
852 for (int j = 0; j < texture_resolution.y; j++) {
853 for (int i = 0; i < texture_resolution.x; i++) {
854 texture_data[i + j * texture_resolution.x] = glyph_ptr->data.at(j).at(i);
855 }
856 }
857
858 // If the texture image is too large, resize it
859 if (texture_resolution.x > maximum_texture_size.x || texture_resolution.y > maximum_texture_size.y) {
860 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.x), std::min(texture_resolution.y, maximum_texture_size.y));
861 resizeTexture(new_texture_resolution);
862 }
863
864 num_channels = 1;
865}
866
867Visualizer::Texture::Texture(const std::vector<unsigned char> &pixel_data, uint textureID, const helios::uint2 &image_resolution, const helios::uint2 &maximum_texture_size) : textureID(textureID) {
868#ifdef HELIOS_DEBUG
869 assert(pixel_data.size() == 4u * image_resolution.x * image_resolution.y);
870#endif
871
872 texture_data = pixel_data;
873 texture_resolution = image_resolution;
874 num_channels = 4;
875
876 // If the texture image is too large, resize it
877 if (texture_resolution.x > maximum_texture_size.x || texture_resolution.y > maximum_texture_size.y) {
878 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.x), std::min(texture_resolution.y, maximum_texture_size.y));
879 resizeTexture(new_texture_resolution);
880 }
881}
882
883void Visualizer::Texture::resizeTexture(const helios::uint2 &new_image_resolution) {
884 int old_width = texture_resolution.x;
885 int old_height = texture_resolution.y;
886 int new_width = new_image_resolution.x;
887 int new_height = new_image_resolution.y;
888
889 std::vector<unsigned char> new_data(new_width * new_height * num_channels);
890
891 // map each new pixel to a floating-point src coordinate
892 float x_ratio = scast<float>(old_width) / scast<float>(new_width);
893 float y_ratio = scast<float>(old_height) / scast<float>(new_height);
894
895 for (int y = 0; y < new_height; ++y) {
896 float srcY = y * y_ratio;
897 int y0 = std::min(scast<int>(std::floor(srcY)), old_height - 1);
898 int y1 = std::min(y0 + 1, old_height - 1);
899 float dy = srcY - y0;
900
901 for (int x = 0; x < new_width; ++x) {
902 float srcX = x * x_ratio;
903 int x0 = std::min(scast<int>(std::floor(srcX)), old_width - 1);
904 int x1 = std::min(x0 + 1, old_width - 1);
905 float dx = srcX - x0;
906
907 // for each channel, fetch 4 neighbors and lerp
908 for (int c = 0; c < num_channels; ++c) {
909 float p00 = texture_data[(y0 * old_width + x0) * num_channels + c];
910 float p10 = texture_data[(y0 * old_width + x1) * num_channels + c];
911 float p01 = texture_data[(y1 * old_width + x0) * num_channels + c];
912 float p11 = texture_data[(y1 * old_width + x1) * num_channels + c];
913
914 float top = p00 * (1.f - dx) + p10 * dx;
915 float bottom = p01 * (1.f - dx) + p11 * dx;
916 float value = top * (1.f - dy) + bottom * dy;
917
918 new_data[(y * new_width + x) * num_channels + c] = scast<unsigned char>(clamp(std::round(value + 0.5f), 0.f, 255.f));
919 }
920 }
921 }
922
923 texture_data = std::move(new_data);
924 texture_resolution = new_image_resolution;
925}
926
927
928uint Visualizer::registerTextureImage(const std::string &texture_file) {
929#ifdef HELIOS_DEBUG
930 // assert( validateTextureFile(texture_file) );
931#endif
932
933 for (const auto &[textureID, texture]: texture_manager) {
934 if (texture.texture_file == texture_file) {
935 // if it does, return its texture ID
936 return textureID;
937 }
938 }
939
940 const uint textureID = texture_manager.size();
941
942 texture_manager.try_emplace(textureID, texture_file, textureID, this->maximum_texture_size, false);
943 textures_dirty = true;
944
945 return textureID;
946}
947
948uint Visualizer::registerTextureImage(const std::vector<unsigned char> &texture_data, const helios::uint2 &image_resolution) {
949#ifdef HELIOS_DEBUG
950 assert(!texture_data.empty() && texture_data.size() == 4 * image_resolution.x * image_resolution.y);
951#endif
952
953 const uint textureID = texture_manager.size();
954
955 texture_manager.try_emplace(textureID, texture_data, textureID, image_resolution, this->maximum_texture_size);
956 textures_dirty = true;
957
958 return textureID;
959}
960
961uint Visualizer::registerTextureTransparencyMask(const std::string &texture_file) {
962#ifdef HELIOS_DEBUG
963 assert(validateTextureFile(texture_file));
964#endif
965
966 for (const auto &[textureID, texture]: texture_manager) {
967 if (texture.texture_file == texture_file) {
968 // if it does, return its texture ID
969 return textureID;
970 }
971 }
972
973 const uint textureID = texture_manager.size();
974
975 texture_manager.try_emplace(textureID, texture_file, textureID, this->maximum_texture_size, true);
976 textures_dirty = true;
977
978 return textureID;
979}
980
981uint Visualizer::registerTextureGlyph(const Glyph *glyph) {
982
983 const uint textureID = texture_manager.size();
984
985 texture_manager.try_emplace(textureID, glyph, textureID, this->maximum_texture_size);
986 textures_dirty = true;
987
988 return textureID;
989}
990
991helios::uint2 Visualizer::getTextureResolution(uint textureID) const {
992 if (texture_manager.find(textureID) == texture_manager.end()) {
993 }
994 return texture_manager.at(textureID).texture_resolution;
995}
996
997bool validateTextureFile(const std::string &texture_file, bool pngonly) {
998 const std::filesystem::path p(texture_file);
999
1000 // Check that the file exists and is a regular file
1001 if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p)) {
1002 return false;
1003 }
1004
1005 // Extract and lowercase the extension
1006 std::string ext = p.extension().string();
1007 std::transform(ext.begin(), ext.end(), ext.begin(), [](const unsigned char c) { return scast<char>(std::tolower(c)); });
1008
1009 // Verify it's .png, .jpg or .jpeg
1010 if (pngonly) {
1011 if (ext != ".png") {
1012 return false;
1013 }
1014 } else {
1015 if (ext != ".png" && ext != ".jpg" && ext != ".jpeg") {
1016 return false;
1017 }
1018 }
1019
1020 return true;
1021}
1022
1024 return window;
1025}
1026
1027std::vector<uint> Visualizer::getFrameBufferSize() const {
1028 return {Wframebuffer, Hframebuffer};
1029}
1030
1031void Visualizer::setFrameBufferSize(int width, int height) {
1032 Wframebuffer = width;
1033 Hframebuffer = height;
1034}
1035
1037 return backgroundColor;
1038}
1039
1040Shader Visualizer::getPrimaryShader() const {
1041 return primaryShader;
1042}
1043
1044std::vector<helios::vec3> Visualizer::getCameraPosition() const {
1045 return {camera_lookat_center, camera_eye_location};
1046}
1047
1049 return perspectiveTransformationMatrix;
1050}
1051
1052glm::mat4 Visualizer::getViewMatrix() const {
1053 vec3 forward = camera_lookat_center - camera_eye_location;
1054 forward = forward.normalize();
1055
1056 vec3 right = cross(vec3(0, 0, 1), forward);
1057 right = right.normalize();
1058
1059 vec3 up = cross(forward, right);
1060 up = up.normalize();
1061
1062 glm::vec3 camera_pos{camera_eye_location.x, camera_eye_location.y, camera_eye_location.z};
1063 glm::vec3 lookat_pos{camera_lookat_center.x, camera_lookat_center.y, camera_lookat_center.z};
1064 glm::vec3 up_vec{up.x, up.y, up.z};
1065
1066 return glm::lookAt(camera_pos, lookat_pos, up_vec);
1067}
1068
1069std::vector<Visualizer::LightingModel> Visualizer::getPrimaryLightingModel() {
1070 return primaryLightingModel;
1071}
1072
1073uint Visualizer::getDepthTexture() const {
1074 return depthTexture;
1075}