summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoradmin <admin@optics-design.com>2025-08-06 11:31:21 +0200
committeradmin <admin@optics-design.com>2025-08-06 11:31:21 +0200
commita3389f25f786be7139feaca837c86442f7745745 (patch)
treee24041fdb924ade084180c4f0cd38159f10b3fbd /src
parent04b3fcb479f5aaae06d18b315a8bdc8c298f4eae (diff)
changed gui stuff
Diffstat (limited to 'src')
-rw-r--r--src/glamac/glamac.c97
-rw-r--r--src/glamac/glamac_events.c20
-rw-r--r--src/glamac/glamac_render.c501
-rw-r--r--src/glamac/glamac_view.c281
4 files changed, 789 insertions, 110 deletions
diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c
index dd06626..a26c7e6 100644
--- a/src/glamac/glamac.c
+++ b/src/glamac/glamac.c
@@ -35,10 +35,10 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) {
return 0;
}
- // Hysteresis parameters
- const f32 threshold = 0.25f; // 25% change threshold
- const u32 minReloadInterval = 500; // Minimum 500ms between font reloads
- const u32 maxPendingTime = 2000; // Maximum time to wait for pending reload
+ // Hysteresis parameters - reduced for better responsiveness
+ const f32 threshold = 0.15f; // 15% change threshold (more sensitive for fullscreen)
+ const u32 minReloadInterval = 50; // Minimum 50ms between font reloads (faster response)
+ const u32 maxPendingTime = 300; // Maximum time to wait for pending reload
u32 currentTime = SDL_GetTicks();
@@ -48,15 +48,24 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) {
b32 significantChange = (widthDiff > (i32)(view->windowWidth * threshold) ||
heightDiff > (i32)(view->windowHeight * threshold));
- if (significantChange) {
+ // Detect major changes (like fullscreen transitions) - force immediate reload
+ b32 majorChange = (widthDiff > 200 || heightDiff > 200 ||
+ (lastWidth > 0 && lastHeight > 0 &&
+ (abs(view->windowWidth - lastWidth) > (view->windowWidth / 2) ||
+ abs(view->windowHeight - lastHeight) > (view->windowHeight / 2))));
+
+ if (significantChange || majorChange) {
// Update pending dimensions
pendingWidth = view->windowWidth;
pendingHeight = view->windowHeight;
- // Check if enough time has passed since last reload
- if (currentTime - lastReloadTime >= minReloadInterval) {
- printf("Window size changed significantly: %dx%d -> %dx%d, reloading fonts...\n",
- lastWidth, lastHeight, view->windowWidth, view->windowHeight);
+ // For major changes, force immediate reload regardless of timing
+ // For normal changes, check if enough time has passed since last reload
+ if (majorChange || currentTime - lastReloadTime >= minReloadInterval) {
+ if (is_debug_mode()) {
+ printf("Window size changed significantly: %dx%d -> %dx%d, reloading fonts...\n",
+ lastWidth, lastHeight, view->windowWidth, view->windowHeight);
+ }
// Clear text cache first
clear_text_cache();
@@ -101,17 +110,24 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) {
lastReloadTime = currentTime;
pendingWidth = pendingHeight = 0; // Clear pending state
- printf("Fonts successfully reloaded for window size: %dx%d\n",
- view->windowWidth, view->windowHeight);
+ // Mark view as dirty to re-render with new fonts
+ mark_view_dirty(view);
+
+ if (is_debug_mode()) {
+ printf("Fonts successfully reloaded for window size: %dx%d\n",
+ view->windowWidth, view->windowHeight);
+ }
} else {
// Can't reload yet due to rate limiting, check if we should force it
if (pendingWidth != 0 && pendingHeight != 0 &&
currentTime - lastReloadTime >= maxPendingTime) {
- printf("Forcing delayed font reload after %ums\n", maxPendingTime);
+ if (is_debug_mode()) {
+ printf("Forcing delayed font reload after %ums\n", maxPendingTime);
+ }
// Recursive call will now pass the time check
return reload_fonts_if_needed(fonts, view, dpi);
}
- printf("Font reload rate limited, pending: %dx%d\n", pendingWidth, pendingHeight);
+ // Silently rate limit font reloads
}
} else if (pendingWidth != 0 && pendingHeight != 0 &&
currentTime - lastReloadTime >= minReloadInterval) {
@@ -122,6 +138,58 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) {
return reload_fonts_if_needed(fonts, view, dpi);
}
+ // If no reload needed and no pending changes, clear stale state
+ if (widthDiff == 0 && heightDiff == 0 && pendingWidth == 0 && pendingHeight == 0) {
+ // Window size is stable, no action needed
+ }
+
+ return 1;
+}
+
+// Force font reload (for fullscreen transitions and other critical updates)
+b32 force_font_reload(FontSet *fonts, ViewState *view, f32 dpi) {
+ if (!fonts || !view) {
+ printf("Error: Invalid parameters for forced font reload\n");
+ return 0;
+ }
+
+ if (is_debug_mode()) {
+ printf("Force reloading fonts for window size: %dx%d\n",
+ view->windowWidth, view->windowHeight);
+ }
+
+ // Clear text cache first
+ clear_text_cache();
+
+ // Store current fonts as backup
+ FontSet backup_fonts = *fonts;
+
+ // Try to reload with new size
+ if (!load_adaptive_fonts(fonts, view->windowWidth, view->windowHeight, dpi)) {
+ printf("Error: Failed to force reload fonts, restoring backup\n");
+
+ // Restore backup fonts
+ *fonts = backup_fonts;
+ return 0;
+ }
+
+ // Free backup fonts if new loading succeeded
+ if (backup_fonts.regular != fonts->regular && backup_fonts.regular) {
+ TTF_CloseFont(backup_fonts.regular);
+ }
+ if (backup_fonts.title != fonts->title && backup_fonts.title) {
+ TTF_CloseFont(backup_fonts.title);
+ }
+ if (backup_fonts.label != fonts->label && backup_fonts.label) {
+ TTF_CloseFont(backup_fonts.label);
+ }
+ if (backup_fonts.axis != fonts->axis && backup_fonts.axis) {
+ TTF_CloseFont(backup_fonts.axis);
+ }
+ if (backup_fonts.label_bold != fonts->label_bold && backup_fonts.label_bold) {
+ TTF_CloseFont(backup_fonts.label_bold);
+ }
+
return 1;
}
@@ -253,13 +321,14 @@ int main(int argc, char* argv[]) {
}
// Render everything
- render(renderer, fonts.regular, fonts.title, fonts.label, &view);
+ render(renderer, &fonts, &view);
}
// Stop text input
SDL_StopTextInput(window);
// Clean up resources
+ free_tight_clusters(&view);
cleanup_glass_data();
free_fonts(&fonts);
clear_text_cache();
diff --git a/src/glamac/glamac_events.c b/src/glamac/glamac_events.c
index 5966efa..8adefa5 100644
--- a/src/glamac/glamac_events.c
+++ b/src/glamac/glamac_events.c
@@ -28,6 +28,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
// If Shift is held (for '?' on most layouts) or it's a direct '?' key
if ((key->mod & SDL_KMOD_SHIFT) || key->key == SDLK_QUESTION) {
view->showHelp = !view->showHelp;
+ mark_view_dirty(view);
}
view->gKeyPressed = 0;
return 1;
@@ -38,8 +39,10 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
// ESC closes glass selection first, then help window, finally quits
if (view->selectedGlass >= 0) {
view->selectedGlass = -1; // Clear selection
+ mark_view_dirty(view);
} else if (view->showHelp) {
view->showHelp = 0;
+ mark_view_dirty(view);
} else {
*quit = 1;
}
@@ -49,6 +52,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
case SDLK_Q:
if (view->showHelp) {
view->showHelp = 0;
+ mark_view_dirty(view);
} else {
*quit = 1;
}
@@ -61,6 +65,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
// Zoom in at center
view->zoomLevel *= ZOOM_FACTOR;
if (view->zoomLevel > MAX_ZOOM) view->zoomLevel = MAX_ZOOM;
+ mark_view_dirty(view);
return 1;
case SDLK_MINUS:
@@ -68,6 +73,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
view->zoomLevel /= ZOOM_FACTOR;
if (view->zoomLevel < MIN_ZOOM) view->zoomLevel = MIN_ZOOM;
view->gKeyPressed = 0; // Reset g key state
+ mark_view_dirty(view);
return 1;
case SDLK_R:
@@ -78,8 +84,8 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
case SDLK_F:
// Toggle fullscreen
- toggle_fullscreen(window);
- view->gKeyPressed = 0; // Reset g key state
+ toggle_fullscreen(window, view);
+ view->gKeyPressed = 0; // Reset g key state
return 1;
// Vim-like navigation
@@ -95,6 +101,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
} else {
// Move left
view->offsetX += PAN_STEP;
+ mark_view_dirty(view);
}
view->gKeyPressed = 0; // Reset g key state
return 1;
@@ -110,6 +117,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
} else {
// Move right
view->offsetX -= PAN_STEP;
+ mark_view_dirty(view);
}
view->gKeyPressed = 0; // Reset g key state
return 1;
@@ -118,12 +126,14 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
// Move up
view->offsetY -= PAN_STEP;
view->gKeyPressed = 0; // Reset g key state
+ mark_view_dirty(view);
return 1;
case SDLK_J:
// Move down
view->offsetY += PAN_STEP;
view->gKeyPressed = 0; // Reset g key state
+ mark_view_dirty(view);
return 1;
}
@@ -145,9 +155,11 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las
if (nearestGlass >= 0) {
// Found a glass within click tolerance - select it
view->selectedGlass = nearestGlass;
+ mark_view_dirty(view);
} else {
// No glass clicked - clear selection
view->selectedGlass = -1;
+ mark_view_dirty(view);
}
if (nearestGlass < 0) {
@@ -214,6 +226,9 @@ b32 process_mouse_motion(SDL_MouseMotionEvent *motion __attribute__((unused)), V
view->offsetX += dx;
view->offsetY -= dy;
+ // Mark view as dirty for re-rendering
+ mark_view_dirty(view);
+
// Update last mouse position
*lastMouseX = (i32)mouseX;
*lastMouseY = (i32)mouseY;
@@ -319,6 +334,7 @@ b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window,
view->windowWidth = newWidth;
view->windowHeight = newHeight;
+ mark_view_dirty(view);
return 1;
}
}
diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c
index 1fdaf30..9743e7f 100644
--- a/src/glamac/glamac_render.c
+++ b/src/glamac/glamac_render.c
@@ -16,7 +16,7 @@
#define MIN_FONT_SIZE 8
#define MAX_FONT_SIZE 72
#define DEFAULT_FONT_SIZE 16
-#define AXIS_FONT_SIZE 12
+#define AXIS_FONT_SIZE 8
#define TITLE_FONT_SIZE 20
#define LABEL_FONT_SIZE 11
@@ -105,12 +105,18 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons
return NULL;
}
- // Validate text length
- size_t text_len = strlen(text);
+ // Security: Validate text length and ensure null termination
+ size_t text_len = strnlen(text, MAX_TEXT_LENGTH);
if (text_len == 0 || text_len >= MAX_TEXT_LENGTH) {
return NULL;
}
+ // Security: Ensure text is properly null-terminated
+ if (text[text_len] != '\0') {
+ printf("Warning: Cached text not properly null-terminated\n");
+ return NULL;
+ }
+
cache_access_counter++;
// Search for existing cache entry
@@ -154,9 +160,11 @@ static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, cons
evict_cache_entry(cache_index);
}
- // Store in cache
- strncpy(textCache[cache_index].text, text, sizeof(textCache[cache_index].text) - 1);
- textCache[cache_index].text[sizeof(textCache[cache_index].text) - 1] = '\0';
+ // Store in cache with safe string copying
+ size_t max_copy = sizeof(textCache[cache_index].text) - 1;
+ size_t copy_len = (text_len < max_copy) ? text_len : max_copy;
+ memcpy(textCache[cache_index].text, text, copy_len);
+ textCache[cache_index].text[copy_len] = '\0';
textCache[cache_index].texture = texture;
textCache[cache_index].width = surface->w;
textCache[cache_index].height = surface->h;
@@ -199,7 +207,24 @@ void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x,
// Draw text directly without caching (for axes labels)
void draw_text_direct(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x, i32 y, SDL_Color color) {
- SDL_Surface* surface = TTF_RenderText_Blended(font, text, strlen(text), color);
+ // Security: Validate input parameters
+ if (!renderer || !font || !text) {
+ return;
+ }
+
+ // Security: Validate text length and content
+ size_t text_len = strnlen(text, 1024); // Max text length check
+ if (text_len == 0 || text_len >= 1024) {
+ return; // Reject empty or overly long text
+ }
+
+ // Security: Ensure text is properly null-terminated
+ if (text[text_len] != '\0') {
+ printf("Warning: Text not properly null-terminated, skipping render\n");
+ return;
+ }
+
+ SDL_Surface* surface = TTF_RenderText_Blended(font, text, (int)text_len, color);
if (surface) {
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
if (texture) {
@@ -211,13 +236,22 @@ void draw_text_direct(SDL_Renderer *renderer, TTF_Font *font, const char *text,
}
}
-// Draw filled circle
+// Draw filled circle using optimized scan-line algorithm
void draw_filled_circle(SDL_Renderer *renderer, i32 centerX, i32 centerY, i32 radius) {
+ if (radius <= 0) return;
+
+ // Use scan-line algorithm for filled circle - much faster than pixel-by-pixel
+ i32 radiusSquared = radius * radius;
+
for (i32 y = -radius; y <= radius; y++) {
- for (i32 x = -radius; x <= radius; x++) {
- if (x*x + y*y <= radius*radius) {
- SDL_RenderPoint(renderer, centerX + x, centerY + y);
- }
+ i32 ySquared = y * y;
+ i32 halfWidth = (i32)(sqrtf((f32)(radiusSquared - ySquared)) + 0.5f);
+
+ if (halfWidth > 0) {
+ // Draw horizontal line from -halfWidth to +halfWidth
+ SDL_RenderLine(renderer,
+ centerX - halfWidth, centerY + y,
+ centerX + halfWidth, centerY + y);
}
}
}
@@ -261,31 +295,59 @@ static TTF_Font* load_font_safe(const char* const* font_paths, int size) {
continue;
}
- // Check for directory traversal attempts
- if (strstr(path, "..") || strstr(path, "//")) {
- printf("Warning: Skipping potentially unsafe font path: %s\n", path);
+ // Enhanced security: Check for directory traversal and other unsafe patterns
+ if (strstr(path, "..") || strstr(path, "//") ||
+ strstr(path, "%2E%2E") || // URL-encoded ..
+ strstr(path, "%2F%2F") || // URL-encoded //
+ strstr(path, "\\\\") || // Windows double backslash
+ strstr(path, "\\.\\")) { // Windows .\ pattern
+ if (is_debug_mode()) {
+ printf("Warning: Skipping potentially unsafe font path: %s\n", path);
+ }
+ continue;
+ }
+
+ // Additional validation: ensure path is within safe directories
+ b32 safe_path = 0;
+ if (strncmp(path, "/usr/share/fonts/", 17) == 0 ||
+ strncmp(path, "/System/Library/Fonts/", 22) == 0 ||
+ strncmp(path, "C:\\Windows\\Fonts\\", 17) == 0 ||
+ strncmp(path, "fonts/", 6) == 0 ||
+ (strlen(path) < 50 && strchr(path, '/') == NULL)) { // Simple filename only
+ safe_path = 1;
+ }
+
+ if (!safe_path) {
+ if (is_debug_mode()) {
+ printf("Warning: Font path not in safe directory list: %s\n", path);
+ }
continue;
}
TTF_Font* font = TTF_OpenFont(path, size);
if (font) {
- printf("Successfully loaded font: %s (size %d)\n", path, size);
+ if (is_debug_mode()) {
+ printf("Successfully loaded font: %s (size %d)\n", path, size);
+ }
return font;
}
}
- printf("Warning: Failed to load any font from provided paths\n");
+ if (is_debug_mode()) {
+ printf("Warning: Failed to load any font from provided paths\n");
+ }
return NULL;
}
// Draw axes
-void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
- // Create smaller font for axis scales with safe loading
- TTF_Font *axisFont = load_font_safe(SAFE_FONT_PATHS, 12);
- if (!axisFont) {
- axisFont = font; // Fallback to regular font if loading fails
- printf("Warning: Using fallback font for axes\n");
+void draw_axes(SDL_Renderer *renderer, const FontSet *fonts, const ViewState* view) {
+ if (!fonts || !fonts->axis || !fonts->title) {
+ printf("Error: Invalid fonts for draw_axes\n");
+ return;
}
+
+ // Use preloaded axis font (no runtime font loading)
+ TTF_Font *axisFont = fonts->axis;
const i32 padding = get_adaptive_padding(view);
const i32 plotWidth = view->windowWidth - 2 * padding;
const i32 plotHeight = view->windowHeight - 2 * padding;
@@ -349,46 +411,83 @@ void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, cons
} else {
snprintf(buffer, sizeof(buffer), "%.3f", riValue);
}
- draw_text_direct(renderer, axisFont, buffer, padding - 45, y - 8, black);
+ // Move the bottom-most number up slightly to avoid overlap with X-axis labels
+ i32 yOffset = (i == 0) ? -15 : -8;
+ // Adaptive positioning based on window size
+ i32 axisLabelOffset = (view->windowWidth < 600) ? padding - 35 : padding - 45;
+ draw_text_direct(renderer, axisFont, buffer, axisLabelOffset, y + yOffset, black);
}
- // Axis titles with subscripts (approximated)
- draw_text(renderer, titleFont, "Abbe Number (Vd)", view->windowWidth/2 - 80, view->windowHeight - 25, black);
+ // Axis titles with subscripts - using regular font (2pt smaller than title)
+ // Adaptive positioning based on window size
+ i32 abbeNumberY = (view->windowHeight < 500) ? view->windowHeight - 25 : view->windowHeight - 35;
+ draw_text(renderer, fonts->regular, "Abbe Number (Vd)", view->windowWidth/2 - 80, abbeNumberY, black);
// Vertical text for Y-axis (simplified)
- draw_text(renderer, titleFont, "Refractive Index (nd)", 5, 10, black);
+ draw_text(renderer, fonts->regular, "Refractive Index (nd)", 5, 10, black);
// Display current catalog name at top center
const char* catalogName = get_current_catalog_name();
char catalogTitle[100];
snprintf(catalogTitle, sizeof(catalogTitle), "Catalog: %s", catalogName);
int titleWidth = strlen(catalogTitle) * 8; // Approximate width
- draw_text(renderer, titleFont, catalogTitle, (view->windowWidth - titleWidth) / 2, 10, black);
+ draw_text(renderer, fonts->title, catalogTitle, (view->windowWidth - titleWidth) / 2, 10, black);
- // Clean up axis font if we created it
- if (axisFont != font) {
- TTF_CloseFont(axisFont);
- }
+ // No font cleanup needed - fonts are managed externally
}
-// Draw glass points and labels - simplified version showing all labels
-void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view) {
+// Draw glass points and labels with viewport culling and zoom-based visibility
+void draw_glass_points(SDL_Renderer *renderer, const FontSet *fonts, const ViewState* view) {
+ if (!fonts || !fonts->label || !fonts->label_bold) {
+ printf("Error: Invalid fonts for draw_glass_points\n");
+ return;
+ }
+
+ // Use preloaded fonts (no runtime font loading)
+ TTF_Font *regularFont = fonts->label;
+ TTF_Font *boldFontSameSize = fonts->label_bold;
const u32 glassCount = get_glass_count();
SDL_Color selectedColor = {255, 0, 0, 255}; // Red for selected
SDL_Color normalColor = {0, 150, 0, 255}; // Green for normal
- SDL_Color labelColor = {0, 0, 0, 255}; // Black for labels
+ SDL_Color labelColor = {0, 0, 0, 255}; // Black for all labels
+
+ // Performance counters for debugging
+ u32 visibleCount = 0;
+ u32 labelCount = 0;
+ const b32 showLabels = (view->zoomLevel >= LABEL_VISIBILITY_THRESHOLD);
+
+ // Calculate visible range once for efficient viewport culling
+ f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI;
+ get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI);
+ // Add some margin for better culling (in data space)
+ const f32 abbeRange = visibleMaxAbbe - visibleMinAbbe;
+ const f32 riRange = visibleMaxRI - visibleMinRI;
+ const f32 abbeMargin = abbeRange * 0.1f; // 10% margin
+ const f32 riMargin = riRange * 0.1f;
+ visibleMinAbbe -= abbeMargin;
+ visibleMaxAbbe += abbeMargin;
+ visibleMinRI -= riMargin;
+ visibleMaxRI += riMargin;
- // Draw all glass points and labels
+ // Draw all visible glass points and labels
for (u32 i = 0; i < glassCount; i++) {
const Glass* glass = get_glass(i);
if (!glass) continue;
+ // Optimized viewport culling - data space bounds check (much faster)
+ if (!is_glass_visible_fast(glass->abbeNumber, glass->refractiveIndex,
+ visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI)) {
+ continue;
+ }
+
+ visibleCount++;
+
i32 x, y;
data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &x, &y);
- // Check if point is within the plot area (respecting axes padding)
+ // Additional padding check for plot area
const i32 padding = get_adaptive_padding(view);
if (x < padding || x >= view->windowWidth - padding ||
y < padding || y >= view->windowHeight - padding) {
@@ -405,9 +504,43 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt
// Draw glass point (larger)
draw_filled_circle(renderer, x, y, 5);
- // Show all glass labels (clustering removed)
- const char* glassName = (const char*)glass->name;
- draw_text(renderer, labelFont, glassName, x + 12, y - 12, labelColor);
+ // Show labels with tight clustering logic
+ if (showLabels && should_show_glass_label_tight(i, view)) {
+ labelCount++;
+ const char* glassName = (const char*)glass->name;
+
+ // Font selection: Bold for cluster representatives, Regular for single glasses (same size)
+ i32 clusterIndex = find_tight_cluster_for_glass(i, view);
+ TTF_Font* fontToUse;
+
+ if (clusterIndex >= 0) {
+ // Glass is in a cluster - use bold font (same size as regular)
+ const TightCluster* cluster = &view->tightClusters[clusterIndex];
+ if ((u32)i == cluster->representativeIndex) {
+ // This is a cluster representative - use bold font
+ fontToUse = boldFontSameSize;
+ } else {
+ // This shouldn't happen since non-representatives shouldn't show labels
+ fontToUse = regularFont;
+ }
+ } else {
+ // Single glass (not in any cluster) - use regular font (same size as bold)
+ fontToUse = regularFont;
+ }
+
+ draw_text(renderer, fontToUse, glassName, x + LABEL_OFFSET_X, y + LABEL_OFFSET_Y, labelColor);
+ }
+ }
+
+ // Debug output if enabled
+ if (is_debug_mode()) {
+ static u32 lastReportTime = 0;
+ u32 currentTime = SDL_GetTicks();
+ if (currentTime - lastReportTime > 1000) { // Report every second
+ printf("Rendering: %u/%u glasses visible, %u labels drawn (zoom: %.2f)\n",
+ visibleCount, glassCount, labelCount, view->zoomLevel);
+ lastReportTime = currentTime;
+ }
}
}
@@ -434,26 +567,53 @@ void calculate_smart_window_position(i32 glassX, i32 glassY, i32 windowWidth, i3
}
-// Draw glass properties window
-void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
+// Draw glass properties window with tight cluster support
+void draw_glass_properties(SDL_Renderer *renderer, const FontSet *fonts, const ViewState* view) {
+ if (!fonts || !fonts->regular || !fonts->title) {
+ printf("Error: Invalid fonts for draw_glass_properties\n");
+ return;
+ }
if (view->selectedGlass < 0) return;
- const Glass* glass = get_glass(view->selectedGlass);
- if (!glass) return;
+ const Glass* selectedGlass = get_glass(view->selectedGlass);
+ if (!selectedGlass) return;
+
+ // Check if selected glass is part of a tight cluster
+ i32 clusterIndex = find_tight_cluster_for_glass(view->selectedGlass, view);
// Calculate glass position on screen
i32 glassX, glassY;
- data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY);
+ data_to_screen_coords(selectedGlass->abbeNumber, selectedGlass->refractiveIndex, view, &glassX, &glassY);
+
+ // Compact window sizing - just slightly larger than content
+ const i32 windowWidth = 220; // Fixed compact width (slightly larger)
- // Window properties
- const i32 windowWidth = 300;
- const i32 windowHeight = 120;
+ // Minimal line heights and spacing for label font size
+ const i32 lineHeight = 15;
+ const i32 glassSpacing = 17;
+ const i32 basePadding = 12;
+
+ i32 windowHeight;
+ u32 glassesToShow;
+
+ if (clusterIndex >= 0) {
+ // Show all glasses in cluster - minimal padding
+ const TightCluster* cluster = &view->tightClusters[clusterIndex];
+ glassesToShow = cluster->count;
+ // Each glass: title + 2 detail lines + small gap
+ windowHeight = basePadding + (glassesToShow * (glassSpacing + 2 * lineHeight + 8)) + basePadding;
+ } else {
+ // Show single glass - minimal padding
+ glassesToShow = 1;
+ // Title + 3 detail lines
+ windowHeight = basePadding + glassSpacing + 3 * lineHeight + basePadding;
+ }
i32 windowX, windowY;
calculate_smart_window_position(glassX, glassY, windowWidth, windowHeight, view, &windowX, &windowY);
// Draw background
- SDL_SetRenderDrawColor(renderer, 255, 255, 255, 200);
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 220);
SDL_FRect bgRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight};
SDL_RenderFillRect(renderer, &bgRect);
@@ -461,25 +621,65 @@ void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *tit
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
SDL_RenderRect(renderer, &bgRect);
- // Draw text
+ // Draw content
SDL_Color black = {0, 0, 0, 255};
char buffer[256];
- snprintf(buffer, sizeof(buffer), "%s", (const char*)glass->name);
- draw_text(renderer, titleFont, buffer, windowX + 10, windowY + 10, black);
-
- snprintf(buffer, sizeof(buffer), "nd: %.5f", glass->refractiveIndex);
- draw_text(renderer, font, buffer, windowX + 10, windowY + 40, black);
-
- snprintf(buffer, sizeof(buffer), "vd: %.2f", glass->abbeNumber);
- draw_text(renderer, font, buffer, windowX + 10, windowY + 60, black);
+ i32 yPos = windowY + 10;
- snprintf(buffer, sizeof(buffer), "Manufacturer: %s", (const char*)glass->manufacturer);
- draw_text(renderer, font, buffer, windowX + 10, windowY + 80, black);
+ if (clusterIndex >= 0) {
+ // Clustered glass - show all glasses in cluster without cluster text
+ const TightCluster* cluster = &view->tightClusters[clusterIndex];
+
+ // Show all glasses in cluster
+ for (u32 i = 0; i < cluster->count; i++) {
+ const Glass* glass = get_glass(cluster->glassIndices[i]);
+ if (!glass) continue;
+
+ // Use same color for all glasses
+ SDL_Color textColor = black;
+ // Use bold font for representative glass (shortest name) - but smaller size
+ TTF_Font* fontToUse = (cluster->glassIndices[i] == cluster->representativeIndex) ? fonts->label_bold : fonts->label;
+
+ // Show glass name as title
+ snprintf(buffer, sizeof(buffer), "%s", (const char*)glass->name);
+ draw_text(renderer, fontToUse, buffer, windowX + 10, yPos, textColor);
+ yPos += glassSpacing;
+
+ // Show details
+ snprintf(buffer, sizeof(buffer), "nd: %.5f, vd: %.2f", glass->refractiveIndex, glass->abbeNumber);
+ draw_text(renderer, fonts->label, buffer, windowX + 10, yPos, textColor);
+ yPos += lineHeight;
+
+ snprintf(buffer, sizeof(buffer), "Manufacturer: %s", (const char*)glass->manufacturer);
+ draw_text(renderer, fonts->label, buffer, windowX + 10, yPos, textColor);
+ yPos += glassSpacing; // Extra space between glasses
+ }
+ } else {
+ // Single glass - traditional display with scaled spacing
+ snprintf(buffer, sizeof(buffer), "%s", (const char*)selectedGlass->name);
+ draw_text(renderer, fonts->label_bold, buffer, windowX + 10, yPos, black);
+ yPos += glassSpacing;
+
+ snprintf(buffer, sizeof(buffer), "nd: %.5f", selectedGlass->refractiveIndex);
+ draw_text(renderer, fonts->label, buffer, windowX + 10, yPos, black);
+ yPos += lineHeight;
+
+ snprintf(buffer, sizeof(buffer), "vd: %.2f", selectedGlass->abbeNumber);
+ draw_text(renderer, fonts->label, buffer, windowX + 10, yPos, black);
+ yPos += lineHeight;
+
+ snprintf(buffer, sizeof(buffer), "Manufacturer: %s", (const char*)selectedGlass->manufacturer);
+ draw_text(renderer, fonts->label, buffer, windowX + 10, yPos, black);
+ }
}
// Draw help window
-void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
+void draw_help_window(SDL_Renderer *renderer, const FontSet *fonts, const ViewState* view) {
+ if (!fonts || !fonts->regular || !fonts->title) {
+ printf("Error: Invalid fonts for draw_help_window\n");
+ return;
+ }
if (!view->showHelp) return;
const i32 windowWidth = 400;
@@ -500,34 +700,43 @@ void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFon
SDL_Color black = {0, 0, 0, 255};
i32 yPos = windowY + 20;
- draw_text(renderer, titleFont, "GlaMaC Help", windowX + 20, yPos, black);
+ draw_text(renderer, fonts->title, "GlaMaC Help", windowX + 20, yPos, black);
yPos += 40;
- draw_text(renderer, font, "Mouse Controls:", windowX + 20, yPos, black);
+ draw_text(renderer, fonts->regular, "Mouse Controls:", windowX + 20, yPos, black);
yPos += 25;
- draw_text(renderer, font, " Click: Select glass", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " Click: Select glass", windowX + 30, yPos, black);
yPos += 20;
- draw_text(renderer, font, " Drag: Pan view", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " Drag: Pan view", windowX + 30, yPos, black);
yPos += 20;
- draw_text(renderer, font, " Wheel: Zoom in/out", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " Wheel: Zoom in/out", windowX + 30, yPos, black);
yPos += 30;
- draw_text(renderer, font, "Keyboard Controls:", windowX + 20, yPos, black);
+ draw_text(renderer, fonts->regular, "Keyboard Controls:", windowX + 20, yPos, black);
yPos += 25;
- draw_text(renderer, font, " g?: Show/hide this help", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " g?: Show/hide this help", windowX + 30, yPos, black);
yPos += 20;
- draw_text(renderer, font, " r: Reset view", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " r: Reset view", windowX + 30, yPos, black);
yPos += 20;
- draw_text(renderer, font, " ESC: Clear selection or quit", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " ESC: Clear selection or quit", windowX + 30, yPos, black);
yPos += 20;
- draw_text(renderer, font, " q: Quit", windowX + 30, yPos, black);
+ draw_text(renderer, fonts->regular, " q: Quit", windowX + 30, yPos, black);
yPos += 30;
- draw_text(renderer, font, "Press ESC or g? to close", windowX + 20, yPos, black);
+ draw_text(renderer, fonts->regular, "Press ESC or g? to close", windowX + 20, yPos, black);
}
// Main render function
-void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, ViewState* view) {
+void render(SDL_Renderer *renderer, const FontSet *fonts, ViewState* view) {
+ if (!fonts) {
+ printf("Error: Invalid fonts for render\n");
+ return;
+ }
+ // Only render if view is dirty (needs update)
+ if (!view->viewDirty) {
+ return;
+ }
+
// Clear screen
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
@@ -536,20 +745,22 @@ void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Fon
draw_grid(renderer, view);
// Draw axes
- draw_axes(renderer, font, titleFont, view);
+ draw_axes(renderer, fonts, view);
// Draw glass points and labels
- draw_glass_points(renderer, labelFont, view);
+ draw_glass_points(renderer, fonts, view);
// Draw glass properties if selected
- draw_glass_properties(renderer, font, titleFont, view);
-
+ draw_glass_properties(renderer, fonts, view);
// Draw help window if needed
- draw_help_window(renderer, font, titleFont, view);
+ draw_help_window(renderer, fonts, view);
// Present the rendered frame
SDL_RenderPresent(renderer);
+
+ // Mark view as clean after rendering
+ view->viewDirty = 0;
}
// Font management functions
@@ -560,10 +771,24 @@ b32 load_fonts(FontSet *fonts) {
fonts->regular = NULL;
fonts->title = NULL;
fonts->label = NULL;
+ fonts->axis = NULL;
+ fonts->label_bold = NULL;
fonts->regular = load_font_safe(SAFE_FONT_PATHS, 16);
fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, 20);
fonts->label = load_font_safe(SAFE_FONT_PATHS, 11);
+ fonts->axis = load_font_safe(SAFE_FONT_PATHS, 8);
+ fonts->label_bold = load_font_safe(SAFE_BOLD_FONT_PATHS, 12);
+
+ // Debug output to check font loading
+ if (is_debug_mode()) {
+ printf("Font loading results:\n");
+ printf(" Regular font: %s\n", fonts->regular ? "loaded" : "failed");
+ printf(" Title/Bold font: %s\n", fonts->title ? "loaded" : "failed");
+ printf(" Label font: %s\n", fonts->label ? "loaded" : "failed");
+ printf(" Axis font: %s\n", fonts->axis ? "loaded" : "failed");
+ printf(" Label bold font: %s\n", fonts->label_bold ? "loaded" : "failed");
+ }
// Check if at least one font loaded successfully
if (!fonts->regular && !fonts->title && !fonts->label) {
@@ -574,18 +799,41 @@ b32 load_fonts(FontSet *fonts) {
// Use fallbacks if specific fonts failed
if (!fonts->title && fonts->regular) {
fonts->title = fonts->regular;
- printf("Warning: Using regular font as title font fallback\n");
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as title font fallback - bold/regular distinction will be lost!\n");
+ }
}
if (!fonts->label && fonts->regular) {
fonts->label = fonts->regular;
- printf("Warning: Using regular font as label font fallback\n");
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as label font fallback\n");
+ }
}
if (!fonts->regular && fonts->title) {
fonts->regular = fonts->title;
- printf("Warning: Using title font as regular font fallback\n");
+ if (is_debug_mode()) {
+ printf("Warning: Using title font as regular font fallback\n");
+ }
+ }
+ if (!fonts->axis && fonts->regular) {
+ fonts->axis = fonts->regular;
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as axis font fallback\n");
+ }
+ }
+ if (!fonts->label_bold && fonts->title) {
+ fonts->label_bold = fonts->title;
+ if (is_debug_mode()) {
+ printf("Warning: Using title font as label bold font fallback\n");
+ }
+ } else if (!fonts->label_bold && fonts->label) {
+ fonts->label_bold = fonts->label;
+ if (is_debug_mode()) {
+ printf("Warning: Using label font as label bold font fallback\n");
+ }
}
- return (fonts->regular && fonts->title && fonts->label) ? 1 : 0;
+ return (fonts->regular && fonts->title && fonts->label && fonts->axis && fonts->label_bold) ? 1 : 0;
}
b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) {
@@ -605,23 +853,44 @@ b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 d
i32 titleSize = (i32)(20.0f * dpi / 96.0f * windowWidth / 800.0f);
// Reduce label size for better overview visibility (was 14.0f)
i32 labelSize = (i32)(11.0f * dpi / 96.0f * windowWidth / 800.0f);
+ i32 axisSize = (i32)(8.0f * dpi / 96.0f * windowWidth / 800.0f);
+ i32 labelBoldSize = labelSize; // Same size as regular labels for consistency
- // Clamp font sizes to safe ranges
- if (regularSize < 12) regularSize = 12;
+ // Clamp font sizes to safe ranges - allow smaller sizes for very small windows
+ if (regularSize < 10) regularSize = 10;
if (regularSize > 32) regularSize = 32;
- if (titleSize < 16) titleSize = 16;
+ if (titleSize < 12) titleSize = 12;
if (titleSize > 36) titleSize = 36;
- if (labelSize < 8) labelSize = 8;
+ if (labelSize < 6) labelSize = 6;
if (labelSize > 20) labelSize = 20;
+ if (axisSize < 6) axisSize = 6; // Allow smaller axis fonts for small windows
+ if (axisSize > 18) axisSize = 18;
+ if (labelBoldSize < 6) labelBoldSize = 6;
+ if (labelBoldSize > 20) labelBoldSize = 20;
// Initialize to NULL for safe cleanup
fonts->regular = NULL;
fonts->title = NULL;
fonts->label = NULL;
+ fonts->axis = NULL;
+ fonts->label_bold = NULL;
fonts->regular = load_font_safe(SAFE_FONT_PATHS, regularSize);
fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, titleSize);
fonts->label = load_font_safe(SAFE_FONT_PATHS, labelSize);
+ fonts->axis = load_font_safe(SAFE_FONT_PATHS, axisSize);
+ fonts->label_bold = load_font_safe(SAFE_BOLD_FONT_PATHS, labelBoldSize);
+
+ // Debug output to check adaptive font loading
+ if (is_debug_mode()) {
+ printf("Adaptive font loading results (sizes: regular=%d, title=%d, label=%d, axis=%d, label_bold=%d):\n",
+ regularSize, titleSize, labelSize, axisSize, labelBoldSize);
+ printf(" Regular font: %s\n", fonts->regular ? "loaded" : "failed");
+ printf(" Title/Bold font: %s\n", fonts->title ? "loaded" : "failed");
+ printf(" Label font: %s\n", fonts->label ? "loaded" : "failed");
+ printf(" Axis font: %s\n", fonts->axis ? "loaded" : "failed");
+ printf(" Label bold font: %s\n", fonts->label_bold ? "loaded" : "failed");
+ }
// Check if at least one font loaded successfully
if (!fonts->regular && !fonts->title && !fonts->label) {
@@ -629,21 +898,61 @@ b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 d
return 0;
}
- // Use fallbacks if specific fonts failed
+ // Enhanced fallback logic that preserves font distinction when possible
if (!fonts->title && fonts->regular) {
- fonts->title = fonts->regular;
- printf("Warning: Using regular font as title font fallback\n");
+ // Load bold font at different size to maintain some visual distinction
+ fonts->title = load_font_safe(SAFE_BOLD_FONT_PATHS, titleSize + 2);
+ if (!fonts->title) {
+ // If still no bold font, try regular font at larger size for distinction
+ fonts->title = load_font_safe(SAFE_FONT_PATHS, titleSize + 2);
+ if (!fonts->title) {
+ // Last resort: use regular font but warn about loss of distinction
+ fonts->title = fonts->regular;
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as title font fallback - bold/regular distinction will be lost!\n");
+ }
+ } else {
+ if (is_debug_mode()) {
+ printf("Info: Using larger regular font as title font for visual distinction\n");
+ }
+ }
+ } else {
+ if (is_debug_mode()) {
+ printf("Info: Successfully loaded bold font on second attempt\n");
+ }
+ }
}
if (!fonts->label && fonts->regular) {
fonts->label = fonts->regular;
- printf("Warning: Using regular font as label font fallback\n");
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as label font fallback\n");
+ }
}
if (!fonts->regular && fonts->title) {
fonts->regular = fonts->title;
- printf("Warning: Using title font as regular font fallback\n");
+ if (is_debug_mode()) {
+ printf("Warning: Using title font as regular font fallback\n");
+ }
+ }
+ if (!fonts->axis && fonts->regular) {
+ fonts->axis = fonts->regular;
+ if (is_debug_mode()) {
+ printf("Warning: Using regular font as axis font fallback\n");
+ }
+ }
+ if (!fonts->label_bold && fonts->title) {
+ fonts->label_bold = fonts->title;
+ if (is_debug_mode()) {
+ printf("Info: Using title font as label bold font\n");
+ }
+ } else if (!fonts->label_bold && fonts->label) {
+ fonts->label_bold = fonts->label;
+ if (is_debug_mode()) {
+ printf("Warning: Using label font as label bold font fallback - bold/regular distinction will be lost!\n");
+ }
}
- return (fonts->regular && fonts->title && fonts->label) ? 1 : 0;
+ return (fonts->regular && fonts->title && fonts->label && fonts->axis && fonts->label_bold) ? 1 : 0;
}
void free_fonts(FontSet *fonts) {
@@ -659,4 +968,12 @@ void free_fonts(FontSet *fonts) {
TTF_CloseFont(fonts->label);
fonts->label = NULL;
}
+ if (fonts->axis) {
+ TTF_CloseFont(fonts->axis);
+ fonts->axis = NULL;
+ }
+ if (fonts->label_bold) {
+ TTF_CloseFont(fonts->label_bold);
+ fonts->label_bold = NULL;
+ }
} \ No newline at end of file
diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c
index 244523e..35c5806 100644
--- a/src/glamac/glamac_view.c
+++ b/src/glamac/glamac_view.c
@@ -24,15 +24,30 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) {
view->gKeyPressed = 0;
view->gKeyTime = 0;
view->selectedGlass = -1; // No glass selected initially
+ view->viewDirty = 1; // Initial render needed
+
+ // Initialize tight clustering data
+ view->tightClusters = NULL;
+ view->tightClusterCount = 0;
+ view->glassToCluster = NULL;
+ view->clusteringGlassCount = 0;
// Calculate data range
find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI);
+
+ // Create tight clusters for the initial catalog
+ create_tight_clusters(view);
}
// Refresh view state data range when catalog changes
void refresh_view_data_range(ViewState* view) {
find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI);
+ // Recreate tight clusters for new catalog
+ free_tight_clusters(view);
+ create_tight_clusters(view);
+
+ mark_view_dirty(view);
}
// Calculate visible data range
@@ -97,6 +112,9 @@ void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view
// If zoom didn't change, don't recalculate
if (oldZoom == view->zoomLevel) return;
+ // Mark view as dirty for re-rendering
+ mark_view_dirty(view);
+
// Calculate where the mouse point would be after zoom
i32 newMouseScreenX, newMouseScreenY;
data_to_screen_coords(mouseDataX, mouseDataY, view, &newMouseScreenX, &newMouseScreenY);
@@ -108,15 +126,43 @@ void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view
}
-// Toggle fullscreen
-void toggle_fullscreen(SDL_Window* window) {
+// Toggle fullscreen with proper state synchronization
+void toggle_fullscreen(SDL_Window* window, ViewState* view) {
+ if (!window || !view) return;
+
bool isFullscreen = SDL_GetWindowFlags(window) & SDL_WINDOW_FULLSCREEN;
if (!isFullscreen) {
+ // Entering fullscreen
SDL_SetWindowFullscreen(window, true);
} else {
+ // Exiting fullscreen
SDL_SetWindowFullscreen(window, false);
}
+
+ // Wait a bit longer for the window system to process the state change
+ SDL_Delay(100);
+
+ // Get updated window dimensions after fullscreen toggle
+ i32 newWidth, newHeight;
+ SDL_GetWindowSize(window, &newWidth, &newHeight);
+
+ // Validate new dimensions
+ if (newWidth > 0 && newHeight > 0) {
+ view->windowWidth = newWidth;
+ view->windowHeight = newHeight;
+
+ // Force a complete refresh after fullscreen toggle
+ mark_view_dirty(view);
+
+ if (is_debug_mode()) {
+ printf("Fullscreen toggled: %s, new size: %dx%d\n",
+ isFullscreen ? "OFF" : "ON", newWidth, newHeight);
+ }
+ } else {
+ printf("Warning: Invalid window dimensions after fullscreen toggle: %dx%d\n",
+ newWidth, newHeight);
+ }
}
// Reset view to default
@@ -126,9 +172,240 @@ void reset_view(ViewState* view) {
view->offsetY = 0.0f;
view->selectedGlass = -1;
+ // Force complete refresh - this ensures fonts and layout are recalculated
+ mark_view_dirty(view);
+}
+
+// Helper function to calculate string length for name comparison
+static size_t safe_glass_name_length(const char* name) {
+ if (!name) return SIZE_MAX; // Invalid name gets maximum length
+ return strnlen(name, 50); // Glass names are max 50 chars
+}
+
+// Free tight clustering data
+void free_tight_clusters(ViewState* view) {
+ if (!view) return;
+
+ if (is_debug_mode()) {
+ printf("Freeing tight clusters: %u clusters, %u glass count\n",
+ view->tightClusterCount, view->clusteringGlassCount);
+ }
+
+ // Safe cleanup - use free() directly since data was allocated with safe_malloc/safe_calloc
+ if (view->tightClusters) {
+ free(view->tightClusters);
+ view->tightClusters = NULL;
+ }
+
+ if (view->glassToCluster) {
+ free(view->glassToCluster);
+ view->glassToCluster = NULL;
+ }
+
+ // Reset counters
+ view->tightClusterCount = 0;
+ view->clusteringGlassCount = 0;
+}
+
+// Check if two glasses are close enough for tight clustering
+static b32 glasses_are_close(const Glass* glass1, const Glass* glass2) {
+ if (!glass1 || !glass2) return 0;
+
+ f32 ndDiff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex);
+ f32 vdDiff = fabsf(glass1->abbeNumber - glass2->abbeNumber);
+
+ return (ndDiff <= TIGHT_CLUSTER_ND_THRESHOLD && vdDiff <= TIGHT_CLUSTER_VD_THRESHOLD);
+}
+
+
+// Find the glass with the shortest name in a cluster
+static u32 find_representative_glass(const u32* glassIndices, u32 count) {
+ if (count == 0) return 0;
+
+ u32 representative = glassIndices[0];
+ size_t shortestLength = SIZE_MAX;
+
+ for (u32 i = 0; i < count; i++) {
+ const Glass* glass = get_glass(glassIndices[i]);
+ if (!glass) continue;
+
+ size_t nameLength = safe_glass_name_length((const char*)glass->name);
+ if (nameLength < shortestLength) {
+ shortestLength = nameLength;
+ representative = glassIndices[i];
+ }
+ }
+
+ return representative;
+}
+
+// Create tight clusters for glasses with similar optical properties
+void create_tight_clusters(ViewState* view) {
+ if (!view) return;
+
+ const u32 glassCount = get_glass_count();
+ if (glassCount == 0) {
+ if (is_debug_mode()) {
+ printf("No glasses available for clustering\n");
+ }
+ return;
+ }
+
+ if (is_debug_mode()) {
+ printf("Starting clustering for %u glasses\n", glassCount);
+ }
+
+ // Free existing clustering data
+ free_tight_clusters(view);
+
+ // Allocate glass-to-cluster mapping using safe allocation
+ view->glassToCluster = new_zero(i32, glassCount);
+ if (!view->glassToCluster) {
+ printf("Error: Failed to allocate glass-to-cluster mapping for %u glasses\n", glassCount);
+ return;
+ }
+
+ // Initialize all glasses as unclustered (-1)
+ for (u32 i = 0; i < glassCount; i++) {
+ view->glassToCluster[i] = -1;
+ }
+
+ // Allocate clusters array - worst case is every glass is its own cluster
+ view->tightClusters = new_zero(TightCluster, glassCount);
+ if (!view->tightClusters) {
+ printf("Error: Failed to allocate tight clusters array\n");
+ free(view->glassToCluster);
+ view->glassToCluster = NULL;
+ return;
+ }
+
+ view->tightClusterCount = 0;
+ view->clusteringGlassCount = glassCount;
+
+ // Create clusters using simple greedy algorithm
+ for (u32 i = 0; i < glassCount; i++) {
+ // Skip if glass is already in a cluster
+ if (view->glassToCluster[i] != -1) continue;
+
+ const Glass* baseGlass = get_glass(i);
+ if (!baseGlass) continue;
+
+ // First, find all similar glasses to this one
+ u32 similarGlasses[MAX_CLUSTER_SIZE];
+ u32 similarCount = 1;
+ similarGlasses[0] = i; // Include the base glass
+
+ for (u32 j = i + 1; j < glassCount && similarCount < MAX_CLUSTER_SIZE; j++) {
+ if (view->glassToCluster[j] != -1) continue; // Already clustered
+
+ const Glass* candidate = get_glass(j);
+ if (!candidate) continue;
+
+ // Check if glasses are similar enough to cluster
+ if (glasses_are_close(baseGlass, candidate)) {
+ similarGlasses[similarCount] = j;
+ similarCount++;
+ }
+ }
+
+ // Only create a cluster if we have MORE than one glass (i.e., actual clustering)
+ if (similarCount > 1) {
+ // Start new cluster with all similar glasses
+ TightCluster* cluster = &view->tightClusters[view->tightClusterCount];
+ cluster->count = similarCount;
+
+ for (u32 k = 0; k < similarCount; k++) {
+ cluster->glassIndices[k] = similarGlasses[k];
+ view->glassToCluster[similarGlasses[k]] = (i32)view->tightClusterCount;
+ }
+
+ view->tightClusterCount++;
+
+ // Calculate cluster center (average properties)
+ f32 totalNd = 0.0f, totalVd = 0.0f;
+ for (u32 k = 0; k < cluster->count; k++) {
+ const Glass* glass = get_glass(cluster->glassIndices[k]);
+ if (glass) {
+ totalNd += glass->refractiveIndex;
+ totalVd += glass->abbeNumber;
+ }
+ }
+
+ cluster->centerNd = totalNd / cluster->count;
+ cluster->centerVd = totalVd / cluster->count;
+
+ // Find representative glass (shortest name)
+ cluster->representativeIndex = find_representative_glass(cluster->glassIndices, cluster->count);
+ }
+ // If similarCount == 1, leave the glass unclustered (glassToCluster[i] remains -1)
+ }
+
+ // Debug output
+ if (is_debug_mode()) {
+ u32 clusteredGlasses = 0;
+ u32 multiGlassClusters = 0;
+
+ for (u32 i = 0; i < view->tightClusterCount; i++) {
+ clusteredGlasses += view->tightClusters[i].count;
+ if (view->tightClusters[i].count > 1) {
+ multiGlassClusters++;
+ }
+ }
+
+ printf("Created %u clusters (%u multi-glass) containing %u/%u glasses\n",
+ view->tightClusterCount, multiGlassClusters, clusteredGlasses, glassCount);
+ }
}
+// Find the tight cluster index for a given glass (-1 if not clustered)
+i32 find_tight_cluster_for_glass(i32 glassIndex, const ViewState* view) {
+ if (!view || !view->glassToCluster || glassIndex < 0) return -1;
+
+ // Bounds check against clustering glass count
+ if (view->clusteringGlassCount == 0 || (u32)glassIndex >= view->clusteringGlassCount) {
+ return -1;
+ }
+
+ // Additional safety check against current glass count
+ const u32 currentGlassCount = get_glass_count();
+ if ((u32)glassIndex >= currentGlassCount) {
+ return -1;
+ }
+
+ i32 clusterIndex = view->glassToCluster[glassIndex];
+
+ // Validate cluster index
+ if (clusterIndex >= 0 && (u32)clusterIndex >= view->tightClusterCount) {
+ return -1; // Invalid cluster index
+ }
+
+ return clusterIndex;
+}
+// Check if a glass label should be shown (only representatives in tight clusters)
+b32 should_show_glass_label_tight(i32 glassIndex, const ViewState* view) {
+ if (!view || glassIndex < 0) return 0;
+
+ // If clustering is not initialized, show all labels
+ if (!view->tightClusters || !view->glassToCluster || view->tightClusterCount == 0) {
+ return 1;
+ }
+
+ // Bounds check
+ if ((u32)glassIndex >= view->clusteringGlassCount) return 0;
+
+ // Find cluster for this glass
+ i32 clusterIndex = view->glassToCluster[glassIndex];
+ if (clusterIndex < 0 || (u32)clusterIndex >= view->tightClusterCount) {
+ // Glass is not in any cluster, show its label
+ return 1;
+ }
+
+ const TightCluster* cluster = &view->tightClusters[clusterIndex];
+
+ // Only show cluster representatives (shortest name in multi-glass clusters)
+ return ((u32)glassIndex == cluster->representativeIndex);
+}
Back to https://optics-design.com