1.3.64
 
Loading...
Searching...
No Matches
VisualizerRendering.cpp
Go to the documentation of this file.
1
16// OpenGL Includes
17#include <GL/glew.h>
18#include <GLFW/glfw3.h>
19
20// #include <chrono>
21
22#include "Visualizer.h"
23
24using namespace helios;
25
26float dphi = 0.0;
27float dtheta = 0.0;
28float dx = 0.0;
29float dy = 0.0;
30float dz = 0.0;
31float dx_m = 0.0;
32float dy_m = 0.0;
33float dscroll = 0.0;
34
36 char outfile[100];
37 if (context != nullptr) { // context has been given to visualizer via buildContextGeometry()
38 Date date = context->getDate();
39 Time time = context->getTime();
40 std::snprintf(outfile, 100, "%02d-%02d-%4d_%02d-%02d-%02d_frame%d.jpg", date.day, date.month, date.year, time.hour, time.minute, time.second, frame_counter);
41 } else {
42 std::snprintf(outfile, 100, "frame%d.jpg", frame_counter);
43 }
44 frame_counter++;
45
46 printWindow(outfile);
47}
48
49void Visualizer::printWindow(const char *outfile, const std::string &image_format) {
50
51 // Save current navigation gizmo state and temporarily hide it for screenshot
52 bool gizmo_was_visible = navigation_gizmo_enabled;
53 if (gizmo_was_visible) {
55 }
56
57 // Update the plot window to ensure latest rendering
58 this->plotUpdate(true);
59
60 std::string outfile_str = outfile;
61
62 // Validate image format
63 std::string format_lower = image_format;
64 std::transform(format_lower.begin(), format_lower.end(), format_lower.begin(), ::tolower);
65
66 bool is_png = (format_lower == "png");
67 bool is_jpeg = (format_lower == "jpeg" || format_lower == "jpg");
68
69 if (!is_png && !is_jpeg) {
70 helios_runtime_error("ERROR (Visualizer::printWindow): Invalid image_format '" + image_format + "'. Must be 'jpeg', 'jpg', or 'png'.");
71 }
72
73 // Add or verify file extension
74 std::string ext = getFileExtension(outfile_str);
75 if (ext.empty()) {
76 outfile_str += is_png ? ".png" : ".jpeg";
77 } else {
78 // Validate extension matches format (getFileExtension returns extension with leading dot, e.g., ".jpg")
79 std::string ext_lower = ext;
80 std::transform(ext_lower.begin(), ext_lower.end(), ext_lower.begin(), ::tolower);
81 if (is_png && ext_lower != ".png") {
82 helios_runtime_error("ERROR (Visualizer::printWindow): File extension '" + ext + "' does not match image_format 'png'.");
83 } else if (is_jpeg && ext_lower != ".jpg" && ext_lower != ".jpeg") {
84 helios_runtime_error("ERROR (Visualizer::printWindow): File extension '" + ext + "' does not match image_format 'jpeg'.");
85 }
86 }
87
88 // Warn if transparent background requested with JPEG format
89 if (background_is_transparent && is_jpeg && message_flag) {
90 std::cerr << "WARNING (Visualizer::printWindow): Transparent background requested but JPEG format does not support transparency. Output will have opaque background." << std::endl;
91 }
92
93 // Ensure window is visible and rendering is complete
94 if (window != nullptr && !headless) {
95 // Check if window is minimized or occluded
96 int window_attrib = glfwGetWindowAttrib((GLFWwindow *) window, GLFW_ICONIFIED);
97 if (window_attrib == GLFW_TRUE) {
98 std::cerr << "WARNING (printWindow): Window is minimized - screenshot may be unreliable" << std::endl;
99 }
100
101 glfwPollEvents();
102 }
103
104 // Temporarily delete the transparent background checkerboard rectangle for screenshot
105 // (we want the actual scene with transparent background, not the checkerboard)
106 bool had_transparent_background = background_is_transparent && background_rectangle_ID != 0;
107 if (had_transparent_background) {
108 // Delete the checkerboard completely
109 geometry_handler.deleteGeometry(background_rectangle_ID);
110 background_rectangle_ID = 0;
111
112 // Update buffers to reflect the defragmentation
113 transferBufferData();
114
115 // Ensure GPU has finished processing before rendering
116 glFinish();
117
118 // Bind the appropriate framebuffer
119 if (headless && offscreenFramebufferID != 0) {
120 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
121 glViewport(0, 0, Wframebuffer, Hframebuffer);
122 } else {
123 glBindFramebuffer(GL_FRAMEBUFFER, 0);
124 glViewport(0, 0, Wframebuffer, Hframebuffer);
125 // In windowed mode, explicitly set draw buffer to back buffer
126 glDrawBuffer(GL_BACK);
127 }
128
129 // Clear with transparent background
130 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
131 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
132
133 // Set up shader and render
134 primaryShader.useShader();
135 updatePerspectiveTransformation(false);
136 primaryShader.setTransformationMatrix(perspectiveTransformationMatrix);
137 primaryShader.setViewMatrix(cameraViewMatrix);
138 primaryShader.setProjectionMatrix(cameraProjectionMatrix);
139 primaryShader.enableTextureMaps();
140 primaryShader.enableTextureMasks();
141 primaryShader.setLightingModel(primaryLightingModel);
142 primaryShader.setLightIntensity(lightintensity);
143
144 render(false);
145
146 // After re-rendering, the content is in the BACK buffer (not swapped yet)
147 // Force read buffer to BACK for the screenshot
148 if (!headless) {
149 glReadBuffer(GL_BACK);
150 }
151 buffers_swapped_since_render = false;
152 }
153
154 // Additional safety: ensure all OpenGL commands have completed
155 glFinish();
156
157 // Handle screenshot based on format and headless mode
158 if (is_png) {
159 // PNG output
160 if (headless && offscreenFramebufferID != 0) {
161 // In headless mode, read pixels from the offscreen framebuffer
162 std::vector<helios::RGBAcolor> pixels = readOffscreenPixelsRGBA(background_is_transparent);
163 if (pixels.empty()) {
164 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to read pixels from offscreen framebuffer.");
165 }
166
167 int result = write_PNG_file(outfile_str.c_str(), Wframebuffer, Hframebuffer, pixels, message_flag);
168 if (result == 0) {
169 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to save screenshot to " + outfile_str);
170 }
171 } else {
172 // In windowed mode, use the traditional framebuffer reading
173 int result = write_PNG_file(outfile_str.c_str(), Wframebuffer, Hframebuffer, buffers_swapped_since_render, background_is_transparent, message_flag);
174 if (result == 0) {
175 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to save screenshot to " + outfile_str);
176 }
177 }
178 } else {
179 // JPEG output
180 if (headless && offscreenFramebufferID != 0) {
181 // In headless mode, read pixels from the offscreen framebuffer
182 std::vector<helios::RGBcolor> pixels = readOffscreenPixels();
183 if (pixels.empty()) {
184 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to read pixels from offscreen framebuffer.");
185 }
186
187 int result = write_JPEG_file(outfile_str.c_str(), Wframebuffer, Hframebuffer, pixels, message_flag);
188 if (result == 0) {
189 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to save screenshot to " + outfile_str);
190 }
191 } else {
192 // In windowed mode, use the traditional framebuffer reading
193 int result = write_JPEG_file(outfile_str.c_str(), Wframebuffer, Hframebuffer, buffers_swapped_since_render, message_flag);
194 if (result == 0) {
195 helios_runtime_error("ERROR (Visualizer::printWindow): Failed to save screenshot to " + outfile_str);
196 }
197 }
198 }
199
200 // Restore the transparent background checkerboard rectangle if it was active
201 if (had_transparent_background) {
202 // Define rectangle vertices in normalized window coordinates
203 std::vector<helios::vec3> vertices = {
204 helios::make_vec3(0.f, 0.f, 0.99f), // Bottom-left
205 helios::make_vec3(1.f, 0.f, 0.99f), // Bottom-right
206 helios::make_vec3(1.f, 1.f, 0.99f), // Top-right
207 helios::make_vec3(0.f, 1.f, 0.99f) // Top-left
208 };
209
210 // Calculate aspect ratio and adjust UV coordinates to maintain square checkerboard pattern
211 float aspect_ratio = static_cast<float>(Wframebuffer) / static_cast<float>(Hframebuffer);
212 std::vector<helios::vec2> uvs;
213 if (aspect_ratio > 1.f) {
214 // Window is wider than tall - stretch UV in x-direction
215 uvs = {helios::make_vec2(0.f, 0.f), helios::make_vec2(aspect_ratio, 0.f), helios::make_vec2(aspect_ratio, 1.f), helios::make_vec2(0.f, 1.f)};
216 } else {
217 // Window is taller than wide - stretch UV in y-direction
218 uvs = {helios::make_vec2(0.f, 0.f), helios::make_vec2(1.f, 0.f), helios::make_vec2(1.f, 1.f / aspect_ratio), helios::make_vec2(0.f, 1.f / aspect_ratio)};
219 }
220
221 background_rectangle_ID = addRectangleByVertices(vertices, "plugins/visualizer/textures/transparent.jpg", uvs, COORDINATES_WINDOW_NORMALIZED);
222 }
223
224 // Restore navigation gizmo state
225 if (gizmo_was_visible) {
227 }
228}
229
230void Visualizer::displayImage(const std::vector<unsigned char> &pixel_data, uint width_pixels, uint height_pixels) {
231 if (pixel_data.empty()) {
232 helios_runtime_error("ERROR (Visualizer::displayImage): Pixel data was empty.");
233 }
234 if (pixel_data.size() != 4 * width_pixels * height_pixels) {
235 helios_runtime_error("ERROR (Visualizer::displayImage): Pixel data size does not match the given width and height. Argument 'pixel_data' must have length of 4*width_pixels*height_pixels.");
236 }
237
238 // Clear out any existing geometry
239 geometry_handler.clearAllGeometry();
240
241 // Register the data as a texture
242 uint textureID = registerTextureImage(pixel_data, helios::make_uint2(width_pixels, height_pixels));
243
244 // Figure out size to render
245 vec2 image_size;
246 float data_aspect = float(width_pixels) / float(height_pixels);
247 float window_aspect = float(Wdisplay) / float(Hdisplay);
248 if (data_aspect > window_aspect) {
249 // fill width, shrink height
250 image_size = make_vec2(1.0f, window_aspect / data_aspect);
251 } else {
252 // fill height, shrink width
253 image_size = make_vec2(data_aspect / window_aspect, 1.0f);
254 }
255
256 constexpr vec3 center(0.5, 0.5, 0);
257 const std::vector vertices{center + make_vec3(-0.5f * image_size.x, -0.5f * image_size.y, 0.f), center + make_vec3(+0.5f * image_size.x, -0.5f * image_size.y, 0.f), center + make_vec3(+0.5f * image_size.x, +0.5f * image_size.y, 0.f),
258 center + make_vec3(-0.5f * image_size.x, +0.5f * image_size.y, 0.f)};
259 const std::vector<vec2> uvs{{0, 0}, {1, 0}, {1, 1}, {0, 1}};
260
261 size_t UUID = geometry_handler.sampleUUID();
262 geometry_handler.addGeometry(UUID, GeometryHandler::GEOMETRY_TYPE_RECTANGLE, vertices, RGBA::black, uvs, textureID, false, false, COORDINATES_WINDOW_NORMALIZED, true, false);
263
265
266 // Save navigation gizmo state before hiding it (so we can restore it when building geometry)
267 navigation_gizmo_was_enabled_before_image_display = navigation_gizmo_enabled;
269
271}
272
273
274void Visualizer::displayImage(const std::string &file_name) {
275 if (!validateTextureFile(file_name)) {
276 helios_runtime_error("ERROR (Visualizer::displayImage): File " + file_name + " does not exist or is not a valid image file.");
277 }
278
279 std::vector<unsigned char> image_data;
280 uint image_width, image_height;
281
282 if (file_name.substr(file_name.find_last_of('.') + 1) == "png") {
283 read_png_file(file_name.c_str(), image_data, image_height, image_width);
284 } else { // JPEG
285 read_JPEG_file(file_name.c_str(), image_data, image_height, image_width);
286 }
287
288 displayImage(image_data, image_width, image_height);
289}
290
292 // Validate framebuffer completeness
293 if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
294 helios_runtime_error("ERROR (getWindowPixelsRGB): Framebuffer is not complete");
295 }
296
297 std::vector<GLubyte> buff;
298 buff.resize(3 * Wframebuffer * Hframebuffer);
299
300 // Set proper pixel alignment
301 glPixelStorei(GL_PACK_ALIGNMENT, 1);
302
303 // Handle different pixel reading approaches based on headless mode
304 if (headless && offscreenFramebufferID != 0) {
305 // In headless mode with offscreen framebuffer, ensure we're reading from the correct framebuffer
306 // Bind the offscreen framebuffer to ensure we read from the rendered content
307 GLint current_framebuffer;
308 glGetIntegerv(GL_FRAMEBUFFER_BINDING, &current_framebuffer);
309
310 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
311
312 // Framebuffer objects don't use front/back buffer concepts, read directly
313 glReadPixels(0, 0, GLsizei(Wframebuffer), GLsizei(Hframebuffer), GL_RGB, GL_UNSIGNED_BYTE, &buff[0]);
314 GLenum error = glGetError();
315
316 // Restore the previous framebuffer binding
317 glBindFramebuffer(GL_FRAMEBUFFER, current_framebuffer);
318
319 if (error != GL_NO_ERROR) {
320 helios_runtime_error("ERROR (getWindowPixelsRGB): glReadPixels failed in headless mode (error: " + std::to_string(error) + ")");
321 }
322 } else {
323 // In windowed mode, robustly determine which buffer contains rendered content
324 // Sample multiple pixels to avoid false negatives from legitimately black pixels
325 const int num_samples = 9;
326 const uint sample_positions[][2] = {{Wframebuffer / 4, Hframebuffer / 4}, {Wframebuffer / 2, Hframebuffer / 4}, {3 * Wframebuffer / 4, Hframebuffer / 4},
327 {Wframebuffer / 4, Hframebuffer / 2}, {Wframebuffer / 2, Hframebuffer / 2}, {3 * Wframebuffer / 4, Hframebuffer / 2},
328 {Wframebuffer / 4, 3 * Hframebuffer / 4}, {Wframebuffer / 2, 3 * Hframebuffer / 4}, {3 * Wframebuffer / 4, 3 * Hframebuffer / 4}};
329 GLubyte test_pixels[num_samples * 3];
330
331 auto count_non_black_pixels = [](const GLubyte *pixels, int count) -> int {
332 int non_black = 0;
333 for (int i = 0; i < count * 3; i += 3) {
334 if (pixels[i] > 5 || pixels[i + 1] > 5 || pixels[i + 2] > 5) { // Use small threshold to account for compression artifacts
335 non_black++;
336 }
337 }
338 return non_black;
339 };
340
341 int back_buffer_content_score = 0;
342 int front_buffer_content_score = 0;
343
344 // Test back buffer with multiple samples
345 glReadBuffer(GL_BACK);
346 GLenum error = glGetError();
347 if (error == GL_NO_ERROR) {
348 glFinish();
349 for (int i = 0; i < num_samples; i++) {
350 glReadPixels(sample_positions[i][0], sample_positions[i][1], 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &test_pixels[i * 3]);
351 }
352 if (glGetError() == GL_NO_ERROR) {
353 back_buffer_content_score = count_non_black_pixels(test_pixels, num_samples);
354 }
355 }
356
357 // Test front buffer with multiple samples
358 glReadBuffer(GL_FRONT);
359 error = glGetError();
360 if (error == GL_NO_ERROR) {
361 glFinish();
362 for (int i = 0; i < num_samples; i++) {
363 glReadPixels(sample_positions[i][0], sample_positions[i][1], 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &test_pixels[i * 3]);
364 }
365 if (glGetError() == GL_NO_ERROR) {
366 front_buffer_content_score = count_non_black_pixels(test_pixels, num_samples);
367 }
368 }
369
370 // Choose the buffer with higher content score, prefer back buffer if scores are equal
371 if (back_buffer_content_score >= front_buffer_content_score && back_buffer_content_score > 0) {
372 glReadBuffer(GL_BACK);
373 } else if (front_buffer_content_score > 0) {
374 glReadBuffer(GL_FRONT);
375 } else {
376 // Neither buffer has detectable content, default to back buffer
377 glReadBuffer(GL_BACK);
378 error = glGetError();
379 if (error != GL_NO_ERROR) {
380 glReadBuffer(GL_FRONT);
381 error = glGetError();
382 if (error != GL_NO_ERROR) {
383 helios_runtime_error("ERROR (getWindowPixelsRGB): Cannot set read buffer");
384 }
385 }
386 }
387
388 glReadPixels(0, 0, GLsizei(Wframebuffer), GLsizei(Hframebuffer), GL_RGB, GL_UNSIGNED_BYTE, &buff[0]);
389 error = glGetError();
390 if (error != GL_NO_ERROR) {
391 helios_runtime_error("ERROR (getWindowPixelsRGB): glReadPixels failed");
392 }
393 }
394
395 glFinish();
396
397 for (int i = 0; i < 3 * Wframebuffer * Hframebuffer; i++) {
398 buffer[i] = (unsigned int) buff[i];
399 }
400}
401
402void Visualizer::getDepthMap(float *buffer) {
403 // if (depth_buffer_data.empty()) {
404 // helios_runtime_error("ERROR (Visualizer::getDepthMap): No depth map data available. You must run 'plotDepthMap' before depth map can be retrieved.");
405 // }
406 //
407 // updatePerspectiveTransformation(camera_lookat_center, camera_eye_location, true);
408 //
409 // for (int i = 0; i < depth_buffer_data.size(); i++) {
410 // buffer[i] = -perspectiveTransformationMatrix[3].z / (depth_buffer_data.at(i) * -2.0f + 1.0f - perspectiveTransformationMatrix[2].z);
411 // }
412
413 std::vector<float> depth_pixels;
414 uint width, height;
415 getDepthMap(depth_pixels, width, height);
416 for (size_t i = 0; i < depth_pixels.size(); ++i) {
417 buffer[i] = depth_pixels[i];
418 }
419}
420
421void Visualizer::getDepthMap(std::vector<float> &depth_pixels, uint &width_pixels, uint &height_pixels) {
422 width_pixels = Wdisplay;
423 height_pixels = Hdisplay;
424
425 depth_pixels.resize(width_pixels * height_pixels);
426
427 updateDepthBuffer();
428 // updatePerspectiveTransformation( false );
429
430 // un-project depth values to give physical depth
431
432 // build a viewport vector for unProject
433 // const glm::vec4 viewport(0, 0, width_pixels, height_pixels);
434 //
435 // for (size_t i = 0; i < width_pixels*height_pixels; ++i) {
436 // // compute pixel coords from linear index
437 // int x = int(i % width_pixels);
438 // int y = int(i / height_pixels);
439 //
440 // // center of the pixel
441 // float winx = float(x) + 0.5f;
442 // float winy = float(y) + 0.5f;
443 // float depth = depth_buffer_data.at(i); // in [0..1]
444 //
445 // // build the window‐space coordinate
446 // glm::vec3 winCoord(winx, winy, depth);
447 //
448 // // unProject to get world‐space position
449 // glm::vec3 worldPos = glm::unProject( winCoord, cameraViewMatrix, cameraProjectionMatrix, viewport );
450 //
451 // // transform into camera‐space
452 // const glm::vec4 camPos = cameraViewMatrix * glm::vec4(worldPos, 1.0f);
453 //
454 // // camPos.z is negative in front of the eye; flip sign for a positive distance
455 // // depth_pixels[i] = -camPos.z;
456 // depth_pixels[i] = depth*255.f;
457 // }
458
459 // normalize data and invert the color space so white=closest, black = furthest
460 float depth_min = (std::numeric_limits<float>::max)();
461 float depth_max = (std::numeric_limits<float>::min)();
462 for (auto depth: depth_buffer_data) {
463 if (depth < depth_min) {
464 depth_min = depth;
465 }
466 if (depth > depth_max) {
467 depth_max = depth;
468 }
469 }
470 for (size_t i = 0; i < depth_pixels.size(); i++) {
471 float value = std::round((depth_buffer_data.at(i) - depth_min) / (depth_max - depth_min) * 255);
472 value = clamp(value, 0.f, 255.f);
473 depth_pixels.at(i) = 255.f - value;
474 }
475
476 //\todo This is not working. Basically the same code works in the plotDepthMap() method, but for some reason doesn't seem to yield the correct float values.
477}
478
479void Visualizer::getWindowSize(uint &width, uint &height) const {
480 width = Wdisplay;
481 height = Hdisplay;
482}
483
484void Visualizer::getFramebufferSize(uint &width, uint &height) const {
485 width = Wframebuffer;
486 height = Hframebuffer;
487}
488
490 glfwHideWindow((GLFWwindow *) window);
491 glfwPollEvents();
492}
493
494std::vector<helios::vec3> Visualizer::plotInteractive() {
495 if (message_flag) {
496 std::cout << "Generating interactive plot..." << std::flush;
497 }
498
499
500 // Update the Context geometry
501 buildContextGeometry_private();
502
503 // Set the view to fit window
504 if (camera_lookat_center.x == 0 && camera_lookat_center.y == 0 && camera_lookat_center.z == 0) { // default center
505 if (camera_eye_location.x < 1e-4 && camera_eye_location.y < 1e-4 && camera_eye_location.z == 2.f) { // default eye position
506
507 vec3 center_sph;
508 vec3 radius;
509 geometry_handler.getDomainBoundingSphere(center_sph, radius);
510 float domain_bounding_radius = radius.magnitude();
511
512 vec2 xbounds, ybounds, zbounds;
513 geometry_handler.getDomainBoundingBox(xbounds, ybounds, zbounds);
514 camera_lookat_center = make_vec3(0.5f * (xbounds.x + xbounds.y), 0.5f * (ybounds.x + ybounds.y), zbounds.x);
515 camera_eye_location = camera_lookat_center + sphere2cart(make_SphericalCoord(2.f * domain_bounding_radius, 20.f * PI_F / 180.f, 0));
516 }
517 }
518
519 // Colorbar - update with aspect ratio correction
520 updateColorbar();
521
522 // Watermark
524
525 // Navigation gizmo - initialize before entering render loop
526 if (navigation_gizmo_enabled) {
527 updateNavigationGizmo();
528 previous_camera_eye_location = camera_eye_location;
529 previous_camera_lookat_center = camera_lookat_center;
530 }
531
532 transferBufferData();
533
534 assert(checkerrors());
535
536 bool shadow_flag = (primaryLightingModel == Visualizer::LIGHTING_PHONG_SHADOWED);
537
538 glm::mat4 depthMVP;
539
540 assert(checkerrors());
541
542 std::vector<vec3> camera_output;
543
544 glfwShowWindow((GLFWwindow *) window);
545
546 do {
547 // Update navigation gizmo if camera has changed
548 if (navigation_gizmo_enabled && cameraHasChanged()) {
549 updateNavigationGizmo();
550 previous_camera_eye_location = camera_eye_location;
551 previous_camera_lookat_center = camera_lookat_center;
552 // Transfer updated geometry to GPU immediately after updating gizmo
553 transferBufferData();
554 }
555
556 if (shadow_flag) {
557 assert(checkerrors());
558 // Depth buffer for shadows
559 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
560 glViewport(0, 0, shadow_buffer_size.x, shadow_buffer_size.y); // Render on the whole framebuffer, complete from the lower left corner to the upper right
561
562 // Clear the screen
563 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
564
565 depthShader.useShader();
566
567 updatePerspectiveTransformation(true);
568
569 // Compute the MVP matrix from the light's point of view
570 depthMVP = computeShadowDepthMVP();
571 depthShader.setTransformationMatrix(depthMVP);
572
573 // bind depth texture
574 glActiveTexture(GL_TEXTURE1);
575 glBindTexture(GL_TEXTURE_2D, depthTexture);
576 glActiveTexture(GL_TEXTURE0);
577
578 depthShader.enableTextureMaps();
579 depthShader.enableTextureMasks();
580
581 assert(checkerrors());
582
583 render(true);
584 } else {
585 depthMVP = glm::mat4(1.0);
586 }
587
588 // Render to the screen
589 glBindFramebuffer(GL_FRAMEBUFFER, 0);
590 glViewport(0, 0, Wframebuffer, Hframebuffer);
591
592 if (background_is_transparent) {
593 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
594 } else {
595 glClearColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, 1.0f);
596 }
597
598 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
599
600 primaryShader.useShader();
601
602 glm::mat4 DepthBiasMVP = biasMatrix * depthMVP;
603
604 primaryShader.setDepthBiasMatrix(DepthBiasMVP);
605
606 updatePerspectiveTransformation(false);
607
608 primaryShader.setTransformationMatrix(perspectiveTransformationMatrix);
609 primaryShader.setViewMatrix(cameraViewMatrix);
610 primaryShader.setProjectionMatrix(cameraProjectionMatrix);
611
612 primaryShader.enableTextureMaps();
613 primaryShader.enableTextureMasks();
614
615 primaryShader.setLightingModel(primaryLightingModel);
616 primaryShader.setLightIntensity(lightintensity);
617
618 glActiveTexture(GL_TEXTURE1);
619 glBindTexture(GL_TEXTURE_2D, depthTexture);
620 glUniform1i(primaryShader.shadowmapUniform, 1);
621 glActiveTexture(GL_TEXTURE0);
622
623 buffers_swapped_since_render = false;
624 render(false);
625
626 assert(checkerrors());
627
628 glfwPollEvents();
629 getViewKeystrokes(camera_eye_location, camera_lookat_center);
630
631 glfwSwapBuffers((GLFWwindow *) window);
632 buffers_swapped_since_render = true;
633
634 glfwWaitEventsTimeout(1.0 / 30.0);
635
636 int width, height;
637 glfwGetFramebufferSize((GLFWwindow *) window, &width, &height);
638 Wframebuffer = width;
639 Hframebuffer = height;
640 } while (glfwGetKey((GLFWwindow *) window, GLFW_KEY_ESCAPE) != GLFW_PRESS && glfwWindowShouldClose((GLFWwindow *) window) == 0);
641
642 glfwPollEvents();
643
644 assert(checkerrors());
645
646 camera_output.push_back(camera_eye_location);
647 camera_output.push_back(camera_lookat_center);
648
649 if (message_flag) {
650 std::cout << "done." << std::endl;
651 }
652
653 return camera_output;
654}
655
656void Visualizer::plotOnce(bool getKeystrokes) {
657
658 bool shadow_flag = (primaryLightingModel == Visualizer::LIGHTING_PHONG_SHADOWED);
659
660 glm::mat4 depthMVP;
661
662 if (shadow_flag) {
663 // Depth buffer for shadows
664 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
665 glViewport(0, 0, shadow_buffer_size.x, shadow_buffer_size.y); // Render on the whole framebuffer, complete from the lower left corner to the upper right
666
667 // Clear the screen
668 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
669
670 depthShader.useShader();
671
672 updatePerspectiveTransformation(true);
673
674 // Compute the MVP matrix from the light's point of view
675 depthMVP = computeShadowDepthMVP();
676 depthShader.setTransformationMatrix(depthMVP);
677
678 // bind depth texture
679 glActiveTexture(GL_TEXTURE1);
680 glBindTexture(GL_TEXTURE_2D, depthTexture);
681 glActiveTexture(GL_TEXTURE0);
682
683 depthShader.enableTextureMaps();
684 depthShader.enableTextureMasks();
685
686 render(true);
687 } else {
688 depthMVP = glm::mat4(1.0);
689 }
690
691 assert(checkerrors());
692
693 // Render to the screen
694 glBindFramebuffer(GL_FRAMEBUFFER, 0);
695 glViewport(0, 0, Wframebuffer, Hframebuffer);
696
697 if (background_is_transparent) {
698 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
699 } else {
700 glClearColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, 1.0f);
701 }
702
703 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
704
705 primaryShader.useShader();
706
707 glm::mat4 DepthBiasMVP = biasMatrix * depthMVP;
708
709 primaryShader.setDepthBiasMatrix(DepthBiasMVP);
710
711 updatePerspectiveTransformation(false);
712
713 primaryShader.setTransformationMatrix(perspectiveTransformationMatrix);
714 primaryShader.setViewMatrix(cameraViewMatrix);
715 primaryShader.setProjectionMatrix(cameraProjectionMatrix);
716
717 primaryShader.enableTextureMaps();
718 primaryShader.enableTextureMasks();
719
720 primaryShader.setLightingModel(primaryLightingModel);
721 primaryShader.setLightIntensity(lightintensity);
722
723 glActiveTexture(GL_TEXTURE1);
724 glBindTexture(GL_TEXTURE_2D, depthTexture);
725 glUniform1i(primaryShader.shadowmapUniform, 1);
726 glActiveTexture(GL_TEXTURE0);
727
728 // Set buffer state before rendering (plotOnce doesn't call glfwSwapBuffers)
729 buffers_swapped_since_render = false;
730 render(false);
731
732 // glfwPollEvents();
733 if (getKeystrokes) {
734 getViewKeystrokes(camera_eye_location, camera_lookat_center);
735
736 // Update navigation gizmo if camera has changed
737 if (navigation_gizmo_enabled && cameraHasChanged()) {
738 updateNavigationGizmo();
739 previous_camera_eye_location = camera_eye_location;
740 previous_camera_lookat_center = camera_lookat_center;
741 // Transfer updated geometry to GPU
742 transferBufferData();
743 }
744 }
745
746 int width, height;
747 glfwGetFramebufferSize((GLFWwindow *) window, &width, &height);
748 Wframebuffer = width;
749 Hframebuffer = height;
750}
751
752void Visualizer::transferBufferData() {
753 assert(checkerrors());
754
755 const auto &dirty = geometry_handler.getDirtyUUIDs();
756 if (dirty.empty()) {
757 return;
758 }
759
760 auto ensureArrayBuffer = [](GLuint buf, GLenum target, GLsizeiptr size, const void *data) {
761 glBindBuffer(target, buf);
762 GLint current_size = 0;
763 glGetBufferParameteriv(target, GL_BUFFER_SIZE, &current_size);
764 if (current_size != size) {
765 glBufferData(target, size, data, GL_STATIC_DRAW);
766 } else {
767 // Buffer size matches, but data may have changed
768 // Update the buffer contents
769 glBufferSubData(target, 0, size, data);
770 }
771 };
772
773 auto ensureTextureBuffer = [](GLuint buf, GLuint tex, GLenum format, GLsizeiptr size, const void *data) {
774 glBindBuffer(GL_TEXTURE_BUFFER, buf);
775 GLint current_size = 0;
776 glGetBufferParameteriv(GL_TEXTURE_BUFFER, GL_BUFFER_SIZE, &current_size);
777 if (current_size != size) {
778 glBufferData(GL_TEXTURE_BUFFER, size, data, GL_STATIC_DRAW);
779 } else {
780 // Buffer size matches, but data may have changed (e.g., visibility flags)
781 // Update the buffer contents
782 glBufferSubData(GL_TEXTURE_BUFFER, 0, size, data);
783 }
784 glBindTexture(GL_TEXTURE_BUFFER, tex);
785 glTexBuffer(GL_TEXTURE_BUFFER, format, buf);
786 };
787
788 // Ensure buffers are allocated to the correct size
789 for (size_t gi = 0; gi < GeometryHandler::all_geometry_types.size(); ++gi) {
790 const auto geometry_type = GeometryHandler::all_geometry_types[gi];
791 const auto *vertex_data = geometry_handler.getVertexData_ptr(geometry_type);
792 const auto *uv_data = geometry_handler.getUVData_ptr(geometry_type);
793 const auto *face_index_data = geometry_handler.getFaceIndexData_ptr(geometry_type);
794 const auto *color_data = geometry_handler.getColorData_ptr(geometry_type);
795 const auto *normal_data = geometry_handler.getNormalData_ptr(geometry_type);
796 const auto *texture_flag_data = geometry_handler.getTextureFlagData_ptr(geometry_type);
797 const auto *texture_ID_data = geometry_handler.getTextureIDData_ptr(geometry_type);
798 const auto *coordinate_flag_data = geometry_handler.getCoordinateFlagData_ptr(geometry_type);
799 const auto *sky_geometry_flag_data = geometry_handler.getSkyGeometryFlagData_ptr(geometry_type);
800 const auto *visible_flag_data = geometry_handler.getVisibilityFlagData_ptr(geometry_type);
801
802 ensureArrayBuffer(vertex_buffer.at(gi), GL_ARRAY_BUFFER, vertex_data->size() * sizeof(GLfloat), vertex_data->data());
803 ensureArrayBuffer(uv_buffer.at(gi), GL_ARRAY_BUFFER, uv_data->size() * sizeof(GLfloat), uv_data->data());
804 ensureArrayBuffer(face_index_buffer.at(gi), GL_ARRAY_BUFFER, face_index_data->size() * sizeof(GLint), face_index_data->data());
805 ensureTextureBuffer(color_buffer.at(gi), color_texture_object.at(gi), GL_RGBA32F, color_data->size() * sizeof(GLfloat), color_data->data());
806 ensureTextureBuffer(normal_buffer.at(gi), normal_texture_object.at(gi), GL_RGB32F, normal_data->size() * sizeof(GLfloat), normal_data->data());
807 ensureTextureBuffer(texture_flag_buffer.at(gi), texture_flag_texture_object.at(gi), GL_R32I, texture_flag_data->size() * sizeof(GLint), texture_flag_data->data());
808 ensureTextureBuffer(texture_ID_buffer.at(gi), texture_ID_texture_object.at(gi), GL_R32I, texture_ID_data->size() * sizeof(GLint), texture_ID_data->data());
809 ensureTextureBuffer(coordinate_flag_buffer.at(gi), coordinate_flag_texture_object.at(gi), GL_R32I, coordinate_flag_data->size() * sizeof(GLint), coordinate_flag_data->data());
810 ensureTextureBuffer(sky_geometry_flag_buffer.at(gi), sky_geometry_flag_texture_object.at(gi), GL_R8I, sky_geometry_flag_data->size() * sizeof(GLbyte), sky_geometry_flag_data->data());
811 ensureTextureBuffer(hidden_flag_buffer.at(gi), hidden_flag_texture_object.at(gi), GL_R8I, visible_flag_data->size() * sizeof(GLbyte), visible_flag_data->data());
812
813 glBindBuffer(GL_ARRAY_BUFFER, 0);
814 glBindTexture(GL_TEXTURE_BUFFER, 0);
815 }
816
817 bool rect_dirty = false;
818 for (size_t UUID: dirty) {
819 if (!geometry_handler.doesGeometryExist(UUID)) {
820 continue;
821 }
822
823 const auto &index_map = geometry_handler.getIndexMap(UUID);
824 auto geometry_type = index_map.geometry_type;
825 size_t i = std::find(GeometryHandler::all_geometry_types.begin(), GeometryHandler::all_geometry_types.end(), geometry_type) - GeometryHandler::all_geometry_types.begin();
826
827 const char vcount = GeometryHandler::getVertexCount(geometry_type);
828
829 glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer.at(i));
830 glBufferSubData(GL_ARRAY_BUFFER, index_map.vertex_index * sizeof(GLfloat), vcount * 3 * sizeof(GLfloat), geometry_handler.getVertexData_ptr(geometry_type)->data() + index_map.vertex_index);
831
832 glBindBuffer(GL_ARRAY_BUFFER, uv_buffer.at(i));
833 glBufferSubData(GL_ARRAY_BUFFER, index_map.uv_index * sizeof(GLfloat), vcount * 2 * sizeof(GLfloat), geometry_handler.getUVData_ptr(geometry_type)->data() + index_map.uv_index);
834
835 glBindBuffer(GL_ARRAY_BUFFER, face_index_buffer.at(i));
836 glBufferSubData(GL_ARRAY_BUFFER, index_map.face_index_index * sizeof(GLint), vcount * sizeof(GLint), geometry_handler.getFaceIndexData_ptr(geometry_type)->data() + index_map.face_index_index);
837
838 glBindBuffer(GL_TEXTURE_BUFFER, color_buffer.at(i));
839 glBufferSubData(GL_TEXTURE_BUFFER, index_map.color_index * sizeof(GLfloat), 4 * sizeof(GLfloat), geometry_handler.getColorData_ptr(geometry_type)->data() + index_map.color_index);
840 glBindTexture(GL_TEXTURE_BUFFER, color_texture_object.at(i));
841 glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, color_buffer.at(i));
842
843 glBindBuffer(GL_ARRAY_BUFFER, normal_buffer.at(i));
844 glBufferSubData(GL_ARRAY_BUFFER, index_map.normal_index * sizeof(GLfloat), 3 * sizeof(GLfloat), geometry_handler.getNormalData_ptr(geometry_type)->data() + index_map.normal_index);
845 glBindTexture(GL_TEXTURE_BUFFER, normal_texture_object.at(i));
846 glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, normal_buffer.at(i));
847
848 glBindBuffer(GL_ARRAY_BUFFER, texture_flag_buffer.at(i));
849 glBufferSubData(GL_ARRAY_BUFFER, index_map.texture_flag_index * sizeof(GLint), sizeof(GLint), geometry_handler.getTextureFlagData_ptr(geometry_type)->data() + index_map.texture_flag_index);
850 glBindTexture(GL_TEXTURE_BUFFER, texture_flag_texture_object.at(i));
851 glTexBuffer(GL_TEXTURE_BUFFER, GL_R32I, texture_flag_buffer.at(i));
852
853 glBindBuffer(GL_ARRAY_BUFFER, texture_ID_buffer.at(i));
854 glBufferSubData(GL_ARRAY_BUFFER, index_map.texture_ID_index * sizeof(GLint), sizeof(GLint), geometry_handler.getTextureIDData_ptr(geometry_type)->data() + index_map.texture_ID_index);
855 glBindTexture(GL_TEXTURE_BUFFER, texture_ID_texture_object.at(i));
856 glTexBuffer(GL_TEXTURE_BUFFER, GL_R32I, texture_ID_buffer.at(i));
857
858 glBindBuffer(GL_ARRAY_BUFFER, coordinate_flag_buffer.at(i));
859 glBufferSubData(GL_ARRAY_BUFFER, index_map.coordinate_flag_index * sizeof(GLint), sizeof(GLint), geometry_handler.getCoordinateFlagData_ptr(geometry_type)->data() + index_map.coordinate_flag_index);
860 glBindTexture(GL_TEXTURE_BUFFER, coordinate_flag_texture_object.at(i));
861 glTexBuffer(GL_TEXTURE_BUFFER, GL_R32I, coordinate_flag_buffer.at(i));
862
863 glBindBuffer(GL_ARRAY_BUFFER, sky_geometry_flag_buffer.at(i));
864 glBufferSubData(GL_ARRAY_BUFFER, index_map.sky_geometry_flag_index * sizeof(GLbyte), sizeof(GLbyte), geometry_handler.getSkyGeometryFlagData_ptr(geometry_type)->data() + index_map.sky_geometry_flag_index);
865 glBindTexture(GL_TEXTURE_BUFFER, sky_geometry_flag_texture_object.at(i));
866 glTexBuffer(GL_TEXTURE_BUFFER, GL_R8I, sky_geometry_flag_buffer.at(i));
867
868 glBindBuffer(GL_ARRAY_BUFFER, hidden_flag_buffer.at(i));
869 glBufferSubData(GL_ARRAY_BUFFER, index_map.visible_index * sizeof(GLbyte), sizeof(GLbyte), geometry_handler.getVisibilityFlagData_ptr(geometry_type)->data() + index_map.visible_index);
870 glBindTexture(GL_TEXTURE_BUFFER, hidden_flag_texture_object.at(i));
871 glTexBuffer(GL_TEXTURE_BUFFER, GL_R8I, hidden_flag_buffer.at(i));
872
873 glBindBuffer(GL_ARRAY_BUFFER, 0);
874 glBindTexture(GL_TEXTURE_BUFFER, 0);
875
876 if (geometry_type == GeometryHandler::GEOMETRY_TYPE_RECTANGLE) {
877 rect_dirty = true;
878 }
879 }
880
881 if (rect_dirty) {
882 size_t rectangle_count = geometry_handler.getRectangleCount();
883
884 rectangle_vertex_group_firsts.resize(rectangle_count);
885 rectangle_vertex_group_counts.resize(rectangle_count, 4);
886 for (int j = 0; j < rectangle_count; ++j) {
887 rectangle_vertex_group_firsts[j] = j * 4;
888 }
889 }
890
891 if (textures_dirty || texArray == 0) {
892 transferTextureData();
893 textures_dirty = false;
894 }
895
896 geometry_handler.clearDirtyUUIDs();
897
898 assert(checkerrors());
899}
900
901void Visualizer::transferTextureData() {
902
903 const size_t layers = std::max<size_t>(1, texture_manager.size());
904
905 // Check if texture needs recreation (Windows requires deleting and recreating texture
906 // when layer count changes due to glTexStorage3D immutable format restrictions)
907 if (texArray == 0 || layers != texture_array_layers) {
908 // Delete existing texture if it exists
909 if (texArray != 0) {
910 glDeleteTextures(1, &texArray);
911 }
912
913 // Create new texture
914 glGenTextures(1, &texArray);
915 glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
916 glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA8, maximum_texture_size.x, maximum_texture_size.y, layers);
917 texture_array_layers = layers;
918 } else {
919 glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
920 }
921
922 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
923 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
924 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
925 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
926 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
927
928 std::vector<GLfloat> uv_rescale;
929 uv_rescale.resize(texture_manager.size() * 2);
930
931 for (const auto &[textureID, texture]: texture_manager) {
932 GLenum externalFormat = 0;
933 switch (texture.num_channels) {
934 case 1:
935 externalFormat = GL_RED;
936 break;
937 case 3:
938 externalFormat = GL_RGB;
939 break;
940 case 4:
941 externalFormat = GL_RGBA;
942 break;
943 default:
944 throw std::runtime_error("unsupported channel count");
945 }
946
947 glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, textureID, texture.texture_resolution.x, texture.texture_resolution.y, 1, externalFormat, GL_UNSIGNED_BYTE, texture.texture_data.data());
948
949 uv_rescale.at(textureID * 2 + 0) = float(texture.texture_resolution.x) / float(maximum_texture_size.x);
950 uv_rescale.at(textureID * 2 + 1) = float(texture.texture_resolution.y) / float(maximum_texture_size.y);
951 }
952
953 glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
954 glUniform1i(glGetUniformLocation(primaryShader.shaderID, "textureSampler"), 0);
955
956 glBindBuffer(GL_TEXTURE_BUFFER, uv_rescale_buffer);
957 glBufferData(GL_TEXTURE_BUFFER, uv_rescale.size() * sizeof(GLfloat), uv_rescale.data(), GL_STATIC_DRAW);
958 glBindTexture(GL_TEXTURE_BUFFER, uv_rescale_texture_object);
959 glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, uv_rescale_buffer);
960 glBindBuffer(GL_TEXTURE_BUFFER, 0);
961}
962
963
964void Visualizer::render(bool shadow) const {
965 size_t rectangle_ind = std::find(GeometryHandler::all_geometry_types.begin(), GeometryHandler::all_geometry_types.end(), GeometryHandler::GEOMETRY_TYPE_RECTANGLE) - GeometryHandler::all_geometry_types.begin();
966 size_t triangle_ind = std::find(GeometryHandler::all_geometry_types.begin(), GeometryHandler::all_geometry_types.end(), GeometryHandler::GEOMETRY_TYPE_TRIANGLE) - GeometryHandler::all_geometry_types.begin();
967 size_t point_ind = std::find(GeometryHandler::all_geometry_types.begin(), GeometryHandler::all_geometry_types.end(), GeometryHandler::GEOMETRY_TYPE_POINT) - GeometryHandler::all_geometry_types.begin();
968 size_t line_ind = std::find(GeometryHandler::all_geometry_types.begin(), GeometryHandler::all_geometry_types.end(), GeometryHandler::GEOMETRY_TYPE_LINE) - GeometryHandler::all_geometry_types.begin();
969
970 size_t triangle_count = geometry_handler.getTriangleCount();
971 size_t rectangle_count = geometry_handler.getRectangleCount();
972 size_t line_count = geometry_handler.getLineCount();
973 size_t point_count = geometry_handler.getPointCount();
974
975 // Look up the currently loaded shader
976 GLint current_shader_program = 0;
977 glGetIntegerv(GL_CURRENT_PROGRAM, &current_shader_program);
978
979 // Bind our texture array
980 glActiveTexture(GL_TEXTURE0);
981 glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
982
983 assert(checkerrors());
984
985 glActiveTexture(GL_TEXTURE9);
986 assert(checkerrors());
987 glBindTexture(GL_TEXTURE_BUFFER, uv_rescale_texture_object);
988 assert(checkerrors());
989 glUniform1i(glGetUniformLocation(current_shader_program, "uv_rescale"), 9);
990
991 //--- Triangles---//
992
993 assert(checkerrors());
994
995 if (triangle_count > 0) {
996 glActiveTexture(GL_TEXTURE3);
997 glBindTexture(GL_TEXTURE_BUFFER, color_texture_object.at(triangle_ind));
998 // Note: Uniform location already set during shader initialization
999
1000 glActiveTexture(GL_TEXTURE4);
1001 glBindTexture(GL_TEXTURE_BUFFER, normal_texture_object.at(triangle_ind));
1002 // Note: Uniform location already set during shader initialization
1003
1004 glActiveTexture(GL_TEXTURE5);
1005 glBindTexture(GL_TEXTURE_BUFFER, texture_flag_texture_object.at(triangle_ind));
1006 // Note: Uniform location already set during shader initialization
1007
1008 glActiveTexture(GL_TEXTURE6);
1009 glBindTexture(GL_TEXTURE_BUFFER, texture_ID_texture_object.at(triangle_ind));
1010 // Note: Uniform location already set during shader initialization
1011
1012 glActiveTexture(GL_TEXTURE7);
1013 glBindTexture(GL_TEXTURE_BUFFER, coordinate_flag_texture_object.at(triangle_ind));
1014 // Note: Uniform location already set during shader initialization
1015
1016 glActiveTexture(GL_TEXTURE2);
1017 glBindTexture(GL_TEXTURE_BUFFER, sky_geometry_flag_texture_object.at(triangle_ind));
1018 // Note: Uniform location already set during shader initialization
1019
1020 glActiveTexture(GL_TEXTURE8);
1021 glBindTexture(GL_TEXTURE_BUFFER, hidden_flag_texture_object.at(triangle_ind));
1022 // Note: Uniform location already set during shader initialization
1023
1024 glBindVertexArray(primaryShader.vertex_array_IDs.at(triangle_ind));
1025 assert(checkerrors());
1026 glDrawArrays(GL_TRIANGLES, 0, triangle_count * 3);
1027 }
1028
1029 assert(checkerrors());
1030
1031 //--- Rectangles---//
1032
1033 if (rectangle_count > 0) {
1034 glActiveTexture(GL_TEXTURE3);
1035 glBindTexture(GL_TEXTURE_BUFFER, color_texture_object.at(rectangle_ind));
1036 // Note: Uniform location already set during shader initialization
1037
1038 glActiveTexture(GL_TEXTURE4);
1039 glBindTexture(GL_TEXTURE_BUFFER, normal_texture_object.at(rectangle_ind));
1040 // Note: Uniform location already set during shader initialization
1041
1042 glActiveTexture(GL_TEXTURE5);
1043 glBindTexture(GL_TEXTURE_BUFFER, texture_flag_texture_object.at(rectangle_ind));
1044 // Note: Uniform location already set during shader initialization
1045
1046 glActiveTexture(GL_TEXTURE6);
1047 glBindTexture(GL_TEXTURE_BUFFER, texture_ID_texture_object.at(rectangle_ind));
1048 // Note: Uniform location already set during shader initialization
1049
1050 glActiveTexture(GL_TEXTURE7);
1051 glBindTexture(GL_TEXTURE_BUFFER, coordinate_flag_texture_object.at(rectangle_ind));
1052 // Note: Uniform location already set during shader initialization
1053
1054 glActiveTexture(GL_TEXTURE2);
1055 glBindTexture(GL_TEXTURE_BUFFER, sky_geometry_flag_texture_object.at(rectangle_ind));
1056 // Note: Uniform location already set during shader initialization
1057
1058 glActiveTexture(GL_TEXTURE8);
1059 glBindTexture(GL_TEXTURE_BUFFER, hidden_flag_texture_object.at(rectangle_ind));
1060 // Note: Uniform location already set during shader initialization
1061
1062 glBindVertexArray(primaryShader.vertex_array_IDs.at(rectangle_ind));
1063
1064 std::vector<GLint> opaque_firsts;
1065 std::vector<GLint> opaque_counts;
1066 struct TransparentRect {
1067 size_t index;
1068 float depth;
1069 };
1070 std::vector<TransparentRect> transparent_rects;
1071
1072 const auto &texFlags = *geometry_handler.getTextureFlagData_ptr(GeometryHandler::GEOMETRY_TYPE_RECTANGLE);
1073 const auto &colors = *geometry_handler.getColorData_ptr(GeometryHandler::GEOMETRY_TYPE_RECTANGLE);
1074 const auto &verts = *geometry_handler.getVertexData_ptr(GeometryHandler::GEOMETRY_TYPE_RECTANGLE);
1075 const auto &visibilityFlags = *geometry_handler.getVisibilityFlagData_ptr(GeometryHandler::GEOMETRY_TYPE_RECTANGLE);
1076
1077 opaque_firsts.reserve(rectangle_count);
1078 opaque_counts.reserve(rectangle_count);
1079 transparent_rects.reserve(rectangle_count);
1080
1081 for (size_t i = 0; i < rectangle_count; ++i) {
1082 // Skip invisible rectangles
1083 if (!visibilityFlags.at(i)) {
1084 continue;
1085 }
1086
1087 bool isGlyph = texFlags.at(i) == 3;
1088 float alpha = colors.at(i * 4 + 3);
1089 if (!isGlyph && alpha >= 1.f) {
1090 opaque_firsts.push_back(static_cast<GLint>(i * 4));
1091 opaque_counts.push_back(4);
1092 } else {
1093 glm::vec3 center(0.f);
1094 for (int j = 0; j < 4; ++j) {
1095 center.x += verts.at(i * 12 + j * 3 + 0);
1096 center.y += verts.at(i * 12 + j * 3 + 1);
1097 center.z += verts.at(i * 12 + j * 3 + 2);
1098 }
1099 center /= 4.f;
1100 glm::vec4 viewPos = cameraViewMatrix * glm::vec4(center, 1.f);
1101 transparent_rects.push_back({i, viewPos.z});
1102 }
1103 }
1104
1105 if (!opaque_firsts.empty()) {
1106 glMultiDrawArrays(GL_TRIANGLE_FAN, opaque_firsts.data(), opaque_counts.data(), static_cast<GLsizei>(opaque_firsts.size()));
1107 }
1108
1109 if (!transparent_rects.empty()) {
1110 std::sort(transparent_rects.begin(), transparent_rects.end(), [](const TransparentRect &a, const TransparentRect &b) {
1111 return a.depth > b.depth; // farthest first
1112 });
1113
1114 glDepthMask(GL_FALSE);
1115 for (const auto &tr: transparent_rects) {
1116 glDrawArrays(GL_TRIANGLE_FAN, static_cast<GLint>(tr.index * 4), 4);
1117 }
1118 glDepthMask(GL_TRUE);
1119 }
1120 }
1121
1122 assert(checkerrors());
1123
1124 if (!shadow) {
1125 //--- Lines ---//
1126
1127 if (line_count > 0) {
1128 // Switch to line shader which uses geometry shader for wide lines
1129 lineShader.useShader();
1130 lineShader.setTransformationMatrix(perspectiveTransformationMatrix);
1131 lineShader.setDepthBiasMatrix(computeShadowDepthMVP());
1132 lineShader.setLightDirection(light_direction);
1133 lineShader.setLightingModel(primaryLightingModel);
1134 lineShader.setLightIntensity(lightintensity);
1135
1136 // Set viewport size for geometry shader
1137 GLint viewportSizeLoc = glGetUniformLocation(lineShader.shaderID, "viewportSize");
1138 glUniform2f(viewportSizeLoc, static_cast<float>(Wframebuffer), static_cast<float>(Hframebuffer));
1139
1140 // Rebind texture array and uv_rescale for line shader
1141 glActiveTexture(GL_TEXTURE0);
1142 glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
1143 glActiveTexture(GL_TEXTURE9);
1144 glBindTexture(GL_TEXTURE_BUFFER, uv_rescale_texture_object);
1145 glUniform1i(glGetUniformLocation(lineShader.shaderID, "uv_rescale"), 9);
1146
1147 glActiveTexture(GL_TEXTURE3);
1148 glBindTexture(GL_TEXTURE_BUFFER, color_texture_object.at(line_ind));
1149 // Note: Uniform location already set during shader initialization
1150
1151 glActiveTexture(GL_TEXTURE4);
1152 glBindTexture(GL_TEXTURE_BUFFER, normal_texture_object.at(line_ind));
1153 // Note: Uniform location already set during shader initialization
1154
1155 glActiveTexture(GL_TEXTURE5);
1156 glBindTexture(GL_TEXTURE_BUFFER, texture_flag_texture_object.at(line_ind));
1157 // Note: Uniform location already set during shader initialization
1158
1159 glActiveTexture(GL_TEXTURE6);
1160 glBindTexture(GL_TEXTURE_BUFFER, texture_ID_texture_object.at(line_ind));
1161 // Note: Uniform location already set during shader initialization
1162
1163 glActiveTexture(GL_TEXTURE7);
1164 glBindTexture(GL_TEXTURE_BUFFER, coordinate_flag_texture_object.at(line_ind));
1165 // Note: Uniform location already set during shader initialization
1166
1167 glActiveTexture(GL_TEXTURE2);
1168 glBindTexture(GL_TEXTURE_BUFFER, sky_geometry_flag_texture_object.at(line_ind));
1169 // Note: Uniform location already set during shader initialization
1170
1171 glActiveTexture(GL_TEXTURE8);
1172 glBindTexture(GL_TEXTURE_BUFFER, hidden_flag_texture_object.at(line_ind));
1173 // Note: Uniform location already set during shader initialization
1174
1175 glBindVertexArray(lineShader.vertex_array_IDs.at(line_ind));
1176
1177 // Group lines by width and render each group separately
1178 const std::vector<float> *size_data = geometry_handler.getSizeData_ptr(GeometryHandler::GEOMETRY_TYPE_LINE);
1179 if (size_data && !size_data->empty()) {
1180 // Create map of line width -> line indices for grouped rendering
1181 std::map<float, std::vector<size_t>> width_groups;
1182 for (size_t i = 0; i < size_data->size(); ++i) {
1183 float width = size_data->at(i);
1184 if (width <= 0)
1185 width = 1.0f; // Default width for invalid values
1186 width_groups[width].push_back(i);
1187 }
1188
1189 // Get the uniform location for line width
1190 GLint lineWidthLoc = glGetUniformLocation(lineShader.shaderID, "lineWidth");
1191
1192 // Render each width group separately
1193 for (const auto &group: width_groups) {
1194 float width = group.first;
1195 const std::vector<size_t> &line_indices = group.second;
1196
1197 // Set line width uniform for geometry shader
1198 glUniform1f(lineWidthLoc, width);
1199
1200 // Render each line individually
1201 // The geometry shader will expand each line into a quad
1202 for (size_t line_idx: line_indices) {
1203 glDrawArrays(GL_LINES, static_cast<GLint>(line_idx * 2), 2);
1204 }
1205 }
1206 } else {
1207 // Fallback to default width of 1.0 if no size data available
1208 GLint lineWidthLoc = glGetUniformLocation(lineShader.shaderID, "lineWidth");
1209 glUniform1f(lineWidthLoc, 1.0f);
1210 glDrawArrays(GL_LINES, 0, line_count * 2);
1211 }
1212
1213 // Switch back to primary shader for subsequent rendering
1214 primaryShader.useShader();
1215 primaryShader.setTransformationMatrix(perspectiveTransformationMatrix);
1216 primaryShader.setDepthBiasMatrix(computeShadowDepthMVP());
1217 primaryShader.setLightDirection(light_direction);
1218 primaryShader.setLightIntensity(lightintensity);
1219
1220 // Rebind texture array and uv_rescale for primary shader
1221 glActiveTexture(GL_TEXTURE0);
1222 glBindTexture(GL_TEXTURE_2D_ARRAY, texArray);
1223 glActiveTexture(GL_TEXTURE9);
1224 glBindTexture(GL_TEXTURE_BUFFER, uv_rescale_texture_object);
1225 glUniform1i(glGetUniformLocation(primaryShader.shaderID, "uv_rescale"), 9);
1226 }
1227
1228 assert(checkerrors());
1229
1230 //--- Points ---//
1231
1232 if (point_count > 0) {
1233 glActiveTexture(GL_TEXTURE3);
1234 glBindTexture(GL_TEXTURE_BUFFER, color_texture_object.at(point_ind));
1235 // Note: Uniform location already set during shader initialization
1236
1237 glActiveTexture(GL_TEXTURE4);
1238 glBindTexture(GL_TEXTURE_BUFFER, normal_texture_object.at(point_ind));
1239 // Note: Uniform location already set during shader initialization
1240
1241 glActiveTexture(GL_TEXTURE5);
1242 glBindTexture(GL_TEXTURE_BUFFER, texture_flag_texture_object.at(point_ind));
1243 // Note: Uniform location already set during shader initialization
1244
1245 glActiveTexture(GL_TEXTURE6);
1246 glBindTexture(GL_TEXTURE_BUFFER, texture_ID_texture_object.at(point_ind));
1247 // Note: Uniform location already set during shader initialization
1248
1249 glActiveTexture(GL_TEXTURE7);
1250 glBindTexture(GL_TEXTURE_BUFFER, coordinate_flag_texture_object.at(point_ind));
1251 // Note: Uniform location already set during shader initialization
1252
1253 glActiveTexture(GL_TEXTURE2);
1254 glBindTexture(GL_TEXTURE_BUFFER, sky_geometry_flag_texture_object.at(point_ind));
1255 // Note: Uniform location already set during shader initialization
1256
1257 glActiveTexture(GL_TEXTURE8);
1258 glBindTexture(GL_TEXTURE_BUFFER, hidden_flag_texture_object.at(point_ind));
1259 // Note: Uniform location already set during shader initialization
1260
1261 glBindVertexArray(primaryShader.vertex_array_IDs.at(point_ind));
1262
1263 // Group points by size and render each group separately
1264 const std::vector<float> *size_data = geometry_handler.getSizeData_ptr(GeometryHandler::GEOMETRY_TYPE_POINT);
1265 if (size_data && !size_data->empty()) {
1266 // Create map of point size -> point indices for grouped rendering
1267 std::map<float, std::vector<size_t>> size_groups;
1268 for (size_t i = 0; i < size_data->size(); ++i) {
1269 float size = size_data->at(i);
1270 if (size <= 0)
1271 size = 1.0f; // Default size for invalid values
1272 size_groups[size].push_back(i);
1273 }
1274
1275 // Render each size group separately
1276 for (const auto &group: size_groups) {
1277 float size = group.first;
1278 const std::vector<size_t> &point_indices = group.second;
1279
1280 glPointSize(size);
1281
1282 // For simplicity, render each point individually
1283 // In a more optimized implementation, we would batch points with same size
1284 for (size_t point_idx: point_indices) {
1285 glDrawArrays(GL_POINTS, point_idx, 1);
1286 }
1287 }
1288 } else {
1289 // Fallback to default behavior if no size data available
1290 glPointSize(point_width);
1291 glDrawArrays(GL_POINTS, 0, point_count);
1292 }
1293 }
1294
1295 // if( !positionData["sky"].empty() ){
1296 // primaryShader.setLightingModel( LIGHTING_NONE );
1297 // glBindTexture(GL_TEXTURE_RECTANGLE,textureIDData["sky"].at(0));
1298 // glDrawArrays(GL_TRIANGLES, triangle_size+line_size+point_size, positionData["sky"].size()/3 );
1299 // }
1300 }
1301
1302 // Clean up texture units to prevent macOS OpenGL warnings
1303 // This helps prevent texture unit binding issues in headless mode
1304 glActiveTexture(GL_TEXTURE0);
1305
1306 assert(checkerrors());
1307}
1308
1310 plotUpdate(false);
1311}
1312
1313void Visualizer::plotUpdate(bool hide_window) {
1314 // Check if window is marked for closure to prevent hanging on glfwSwapBuffers()
1315 if (!headless && window != nullptr && glfwWindowShouldClose(scast<GLFWwindow *>(window))) {
1316 return; // Don't render to a window that should be closed
1317 }
1318
1319 if (message_flag) {
1320 std::cout << "Updating the plot..." << std::flush;
1321 }
1322
1323 // Warn user if they request visible window in headless mode
1324 if (!hide_window && headless) {
1325 if (message_flag) {
1326 std::cout << "\nWARNING: plotUpdate(false) called in headless mode - window cannot be displayed. Use plotUpdate(true) for headless rendering." << std::endl;
1327 }
1328 }
1329
1330 if (!hide_window && !headless && window != nullptr) {
1331 glfwShowWindow(scast<GLFWwindow *>(window));
1332 }
1333
1334 // Update the Context geometry
1335 buildContextGeometry_private();
1336
1337 // Apply point cloud culling for performance optimization
1338 updatePointCulling();
1339
1340 // Set the view to fit window
1341 if (camera_lookat_center.x == 0 && camera_lookat_center.y == 0 && camera_lookat_center.z == 0) { // default center
1342 if (camera_eye_location.x < 1e-4 && camera_eye_location.y < 1e-4 && camera_eye_location.z == 2.f) { // default eye position
1343
1344 vec3 center_sph;
1345 vec3 radius;
1346 geometry_handler.getDomainBoundingSphere(center_sph, radius);
1347 float domain_bounding_radius = radius.magnitude();
1348
1349 vec2 xbounds, ybounds, zbounds;
1350 geometry_handler.getDomainBoundingBox(xbounds, ybounds, zbounds);
1351 camera_lookat_center = make_vec3(0.5f * (xbounds.x + xbounds.y), 0.5f * (ybounds.x + ybounds.y), 0.5f * (zbounds.x + zbounds.y));
1352 camera_eye_location = camera_lookat_center + sphere2cart(make_SphericalCoord(2.f * domain_bounding_radius, 20.f * PI_F / 180.f, 0));
1353 }
1354 }
1355
1356 // Colorbar - update with aspect ratio correction
1357 updateColorbar();
1358
1359 // Watermark
1361
1362 // Navigation gizmo - update if camera has changed or create initial geometry if needed
1363 if (navigation_gizmo_enabled) {
1364 if (navigation_gizmo_IDs.empty() || cameraHasChanged()) {
1365 updateNavigationGizmo();
1366 previous_camera_eye_location = camera_eye_location;
1367 previous_camera_lookat_center = camera_lookat_center;
1368 }
1369 }
1370
1371 transferBufferData();
1372
1373 bool shadow_flag = (primaryLightingModel == Visualizer::LIGHTING_PHONG_SHADOWED);
1374
1375 glm::mat4 depthMVP;
1376
1377 if (shadow_flag) {
1378 // Depth buffer for shadows
1379 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
1380 glViewport(0, 0, shadow_buffer_size.x, shadow_buffer_size.y); // Render on the whole framebuffer, complete from the lower left corner to the upper right
1381
1382 // Clear the screen
1383 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1384
1385 depthShader.useShader();
1386
1387 updatePerspectiveTransformation(true);
1388
1389 // Compute the MVP matrix from the light's point of view
1390 depthMVP = computeShadowDepthMVP();
1391 depthShader.setTransformationMatrix(depthMVP);
1392
1393 // bind depth texture
1394 glActiveTexture(GL_TEXTURE1);
1395 glBindTexture(GL_TEXTURE_2D, depthTexture);
1396 glActiveTexture(GL_TEXTURE0);
1397
1398 depthShader.enableTextureMaps();
1399 depthShader.enableTextureMasks();
1400
1401 render(true);
1402 } else {
1403 depthMVP = glm::mat4(1.0);
1404 }
1405
1406 assert(checkerrors());
1407
1408 // Render to the screen or offscreen framebuffer depending on headless mode
1409 if (headless && offscreenFramebufferID != 0) {
1410 // Render to offscreen framebuffer for headless mode
1411 glBindFramebuffer(GL_FRAMEBUFFER, offscreenFramebufferID);
1412 glViewport(0, 0, Wframebuffer, Hframebuffer);
1413 } else {
1414 // Render to the default framebuffer for windowed mode
1415 glBindFramebuffer(GL_FRAMEBUFFER, 0);
1416 glViewport(0, 0, Wframebuffer, Hframebuffer);
1417 }
1418
1419 if (background_is_transparent) {
1420 glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
1421 } else {
1422 glClearColor(backgroundColor.r, backgroundColor.g, backgroundColor.b, 1.0f);
1423 }
1424
1425 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1426
1427 primaryShader.useShader();
1428
1429 glm::mat4 DepthBiasMVP = biasMatrix * depthMVP;
1430
1431 primaryShader.setDepthBiasMatrix(DepthBiasMVP);
1432
1433 updatePerspectiveTransformation(false);
1434
1435 primaryShader.setTransformationMatrix(perspectiveTransformationMatrix);
1436 primaryShader.setViewMatrix(cameraViewMatrix);
1437 primaryShader.setProjectionMatrix(cameraProjectionMatrix);
1438
1439 primaryShader.enableTextureMaps();
1440 primaryShader.enableTextureMasks();
1441
1442 primaryShader.setLightingModel(primaryLightingModel);
1443 primaryShader.setLightIntensity(lightintensity);
1444
1445 glActiveTexture(GL_TEXTURE1);
1446 glBindTexture(GL_TEXTURE_2D, depthTexture);
1447 glUniform1i(primaryShader.shadowmapUniform, 1);
1448 glActiveTexture(GL_TEXTURE0);
1449
1450 buffers_swapped_since_render = false;
1451 render(false);
1452
1453 // Skip window-specific operations in headless mode
1454 if (!headless && window != nullptr) {
1455 glfwPollEvents();
1456 getViewKeystrokes(camera_eye_location, camera_lookat_center);
1457
1458 // Update navigation gizmo if camera has changed
1459 if (navigation_gizmo_enabled && cameraHasChanged()) {
1460 updateNavigationGizmo();
1461 previous_camera_eye_location = camera_eye_location;
1462 previous_camera_lookat_center = camera_lookat_center;
1463 // Transfer updated geometry to GPU
1464 transferBufferData();
1465 }
1466
1467 int width, height;
1468 glfwGetFramebufferSize((GLFWwindow *) window, &width, &height);
1469 Wframebuffer = width;
1470 Hframebuffer = height;
1471
1472 glfwSwapBuffers((GLFWwindow *) window);
1473 buffers_swapped_since_render = true;
1474 }
1475
1476 if (message_flag) {
1477 std::cout << "done." << std::endl;
1478 }
1479}
1480
1481void Visualizer::updateDepthBuffer() {
1482 // Update the Context geometry (if needed)
1483 if (true) {
1484 buildContextGeometry_private();
1485 }
1486
1487 transferBufferData();
1488
1489 // Depth buffer for shadows
1490 glBindFramebuffer(GL_FRAMEBUFFER, framebufferID);
1491 glViewport(0, 0, Wframebuffer, Hframebuffer);
1492
1493 // bind depth texture
1494 glActiveTexture(GL_TEXTURE1);
1495 glBindTexture(GL_TEXTURE_2D, depthTexture);
1496 glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, Wframebuffer, Hframebuffer, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
1497 glActiveTexture(GL_TEXTURE0);
1498
1499 // Clear the screen
1500 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
1501
1502 depthShader.useShader();
1503
1504 updatePerspectiveTransformation(false);
1505 depthShader.setTransformationMatrix(perspectiveTransformationMatrix);
1506
1507 depthShader.enableTextureMaps();
1508 depthShader.enableTextureMasks();
1509
1510 render(true);
1511
1512 assert(checkerrors());
1513
1514 depth_buffer_data.resize(Wframebuffer * Hframebuffer);
1515
1516#if defined(__APPLE__)
1517 constexpr GLenum read_buf = GL_FRONT;
1518#else
1519 constexpr GLenum read_buf = GL_BACK;
1520#endif
1521 glReadBuffer(read_buf);
1522 glReadPixels(0, 0, Wframebuffer, Hframebuffer, GL_DEPTH_COMPONENT, GL_FLOAT, depth_buffer_data.data());
1523 glFinish();
1524
1525 assert(checkerrors());
1526
1527 // Updates this->depth_buffer_data()
1528}
1529
1531 if (message_flag) {
1532 std::cout << "Rendering depth map..." << std::flush;
1533 }
1534
1535 updateDepthBuffer();
1536
1537 // normalize data, flip in y direction, and invert the color space so white=closest, black = furthest
1538 float depth_min = (std::numeric_limits<float>::max)();
1539 float depth_max = (std::numeric_limits<float>::min)();
1540 for (auto depth: depth_buffer_data) {
1541 if (depth < depth_min) {
1542 depth_min = depth;
1543 }
1544 if (depth > depth_max) {
1545 depth_max = depth;
1546 }
1547 }
1548 std::vector<unsigned char> depth_uchar(depth_buffer_data.size() * 4);
1549 for (size_t i = 0; i < depth_buffer_data.size(); i++) {
1550 auto value = scast<unsigned char>(std::round((depth_buffer_data.at(i) - depth_min) / (depth_max - depth_min) * 255));
1551 value = clamp(value, scast<unsigned char>(0), scast<unsigned char>(255));
1552 size_t row = i / Wframebuffer;
1553 size_t col = i % Wframebuffer;
1554 size_t flipped_i = (Hframebuffer - 1 - row) * Wframebuffer + col; // flipping
1555 depth_uchar.at(flipped_i * 4) = 255 - value; // R
1556 depth_uchar.at(flipped_i * 4 + 1) = 255 - value; // G
1557 depth_uchar.at(flipped_i * 4 + 2) = 255 - value; // B
1558 depth_uchar.at(flipped_i * 4 + 3) = 255; // A
1559 }
1560
1561 displayImage(depth_uchar, Wframebuffer, Hframebuffer);
1562
1563 if (message_flag) {
1564 std::cout << "done." << std::endl;
1565 }
1566}
1567
1568void Shader::initialize(const char *vertex_shader_file, const char *fragment_shader_file, Visualizer *visualizer_ptr, const char *geometry_shader_file) {
1569 // ~~~~~~~~~~~~~~~ COMPILE SHADERS ~~~~~~~~~~~~~~~~~~~~~~~~~//
1570
1571 assert(checkerrors());
1572
1573 // Create the shaders
1574 unsigned int VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
1575 unsigned int FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
1576 unsigned int GeometryShaderID = 0;
1577
1578 // Read the Vertex Shader code from the file
1579 std::string VertexShaderCode;
1580 std::ifstream VertexShaderStream(vertex_shader_file, std::ios::in);
1581 assert(VertexShaderStream.is_open());
1582 std::string Line;
1583 while (getline(VertexShaderStream, Line))
1584 VertexShaderCode += "\n" + Line;
1585 VertexShaderStream.close();
1586
1587 // Read the Fragment Shader code from the file
1588 std::string FragmentShaderCode;
1589 std::ifstream FragmentShaderStream(fragment_shader_file, std::ios::in);
1590 assert(FragmentShaderStream.is_open());
1591 Line = "";
1592 while (getline(FragmentShaderStream, Line))
1593 FragmentShaderCode += "\n" + Line;
1594 FragmentShaderStream.close();
1595
1596 // Read the Geometry Shader code from the file (if provided)
1597 std::string GeometryShaderCode;
1598 bool hasGeometryShader = (geometry_shader_file != nullptr);
1599 if (hasGeometryShader) {
1600 GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);
1601 std::ifstream GeometryShaderStream(geometry_shader_file, std::ios::in);
1602 assert(GeometryShaderStream.is_open());
1603 Line = "";
1604 while (getline(GeometryShaderStream, Line))
1605 GeometryShaderCode += "\n" + Line;
1606 GeometryShaderStream.close();
1607 }
1608
1609 // Compile Vertex Shader
1610 char const *VertexSourcePointer = VertexShaderCode.c_str();
1611 glShaderSource(VertexShaderID, 1, &VertexSourcePointer, nullptr);
1612 glCompileShader(VertexShaderID);
1613
1614 assert(checkerrors());
1615
1616 // check vertex‐shader compile status
1617 GLint compileOK = GL_FALSE;
1618 glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &compileOK);
1619 if (compileOK != GL_TRUE) {
1620 GLint logLen = 0;
1621 glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &logLen);
1622 std::vector<char> log(logLen);
1623 glGetShaderInfoLog(VertexShaderID, logLen, nullptr, log.data());
1624 fprintf(stderr, "Vertex shader compilation failed:\n%s\n", log.data());
1625 throw std::runtime_error("vertex shader compile error");
1626 }
1627
1628 // Compile Fragment Shader
1629 char const *FragmentSourcePointer = FragmentShaderCode.c_str();
1630 glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, nullptr);
1631 glCompileShader(FragmentShaderID);
1632
1633 assert(checkerrors());
1634
1635 // check fragment‐shader compile status
1636 compileOK = GL_FALSE;
1637 glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &compileOK);
1638 if (compileOK != GL_TRUE) {
1639 GLint logLen = 0;
1640 glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &logLen);
1641 std::vector<char> log(logLen);
1642 glGetShaderInfoLog(FragmentShaderID, logLen, nullptr, log.data());
1643 fprintf(stderr, "Fragment shader compilation failed:\n%s\n", log.data());
1644 throw std::runtime_error("fragment shader compile error");
1645 }
1646
1647 // Compile Geometry Shader (if provided)
1648 if (hasGeometryShader) {
1649 char const *GeometrySourcePointer = GeometryShaderCode.c_str();
1650 glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer, nullptr);
1651 glCompileShader(GeometryShaderID);
1652
1653 assert(checkerrors());
1654
1655 // check geometry‐shader compile status
1656 compileOK = GL_FALSE;
1657 glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &compileOK);
1658 if (compileOK != GL_TRUE) {
1659 GLint logLen = 0;
1660 glGetShaderiv(GeometryShaderID, GL_INFO_LOG_LENGTH, &logLen);
1661 std::vector<char> log(logLen);
1662 glGetShaderInfoLog(GeometryShaderID, logLen, nullptr, log.data());
1663 fprintf(stderr, "Geometry shader compilation failed:\n%s\n", log.data());
1664 throw std::runtime_error("geometry shader compile error");
1665 }
1666 }
1667
1668 // Link the program
1669 shaderID = glCreateProgram();
1670 glAttachShader(shaderID, VertexShaderID);
1671 glAttachShader(shaderID, FragmentShaderID);
1672 if (hasGeometryShader) {
1673 glAttachShader(shaderID, GeometryShaderID);
1674 }
1675 glLinkProgram(shaderID);
1676
1677 assert(checkerrors());
1678
1679 GLint linkOK = GL_FALSE;
1680 glGetProgramiv(shaderID, GL_LINK_STATUS, &linkOK);
1681 if (linkOK != GL_TRUE) {
1682 GLint logLen = 0;
1683 glGetProgramiv(shaderID, GL_INFO_LOG_LENGTH, &logLen);
1684 std::vector<char> log(logLen);
1685 glGetProgramInfoLog(shaderID, logLen, nullptr, log.data());
1686 fprintf(stderr, "Shader program link failed:\n%s\n", log.data());
1687 throw std::runtime_error("program link error");
1688 }
1689
1690 assert(checkerrors());
1691
1692 glDeleteShader(VertexShaderID);
1693 glDeleteShader(FragmentShaderID);
1694 if (hasGeometryShader) {
1695 glDeleteShader(GeometryShaderID);
1696 }
1697
1698 assert(checkerrors());
1699
1700 // ~~~~~~~~~~~ Create a Vertex Array Object (VAO) ~~~~~~~~~~//
1701 vertex_array_IDs.resize(GeometryHandler::all_geometry_types.size());
1702 glGenVertexArrays(GeometryHandler::all_geometry_types.size(), vertex_array_IDs.data());
1703
1704 assert(checkerrors());
1705
1706 // set up vertex buffers
1707
1708 int i = 0;
1709 for (const auto &geometry_type: GeometryHandler::all_geometry_types) {
1710 glBindVertexArray(vertex_array_IDs.at(i));
1711
1712 // 1st attribute buffer : vertex positions
1713 glBindBuffer(GL_ARRAY_BUFFER, visualizer_ptr->vertex_buffer.at(i));
1714 glEnableVertexAttribArray(0); // vertices
1715 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
1716
1717 // 2nd attribute buffer : vertex uv
1718 glBindBuffer(GL_ARRAY_BUFFER, visualizer_ptr->uv_buffer.at(i));
1719 glEnableVertexAttribArray(1); // uv
1720 glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
1721
1722 // 3rd attribute buffer : face index
1723 glBindBuffer(GL_ARRAY_BUFFER, visualizer_ptr->face_index_buffer.at(i));
1724 glEnableVertexAttribArray(2); // face index
1725 glVertexAttribIPointer(2, 1, GL_INT, 0, nullptr);
1726
1727 i++;
1728 }
1729
1730 glBindBuffer(GL_ARRAY_BUFFER, 0);
1731
1732 assert(checkerrors());
1733
1734 glUseProgram(shaderID);
1735
1736 assert(checkerrors());
1737
1738 // ~~~~~~~~~~~ Primary Shader Uniforms ~~~~~~~~~~//
1739
1740 // Transformation Matrix
1741 transformMatrixUniform = glGetUniformLocation(shaderID, "MVP");
1742
1743 // View and Projection Matrices (for sky geometry transformations)
1744 viewMatrixUniform = glGetUniformLocation(shaderID, "view");
1745 projectionMatrixUniform = glGetUniformLocation(shaderID, "projection");
1746
1747 // Depth Bias Matrix (for shadows)
1748 depthBiasUniform = glGetUniformLocation(shaderID, "DepthBiasMVP");
1749
1750 // Texture Sampler
1751 textureUniform = glGetUniformLocation(shaderID, "textureSampler");
1752
1753 // Shadow Map Sampler
1754 shadowmapUniform = glGetUniformLocation(shaderID, "shadowMap");
1755 glUniform1i(shadowmapUniform, 1);
1756
1757 // Unit vector in the direction of the light (sun)
1758 lightDirectionUniform = glGetUniformLocation(shaderID, "lightDirection");
1759 glUniform3f(lightDirectionUniform, 0, 0, 1); // Default is directly above
1760
1761 // Lighting model used for shading primitives
1762 lightingModelUniform = glGetUniformLocation(shaderID, "lightingModel");
1763 glUniform1i(lightingModelUniform, 0); // Default is none
1764
1765 RboundUniform = glGetUniformLocation(shaderID, "Rbound");
1766 glUniform1i(RboundUniform, 0);
1767
1768 // Lighting intensity factor
1769 lightIntensityUniform = glGetUniformLocation(shaderID, "lightIntensity");
1770 glUniform1f(lightIntensityUniform, 1.f);
1771
1772 // Texture (u,v) rescaling factor
1773 uvRescaleUniform = glGetUniformLocation(shaderID, "uv_rescale");
1774
1775 // Cache texture buffer uniform locations to avoid glGetUniformLocation during rendering
1776 // This prevents macOS OpenGL warnings about texture unit binding issues
1777 colorTextureObjectUniform = glGetUniformLocation(shaderID, "color_texture_object");
1778 normalTextureObjectUniform = glGetUniformLocation(shaderID, "normal_texture_object");
1779 textureFlagTextureObjectUniform = glGetUniformLocation(shaderID, "texture_flag_texture_object");
1780 textureIDTextureObjectUniform = glGetUniformLocation(shaderID, "texture_ID_texture_object");
1781 coordinateFlagTextureObjectUniform = glGetUniformLocation(shaderID, "coordinate_flag_texture_object");
1782 skyGeometryFlagTextureObjectUniform = glGetUniformLocation(shaderID, "sky_geometry_flag_texture_object");
1783 hiddenFlagTextureObjectUniform = glGetUniformLocation(shaderID, "hidden_flag_texture_object");
1784
1785 // Set the texture unit assignments for texture buffers
1786 if (colorTextureObjectUniform >= 0)
1787 glUniform1i(colorTextureObjectUniform, 3);
1788 if (normalTextureObjectUniform >= 0)
1789 glUniform1i(normalTextureObjectUniform, 4);
1790 if (textureFlagTextureObjectUniform >= 0)
1791 glUniform1i(textureFlagTextureObjectUniform, 5);
1792 if (textureIDTextureObjectUniform >= 0)
1793 glUniform1i(textureIDTextureObjectUniform, 6);
1794 if (coordinateFlagTextureObjectUniform >= 0)
1795 glUniform1i(coordinateFlagTextureObjectUniform, 7);
1796 if (skyGeometryFlagTextureObjectUniform >= 0)
1797 glUniform1i(skyGeometryFlagTextureObjectUniform, 2);
1798 if (hiddenFlagTextureObjectUniform >= 0)
1799 glUniform1i(hiddenFlagTextureObjectUniform, 8);
1800
1801 assert(checkerrors());
1802
1803 initialized = true;
1804}
1805
1806Shader::~Shader() {
1807 if (!initialized) {
1808 return;
1809 }
1810 glDeleteVertexArrays(vertex_array_IDs.size(), vertex_array_IDs.data());
1811 glDeleteProgram(shaderID);
1812}
1813
1815 glEnable(GL_BLEND);
1816 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1817}
1818
1820 glActiveTexture(GL_TEXTURE0);
1821 glUniform1i(textureUniform, 0);
1822 // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1823 // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1824 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1825 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1826 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1827 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1828 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1829 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1830 glEnable(GL_BLEND);
1831 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1832}
1833
1835 glActiveTexture(GL_TEXTURE0);
1836 glUniform1i(textureUniform, 0);
1837 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1838 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1839 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
1840 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
1841 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
1842 glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
1843 glEnable(GL_BLEND);
1844 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1845}
1846
1847void Shader::setTransformationMatrix(const glm::mat4 &matrix) const {
1848 glUniformMatrix4fv(transformMatrixUniform, 1, GL_FALSE, &matrix[0][0]);
1849}
1850
1851void Shader::setViewMatrix(const glm::mat4 &matrix) const {
1852 glUniformMatrix4fv(viewMatrixUniform, 1, GL_FALSE, &matrix[0][0]);
1853}
1854
1855void Shader::setProjectionMatrix(const glm::mat4 &matrix) const {
1856 glUniformMatrix4fv(projectionMatrixUniform, 1, GL_FALSE, &matrix[0][0]);
1857}
1858
1859void Shader::setDepthBiasMatrix(const glm::mat4 &matrix) const {
1860 glUniformMatrix4fv(depthBiasUniform, 1, GL_FALSE, &matrix[0][0]);
1861}
1862
1863void Shader::setLightDirection(const helios::vec3 &direction) const {
1864 glUniform3f(lightDirectionUniform, direction.x, direction.y, direction.z);
1865}
1866
1867void Shader::setLightingModel(uint lightingmodel) const {
1868 glUniform1i(lightingModelUniform, lightingmodel);
1869}
1870
1871void Shader::setLightIntensity(float lightintensity) const {
1872 glUniform1f(lightIntensityUniform, lightintensity);
1873}
1874
1875void Shader::useShader() const {
1876 glUseProgram(shaderID);
1877}
1878
1879void Visualizer::framebufferResizeCallback(GLFWwindow *window, int width, int height) {
1880 if (width <= 0 || height <= 0) {
1881 return;
1882 }
1883 auto *viz = static_cast<Visualizer *>(glfwGetWindowUserPointer(window));
1884 if (viz != nullptr) {
1885 viz->Wframebuffer = static_cast<uint>(width);
1886 viz->Hframebuffer = static_cast<uint>(height);
1887 }
1888}
1889
1890void Visualizer::windowResizeCallback(GLFWwindow *window, int width, int height) {
1891 if (width <= 0 || height <= 0) {
1892 return;
1893 }
1894 auto *viz = static_cast<Visualizer *>(glfwGetWindowUserPointer(window));
1895 if (viz != nullptr) {
1896 int fbw, fbh;
1897 glfwGetFramebufferSize(window, &fbw, &fbh);
1898 if (fbw != width || fbh != height) {
1899 glfwSetWindowSize(window, width, height);
1900 fbw = width;
1901 fbh = height;
1902 }
1903 viz->Wdisplay = static_cast<uint>(width);
1904 viz->Hdisplay = static_cast<uint>(height);
1905 viz->Wframebuffer = static_cast<uint>(fbw);
1906 viz->Hframebuffer = static_cast<uint>(fbh);
1907 viz->updateWatermark();
1908 viz->updateNavigationGizmo();
1909 viz->transferBufferData();
1910 }
1911}
1912
1914 if (!isWatermarkVisible) {
1915 if (watermark_ID != 0) {
1916 geometry_handler.deleteGeometry(watermark_ID);
1917 watermark_ID = 0;
1918 }
1919 return;
1920 }
1921
1922 constexpr float texture_aspect = 675.f / 195.f; // image width / height
1923
1924 float window_aspect = float(Wframebuffer) / float(Hframebuffer);
1925 float width = 0.07f * texture_aspect / window_aspect;
1926 if (watermark_ID != 0) {
1927 geometry_handler.deleteGeometry(watermark_ID);
1928 }
1929 std::string watermarkPath = helios::resolvePluginAsset("visualizer", "textures/Helios_watermark.png").string();
1930 watermark_ID = addRectangleByCenter(make_vec3(0.75f * width, 0.95f, 0), make_vec2(width, 0.07), make_SphericalCoord(0, 0), watermarkPath.c_str(), COORDINATES_WINDOW_NORMALIZED);
1931}
1932
1933void Visualizer::updateColorbar() {
1934 // Check if colorbar should be displayed
1935 if (colorbar_flag != 2) {
1936 // If disabled, delete geometry if it exists
1937 if (!colorbar_IDs.empty()) {
1938 geometry_handler.deleteGeometry(colorbar_IDs);
1939 colorbar_IDs.clear();
1940 }
1941 return;
1942 }
1943
1944 // Calculate window aspect ratio
1945 float window_aspect = float(Wframebuffer) / float(Hframebuffer);
1946
1947 // Apply aspect ratio correction to width to maintain intended aspect ratio
1948 // Formula: corrected_width = height * intended_aspect / window_aspect
1949 // This keeps the colorbar height constant in normalized coordinates while adjusting width
1950 float corrected_width = colorbar_size.y * colorbar_intended_aspect_ratio / window_aspect;
1951
1952 // Delete old colorbar geometry
1953 if (!colorbar_IDs.empty()) {
1954 geometry_handler.deleteGeometry(colorbar_IDs);
1955 colorbar_IDs.clear();
1956 }
1957
1958 // Create new colorbar with aspect-corrected size
1959 colorbar_IDs = addColorbarByCenter(colorbar_title.c_str(), make_vec2(corrected_width, colorbar_size.y), colorbar_position, colorbar_fontcolor, colormap_current);
1960}
1961
1962
1963bool lbutton_down = false;
1964bool rbutton_down = false;
1965bool mbutton_down = false;
1966double startX, startY;
1967double scrollX, scrollY;
1968bool scroll = false;
1969
1970// Click detection state for navigation gizmo
1971double click_startX = 0.0, click_startY = 0.0;
1972bool potential_click = false;
1973const double click_threshold = 5.0; // Maximum mouse movement in pixels to register as click
1974
1975
1976void mouseCallback(GLFWwindow *window, int button, int action, int mods) {
1977 if (action == GLFW_PRESS) {
1978 glfwGetCursorPos(window, &startX, &startY);
1979 }
1980 if (button == GLFW_MOUSE_BUTTON_LEFT) {
1981 if (GLFW_PRESS == action) {
1982 lbutton_down = true;
1983 // Track potential click for gizmo interaction
1984 glfwGetCursorPos(window, &click_startX, &click_startY);
1985 potential_click = true;
1986 } else if (GLFW_RELEASE == action) {
1987 lbutton_down = false;
1988 // Check if this was a click (minimal movement) rather than a drag
1989 if (potential_click) {
1990 double releaseX, releaseY;
1991 glfwGetCursorPos(window, &releaseX, &releaseY);
1992 double dx_click = releaseX - click_startX;
1993 double dy_click = releaseY - click_startY;
1994 double movement = std::sqrt(dx_click * dx_click + dy_click * dy_click);
1995
1996 if (movement < click_threshold) {
1997 // This is a click - check if it hit the navigation gizmo
1998 auto *visualizer = static_cast<Visualizer *>(glfwGetWindowUserPointer(window));
1999 if (visualizer != nullptr) {
2000 visualizer->handleGizmoClick(releaseX, releaseY);
2001 }
2002 }
2003 potential_click = false;
2004 }
2005 }
2006 } else if (button == GLFW_MOUSE_BUTTON_MIDDLE) {
2007 if (GLFW_PRESS == action) {
2008 mbutton_down = true;
2009 } else if (GLFW_RELEASE == action) {
2010 mbutton_down = false;
2011 }
2012 } else if (button == GLFW_MOUSE_BUTTON_RIGHT) {
2013 if (GLFW_PRESS == action) {
2014 rbutton_down = true;
2015 } else if (GLFW_RELEASE == action) {
2016 rbutton_down = false;
2017 }
2018 }
2019}
2020
2021
2022void cursorCallback(GLFWwindow *window, double xpos, double ypos) {
2023 if (rbutton_down) {
2024 dx = xpos - startX;
2025 dy = ypos - startY;
2026 } else if (lbutton_down || mbutton_down) {
2027 dphi = scast<float>(xpos - startX);
2028 dtheta = scast<float>(ypos - startY);
2029
2030 // If mouse moved too much during left button press, invalidate potential click
2031 if (potential_click && lbutton_down) {
2032 double dx_click = xpos - click_startX;
2033 double dy_click = ypos - click_startY;
2034 double movement = std::sqrt(dx_click * dx_click + dy_click * dy_click);
2035 if (movement >= click_threshold) {
2036 potential_click = false;
2037 }
2038 }
2039 } else {
2040 dphi = dtheta = 0.f;
2041
2042 // Check for hover over navigation gizmo bubbles when no buttons are pressed
2043 auto *visualizer = static_cast<Visualizer *>(glfwGetWindowUserPointer(window));
2044 if (visualizer != nullptr) {
2045 visualizer->handleGizmoHover(xpos, ypos);
2046 }
2047 }
2048 startX = xpos;
2049 startY = ypos;
2050}
2051
2052
2053void scrollCallback(GLFWwindow *window, double xoffset, double yoffset) {
2054 dscroll = scast<float>(yoffset);
2055 scrollY = yoffset;
2056 if (yoffset > 0.0 || yoffset < 0.0) {
2057 scroll = true;
2058 } else {
2059 scroll = false;
2060 }
2061}
2062
2063
2064void Visualizer::getViewKeystrokes(vec3 &eye, vec3 &center) {
2065 vec3 forward = center - eye;
2066 forward = forward.normalize();
2067
2068 vec3 right = cross(forward, vec3(0, 0, 1));
2069 right = right.normalize();
2070
2071 vec3 up = cross(right, forward);
2072 up = up.normalize();
2073
2074 SphericalCoord Spherical = cart2sphere(eye - center);
2075 float radius = Spherical.radius;
2076 float theta = Spherical.elevation;
2077 float phi = Spherical.azimuth;
2078
2079 phi += PI_F * (dphi / 160.f);
2080 if (dtheta > 0 && theta + PI_F / 80.f < 0.49f * PI_F) {
2081 theta += PI_F * (dtheta / 120.f);
2082 } else if (dtheta < 0 && theta > -0.25 * PI_F) {
2083 theta += PI_F * (dtheta / 120.f);
2084 }
2085 dtheta = dphi = 0;
2086 if (dx != 0.f) {
2087 center -= 0.025f * dx * right;
2088 }
2089 if (dy != 0.f) {
2090 center += 0.025f * dy * up;
2091 }
2092 dx = dy = 0.f;
2093 if (scroll) {
2094 if (dscroll > 0.0f) {
2095 radius = (radius * 0.9f > minimum_view_radius) ? radius * 0.9f : minimum_view_radius;
2096 } else {
2097 radius *= 1.1f;
2098 }
2099 }
2100 scroll = false;
2101
2102 auto *_window = scast<GLFWwindow *>(window);
2103
2104 //----- Holding SPACEBAR -----//
2105 if (glfwGetKey(_window, GLFW_KEY_SPACE) == GLFW_PRESS) {
2106 // Move center to the left - SPACE + LEFT KEY
2107 if (glfwGetKey(_window, GLFW_KEY_LEFT) == GLFW_PRESS) {
2108 center.x += 0.1f * sin(phi);
2109 center.y += 0.1f * cos(phi);
2110 }
2111 // Move center to the right - SPACE + RIGHT KEY
2112 else if (glfwGetKey(_window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
2113 center.x -= 0.1f * sin(phi);
2114 center.y -= 0.1f * cos(phi);
2115 }
2116 // Move center upward - SPACE + UP KEY
2117 else if (glfwGetKey(_window, GLFW_KEY_UP) == GLFW_PRESS) {
2118 center.z += 0.2f;
2119 }
2120 // Move center downward - SPACE + DOWN KEY
2121 else if (glfwGetKey(_window, GLFW_KEY_DOWN) == GLFW_PRESS) {
2122 center.z -= 0.2f;
2123 }
2124
2125 //----- Not Holding SPACEBAR -----//
2126 } else {
2127 // Orbit left - LEFT ARROW KEY
2128 if (glfwGetKey(_window, GLFW_KEY_LEFT) == GLFW_PRESS) {
2129 phi += PI_F / 40.f;
2130 }
2131 // Orbit right - RIGHT ARROW KEY
2132 else if (glfwGetKey(_window, GLFW_KEY_RIGHT) == GLFW_PRESS) {
2133 phi -= PI_F / 40.f;
2134 }
2135
2136 // Increase Elevation - UP ARROW KEY
2137 else if (glfwGetKey(_window, GLFW_KEY_UP) == GLFW_PRESS) {
2138 if (theta + PI_F / 80.f < 0.49f * PI_F) {
2139 theta += PI_F / 80.f;
2140 }
2141 }
2142 // Decrease Elevation - DOWN ARROW KEY
2143 else if (glfwGetKey(_window, GLFW_KEY_DOWN) == GLFW_PRESS) {
2144 if (theta > -0.25 * PI_F) {
2145 theta -= PI_F / 80.f;
2146 }
2147 }
2148
2149 // Zoom in - "+" KEY
2150 if (glfwGetKey(_window, GLFW_KEY_EQUAL) == GLFW_PRESS) {
2151 radius = (radius * 0.9f > minimum_view_radius) ? radius * 0.9f : minimum_view_radius;
2152 }
2153 // Zoom out - "-" KEY
2154 else if (glfwGetKey(_window, GLFW_KEY_MINUS) == GLFW_PRESS) {
2155 radius *= 1.1;
2156 }
2157 }
2158
2159 if (glfwGetKey(_window, GLFW_KEY_P) == GLFW_PRESS) {
2160 std::cout << "View is angle: (R,theta,phi)=(" << radius << "," << theta << "," << phi << ") at from position (" << camera_eye_location.x << "," << camera_eye_location.y << "," << camera_eye_location.z << ") looking at (" << center.x << ","
2161 << center.y << "," << center.z << ")" << std::endl;
2162 }
2163
2164 camera_eye_location = sphere2cart(make_SphericalCoord(radius, theta, phi)) + center;
2165}
2166
2167void Visualizer::cullPointsByFrustum() {
2168 const std::vector<float> *vertex_data = geometry_handler.getVertexData_ptr(GeometryHandler::GEOMETRY_TYPE_POINT);
2169 if (!vertex_data || vertex_data->empty()) {
2170 return;
2171 }
2172
2173 std::vector<glm::vec4> frustum_planes = extractFrustumPlanes();
2174
2175 // Check each point against all 6 frustum planes
2176 size_t point_count = vertex_data->size() / 3;
2177 for (size_t i = 0; i < point_count; ++i) {
2178 glm::vec3 point(vertex_data->at(i * 3), vertex_data->at(i * 3 + 1), vertex_data->at(i * 3 + 2));
2179
2180 bool inside_frustum = true;
2181 for (const auto &plane: frustum_planes) {
2182 // Plane equation: ax + by + cz + d = 0
2183 // Point is outside if dot(plane.xyz, point) + plane.w < 0
2184 if (glm::dot(glm::vec3(plane), point) + plane.w < 0) {
2185 inside_frustum = false;
2186 break;
2187 }
2188 }
2189
2190 // Find the UUID for this point index and update visibility
2191 std::vector<size_t> all_UUIDs = geometry_handler.getAllGeometryIDs();
2192 size_t point_index = 0;
2193 for (size_t UUID: all_UUIDs) {
2194 if (geometry_handler.getIndexMap(UUID).geometry_type == GeometryHandler::GEOMETRY_TYPE_POINT) {
2195 if (point_index == i) {
2196 geometry_handler.setVisibility(UUID, inside_frustum);
2197 break;
2198 }
2199 point_index++;
2200 }
2201 }
2202 }
2203}
2204
2205void Visualizer::cullPointsByDistance(float maxDistance, float lodFactor) {
2206 const std::vector<float> *vertex_data = geometry_handler.getVertexData_ptr(GeometryHandler::GEOMETRY_TYPE_POINT);
2207 if (!vertex_data || vertex_data->empty()) {
2208 return;
2209 }
2210
2211 glm::vec3 camera_pos(camera_eye_location.x, camera_eye_location.y, camera_eye_location.z);
2212
2213 // Apply distance-based culling with level-of-detail and adaptive sizing
2214 size_t point_count = vertex_data->size() / 3;
2215 for (size_t i = 0; i < point_count; ++i) {
2216 glm::vec3 point(vertex_data->at(i * 3), vertex_data->at(i * 3 + 1), vertex_data->at(i * 3 + 2));
2217
2218 float distance = glm::length(point - camera_pos);
2219 bool should_render = true;
2220 float adaptive_size = 1.0f; // Default point size
2221
2222 // Cull points beyond max distance
2223 if (distance > maxDistance) {
2224 should_render = false;
2225 }
2226 // Apply level-of-detail culling and adaptive sizing
2227 else if (distance > maxDistance * 0.3f) { // Start LOD at 30% of max distance
2228 float distance_ratio = distance / maxDistance;
2229 float lod_threshold = distance_ratio * lodFactor;
2230
2231 // Cull every Nth point based on distance
2232 if ((i % static_cast<size_t>(std::max(1.0f, lod_threshold))) != 0) {
2233 should_render = false;
2234 } else {
2235 // For distant points that we keep, increase their size to maintain visual coverage
2236 adaptive_size = 1.0f + (distance_ratio * 3.0f); // Scale up to 4x size for far points
2237 }
2238 }
2239
2240 // Find the UUID for this point index and update visibility and size
2241 std::vector<size_t> all_UUIDs = geometry_handler.getAllGeometryIDs();
2242 size_t point_index = 0;
2243 for (size_t UUID: all_UUIDs) {
2244 if (geometry_handler.getIndexMap(UUID).geometry_type == GeometryHandler::GEOMETRY_TYPE_POINT) {
2245 if (point_index == i) {
2246 geometry_handler.setVisibility(UUID, should_render);
2247 if (should_render) {
2248 // Apply adaptive sizing to maintain visual quality
2249 float original_size = geometry_handler.getSize(UUID);
2250 if (original_size <= 0)
2251 original_size = 1.0f;
2252 geometry_handler.setSize(UUID, original_size * adaptive_size);
2253 }
2254 break;
2255 }
2256 point_index++;
2257 }
2258 }
2259 }
2260}
2261
2262void Visualizer::updatePointCulling() {
2263 // Update total point count
2264 points_total_count = geometry_handler.getPointCount();
2265
2266 // Only perform culling if enabled and we have enough points
2267 if (!point_culling_enabled || points_total_count < point_culling_threshold) {
2268 points_rendered_count = points_total_count;
2269 last_culling_time_ms = 0;
2270 return;
2271 }
2272
2273 // Measure culling performance
2274 auto start_time = std::chrono::high_resolution_clock::now();
2275
2276 // Apply frustum culling first
2277 cullPointsByFrustum();
2278
2279 // Calculate max distance if not set
2280 float max_distance = point_max_render_distance;
2281 if (max_distance <= 0) {
2282 helios::vec2 xbounds, ybounds, zbounds;
2283 geometry_handler.getDomainBoundingBox(xbounds, ybounds, zbounds);
2284 float scene_size = std::max({xbounds.y - xbounds.x, ybounds.y - ybounds.x, zbounds.y - zbounds.x});
2285 max_distance = scene_size * 5.0f; // Render points up to 5x scene size
2286 }
2287
2288 // Apply distance-based culling with configurable parameters
2289 cullPointsByDistance(max_distance, point_lod_factor);
2290
2291 // Update metrics
2292 points_rendered_count = geometry_handler.getPointCount(false); // Count only visible points
2293
2294 auto end_time = std::chrono::high_resolution_clock::now();
2295 last_culling_time_ms = std::chrono::duration<float, std::milli>(end_time - start_time).count();
2296}
2297
2298std::vector<glm::vec4> Visualizer::extractFrustumPlanes() const {
2299 std::vector<glm::vec4> planes(6);
2300
2301 // Extract frustum planes from the MVP matrix
2302 glm::mat4 mvp = cameraProjectionMatrix * cameraViewMatrix;
2303
2304 // Left plane
2305 planes[0] = glm::vec4(mvp[0][3] + mvp[0][0], mvp[1][3] + mvp[1][0], mvp[2][3] + mvp[2][0], mvp[3][3] + mvp[3][0]);
2306 // Right plane
2307 planes[1] = glm::vec4(mvp[0][3] - mvp[0][0], mvp[1][3] - mvp[1][0], mvp[2][3] - mvp[2][0], mvp[3][3] - mvp[3][0]);
2308 // Bottom plane
2309 planes[2] = glm::vec4(mvp[0][3] + mvp[0][1], mvp[1][3] + mvp[1][1], mvp[2][3] + mvp[2][1], mvp[3][3] + mvp[3][1]);
2310 // Top plane
2311 planes[3] = glm::vec4(mvp[0][3] - mvp[0][1], mvp[1][3] - mvp[1][1], mvp[2][3] - mvp[2][1], mvp[3][3] - mvp[3][1]);
2312 // Near plane
2313 planes[4] = glm::vec4(mvp[0][3] + mvp[0][2], mvp[1][3] + mvp[1][2], mvp[2][3] + mvp[2][2], mvp[3][3] + mvp[3][2]);
2314 // Far plane
2315 planes[5] = glm::vec4(mvp[0][3] - mvp[0][2], mvp[1][3] - mvp[1][2], mvp[2][3] - mvp[2][2], mvp[3][3] - mvp[3][2]);
2316
2317 // Normalize the planes
2318 for (auto &plane: planes) {
2319 float length = glm::length(glm::vec3(plane));
2320 if (length > 0) {
2321 plane /= length;
2322 }
2323 }
2324
2325 return planes;
2326}
2327
2329 point_culling_enabled = enabled;
2330}
2331
2333 point_culling_threshold = threshold;
2334}
2335
2337 point_max_render_distance = distance;
2338}
2339
2341 point_lod_factor = factor;
2342}
2343
2344void Visualizer::getPointRenderingMetrics(size_t &total_points, size_t &rendered_points, float &culling_time_ms) const {
2345 total_points = points_total_count;
2346 rendered_points = points_rendered_count;
2347 culling_time_ms = last_culling_time_ms;
2348}
2349
2350std::string errorString(GLenum err) {
2351 std::string message;
2352 message.assign("");
2353
2354 if (err == GL_INVALID_ENUM) {
2355 message.assign("GL_INVALID_ENUM - An unacceptable value is specified for an enumerated argument.");
2356 } else if (err == GL_INVALID_VALUE) {
2357 message.assign("GL_INVALID_VALUE - A numeric argument is out of range.");
2358 } else if (err == GL_INVALID_OPERATION) {
2359 message.assign("GL_INVALID_OPERATION - The specified operation is not allowed in the current state.");
2360 } else if (err == GL_STACK_OVERFLOW) {
2361 message.assign("GL_STACK_OVERFLOW - This command would cause a stack overflow.");
2362 } else if (err == GL_STACK_UNDERFLOW) {
2363 message.assign("GL_STACK_UNDERFLOW - This command would cause a stack underflow.");
2364 } else if (err == GL_OUT_OF_MEMORY) {
2365 message.assign("GL_OUT_OF_MEMORY - There is not enough memory left to execute the command.");
2366 } else if (err == GL_TABLE_TOO_LARGE) {
2367 message.assign("GL_TABLE_TOO_LARGE - The specified table exceeds the implementation's maximum supported table size.");
2368 }
2369
2370 return message;
2371}
2372
2373int checkerrors() {
2374 GLenum err;
2375 int err_count = 0;
2376 while ((err = glGetError()) != GL_NO_ERROR) {
2377 std::cerr << "glError #" << err_count << ": " << errorString(err) << std::endl;
2378 err_count++;
2379 }
2380 if (err_count > 0) {
2381 return 0;
2382 } else {
2383 return 1;
2384 }
2385}
2386
2387// Safe error checking that throws exceptions instead of using assert
2388void check_opengl_errors_safe(const std::string &context) {
2389 if (!checkerrors()) {
2390 helios_runtime_error("ERROR (Visualizer): OpenGL errors detected in " + context + ". Check console output for specific error details.");
2391 }
2392}