18#include <GLFW/glfw3.h>
28using namespace helios;
33static int glfw_reference_count = 0;
36 std::vector<helios::RGBcolor> rgb_data;
40 texture.reserve(width * height * 4);
42 for (
const auto &pixel: rgb_data) {
43 texture.push_back(
static_cast<unsigned char>(pixel.r * 255.0f));
44 texture.push_back(
static_cast<unsigned char>(pixel.g * 255.0f));
45 texture.push_back(
static_cast<unsigned char>(pixel.b * 255.0f));
46 texture.push_back(255);
52int write_JPEG_file(
const char *filename,
uint width,
uint height,
bool buffers_swapped_since_render,
bool print_messages) {
54 std::cout <<
"writing JPEG image: " << filename << std::endl;
58 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
59 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
60 helios_runtime_error(
"ERROR (write_JPEG_file): Framebuffer is not complete (status: " + std::to_string(framebuffer_status) +
")");
64 while (glGetError() != GL_NO_ERROR) {
67 const size_t bsize = 3 * width * height;
68 std::vector<GLubyte> screen_shot_trans;
69 screen_shot_trans.resize(bsize);
72 glPixelStorei(GL_PACK_ALIGNMENT, 1);
78 if (buffers_swapped_since_render) {
79 glReadBuffer(GL_FRONT);
81 if (error != GL_NO_ERROR) {
83 glReadBuffer(GL_BACK);
85 if (error != GL_NO_ERROR) {
86 helios_runtime_error(
"ERROR (write_JPEG_file): Cannot set read buffer (error: " + std::to_string(error) +
")");
90 glReadBuffer(GL_BACK);
92 if (error != GL_NO_ERROR) {
94 glReadBuffer(GL_FRONT);
96 if (error != GL_NO_ERROR) {
97 helios_runtime_error(
"ERROR (write_JPEG_file): Cannot set read buffer (error: " + std::to_string(error) +
")");
106 glReadPixels(0, 0, scast<GLsizei>(width), scast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, &screen_shot_trans[0]);
107 error = glGetError();
108 if (error != GL_NO_ERROR) {
109 helios_runtime_error(
"ERROR (write_JPEG_file): glReadPixels failed (error: " + std::to_string(error) +
")");
113 bool all_black =
true;
114 for (
size_t i = 0; i < bsize && all_black; i++) {
115 if (screen_shot_trans[i] != 0) {
121 std::cout <<
"WARNING (write_JPEG_file): All pixels are black - this may indicate a timing or buffer issue" << std::endl;
125 std::vector<helios::RGBcolor> rgb_data;
126 rgb_data.reserve(width * height);
128 for (
size_t i = 0; i < width * height; i++) {
129 size_t byte_idx = i * 3;
130 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);
137int write_JPEG_file(
const char *filename,
uint width,
uint height,
const std::vector<helios::RGBcolor> &data,
bool print_messages) {
138 if (print_messages) {
139 std::cout <<
"writing JPEG image: " << filename << std::endl;
146int write_PNG_file(
const char *filename,
uint width,
uint height,
bool buffers_swapped_since_render,
bool transparent_background,
bool print_messages) {
147 if (print_messages) {
148 std::cout <<
"writing PNG image: " << filename << std::endl;
152 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
153 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
154 helios_runtime_error(
"ERROR (write_PNG_file): Framebuffer is not complete (status: " + std::to_string(framebuffer_status) +
")");
158 while (glGetError() != GL_NO_ERROR) {
162 glPixelStorei(GL_PACK_ALIGNMENT, 1);
166 if (buffers_swapped_since_render) {
167 glReadBuffer(GL_FRONT);
168 error = glGetError();
169 if (error != GL_NO_ERROR) {
171 glReadBuffer(GL_BACK);
172 error = glGetError();
173 if (error != GL_NO_ERROR) {
174 helios_runtime_error(
"ERROR (write_PNG_file): Cannot set read buffer (error: " + std::to_string(error) +
")");
178 glReadBuffer(GL_BACK);
179 error = glGetError();
180 if (error != GL_NO_ERROR) {
182 glReadBuffer(GL_FRONT);
183 error = glGetError();
184 if (error != GL_NO_ERROR) {
185 helios_runtime_error(
"ERROR (write_PNG_file): Cannot set read buffer (error: " + std::to_string(error) +
")");
193 std::vector<helios::RGBAcolor> rgba_data;
194 rgba_data.reserve(width * height);
196 if (transparent_background) {
198 const size_t bsize = 4 * width * height;
199 std::vector<GLubyte> screen_shot_trans(bsize);
201 glReadPixels(0, 0, scast<GLsizei>(width), scast<GLsizei>(height), GL_RGBA, GL_UNSIGNED_BYTE, &screen_shot_trans[0]);
202 error = glGetError();
203 if (error != GL_NO_ERROR) {
204 helios_runtime_error(
"ERROR (write_PNG_file): glReadPixels failed (error: " + std::to_string(error) +
")");
208 for (
int row = height - 1; row >= 0; row--) {
209 for (
size_t col = 0; col < width; col++) {
210 size_t byte_idx = (row * width + col) * 4;
211 rgba_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, screen_shot_trans[byte_idx + 3] / 255.0f);
216 const size_t bsize = 3 * width * height;
217 std::vector<GLubyte> screen_shot_trans(bsize);
219 glReadPixels(0, 0, scast<GLsizei>(width), scast<GLsizei>(height), GL_RGB, GL_UNSIGNED_BYTE, &screen_shot_trans[0]);
220 error = glGetError();
221 if (error != GL_NO_ERROR) {
222 helios_runtime_error(
"ERROR (write_PNG_file): glReadPixels failed (error: " + std::to_string(error) +
")");
226 for (
int row = height - 1; row >= 0; row--) {
227 for (
size_t col = 0; col < width; col++) {
228 size_t byte_idx = (row * width + col) * 3;
229 rgba_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, 1.0f);
238int write_PNG_file(
const char *filename,
uint width,
uint height,
const std::vector<helios::RGBAcolor> &data,
bool print_messages) {
239 if (print_messages) {
240 std::cout <<
"writing PNG image: " << filename << std::endl;
248 std::vector<helios::RGBAcolor> rgba_data;
252 texture.reserve(width * height * 4);
254 for (
const auto &pixel: rgba_data) {
255 texture.push_back(
static_cast<unsigned char>(pixel.r * 255.0f));
256 texture.push_back(
static_cast<unsigned char>(pixel.g * 255.0f));
257 texture.push_back(
static_cast<unsigned char>(pixel.b * 255.0f));
258 texture.push_back(
static_cast<unsigned char>(pixel.a * 255.0f));
262Visualizer::Visualizer(
uint Wdisplay) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray(), colormap_lines() {
263 initialize(Wdisplay,
uint(std::round(Wdisplay * 0.8)), 16,
true,
false);
266Visualizer::Visualizer(
uint Wdisplay,
uint Hdisplay) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray(), colormap_lines() {
267 initialize(Wdisplay, Hdisplay, 16,
true,
false);
270Visualizer::Visualizer(
uint Wdisplay,
uint Hdisplay,
int aliasing_samples) : colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray(), colormap_lines() {
271 initialize(Wdisplay, Hdisplay, aliasing_samples,
true,
false);
275 colormap_current(), colormap_hot(), colormap_cool(), colormap_lava(), colormap_rainbow(), colormap_parula(), colormap_gray(), colormap_lines() {
276 initialize(Wdisplay, Hdisplay, aliasing_samples, window_decorations, headless);
279void Visualizer::openWindow() {
281 GLFWwindow *_window = glfwCreateWindow(Wdisplay, Hdisplay,
"Helios 3D Simulation",
nullptr,
nullptr);
282 if (_window ==
nullptr) {
283 std::string errorsrtring;
284 errorsrtring.append(
"ERROR(Visualizer): Failed to initialize graphics.\n");
285 errorsrtring.append(
"Common causes for this error:\n");
286 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");
287 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");
290 glfwMakeContextCurrent(_window);
294 glfwSetWindowUserPointer(_window,
this);
297 glfwSetInputMode(_window, GLFW_STICKY_KEYS, GL_TRUE);
299 window = (
void *) _window;
301 int window_width, window_height;
302 glfwGetWindowSize(_window, &window_width, &window_height);
304 int framebuffer_width, framebuffer_height;
305 glfwGetFramebufferSize(_window, &framebuffer_width, &framebuffer_height);
307 Wframebuffer =
uint(framebuffer_width);
308 Hframebuffer =
uint(framebuffer_height);
310 if (window_width < Wdisplay || window_height < Hdisplay) {
311 std::cerr <<
"WARNING (Visualizer): requested size of window is larger than the screen area." << std::endl;
312 Wdisplay =
uint(window_width);
313 Hdisplay =
uint(window_height);
316 glfwSetWindowSize(_window, window_width, window_height);
322 glfwSetWindowAspectRatio(_window, GLFW_DONT_CARE, GLFW_DONT_CARE);
326 glfwSetWindowSizeCallback(_window, Visualizer::windowResizeCallback);
327 glfwSetFramebufferSizeCallback(_window, Visualizer::framebufferResizeCallback);
330 glewExperimental = GL_TRUE;
331 GLenum glew_result = glewInit();
332 if (glew_result != GLEW_OK) {
333 std::string error_msg =
"ERROR (Visualizer): Failed to initialize GLEW. ";
334 error_msg +=
"GLEW error: " + std::string((
const char *) glewGetErrorString(glew_result));
340 GLenum gl_error = glGetError();
341 if (gl_error != GL_NO_ERROR && gl_error != GL_INVALID_ENUM) {
342 std::string error_msg =
"ERROR (Visualizer): Unexpected OpenGL error after GLEW initialization: ";
343 error_msg += std::to_string(gl_error);
348void Visualizer::createOffscreenContext() {
357 const char *ci_env = std::getenv(
"CI");
358 const char *display_env = std::getenv(
"DISPLAY");
359 const char *force_offscreen = std::getenv(
"HELIOS_FORCE_OFFSCREEN");
361 bool is_ci = (ci_env !=
nullptr && std::string(ci_env) ==
"true");
362 bool has_display = (display_env !=
nullptr && std::strlen(display_env) > 0);
363 bool force_software = (force_offscreen !=
nullptr && std::string(force_offscreen) ==
"1");
369 glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
370 glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
374 glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
375 if (is_ci || force_software) {
377 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
381 if (is_ci && !has_display) {
383 glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
384 glfwWindowHint(GLFW_SAMPLES, 0);
388 if (is_ci || force_software) {
389 glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
395 GLFWwindow *_window = glfwCreateWindow(1, 1,
"Helios Offscreen",
nullptr,
nullptr);
397 if (_window ==
nullptr) {
399 std::string error_msg =
"ERROR (Visualizer::createOffscreenContext): Unable to create OpenGL context for headless rendering.\n";
400 error_msg +=
"This typically occurs in CI environments without GPU drivers or display servers.\n";
401 error_msg +=
"Platform-specific solutions:\n";
404 error_msg +=
"-- macOS CI: Install Xcode command line tools and ensure graphics frameworks are available\n";
405 error_msg +=
"-- Try setting HELIOS_FORCE_OFFSCREEN=1 environment variable\n";
406 error_msg +=
"-- Consider using a macOS runner with graphics support\n";
408 error_msg +=
"-- Linux CI: Install virtual display server: apt-get install xvfb\n";
409 error_msg +=
"-- Start virtual display: Xvfb :99 -screen 0 1024x768x24 &\n";
410 error_msg +=
"-- Set display variable: export DISPLAY=:99\n";
411 error_msg +=
"-- Install Mesa software rendering: apt-get install mesa-utils libgl1-mesa-dev\n";
412 error_msg +=
"-- Force software rendering: export LIBGL_ALWAYS_SOFTWARE=1\n";
414 error_msg +=
"-- Windows CI: Ensure OpenGL drivers are available\n";
415 error_msg +=
"-- Install Mesa3D for software rendering\n";
416 error_msg +=
"-- Try using a Windows runner with graphics support\n";
418 error_msg +=
"-- Alternative: Skip visualizer tests in CI with conditional test execution\n";
419 error_msg +=
"-- Set HELIOS_FORCE_OFFSCREEN=1 to attempt software rendering";
424 glfwMakeContextCurrent(_window);
425 window = (
void *) _window;
428 const char *gl_version = (
const char *) glGetString(GL_VERSION);
429 if (gl_version ==
nullptr) {
430 glfwDestroyWindow(_window);
431 helios_runtime_error(
"ERROR (Visualizer::createOffscreenContext): Failed to obtain OpenGL version. Context creation failed.");
435 Wframebuffer = Wdisplay;
436 Hframebuffer = Hdisplay;
450 const char *gl_version = (
const char *) glGetString(GL_VERSION);
451 if (gl_version ==
nullptr) {
452 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): OpenGL context is not valid - unable to retrieve version string.");
456 if (!GLEW_VERSION_3_0 && !GLEW_ARB_framebuffer_object) {
457 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): OpenGL context does not support framebuffer objects (requires OpenGL 3.0+ or ARB_framebuffer_object extension).");
461 if (Wframebuffer == 0 || Hframebuffer == 0) {
462 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Invalid framebuffer dimensions (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
"). Dimensions must be positive.");
466 GLint max_texture_size;
467 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
468 if (
static_cast<GLint
>(Wframebuffer) > max_texture_size ||
static_cast<GLint
>(Hframebuffer) > max_texture_size) {
469 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Requested framebuffer size (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
") exceeds maximum texture size (" + std::to_string(max_texture_size) +
474 if (!checkerrors()) {
475 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): OpenGL errors detected before framebuffer setup.");
479 glGenFramebuffers(1, &offscreenFramebufferID);
480 GLenum error = glGetError();
481 if (error != GL_NO_ERROR || offscreenFramebufferID == 0) {
482 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to generate framebuffer object. OpenGL error: " + std::to_string(error));
485 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
486 error = glGetError();
487 if (error != GL_NO_ERROR) {
488 glDeleteFramebuffers(1, &offscreenFramebufferID);
489 offscreenFramebufferID = 0;
490 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to bind framebuffer object. OpenGL error: " + std::to_string(error));
494 glGenTextures(1, &offscreenColorTexture);
495 error = glGetError();
496 if (error != GL_NO_ERROR || offscreenColorTexture == 0) {
497 glBindFramebuffer(GL_FRAMEBUFFER, 0);
498 glDeleteFramebuffers(1, &offscreenFramebufferID);
499 offscreenFramebufferID = 0;
500 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to generate color texture. OpenGL error: " + std::to_string(error));
503 glBindTexture(GL_TEXTURE_2D, offscreenColorTexture);
504 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
static_cast<GLsizei
>(Wframebuffer),
static_cast<GLsizei
>(Hframebuffer), 0, GL_RGBA, GL_UNSIGNED_BYTE,
nullptr);
505 error = glGetError();
506 if (error != GL_NO_ERROR) {
507 glBindFramebuffer(GL_FRAMEBUFFER, 0);
508 glDeleteTextures(1, &offscreenColorTexture);
509 glDeleteFramebuffers(1, &offscreenFramebufferID);
510 offscreenColorTexture = 0;
511 offscreenFramebufferID = 0;
512 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to create color texture storage. OpenGL error: " + std::to_string(error) +
". This may indicate insufficient GPU memory or unsupported texture format.");
515 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
516 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
517 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, offscreenColorTexture, 0);
520 glGenTextures(1, &offscreenDepthTexture);
521 error = glGetError();
522 if (error != GL_NO_ERROR || offscreenDepthTexture == 0) {
523 glBindFramebuffer(GL_FRAMEBUFFER, 0);
524 glDeleteTextures(1, &offscreenColorTexture);
525 glDeleteFramebuffers(1, &offscreenFramebufferID);
526 offscreenColorTexture = 0;
527 offscreenFramebufferID = 0;
528 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to generate depth texture. OpenGL error: " + std::to_string(error));
531 glBindTexture(GL_TEXTURE_2D, offscreenDepthTexture);
532 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24,
static_cast<GLsizei
>(Wframebuffer),
static_cast<GLsizei
>(Hframebuffer), 0, GL_DEPTH_COMPONENT, GL_FLOAT,
nullptr);
533 error = glGetError();
534 if (error != GL_NO_ERROR) {
535 glBindFramebuffer(GL_FRAMEBUFFER, 0);
536 glDeleteTextures(1, &offscreenDepthTexture);
537 glDeleteTextures(1, &offscreenColorTexture);
538 glDeleteFramebuffers(1, &offscreenFramebufferID);
539 offscreenDepthTexture = 0;
540 offscreenColorTexture = 0;
541 offscreenFramebufferID = 0;
542 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): Failed to create depth texture storage. OpenGL error: " + std::to_string(error) +
". This may indicate insufficient GPU memory or unsupported depth format.");
545 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
546 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
547 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, offscreenDepthTexture, 0);
550 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
551 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
553 glBindFramebuffer(GL_FRAMEBUFFER, 0);
556 std::string error_message =
"ERROR (Visualizer::setupOffscreenFramebuffer): Offscreen framebuffer is not complete. Status: ";
557 switch (framebuffer_status) {
558 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
559 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT - One or more attachment points are not framebuffer attachment complete";
561 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
562 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT - No images are attached to the framebuffer";
564 case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
565 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER - Draw buffer configuration error";
567 case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
568 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER - Read buffer configuration error";
570 case GL_FRAMEBUFFER_UNSUPPORTED:
571 error_message +=
"GL_FRAMEBUFFER_UNSUPPORTED - Combination of internal formats is not supported";
573 case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
574 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE - Multisample configuration error";
577 error_message +=
"Unknown framebuffer error (code: " + std::to_string(framebuffer_status) +
")";
580 error_message +=
". This typically occurs in virtualized graphics environments or with limited OpenGL driver support.";
585 glBindFramebuffer(GL_FRAMEBUFFER, 0);
588 if (!checkerrors()) {
590 helios_runtime_error(
"ERROR (Visualizer::setupOffscreenFramebuffer): OpenGL errors occurred during offscreen framebuffer setup completion.");
597 if (window !=
nullptr) {
598 if (offscreenFramebufferID != 0) {
599 glDeleteFramebuffers(1, &offscreenFramebufferID);
600 offscreenFramebufferID = 0;
602 if (offscreenColorTexture != 0) {
603 glDeleteTextures(1, &offscreenColorTexture);
604 offscreenColorTexture = 0;
606 if (offscreenDepthTexture != 0) {
607 glDeleteTextures(1, &offscreenDepthTexture);
608 offscreenDepthTexture = 0;
613std::vector<helios::RGBcolor> Visualizer::readOffscreenPixels()
const {
616 if (offscreenFramebufferID == 0) {
617 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): No offscreen framebuffer available. "
618 "Ensure setupOffscreenFramebuffer() was called successfully in headless mode.");
622 if (Wframebuffer == 0 || Hframebuffer == 0) {
623 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Invalid framebuffer dimensions (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
624 "). This indicates the offscreen framebuffer was not properly initialized.");
628 const char *gl_version = (
const char *) glGetString(GL_VERSION);
629 if (gl_version ==
nullptr) {
631 "This indicates OpenGL initialization failed or the context was lost.");
635 while (glGetError() != GL_NO_ERROR) {
639 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
640 GLenum error = glGetError();
641 if (error != GL_NO_ERROR) {
642 glBindFramebuffer(GL_FRAMEBUFFER, 0);
643 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Failed to bind offscreen framebuffer (OpenGL error: " + std::to_string(error) +
"). This indicates graphics driver issues or corrupted framebuffer.");
647 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
648 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
649 glBindFramebuffer(GL_FRAMEBUFFER, 0);
650 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Framebuffer is not complete (status: " + std::to_string(framebuffer_status) +
"). This indicates missing attachments or graphics driver incompatibility.");
654 const size_t pixel_count =
static_cast<size_t>(Wframebuffer) *
static_cast<size_t>(Hframebuffer);
655 const size_t data_size = pixel_count * 3;
658 if (pixel_count > SIZE_MAX / 3 || data_size > SIZE_MAX /
sizeof(
unsigned char)) {
659 glBindFramebuffer(GL_FRAMEBUFFER, 0);
660 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Framebuffer dimensions too large (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
"). This would cause memory allocation overflow.");
663 std::vector<helios::RGBcolor> pixels;
666 std::vector<unsigned char> pixel_data(data_size);
667 glReadPixels(0, 0,
static_cast<GLsizei
>(Wframebuffer),
static_cast<GLsizei
>(Hframebuffer), GL_RGB, GL_UNSIGNED_BYTE, pixel_data.data());
669 error = glGetError();
670 if (error != GL_NO_ERROR) {
671 glBindFramebuffer(GL_FRAMEBUFFER, 0);
672 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Failed to read pixels from framebuffer (OpenGL error: " + std::to_string(error) +
"). This indicates graphics driver issues or framebuffer format problems.");
676 pixels.reserve(pixel_count);
677 for (
size_t i = 0; i + 2 < pixel_data.size(); i += 3) {
678 float r = pixel_data[i] / 255.0f;
679 float g = pixel_data[i + 1] / 255.0f;
680 float b = pixel_data[i + 2] / 255.0f;
683 }
catch (
const std::exception &e) {
684 glBindFramebuffer(GL_FRAMEBUFFER, 0);
685 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixels): Memory allocation or conversion failed: " + std::string(e.what()) +
". This may indicate insufficient memory or data corruption.");
689 glBindFramebuffer(GL_FRAMEBUFFER, 0);
694std::vector<helios::RGBAcolor> Visualizer::readOffscreenPixelsRGBA(
bool read_alpha)
const {
697 if (offscreenFramebufferID == 0) {
698 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): No offscreen framebuffer available. "
699 "Ensure setupOffscreenFramebuffer() was called successfully in headless mode.");
703 if (Wframebuffer == 0 || Hframebuffer == 0) {
704 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Invalid framebuffer dimensions (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
705 "). This indicates the offscreen framebuffer was not properly initialized.");
709 const char *gl_version = (
const char *) glGetString(GL_VERSION);
710 if (gl_version ==
nullptr) {
712 "This indicates OpenGL initialization failed or the context was lost.");
716 while (glGetError() != GL_NO_ERROR) {
720 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
721 GLenum error = glGetError();
722 if (error != GL_NO_ERROR) {
723 glBindFramebuffer(GL_FRAMEBUFFER, 0);
724 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Failed to bind offscreen framebuffer (OpenGL error: " + std::to_string(error) +
"). This indicates graphics driver issues or corrupted framebuffer.");
728 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
729 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
730 glBindFramebuffer(GL_FRAMEBUFFER, 0);
731 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Framebuffer is not complete (status: " + std::to_string(framebuffer_status) +
"). This indicates missing attachments or graphics driver incompatibility.");
735 const size_t pixel_count =
static_cast<size_t>(Wframebuffer) *
static_cast<size_t>(Hframebuffer);
736 const size_t channels = read_alpha ? 4 : 3;
737 const size_t data_size = pixel_count * channels;
740 if (pixel_count > SIZE_MAX / 4 || data_size > SIZE_MAX /
sizeof(
unsigned char)) {
741 glBindFramebuffer(GL_FRAMEBUFFER, 0);
742 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Framebuffer dimensions too large (" + std::to_string(Wframebuffer) +
"x" + std::to_string(Hframebuffer) +
"). This would cause memory allocation overflow.");
745 std::vector<helios::RGBAcolor> pixels;
748 std::vector<unsigned char> pixel_data(data_size);
750 glReadPixels(0, 0,
static_cast<GLsizei
>(Wframebuffer),
static_cast<GLsizei
>(Hframebuffer), GL_RGBA, GL_UNSIGNED_BYTE, pixel_data.data());
752 glReadPixels(0, 0,
static_cast<GLsizei
>(Wframebuffer),
static_cast<GLsizei
>(Hframebuffer), GL_RGB, GL_UNSIGNED_BYTE, pixel_data.data());
755 error = glGetError();
756 if (error != GL_NO_ERROR) {
757 glBindFramebuffer(GL_FRAMEBUFFER, 0);
758 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Failed to read pixels from framebuffer (OpenGL error: " + std::to_string(error) +
"). This indicates graphics driver issues or framebuffer format problems.");
762 pixels.reserve(pixel_count);
764 for (
int row = Hframebuffer - 1; row >= 0; row--) {
765 for (
size_t col = 0; col < Wframebuffer; col++) {
766 size_t byte_idx = (row * Wframebuffer + col) * 4;
767 float r = pixel_data[byte_idx] / 255.0f;
768 float g = pixel_data[byte_idx + 1] / 255.0f;
769 float b = pixel_data[byte_idx + 2] / 255.0f;
770 float a = pixel_data[byte_idx + 3] / 255.0f;
775 for (
int row = Hframebuffer - 1; row >= 0; row--) {
776 for (
size_t col = 0; col < Wframebuffer; col++) {
777 size_t byte_idx = (row * Wframebuffer + col) * 3;
778 float r = pixel_data[byte_idx] / 255.0f;
779 float g = pixel_data[byte_idx + 1] / 255.0f;
780 float b = pixel_data[byte_idx + 2] / 255.0f;
785 }
catch (
const std::exception &e) {
786 glBindFramebuffer(GL_FRAMEBUFFER, 0);
787 helios_runtime_error(
"ERROR (Visualizer::readOffscreenPixelsRGBA): Memory allocation or conversion failed: " + std::string(e.what()) +
". This may indicate insufficient memory or data corruption.");
791 glBindFramebuffer(GL_FRAMEBUFFER, 0);
798 if (offscreenFramebufferID != 0) {
799 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
800 glViewport(0, 0, Wframebuffer, Hframebuffer);
804void Visualizer::initialize(
uint window_width_pixels,
uint window_height_pixels,
int aliasing_samples,
bool window_decorations,
bool headless_mode) {
805 Wdisplay = window_width_pixels;
806 Hdisplay = window_height_pixels;
809 const char *force_offscreen = std::getenv(
"HELIOS_FORCE_OFFSCREEN");
810 const char *ci_env = std::getenv(
"CI");
811 const char *display_env = std::getenv(
"DISPLAY");
813 bool should_force_headless =
false;
814 if (force_offscreen !=
nullptr && std::string(force_offscreen) ==
"1") {
815 should_force_headless =
true;
816 }
else if (ci_env !=
nullptr && std::string(ci_env) ==
"true") {
819 if (display_env ==
nullptr || std::strlen(display_env) == 0) {
820 should_force_headless =
true;
822#elif __APPLE__ || _WIN32
824 should_force_headless =
true;
829 headless = headless_mode || should_force_headless;
833 maximum_texture_size =
make_uint2(2048, 2048);
836 texture_array_layers = 0;
837 textures_dirty =
false;
840 offscreenFramebufferID = 0;
841 offscreenColorTexture = 0;
842 offscreenDepthTexture = 0;
848 buffers_swapped_since_render =
false;
852 minimum_view_radius = 0.05f;
855 primitiveColorsNeedUpdate =
false;
857 isWatermarkVisible =
true;
860 navigation_gizmo_enabled =
true;
861 previous_camera_eye_location =
make_vec3(0, 0, 0);
862 previous_camera_lookat_center =
make_vec3(0, 0, 0);
863 navigation_gizmo_IDs.clear();
864 hovered_gizmo_bubble = -1;
866 background_is_transparent =
false;
867 watermark_was_visible_before_transparent =
false;
868 navigation_gizmo_was_enabled_before_image_display =
false;
869 background_rectangle_ID = 0;
875 colorbar_integer_data =
false;
878 colorbar_fontsize = 12;
879 colorbar_fontcolor = RGB::black;
881 colorbar_position =
make_vec3(0.65, 0.1, 0.1);
883 colorbar_intended_aspect_ratio = 1.5f;
884 colorbar_IDs.clear();
889 point_culling_enabled =
true;
890 point_culling_threshold = 10000;
891 point_max_render_distance = 0;
892 point_lod_factor = 10.0f;
895 points_total_count = 0;
896 points_rendered_count = 0;
897 last_culling_time_ms = 0;
905 if (glfw_reference_count == 0) {
912 glfw_reference_count++;
921 glfwDefaultWindowHints();
923 glfwWindowHint(GLFW_SAMPLES, std::max(0, aliasing_samples));
924 glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
925 glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
927 glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
928 glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
933 createOffscreenContext();
936 glfwWindowHint(GLFW_VISIBLE, 0);
938 if (!window_decorations) {
939 glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
946 glewExperimental = GL_TRUE;
947 GLenum glew_result = glewInit();
948 if (glew_result != GLEW_OK) {
949 std::string error_msg =
"ERROR (Visualizer::initialize): Failed to initialize GLEW. ";
950 error_msg +=
"GLEW error: " + std::string((
const char *) glewGetErrorString(glew_result));
953 error_msg +=
"\nIn headless mode, this usually indicates:";
954 error_msg +=
"\n- Missing or incompatible OpenGL drivers";
955 error_msg +=
"\n- Virtual display server not properly configured";
956 error_msg +=
"\n- VirtualGL or Mesa software rendering issues";
957 error_msg +=
"\nConsider setting LIBGL_ALWAYS_SOFTWARE=1 for software rendering";
965 GLenum gl_error = glGetError();
966 if (gl_error != GL_NO_ERROR && gl_error != GL_INVALID_ENUM) {
967 std::string error_msg =
"ERROR (Visualizer): Unexpected OpenGL error after GLEW initialization: ";
968 error_msg += std::to_string(gl_error);
970 error_msg +=
"\nThis indicates a serious OpenGL context or driver issue in headless mode.";
976 const char *gl_version = (
const char *) glGetString(GL_VERSION);
977 const char *gl_vendor = (
const char *) glGetString(GL_VENDOR);
978 const char *gl_renderer = (
const char *) glGetString(GL_RENDERER);
980 if (gl_version ==
nullptr || gl_vendor ==
nullptr || gl_renderer ==
nullptr) {
981 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL context is not functional - unable to query basic GL information. "
982 "This indicates a fundamental issue with OpenGL context creation or driver compatibility.");
986 if (headless && (std::getenv(
"CI") !=
nullptr || std::getenv(
"HELIOS_DEBUG") !=
nullptr)) {
987 std::cout <<
"OpenGL Version: " << gl_version << std::endl;
988 std::cout <<
"OpenGL Vendor: " << gl_vendor << std::endl;
989 std::cout <<
"OpenGL Renderer: " << gl_renderer << std::endl;
993 GLint max_texture_size;
994 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size);
995 GLenum validation_error = glGetError();
996 if (validation_error != GL_NO_ERROR) {
997 helios_runtime_error(
"ERROR (Visualizer::initialize): Basic OpenGL query operations failed (error: " + std::to_string(validation_error) +
999 "This indicates the OpenGL context is not properly initialized or lacks required functionality.");
1003 if (max_texture_size < 1024 && headless) {
1004 std::cerr <<
"WARNING (Visualizer::initialize): Maximum texture size is very small (" << max_texture_size <<
"x" << max_texture_size <<
"). This may indicate software rendering or limited driver support." << std::endl;
1008 if (!checkerrors()) {
1009 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL context initialization failed after GLEW setup and validation. "
1010 "This often occurs in headless CI environments without proper GPU drivers or display servers. "
1011 "For headless operation, ensure proper virtual display or software rendering is configured.");
1016 glEnable(GL_DEPTH_TEST);
1017 glDepthFunc(GL_LEQUAL);
1020 if (aliasing_samples <= 0) {
1021 glDisable(GL_MULTISAMPLE);
1022 glDisable(GL_MULTISAMPLE_ARB);
1025 if (aliasing_samples <= 1) {
1026 glDisable(GL_POLYGON_SMOOTH);
1028 glEnable(GL_POLYGON_SMOOTH);
1037 if (!checkerrors()) {
1038 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL context setup failed during basic parameter configuration. "
1039 "This typically indicates graphics driver incompatibility or missing OpenGL support in the execution environment.");
1048 glEnable(GL_POLYGON_OFFSET_FILL);
1049 glPolygonOffset(1.0f, 1.0f);
1050 glDisable(GL_CULL_FACE);
1053 if (!checkerrors()) {
1054 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL context setup failed during advanced parameter configuration. "
1055 "Verify that the graphics environment supports the required OpenGL version and features.");
1059 constexpr size_t Ntypes = GeometryHandler::all_geometry_types.size();
1062 if (Ntypes == 0 || Ntypes > 1000) {
1063 helios_runtime_error(
"ERROR (Visualizer::initialize): Invalid number of geometry types (" + std::to_string(Ntypes) +
1065 "This indicates a configuration issue with GeometryHandler.");
1070 face_index_buffer.resize(Ntypes);
1071 vertex_buffer.resize(Ntypes);
1072 uv_buffer.resize(Ntypes);
1075 glGenBuffers((GLsizei) face_index_buffer.size(), face_index_buffer.data());
1076 GLenum error = glGetError();
1077 if (error != GL_NO_ERROR) {
1078 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate face index buffers. OpenGL error: " + std::to_string(error));
1081 glGenBuffers((GLsizei) vertex_buffer.size(), vertex_buffer.data());
1082 error = glGetError();
1083 if (error != GL_NO_ERROR) {
1084 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate vertex buffers. OpenGL error: " + std::to_string(error));
1087 glGenBuffers((GLsizei) uv_buffer.size(), uv_buffer.data());
1088 error = glGetError();
1089 if (error != GL_NO_ERROR) {
1090 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate UV buffers. OpenGL error: " + std::to_string(error));
1094 color_buffer.resize(Ntypes);
1095 color_texture_object.resize(Ntypes);
1096 normal_buffer.resize(Ntypes);
1097 normal_texture_object.resize(Ntypes);
1098 texture_flag_buffer.resize(Ntypes);
1099 texture_flag_texture_object.resize(Ntypes);
1100 texture_ID_buffer.resize(Ntypes);
1101 texture_ID_texture_object.resize(Ntypes);
1102 coordinate_flag_buffer.resize(Ntypes);
1103 coordinate_flag_texture_object.resize(Ntypes);
1104 hidden_flag_buffer.resize(Ntypes);
1105 hidden_flag_texture_object.resize(Ntypes);
1106 sky_geometry_flag_buffer.resize(Ntypes);
1107 sky_geometry_flag_texture_object.resize(Ntypes);
1110 glGenBuffers((GLsizei) color_buffer.size(), color_buffer.data());
1111 error = glGetError();
1112 if (error != GL_NO_ERROR) {
1113 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate color buffers. OpenGL error: " + std::to_string(error));
1116 glGenTextures((GLsizei) color_texture_object.size(), color_texture_object.data());
1117 error = glGetError();
1118 if (error != GL_NO_ERROR) {
1119 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate color texture objects. OpenGL error: " + std::to_string(error));
1122 glGenBuffers((GLsizei) normal_buffer.size(), normal_buffer.data());
1123 error = glGetError();
1124 if (error != GL_NO_ERROR) {
1125 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate normal buffers. OpenGL error: " + std::to_string(error));
1128 glGenTextures((GLsizei) normal_texture_object.size(), normal_texture_object.data());
1129 error = glGetError();
1130 if (error != GL_NO_ERROR) {
1131 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate normal texture objects. OpenGL error: " + std::to_string(error));
1134 glGenBuffers((GLsizei) texture_flag_buffer.size(), texture_flag_buffer.data());
1135 error = glGetError();
1136 if (error != GL_NO_ERROR) {
1137 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate texture flag buffers. OpenGL error: " + std::to_string(error));
1140 glGenTextures((GLsizei) texture_flag_texture_object.size(), texture_flag_texture_object.data());
1141 error = glGetError();
1142 if (error != GL_NO_ERROR) {
1143 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate texture flag texture objects. OpenGL error: " + std::to_string(error));
1146 glGenBuffers((GLsizei) texture_ID_buffer.size(), texture_ID_buffer.data());
1147 error = glGetError();
1148 if (error != GL_NO_ERROR) {
1149 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate texture ID buffers. OpenGL error: " + std::to_string(error));
1152 glGenTextures((GLsizei) texture_ID_texture_object.size(), texture_ID_texture_object.data());
1153 error = glGetError();
1154 if (error != GL_NO_ERROR) {
1155 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate texture ID texture objects. OpenGL error: " + std::to_string(error));
1158 glGenBuffers((GLsizei) coordinate_flag_buffer.size(), coordinate_flag_buffer.data());
1159 error = glGetError();
1160 if (error != GL_NO_ERROR) {
1161 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate coordinate flag buffers. OpenGL error: " + std::to_string(error));
1164 glGenTextures((GLsizei) coordinate_flag_texture_object.size(), coordinate_flag_texture_object.data());
1165 error = glGetError();
1166 if (error != GL_NO_ERROR) {
1167 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate coordinate flag texture objects. OpenGL error: " + std::to_string(error));
1170 glGenBuffers((GLsizei) hidden_flag_buffer.size(), hidden_flag_buffer.data());
1171 error = glGetError();
1172 if (error != GL_NO_ERROR) {
1173 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate hidden flag buffers. OpenGL error: " + std::to_string(error));
1176 glGenTextures((GLsizei) hidden_flag_texture_object.size(), hidden_flag_texture_object.data());
1177 error = glGetError();
1178 if (error != GL_NO_ERROR) {
1179 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate hidden flag texture objects. OpenGL error: " + std::to_string(error));
1182 glGenBuffers((GLsizei) sky_geometry_flag_buffer.size(), sky_geometry_flag_buffer.data());
1183 error = glGetError();
1184 if (error != GL_NO_ERROR) {
1185 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate sky geometry flag buffers. OpenGL error: " + std::to_string(error));
1188 glGenTextures((GLsizei) sky_geometry_flag_texture_object.size(), sky_geometry_flag_texture_object.data());
1189 error = glGetError();
1190 if (error != GL_NO_ERROR) {
1191 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate sky geometry flag texture objects. OpenGL error: " + std::to_string(error));
1195 glGenBuffers(1, &uv_rescale_buffer);
1196 error = glGetError();
1197 if (error != GL_NO_ERROR) {
1198 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate UV rescale buffer. OpenGL error: " + std::to_string(error));
1201 glGenTextures(1, &uv_rescale_texture_object);
1202 error = glGetError();
1203 if (error != GL_NO_ERROR) {
1204 helios_runtime_error(
"ERROR (Visualizer::initialize): Failed to generate UV rescale texture object. OpenGL error: " + std::to_string(error));
1207 }
catch (
const std::exception &e) {
1208 helios_runtime_error(
"ERROR (Visualizer::initialize): Exception during buffer allocation: " + std::string(e.what()) +
". This may indicate insufficient memory or OpenGL driver issues.");
1212 if (!checkerrors()) {
1213 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL buffer creation failed with accumulated errors. "
1214 "This indicates insufficient graphics memory or unsupported buffer operations in the current OpenGL context.");
1226 primaryShader.
initialize(primaryVertShader.c_str(), primaryFragShader.c_str(),
this);
1227 depthShader.
initialize(shadowVertShader.c_str(), shadowFragShader.c_str(),
this);
1228 lineShader.
initialize(lineVertShader.c_str(), primaryFragShader.c_str(),
this, lineGeomShader.c_str());
1231 if (!checkerrors()) {
1233 "Verify that shader files are accessible and the OpenGL context supports the required shading language version.");
1241 glGenFramebuffers(1, &framebufferID);
1242 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
1245 glActiveTexture(GL_TEXTURE1);
1246 glGenTextures(1, &depthTexture);
1247 glBindTexture(GL_TEXTURE_2D, depthTexture);
1248 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, shadow_buffer_size.
x, shadow_buffer_size.
y, 0, GL_DEPTH_COMPONENT, GL_FLOAT,
nullptr);
1250 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1251 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1254 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
1255 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
1256 GLfloat borderColor[4] = {1.0f, 1.0f, 1.0f, 1.0f};
1257 glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
1260 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
1262 if (!checkerrors()) {
1263 helios_runtime_error(
"ERROR (Visualizer::initialize): OpenGL setup failed during texture configuration. "
1264 "This may indicate graphics driver issues or insufficient OpenGL support.");
1268 glActiveTexture(GL_TEXTURE0);
1270 glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexture, 0);
1272 glDrawBuffer(GL_NONE);
1275 int max_checks = 10000;
1277 while (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE && checks < max_checks) {
1281 GLenum framebuffer_status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
1282 if (framebuffer_status != GL_FRAMEBUFFER_COMPLETE) {
1283 std::string error_message =
"ERROR (Visualizer::initialize): Framebuffer is incomplete. Status: ";
1284 switch (framebuffer_status) {
1285 case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
1286 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT - Framebuffer attachment is incomplete";
1288 case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
1289 error_message +=
"GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT - No attachments";
1291 case GL_FRAMEBUFFER_UNSUPPORTED:
1292 error_message +=
"GL_FRAMEBUFFER_UNSUPPORTED - Unsupported framebuffer format";
1295 error_message +=
"Unknown framebuffer error code: " + std::to_string(framebuffer_status);
1298 error_message +=
". This typically occurs in CI environments with limited graphics support or missing GPU drivers.";
1304 if (!checkerrors()) {
1306 "This indicates issues with OpenGL framebuffer operations, often related to graphics driver limitations or insufficient resources.");
1310 Wframebuffer = Wdisplay;
1311 Hframebuffer = Hdisplay;
1316 perspectiveTransformationMatrix = glm::mat4(1.f);
1318 customTransformationMatrix = glm::mat4(1.f);
1330 camera_lookat_center =
make_vec3(0, 0, 0);
1338 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}};
1340 std::vector<float> clocs_c{0.f, 0.25f, 0.5f, 0.75f, 1.f};
1342 colormap_hot.set(ctable_c, clocs_c, 100, 0, 1);
1345 ctable_c = {RGB::cyan, RGB::magenta};
1347 clocs_c = {0.f, 1.f};
1349 colormap_cool.set(ctable_c, clocs_c, 100, 0, 1);
1352 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}};
1354 clocs_c = {0.f, 0.4f, 0.5f, 0.6f, 1.f};
1356 colormap_lava.set(ctable_c, clocs_c, 100, 0, 1);
1359 ctable_c = {RGB::navy, RGB::cyan, RGB::yellow,
make_RGBcolor(0.75f, 0.f, 0.f)};
1361 clocs_c = {0, 0.3f, 0.7f, 1.f};
1363 colormap_rainbow.set(ctable_c, clocs_c, 100, 0, 1);
1366 ctable_c = {RGB::navy,
make_RGBcolor(0, 0.6, 0.6), RGB::goldenrod, RGB::yellow};
1368 clocs_c = {0, 0.4f, 0.7f, 1.f};
1370 colormap_parula.set(ctable_c, clocs_c, 100, 0, 1);
1373 ctable_c = {RGB::black, RGB::white};
1375 clocs_c = {0.f, 1.f};
1377 colormap_gray.set(ctable_c, clocs_c, 100, 0, 1);
1381 {0.f, 0.4470f, 0.7410f},
1382 {0.8500f, 0.3250f, 0.0980f},
1383 {0.9290f, 0.6940f, 0.1250f},
1384 {0.4940f, 0.1840f, 0.5560f},
1385 {0.4660f, 0.6740f, 0.1880f},
1386 {0.3010f, 0.7450f, 0.9330f},
1387 {0.6350f, 0.0780f, 0.1840f}
1390 clocs_c = {0.f, 1.f / 6.f, 2.f / 6.f, 3.f / 6.f, 4.f / 6.f, 5.f / 6.f, 1.f};
1392 colormap_lines.set(ctable_c, clocs_c, 100, 0, 1);
1394 colormap_current = colormap_hot;
1397 glfwSetMouseButtonCallback((GLFWwindow *) window,
mouseCallback);
1402 if (!checkerrors()) {
1403 helios_runtime_error(
"ERROR (Visualizer::initialize): Final OpenGL setup failed during callback configuration. "
1404 "The OpenGL context may be in an invalid state or missing required extensions.");
1409 setBackgroundGradient();
1417 if (framebufferID != 0) {
1418 glDeleteFramebuffers(1, &framebufferID);
1420 if (depthTexture != 0) {
1421 glDeleteTextures(1, &depthTexture);
1426 if (window !=
nullptr) {
1428 glDeleteBuffers((GLsizei) face_index_buffer.size(), face_index_buffer.data());
1429 glDeleteBuffers((GLsizei) vertex_buffer.size(), vertex_buffer.data());
1430 glDeleteBuffers((GLsizei) uv_buffer.size(), uv_buffer.data());
1432 glDeleteBuffers((GLsizei) color_buffer.size(), color_buffer.data());
1433 glDeleteTextures((GLsizei) color_texture_object.size(), color_texture_object.data());
1434 glDeleteBuffers((GLsizei) normal_buffer.size(), normal_buffer.data());
1435 glDeleteTextures((GLsizei) normal_texture_object.size(), normal_texture_object.data());
1436 glDeleteBuffers((GLsizei) texture_flag_buffer.size(), texture_flag_buffer.data());
1437 glDeleteTextures((GLsizei) texture_flag_texture_object.size(), texture_flag_texture_object.data());
1438 glDeleteBuffers((GLsizei) texture_ID_buffer.size(), texture_ID_buffer.data());
1439 glDeleteTextures((GLsizei) texture_ID_texture_object.size(), texture_ID_texture_object.data());
1440 glDeleteBuffers((GLsizei) coordinate_flag_buffer.size(), coordinate_flag_buffer.data());
1441 glDeleteTextures((GLsizei) coordinate_flag_texture_object.size(), coordinate_flag_texture_object.data());
1442 glDeleteBuffers((GLsizei) hidden_flag_buffer.size(), hidden_flag_buffer.data());
1443 glDeleteTextures((GLsizei) hidden_flag_texture_object.size(), hidden_flag_texture_object.data());
1444 glDeleteBuffers((GLsizei) sky_geometry_flag_buffer.size(), sky_geometry_flag_buffer.data());
1445 glDeleteTextures((GLsizei) sky_geometry_flag_texture_object.size(), sky_geometry_flag_texture_object.data());
1448 if (texArray != 0) {
1449 glDeleteTextures(1, &texArray);
1451 glDeleteBuffers(1, &uv_rescale_buffer);
1452 glDeleteTextures(1, &uv_rescale_texture_object);
1459 glfwDestroyWindow(scast<GLFWwindow *>(window));
1467 glfw_reference_count--;
1468 if (glfw_reference_count == 0) {
1475 message_flag =
true;
1479 message_flag =
false;
1483 camera_eye_location = cameraPosition;
1484 camera_lookat_center = lookAt;
1488 camera_lookat_center = lookAt;
1489 camera_eye_location = camera_lookat_center +
sphere2cart(cameraAngle);
1493 camera_FOV = angle_FOV;
1497 light_direction = direction / direction.
magnitude();
1502 primaryLightingModel = lightingmodel;
1506 lightintensity = lightintensityfactor;
1509void Visualizer::removeBackgroundRectangle() {
1511 if (background_rectangle_ID != 0) {
1513 background_rectangle_ID = 0;
1517 for (
size_t id: background_sky_IDs) {
1520 background_sky_IDs.clear();
1524 backgroundColor = color;
1525 background_is_transparent =
false;
1528 removeBackgroundRectangle();
1531 if (watermark_was_visible_before_transparent) {
1533 watermark_was_visible_before_transparent =
false;
1537void Visualizer::setBackgroundGradient() {
1538 background_is_transparent =
false;
1541 removeBackgroundRectangle();
1545 std::vector<helios::vec3> vertices = {
1553 std::vector<helios::vec2> uvs = {
1565 background_is_transparent =
true;
1568 watermark_was_visible_before_transparent = isWatermarkVisible;
1572 removeBackgroundRectangle();
1576 std::vector<helios::vec3> vertices = {
1584 float aspect_ratio =
static_cast<float>(Wframebuffer) /
static_cast<float>(Hframebuffer);
1585 std::vector<helios::vec2> uvs;
1586 if (aspect_ratio > 1.f) {
1599 background_is_transparent =
false;
1602 removeBackgroundRectangle();
1606 std::vector<helios::vec3> vertices = {
1614 std::vector<helios::vec2> uvs = {
1625 if (watermark_was_visible_before_transparent) {
1627 watermark_was_visible_before_transparent =
false;
1632 using namespace helios;
1634 background_is_transparent =
false;
1637 removeBackgroundRectangle();
1640 std::string texture_path;
1641 if (texture_file ==
nullptr) {
1642 texture_path =
resolvePluginAsset(
"visualizer",
"textures/SkyDome_clouds.jpg").string();
1644 texture_path = texture_file;
1648 uint textureID = registerTextureImage(texture_path.c_str());
1652 float radius = 1.0f;
1655 float thetaStart = -0.5f *
M_PI;
1656 float dtheta = (0.5f *
M_PI - thetaStart) /
float(Ndivisions - 1);
1657 float dphi = 2.f *
M_PI / float(Ndivisions - 1);
1662 background_sky_IDs.reserve(2u * Ndivisions * Ndivisions);
1665 for (
int j = 0; j < scast<int>(Ndivisions - 1); j++) {
1667 vec3 v0 = center + radius * cart;
1669 vec3 v1 = center + radius * cart;
1671 vec3 v2 = center + radius * cart;
1673 vec3 n0 = v0 - center;
1675 vec3 n1 = v1 - center;
1677 vec3 n2 = v2 - center;
1680 vec2 uv0 =
make_vec2(1.f - atan2f(sinf((
float(j) + 0.5f) * dphi), -cosf((
float(j) + 0.5f) * dphi)) / (2.f *
M_PI) - 0.5f, n0.
z * 0.5f + 0.5f);
1685 if (j == scast<int>(Ndivisions - 2)) {
1689 std::vector<vec3> vertices = {v0, v1, v2};
1690 std::vector<vec2> uvs = {uv0, uv1, uv2};
1693 geometry_handler.
addGeometry(UUID, GeometryHandler::GEOMETRY_TYPE_TRIANGLE, vertices,
make_RGBAcolor(1, 1, 1, 1), uvs, textureID,
false,
false, 1,
true,
false,
true, 0);
1694 background_sky_IDs.push_back(UUID);
1698 for (
int i = 1; i < scast<int>(Ndivisions - 1); i++) {
1699 for (
int j = 0; j < scast<int>(Ndivisions - 1); j++) {
1701 vec3 v0 = center + radius * cart;
1703 vec3 v1 = center + radius * cart;
1705 vec3 v2 = center + radius * cart;
1707 vec3 v3 = center + radius * cart;
1709 vec3 n0 = v0 - center;
1711 vec3 n1 = v1 - center;
1713 vec3 n2 = v2 - center;
1715 vec3 n3 = v3 - center;
1724 if (j == scast<int>(Ndivisions - 2)) {
1731 std::vector<vec3> vertices = {v0, v1, v2};
1732 std::vector<vec2> uvs = {uv0, uv1, uv2};
1734 geometry_handler.
addGeometry(UUID, GeometryHandler::GEOMETRY_TYPE_TRIANGLE, vertices,
make_RGBAcolor(1, 1, 1, 1), uvs, textureID,
false,
false, 1,
true,
false,
true, 0);
1735 background_sky_IDs.push_back(UUID);
1740 std::vector<vec3> vertices = {v2, v1, v3};
1741 std::vector<vec2> uvs = {uv2, uv1, uv3};
1743 geometry_handler.
addGeometry(UUID, GeometryHandler::GEOMETRY_TYPE_TRIANGLE, vertices,
make_RGBAcolor(1, 1, 1, 1), uvs, textureID,
false,
false, 1,
true,
false,
true, 0);
1744 background_sky_IDs.push_back(UUID);
1750 if (watermark_was_visible_before_transparent) {
1752 watermark_was_visible_before_transparent =
false;
1757 isWatermarkVisible =
false;
1758 if (watermark_ID != 0) {
1765 isWatermarkVisible =
true;
1770 navigation_gizmo_enabled =
false;
1771 hovered_gizmo_bubble = -1;
1772 if (!navigation_gizmo_IDs.empty()) {
1774 navigation_gizmo_IDs.clear();
1779 navigation_gizmo_enabled =
true;
1780 updateNavigationGizmo();
1783bool Visualizer::testGizmoBubbleHit(
const helios::vec2 &normalized_pos,
int bubble_index)
const {
1784 if (!navigation_gizmo_enabled || navigation_gizmo_IDs.empty() || bubble_index < 0 || bubble_index > 2) {
1790 size_t bubble_id = navigation_gizmo_IDs[3 + bubble_index];
1793 if (vertices.size() != 4) {
1798 float min_x = std::min({vertices[0].x, vertices[1].x, vertices[2].x, vertices[3].x});
1799 float max_x = std::max({vertices[0].x, vertices[1].x, vertices[2].x, vertices[3].x});
1800 float min_y = std::min({vertices[0].y, vertices[1].y, vertices[2].y, vertices[3].y});
1801 float max_y = std::max({vertices[0].y, vertices[1].y, vertices[2].y, vertices[3].y});
1804 return (normalized_pos.
x >= min_x && normalized_pos.
x <= max_x && normalized_pos.
y >= min_y && normalized_pos.
y <= max_y);
1808 if (!navigation_gizmo_enabled) {
1814 float normalized_x =
static_cast<float>(screen_x) /
static_cast<float>(Wdisplay);
1815 float normalized_y = 1.0f - (
static_cast<float>(screen_y) /
static_cast<float>(Hdisplay));
1819 for (
int axis = 0; axis < 3; axis++) {
1820 if (testGizmoBubbleHit(normalized_pos, axis)) {
1821 reorientCameraToAxis(axis);
1828 if (!navigation_gizmo_enabled) {
1834 if (navigation_gizmo_IDs.size() != 6) {
1840 float normalized_x =
static_cast<float>(screen_x) /
static_cast<float>(Wdisplay);
1841 float normalized_y = 1.0f - (
static_cast<float>(screen_y) /
static_cast<float>(Hdisplay));
1845 int new_hovered_bubble = -1;
1846 for (
int axis = 0; axis < 3; axis++) {
1847 if (testGizmoBubbleHit(normalized_pos, axis)) {
1848 new_hovered_bubble = axis;
1854 if (new_hovered_bubble != hovered_gizmo_bubble) {
1855 hovered_gizmo_bubble = new_hovered_bubble;
1856 updateNavigationGizmo();
1858 transferBufferData();
1862void Visualizer::reorientCameraToAxis(
int axis_index) {
1863 if (axis_index < 0 || axis_index > 2) {
1869 float radius = current_spherical.
radius;
1875 switch (axis_index) {
1891bool Visualizer::cameraHasChanged()
const {
1892 constexpr float epsilon = 1e-6f;
1893 return (camera_eye_location - previous_camera_eye_location).magnitude() > epsilon || (camera_lookat_center - previous_camera_lookat_center).magnitude() > epsilon;
1896void Visualizer::updatePerspectiveTransformation(
bool shadow) {
1897 float dist = glm::distance(glm_vec3(camera_lookat_center), glm_vec3(camera_eye_location));
1898 float nearPlane = std::max(0.1f, 0.05f * dist);
1900 float farPlane = std::max(5.f * camera_eye_location.
z, 2.0f * dist);
1901 cameraProjectionMatrix = glm::perspective(glm::radians(camera_FOV),
float(Wframebuffer) /
float(Hframebuffer), nearPlane, farPlane);
1903 cameraProjectionMatrix = glm::infinitePerspective(glm::radians(camera_FOV),
float(Wframebuffer) /
float(Hframebuffer), nearPlane);
1905 cameraViewMatrix = glm::lookAt(glm_vec3(camera_eye_location), glm_vec3(camera_lookat_center), glm::vec3(0, 0, 1));
1907 perspectiveTransformationMatrix = cameraProjectionMatrix * cameraViewMatrix;
1910void Visualizer::updateCustomTransformation(
const glm::mat4 &matrix) {
1911 customTransformationMatrix = matrix;
1919 if (!colorbar_IDs.empty()) {
1921 colorbar_IDs.clear();
1927 if (position.
x < 0 || position.
x > 1 || position.
y < 0 || position.
y > 1 || position.
z < -1 || position.
z > 1) {
1928 helios_runtime_error(
"ERROR (Visualizer::setColorbarPosition): position is out of range. Coordinates must be: 0<x<1, 0<y<1, -1<z<1.");
1930 colorbar_position = position;
1934 if (size.
x < 0 || size.
x > 1 || size.
y < 0 || size.
y > 1) {
1935 helios_runtime_error(
"ERROR (Visualizer::setColorbarSize): Size must be greater than 0 and less than the window size (i.e., 1).");
1937 colorbar_size = size;
1940 colorbar_intended_aspect_ratio = size.
x / size.
y;
1942 colorbar_intended_aspect_ratio = 0.1f;
1947 if (message_flag && cmin > cmax) {
1948 std::cerr <<
"WARNING (Visualizer::setColorbarRange): Maximum colorbar value must be greater than minimum value...Ignoring command." << std::endl;
1951 colorbar_min = cmin;
1952 colorbar_max = cmax;
1957 if (ticks.empty()) {
1962 for (
int i = 1; i < ticks.size(); i++) {
1963 if (ticks.at(i) <= ticks.at(i - 1)) {
1964 helios_runtime_error(
"ERROR (Visualizer::setColorbarTicks): Colorbar ticks must be monotonically increasing.");
1969 for (
int i = ticks.size() - 1; i >= 0; i--) {
1970 if (ticks.at(i) < colorbar_min) {
1971 colorbar_min = ticks.at(i);
1974 for (
float tick: ticks) {
1975 if (tick > colorbar_max) {
1976 colorbar_max = tick;
1980 colorbar_ticks = ticks;
1985 if (value == 0.0 || !std::isfinite(value)) {
1990 double exp = std::floor(std::log10(std::fabs(value)));
1992 double frac = std::fabs(value) / std::pow(10.0, exp);
1999 }
else if (frac < 3.0) {
2001 }
else if (frac < 7.0) {
2010 }
else if (frac <= 2.0) {
2012 }
else if (frac <= 5.0) {
2020 double result = niceFrac * std::pow(10.0, exp);
2021 return value < 0.0 ? -result : result;
2025 std::ostringstream oss;
2028 if (!std::isfinite(value)) {
2033 if (isIntegerData) {
2035 if (std::fabs(value) >= 10000) {
2036 oss << std::scientific << std::setprecision(0) << value;
2039 oss << static_cast<int>(std::round(value));
2045 bool useScientific = (std::fabs(value) >= 1e4 || (std::fabs(value) < 1e-3 && value != 0.0));
2047 if (useScientific) {
2049 int decimalPlaces = std::max(0,
static_cast<int>(-std::floor(std::log10(std::fabs(spacing)))));
2050 decimalPlaces = std::min(decimalPlaces, 6);
2051 oss << std::scientific << std::setprecision(decimalPlaces) << value;
2056 if (spacing >= 1.0) {
2059 decimalPlaces = std::max(0,
static_cast<int>(-std::floor(std::log10(spacing))));
2060 decimalPlaces = std::min(decimalPlaces, 6);
2063 oss << std::fixed << std::setprecision(decimalPlaces) << value;
2070 std::vector<float> ticks;
2073 if (!std::isfinite(dataMin) || !std::isfinite(dataMax) || dataMax <= dataMin) {
2075 ticks.push_back(dataMin);
2076 ticks.push_back(dataMax);
2081 double range = dataMax - dataMin;
2082 if (range < 1e-10) {
2083 ticks.push_back(dataMin);
2088 double niceRange =
niceNumber(range / (targetTicks - 1),
true);
2091 if (isIntegerData && niceRange < 1.0) {
2096 double graphMin = std::floor(dataMin / niceRange) * niceRange;
2097 double graphMax = std::ceil(dataMax / niceRange) * niceRange;
2100 if (isIntegerData) {
2101 graphMin = std::floor(graphMin);
2102 graphMax = std::ceil(graphMax);
2103 niceRange = std::max(1.0, niceRange);
2108 double epsilon = niceRange * 0.5;
2109 for (
double tick = graphMin; tick <= graphMax + epsilon; tick += niceRange) {
2110 double tickValue = tick;
2113 if (isIntegerData) {
2114 tickValue = std::round(tick);
2118 if (ticks.empty() || std::fabs(tickValue - ticks.back()) > niceRange * 0.01) {
2119 ticks.push_back(
static_cast<float>(tickValue));
2124 if (ticks.size() < 2) {
2126 ticks.push_back(dataMin);
2127 ticks.push_back(dataMax);
2134 colorbar_title = title;
2138 if (font_size <= 0) {
2139 helios_runtime_error(
"ERROR (Visualizer::setColorbarFontSize): Font size must be greater than zero.");
2141 colorbar_fontsize = font_size;
2145 colorbar_fontcolor = fontcolor;
2150 colormap_current = colormap_hot;
2152 colormap_current = colormap_cool;
2154 colormap_current = colormap_lava;
2156 colormap_current = colormap_rainbow;
2158 colormap_current = colormap_parula;
2160 colormap_current = colormap_gray;
2162 colormap_current = colormap_lines;
2164 helios_runtime_error(
"ERROR (Visualizer::setColormap): Setting a custom colormap requires calling setColormap with additional arguments defining the colormap.");
2171 if (colors.size() != divisions.size()) {
2172 helios_runtime_error(
"ERROR (Visualizer::setColormap): The number of colors must be equal to the number of divisions.");
2175 Colormap colormap_custom(colors, divisions, 100, 0, 1);
2177 colormap_current = colormap_custom;
2181 return colormap_current;
2184glm::mat4 Visualizer::computeShadowDepthMVP()
const {
2185 glm::vec3 lightDir = -glm::normalize(glm_vec3(light_direction));
2187 const float margin = 0.01;
2192 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),
2193 glm::vec4(-1, -1, +1, 1), glm::vec4(+1, -1, +1, 1), glm::vec4(+1, +1, +1, 1), glm::vec4(-1, +1, +1, 1)};
2195 glm::mat4 invCam = glm::inverse(this->perspectiveTransformationMatrix);
2197 std::array<glm::vec3, 8> frustumWs;
2198 for (std::size_t i = 0; i < 8; i++) {
2199 glm::vec4 ws = invCam * ndcCorners[i];
2200 frustumWs[i] = glm::vec3(ws) / ws.w;
2204 glm::vec3 lightUp(0.0f, 1.0f, 0.0f);
2205 if (glm::abs(glm::dot(lightUp, lightDir)) > 0.9f)
2206 lightUp = glm::vec3(1, 0, 0);
2211 glm::vec3 centroid(0);
2212 for (
auto &c: frustumWs)
2216 glm::vec3 lightPos = centroid - lightDir * 100.0f;
2218 glm::mat4 lightView = glm::lookAt(lightPos, centroid, lightUp);
2221 glm::vec3 minL(std::numeric_limits<float>::infinity());
2222 glm::vec3 maxL(-std::numeric_limits<float>::infinity());
2224 for (
auto &c: frustumWs) {
2225 glm::vec3 p = glm::vec3(lightView * glm::vec4(c, 1));
2226 minL = glm::min(minL, p);
2227 maxL = glm::max(maxL, p);
2231 glm::vec3 extent = maxL - minL;
2232 minL -= extent * margin;
2233 maxL += extent * margin;
2235 float zNear = -maxL.z;
2236 float zFar = -minL.z;
2238 glm::mat4 lightProj = glm::ortho(minL.x, maxL.x, minL.y, maxL.y, zNear, zFar);
2241 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);
2243 return bias * lightProj * lightView;
2246Visualizer::Texture::Texture(
const std::string &texture_file,
uint textureID,
const helios::uint2 &maximum_texture_size,
bool loadalphaonly) : texture_file(texture_file), glyph(), textureID(textureID) {
2248 if (loadalphaonly) {
2257 if (loadalphaonly) {
2263 std::vector<unsigned char> image_data;
2265 if (texture_file.substr(texture_file.find_last_of(
'.') + 1) ==
"png") {
2266 read_png_file(texture_file.c_str(), image_data, texture_resolution.y, texture_resolution.x);
2268 read_JPEG_file(texture_file.c_str(), image_data, texture_resolution.y, texture_resolution.x);
2271 texture_data = std::move(image_data);
2274 if (texture_resolution.x > maximum_texture_size.
x || texture_resolution.y > maximum_texture_size.
y) {
2275 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.
x), std::min(texture_resolution.y, maximum_texture_size.
y));
2276 resizeTexture(new_texture_resolution);
2280Visualizer::Texture::Texture(
const Glyph *glyph_ptr,
uint textureID,
const helios::uint2 &maximum_texture_size) : textureID(textureID) {
2281 assert(glyph_ptr !=
nullptr);
2285 texture_resolution = glyph_ptr->size;
2288 texture_data.resize(texture_resolution.x * texture_resolution.y);
2289 for (
int j = 0; j < texture_resolution.y; j++) {
2290 for (
int i = 0; i < texture_resolution.x; i++) {
2291 texture_data[i + j * texture_resolution.x] = glyph_ptr->data.at(j).at(i);
2296 if (texture_resolution.x > maximum_texture_size.
x || texture_resolution.y > maximum_texture_size.
y) {
2297 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.
x), std::min(texture_resolution.y, maximum_texture_size.
y));
2298 resizeTexture(new_texture_resolution);
2304Visualizer::Texture::Texture(
const std::vector<unsigned char> &pixel_data,
uint textureID,
const helios::uint2 &image_resolution,
const helios::uint2 &maximum_texture_size) : textureID(textureID) {
2306 assert(pixel_data.size() == 4u * image_resolution.
x * image_resolution.
y);
2309 texture_data = pixel_data;
2310 texture_resolution = image_resolution;
2314 if (texture_resolution.x > maximum_texture_size.
x || texture_resolution.y > maximum_texture_size.
y) {
2315 const uint2 new_texture_resolution(std::min(texture_resolution.x, maximum_texture_size.
x), std::min(texture_resolution.y, maximum_texture_size.
y));
2316 resizeTexture(new_texture_resolution);
2320void Visualizer::Texture::resizeTexture(
const helios::uint2 &new_image_resolution) {
2321 int old_width = texture_resolution.x;
2322 int old_height = texture_resolution.y;
2323 int new_width = new_image_resolution.
x;
2324 int new_height = new_image_resolution.
y;
2326 std::vector<unsigned char> new_data(new_width * new_height * num_channels);
2329 float x_ratio = scast<float>(old_width) / scast<float>(new_width);
2330 float y_ratio = scast<float>(old_height) / scast<float>(new_height);
2332 for (
int y = 0; y < new_height; ++y) {
2333 float srcY = y * y_ratio;
2334 int y0 = std::min(scast<int>(std::floor(srcY)), old_height - 1);
2335 int y1 = std::min(y0 + 1, old_height - 1);
2336 float dy = srcY - y0;
2338 for (
int x = 0; x < new_width; ++x) {
2339 float srcX = x * x_ratio;
2340 int x0 = std::min(scast<int>(std::floor(srcX)), old_width - 1);
2341 int x1 = std::min(x0 + 1, old_width - 1);
2342 float dx = srcX - x0;
2345 for (
int c = 0; c < num_channels; ++c) {
2346 float p00 = texture_data[(y0 * old_width + x0) * num_channels + c];
2347 float p10 = texture_data[(y0 * old_width + x1) * num_channels + c];
2348 float p01 = texture_data[(y1 * old_width + x0) * num_channels + c];
2349 float p11 = texture_data[(y1 * old_width + x1) * num_channels + c];
2351 float top = p00 * (1.f - dx) + p10 * dx;
2352 float bottom = p01 * (1.f - dx) + p11 * dx;
2353 float value = top * (1.f - dy) + bottom * dy;
2355 new_data[(y * new_width + x) * num_channels + c] = scast<unsigned char>(
clamp(std::round(value + 0.5f), 0.f, 255.f));
2360 texture_data = std::move(new_data);
2361 texture_resolution = new_image_resolution;
2365uint Visualizer::registerTextureImage(
const std::string &texture_file) {
2370 for (
const auto &[textureID, texture]: texture_manager) {
2371 if (texture.texture_file == texture_file) {
2377 const uint textureID = texture_manager.size();
2379 texture_manager.try_emplace(textureID, texture_file, textureID, this->maximum_texture_size,
false);
2380 textures_dirty =
true;
2385uint Visualizer::registerTextureImage(
const std::vector<unsigned char> &texture_data,
const helios::uint2 &image_resolution) {
2387 assert(!texture_data.empty() && texture_data.size() == 4 * image_resolution.
x * image_resolution.
y);
2390 const uint textureID = texture_manager.size();
2392 texture_manager.try_emplace(textureID, texture_data, textureID, image_resolution, this->maximum_texture_size);
2393 textures_dirty =
true;
2398uint Visualizer::registerTextureTransparencyMask(
const std::string &texture_file) {
2403 for (
const auto &[textureID, texture]: texture_manager) {
2404 if (texture.texture_file == texture_file) {
2410 const uint textureID = texture_manager.size();
2412 texture_manager.try_emplace(textureID, texture_file, textureID, this->maximum_texture_size,
true);
2413 textures_dirty =
true;
2418uint Visualizer::registerTextureGlyph(
const Glyph *glyph) {
2420 const uint textureID = texture_manager.size();
2422 texture_manager.try_emplace(textureID, glyph, textureID, this->maximum_texture_size);
2423 textures_dirty =
true;
2429 if (texture_manager.find(textureID) == texture_manager.end()) {
2431 return texture_manager.at(textureID).texture_resolution;
2435 const std::filesystem::path p(texture_file);
2438 if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p)) {
2443 std::string ext = p.extension().string();
2444 std::transform(ext.begin(), ext.end(), ext.begin(), [](
const unsigned char c) { return scast<char>(std::tolower(c)); });
2448 if (ext !=
".png") {
2452 if (ext !=
".png" && ext !=
".jpg" && ext !=
".jpeg") {
2464std::vector<uint> Visualizer::getFrameBufferSize()
const {
2465 return {Wframebuffer, Hframebuffer};
2468void Visualizer::setFrameBufferSize(
int width,
int height) {
2469 Wframebuffer = width;
2470 Hframebuffer = height;
2474 return backgroundColor;
2477Shader Visualizer::getPrimaryShader()
const {
2478 return primaryShader;
2482 return {camera_lookat_center, camera_eye_location};
2486 return perspectiveTransformationMatrix;
2489glm::mat4 Visualizer::getViewMatrix()
const {
2490 vec3 forward = camera_lookat_center - camera_eye_location;
2499 glm::vec3 camera_pos{camera_eye_location.
x, camera_eye_location.y, camera_eye_location.z};
2500 glm::vec3 lookat_pos{camera_lookat_center.
x, camera_lookat_center.
y, camera_lookat_center.
z};
2501 glm::vec3 up_vec{up.
x, up.
y, up.
z};
2503 return glm::lookAt(camera_pos, lookat_pos, up_vec);
2507 return primaryLightingModel;
2510uint Visualizer::getDepthTexture()
const {
2511 return depthTexture;