From 9496ae0a50e6848121c7e913ca2dc55c8e6c84c1 Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 5 Aug 2025 10:11:14 +0200 Subject: rewrote clustering --- include/glamac_render.h | 34 +- include/glamac_view.h | 66 ++- src/glamac/glamac.c | 74 +-- src/glamac/glamac_events.c | 54 +- src/glamac/glamac_render.c | 1246 ++++++++++++-------------------------------- src/glamac/glamac_view.c | 398 ++++++++++++-- src/glamac/glass_data.c | 4 +- src/glautils/fgla.c | 72 ++- test.txt | 302 +++++++++++ 9 files changed, 1162 insertions(+), 1088 deletions(-) create mode 100644 test.txt diff --git a/include/glamac_render.h b/include/glamac_render.h index a07e055..899702d 100644 --- a/include/glamac_render.h +++ b/include/glamac_render.h @@ -16,12 +16,12 @@ void draw_filled_circle(SDL_Renderer *renderer, i32 centerX, i32 centerY, i32 ra // UI element rendering void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view); void draw_grid(SDL_Renderer *renderer, const ViewState* view); -void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view, GlassCluster* clusters, i32 clusterCount); +void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view); void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view); void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view); // Main render function -void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, ViewState* view, GlassCluster* clusters, i32 clusterCount); +void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, ViewState* view); // Font management typedef struct { @@ -40,33 +40,11 @@ b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 d void free_fonts(FontSet *fonts); void clear_text_cache(void); -// Simple collision detection structures -typedef struct { - i32 x, y; // Screen position - i32 width, height; // Label dimensions -} LabelRect; - -// Pre-calculated label positions (updated only on catalog changes) -typedef struct { - i32 glassIndex; // Which glass this label belongs to - i32 screenX, screenY; // Fixed screen position - b32 visible; // Whether to show this label - char text[64]; // Label text -} PreCalculatedLabel; - -// Global label positioning system -void recalculate_label_positions(const ViewState* view, GlassCluster* clusters, i32 clusterCount); -void draw_precalculated_labels(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view); -b32 needs_label_recalculation(ViewState* view); - -// Collision detection functions for debug mode -b32 rects_overlap(const LabelRect* a, const LabelRect* b); -b32 label_collides_with_point(const LabelRect* label, i32 pointX, i32 pointY, i32 pointRadius); - -// Advanced label visibility functions for testing +// Simple glass property display helper void calculate_smart_window_position(i32 glassX, i32 glassY, i32 windowWidth, i32 windowHeight, const ViewState* view, i32* windowX, i32* windowY); -f32 calculate_label_priority(i32 glassIndex, const ViewState* view, GlassCluster* clusters, i32 clusterCount); -b32 should_show_label_advanced(i32 glassIndex, const ViewState* view, GlassCluster* clusters, i32 clusterCount); + +// Debug mode check +b32 is_debug_mode(void); #endif /* GLAMAC_RENDER_H */ diff --git a/include/glamac_view.h b/include/glamac_view.h index 9eec63a..6ca4b9b 100644 --- a/include/glamac_view.h +++ b/include/glamac_view.h @@ -20,14 +20,45 @@ #define LABEL_OFFSET_Y -8 // Vertical offset from glass point (pixels) #define LABEL_SIZE_SCALE 1.4f // Scale factor for glass name labels -// Glass clustering for nearly identical glasses -#define MAX_CLUSTER_SIZE 8 +// Tight clustering parameters +#define MAX_CLUSTER_SIZE 8 // Maximum glasses per cluster +#define DEFAULT_TIGHT_CLUSTER_ND_THRESHOLD 0.0005f // Default nd difference threshold +#define DEFAULT_TIGHT_CLUSTER_VD_THRESHOLD 0.15f // Default vd difference threshold + +// Loose clustering parameters (zoom-dependent) +#define DEFAULT_LOOSE_CLUSTER_ND_THRESHOLD 0.3f // Default nd base threshold +#define DEFAULT_LOOSE_CLUSTER_VD_THRESHOLD 0.55f // Default vd base threshold +#define DEFAULT_LOOSE_CLUSTER_ND_FRACTION 4.9f // Default nd fraction of visible range +#define DEFAULT_LOOSE_CLUSTER_VD_FRACTION 0.9f // Default vd fraction of visible range + +// Global tight cluster thresholds (adjustable parameters) +extern f32 g_tight_cluster_nd_threshold; +extern f32 g_tight_cluster_vd_threshold; + +// Global loose cluster parameters (adjustable parameters) +extern f32 g_loose_cluster_nd_threshold; // Base threshold +extern f32 g_loose_cluster_vd_threshold; // Base threshold +extern f32 g_loose_cluster_nd_fraction; // Zoom scaling fraction +extern f32 g_loose_cluster_vd_fraction; // Zoom scaling fraction + +// Tight cluster structure typedef struct { i32 glassIndices[MAX_CLUSTER_SIZE]; // Indices of glasses in this cluster i32 count; // Number of glasses in cluster - f32 avgAbbeNumber; // Average position for rendering + i32 representativeIndex; // Index of glass with shortest name + f32 avgAbbeNumber; // Average position for reference f32 avgRefractiveIndex; -} GlassCluster; +} TightCluster; + +// Loose cluster structure (zoom-dependent) +typedef struct { + i32 glassIndices[MAX_CLUSTER_SIZE]; // Indices of glasses in this cluster + i32 count; // Number of glasses in cluster + i32 representativeIndex; // Index of glass with shortest name + f32 avgAbbeNumber; // Average position for reference + f32 avgRefractiveIndex; +} LooseCluster; + // State for zooming and panning typedef struct { @@ -44,7 +75,15 @@ typedef struct { b32 gKeyPressed; // Flag to track if 'g' was pressed u32 gKeyTime; // Time when 'g' was pressed for sequence timing i32 selectedGlass; // Index of selected glass (-1 if none) - i32 selectedCluster; // Index of selected cluster (-1 if none) + i32 selectedCluster; // Index of selected tight cluster (-1 if none) + + // Tight cluster data (per catalog) + TightCluster* tightClusters; // Array of tight clusters for current catalog + i32 tightClusterCount; // Number of tight clusters + + // Loose cluster data (per catalog, zoom-dependent) + LooseCluster* looseClusters; // Array of loose clusters for current catalog + i32 looseClusterCount; // Number of loose clusters // Label recalculation tracking f32 lastZoomLevel; // Last zoom level for which labels were calculated @@ -119,9 +158,18 @@ void toggle_fullscreen(SDL_Window* window); // Reset view to default void reset_view(ViewState* view); -// Glass clustering functions -GlassCluster* create_glass_clusters(i32* clusterCount); -void free_glass_clusters(GlassCluster* clusters); -i32 find_cluster_at_position(GlassCluster* clusters, i32 clusterCount, i32 x, i32 y, const ViewState* view); +// Tight clustering functions +void create_tight_clusters(ViewState* view); +void free_tight_clusters(ViewState* view); +i32 find_tight_cluster_for_glass(i32 glassIndex, const ViewState* view); + +// Loose clustering functions (zoom-dependent) +void create_loose_clusters(ViewState* view); +void free_loose_clusters(ViewState* view); +i32 find_loose_cluster_for_glass(i32 glassIndex, const ViewState* view); + +// Combined clustering logic +b32 should_show_glass_label(i32 glassIndex, const ViewState* view); + #endif /* GLAMAC_VIEW_H */ diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c index 4b2f0fa..dba2fa1 100644 --- a/src/glamac/glamac.c +++ b/src/glamac/glamac.c @@ -12,8 +12,7 @@ // External function declarations from glamac_events.c extern b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, - i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit, - GlassCluster *clusters, i32 clusterCount); + i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit); // Function to reload fonts on window resize b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { @@ -51,59 +50,20 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { // Debug mode flag static b32 g_debug_mode = 0; +// Function to check debug mode (accessible from other files) +b32 is_debug_mode(void) { + return g_debug_mode; +} + int main(int argc, char* argv[]) { // Check for debug flag - for (int i = 1; i < argc; i++) { + for (i32 i = 1; i < argc; i++) { if (strcmp(argv[i], "--debug") == 0 || strcmp(argv[i], "-d") == 0) { g_debug_mode = 1; printf("Debug mode enabled\n"); break; } } - - // If debug mode, run without GUI - if (g_debug_mode) { - printf("Running in debug mode (no GUI)\n"); - - // Initialize glass data - initialize_glass_data(); - - // Create glass clusters for testing - i32 clusterCount = 0; - GlassCluster* clusters = create_glass_clusters(&clusterCount); - - printf("Glass data loaded: %d glasses\n", get_glass_count()); - printf("Created %d clusters\n", clusterCount); - - // Print first few glasses for verification - for (i32 i = 0; i < 10 && i < (i32)get_glass_count(); i++) { - const Glass* glass = get_glass(i); - if (glass) { - printf("Glass %d: %s (nd=%.4f, vd=%.2f, mfg=%s)\n", - i, glass->name, glass->refractiveIndex, glass->abbeNumber, glass->manufacturer); - } - } - - // Test collision detection with dummy data - printf("\nTesting collision detection...\n"); - LabelRect rect1 = {10, 10, 50, 20}; - LabelRect rect2 = {40, 15, 50, 20}; - LabelRect rect3 = {100, 100, 50, 20}; - - printf("Rect1 vs Rect2 overlap: %s\n", rects_overlap(&rect1, &rect2) ? "YES" : "NO"); - printf("Rect1 vs Rect3 overlap: %s\n", rects_overlap(&rect1, &rect3) ? "YES" : "NO"); - - printf("Label collision with point test:\n"); - printf("Label at point collision: %s\n", label_collides_with_point(&rect1, 30, 20, 5) ? "YES" : "NO"); - printf("Label far from point collision: %s\n", label_collides_with_point(&rect3, 30, 20, 5) ? "YES" : "NO"); - - // Cleanup - free_glass_clusters(clusters); - cleanup_glass_data(); - - printf("Debug mode complete\n"); - return 0; - } SDL_Window* window = NULL; SDL_Renderer* renderer = NULL; FontSet fonts = {0}; @@ -174,10 +134,14 @@ int main(int argc, char* argv[]) { ViewState view; init_view_state(&view, INITIAL_WIDTH, INITIAL_HEIGHT); - // Create glass clusters for similar glasses - i32 clusterCount = 0; - GlassCluster* clusters = create_glass_clusters(&clusterCount); - printf("Created %d glass clusters from %d glasses\n", clusterCount, get_glass_count()); + printf("Loaded %u glasses from %u catalogs\n", get_glass_count(), get_catalog_count()); + + // Create tight clusters for the initial catalog + create_tight_clusters(&view); + + // Create loose clusters for the initial catalog + create_loose_clusters(&view); + // Main loop flag b32 quit = 0; @@ -196,7 +160,7 @@ int main(int argc, char* argv[]) { while (!quit) { // Handle events on queue while (SDL_PollEvent(&e)) { - process_events(&e, &view, window, &lastMouseX, &lastMouseY, &dragging, &quit, clusters, clusterCount); + process_events(&e, &view, window, &lastMouseX, &lastMouseY, &dragging, &quit); } // Reload fonts if window size changed significantly @@ -205,18 +169,18 @@ int main(int argc, char* argv[]) { } // Render everything - render(renderer, fonts.regular, fonts.title, fonts.label, &view, clusters, clusterCount); + render(renderer, fonts.regular, fonts.title, fonts.label, &view); } // Stop text input SDL_StopTextInput(window); // Clean up resources - free_glass_clusters(clusters); + free_tight_clusters(&view); + free_loose_clusters(&view); cleanup_glass_data(); free_fonts(&fonts); clear_text_cache(); - // cleanup_static_label_positions(); // Removed with new collision system SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); TTF_Quit(); diff --git a/src/glamac/glamac_events.c b/src/glamac/glamac_events.c index 73d1133..51d1e9d 100644 --- a/src/glamac/glamac_events.c +++ b/src/glamac/glamac_events.c @@ -3,8 +3,13 @@ */ #include #include +#include #include "glamac_view.h" #include "glass_data.h" +#include "glamac_render.h" // For clear_text_cache + +// External debug mode function +extern b32 is_debug_mode(void); // Process key event b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *window, b32 *quit) { @@ -57,12 +62,14 @@ 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; + create_loose_clusters(view); // Recreate loose clusters on zoom return 1; case SDLK_MINUS: // Zoom out at center view->zoomLevel /= ZOOM_FACTOR; if (view->zoomLevel < MIN_ZOOM) view->zoomLevel = MIN_ZOOM; + create_loose_clusters(view); // Recreate loose clusters on zoom view->gKeyPressed = 0; // Reset g key state return 1; @@ -85,8 +92,12 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo cycle_catalog(-1); refresh_view_data_range(view); // Update view for new catalog view->selectedGlass = -1; // Clear selection - view->selectedCluster = -1; - printf("Switched to catalog: %s\n", get_current_catalog_name()); + view->selectedCluster = -1; // Clear cluster selection + clear_text_cache(); // Clear text cache when switching catalogs + create_tight_clusters(view); // Recreate tight clusters for new catalog + create_loose_clusters(view); // Recreate loose clusters for new catalog + printf("Switched to catalog: %s (%u glasses)\n", get_current_catalog_name(), get_glass_count()); + } else { // Move left view->offsetX += PAN_STEP; @@ -100,8 +111,11 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo cycle_catalog(1); refresh_view_data_range(view); // Update view for new catalog view->selectedGlass = -1; // Clear selection - view->selectedCluster = -1; - printf("Switched to catalog: %s\n", get_current_catalog_name()); + view->selectedCluster = -1; // Clear cluster selection + clear_text_cache(); // Clear text cache when switching catalogs + create_tight_clusters(view); // Recreate tight clusters for new catalog + create_loose_clusters(view); // Recreate loose clusters for new catalog + printf("Switched to catalog: %s (%u glasses)\n", get_current_catalog_name(), get_glass_count()); } else { // Move right view->offsetX -= PAN_STEP; @@ -126,7 +140,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo } // Process mouse button event -b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, GlassCluster *clusters, i32 clusterCount) { +b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging) { f32 mouseX, mouseY; switch (button->button) { @@ -134,17 +148,30 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las if (button->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { SDL_GetMouseState(&mouseX, &mouseY); - // Find the nearest glass point (no clustering) + // Find the nearest glass point i32 nearestGlass = find_nearest_glass((i32)mouseX, (i32)mouseY, view, 15); + if (nearestGlass >= 0) { - // Select the individual glass - view->selectedGlass = nearestGlass; - view->selectedCluster = -1; + // Check if clicked glass is part of a tight cluster + i32 clusterIndex = find_tight_cluster_for_glass(nearestGlass, view); + + if (clusterIndex >= 0 && view->tightClusters[clusterIndex].count > 1) { + // Clicked on a multi-glass tight cluster + view->selectedCluster = clusterIndex; + view->selectedGlass = -1; // Clear individual glass selection + } else { + // Clicked on individual glass or single-glass cluster + view->selectedGlass = nearestGlass; + view->selectedCluster = -1; // Clear cluster selection + } } else { - // If clicking outside any point, clear selection + // No glass clicked view->selectedGlass = -1; view->selectedCluster = -1; - // Start dragging for panning + } + + if (nearestGlass < 0) { + // Start dragging for panning if no glass was clicked *dragging = 1; *lastMouseX = (i32)mouseX; *lastMouseY = (i32)mouseY; @@ -203,8 +230,7 @@ b32 process_window_event(SDL_WindowEvent *window_event, ViewState *view) { // Process all events on queue b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, - i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit, - GlassCluster *clusters, i32 clusterCount) { + i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit) { switch (event->type) { case SDL_EVENT_QUIT: *quit = 1; @@ -222,7 +248,7 @@ b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window, case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: - return process_mouse_button(&event->button, view, lastMouseX, lastMouseY, dragging, clusters, clusterCount); + return process_mouse_button(&event->button, view, lastMouseX, lastMouseY, dragging); case SDL_EVENT_MOUSE_MOTION: return process_mouse_motion(&event->motion, view, lastMouseX, lastMouseY, *dragging); diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c index 8536218..5230f1b 100644 --- a/src/glamac/glamac_render.c +++ b/src/glamac/glamac_render.c @@ -10,1041 +10,487 @@ #include "glamac_render.h" #include "glass_data.h" -// Drawing primitives - -// Helper function to check if a glass is part of a cluster -static inline i32 find_glass_cluster(i32 glassIndex, GlassCluster* clusters, i32 clusterCount) { - for (i32 i = 0; i < clusterCount; i++) { - for (i32 j = 0; j < clusters[i].count; j++) { - if (clusters[i].glassIndices[j] == glassIndex) { - return i; // Return cluster index - } - } - } - return -1; // Not in any cluster -} - -// Simple label positioning +// Text rendering cache typedef struct { - i32 dx, dy; -} LabelOffset; - -// Simplified positions - only 4 basic directions -static const LabelOffset LABEL_POSITIONS[] = { - {0, -1}, // Up - {1, 0}, // Right - {0, 1}, // Down - {-1, 0} // Left -}; -#define NUM_LABEL_POSITIONS (sizeof(LABEL_POSITIONS) / sizeof(LABEL_POSITIONS[0])) -#define LABEL_OFFSET_DISTANCE 8 // Reduced distance from glass point -#define LABEL_COLLISION_PADDING 1 // Minimal padding - - -// Simple collision check between two rectangles -b32 rects_overlap(const LabelRect* a, const LabelRect* b) { - return !(a->x + a->width + LABEL_COLLISION_PADDING < b->x || - b->x + b->width + LABEL_COLLISION_PADDING < a->x || - a->y + a->height + LABEL_COLLISION_PADDING < b->y || - b->y + b->height + LABEL_COLLISION_PADDING < a->y); -} - -// Check if label collides with glass point -b32 label_collides_with_point(const LabelRect* label, i32 pointX, i32 pointY, i32 pointRadius) { - i32 expandedRadius = pointRadius + LABEL_COLLISION_PADDING; - return !(label->x > pointX + expandedRadius || - label->x + label->width < pointX - expandedRadius || - label->y > pointY + expandedRadius || - label->y + label->height < pointY - expandedRadius); -} - -// Forward declarations -static inline i32 get_adaptive_radius(const ViewState* view, i32 baseRadius); -static inline const char* get_cluster_shortest_name(const GlassCluster* cluster); - -// Global storage for pre-calculated labels -static PreCalculatedLabel preCalculatedLabels[512]; -static i32 preCalculatedLabelCount = 0; - -// Simplified priority calculation - no clustering -static inline f32 calculate_simple_priority(i32 glassIndex, const ViewState* view, GlassCluster* clusters __attribute__((unused)), i32 clusterCount __attribute__((unused))) { - // Highest priority: Selected glass (always shows) - if (glassIndex == view->selectedGlass) { - return 1000.0f; - } - - // All other glasses: medium priority (always visible at default zoom) - return 150.0f; -} - -// Calculate label priority score (higher = more likely to show) -f32 calculate_label_priority(i32 glassIndex, const ViewState* view, GlassCluster* clusters, i32 clusterCount) { - return calculate_simple_priority(glassIndex, view, clusters, clusterCount); -} - -// Simplified label visibility - no clustering -b32 should_show_label_advanced(i32 glassIndex, const ViewState* view, GlassCluster* clusters, i32 clusterCount) { - f32 priority = calculate_label_priority(glassIndex, view, clusters, clusterCount); - - // Lower threshold to show labels at default zoom - const f32 threshold = 100.0f; - - return priority >= threshold; -} + char text[128]; + SDL_Texture* texture; + i32 width, height; + SDL_Color color; + TTF_Font* font; +} CachedText; -// Simple label positioning - always relative to glass points -void recalculate_label_positions(const ViewState* view, GlassCluster* clusters, i32 clusterCount) { - preCalculatedLabelCount = 0; - - // Simple array to track occupied positions - LabelRect occupiedRects[512]; - i32 occupiedCount = 0; - - const i32 padding = get_adaptive_padding(view); - const i32 baseRadius = 4; - const i32 radius = get_adaptive_radius(view, baseRadius); - const i32 labelOffset = radius + 8; // Fixed offset from glass point - - // Get visible data range - f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; - get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - - // Collect candidates and sort by priority - typedef struct { - i32 index; - f32 priority; - const char* labelText; - } LabelCandidate; - - LabelCandidate candidates[512]; - i32 candidateCount = 0; - - for (i32 i = 0; i < (i32)get_glass_count() && candidateCount < 512; i++) { - const Glass* glass = get_glass(i); - if (!glass) continue; - - if (!should_show_label_advanced(i, view, clusters, clusterCount)) continue; - - // Check visibility - if (glass->abbeNumber < visibleMinAbbe || glass->abbeNumber > visibleMaxAbbe || - glass->refractiveIndex < visibleMinRI || glass->refractiveIndex > visibleMaxRI) { - continue; - } - - i32 glassX, glassY; - data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); - - if (glassX < padding || glassX > view->windowWidth - padding || - glassY < padding || glassY > view->windowHeight - padding) { - continue; - } - - // No clustering - just use glass name - const char* labelText = (const char*)glass->name; - - candidates[candidateCount].index = i; - candidates[candidateCount].priority = calculate_label_priority(i, view, clusters, clusterCount); - candidates[candidateCount].labelText = labelText; - candidateCount++; - } - - // Sort by priority (highest first) - for (i32 i = 0; i < candidateCount - 1; i++) { - for (i32 j = i + 1; j < candidateCount; j++) { - if (candidates[j].priority > candidates[i].priority) { - LabelCandidate temp = candidates[i]; - candidates[i] = candidates[j]; - candidates[j] = temp; - } +static CachedText textCache[512]; +static i32 cacheSize = 0; + +// Find cached text texture +static SDL_Texture* get_cached_text(SDL_Renderer* renderer, TTF_Font* font, const char* text, SDL_Color color, i32* width, i32* height) { + for (i32 i = 0; i < cacheSize; i++) { + if (textCache[i].font == font && + textCache[i].color.r == color.r && + textCache[i].color.g == color.g && + textCache[i].color.b == color.b && + strcmp(textCache[i].text, text) == 0) { + *width = textCache[i].width; + *height = textCache[i].height; + return textCache[i].texture; } } - // Place labels in priority order with simple collision detection - for (i32 c = 0; c < candidateCount && preCalculatedLabelCount < 512; c++) { - LabelCandidate* candidate = &candidates[c]; - const Glass* glass = get_glass(candidate->index); - if (!glass) continue; - - i32 glassX, glassY; - data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); - - // Simple position: right of the glass point - i32 labelX = glassX + labelOffset; - i32 labelY = glassY - 7; // Slightly above center - - // Get approximate text size (simplified) - i32 textWidth = strlen(candidate->labelText) * 8; - i32 textHeight = 14; - - // Check for collisions - LabelRect newRect = {labelX, labelY, textWidth, textHeight}; - b32 collision = 0; - - // Check against existing labels - for (i32 i = 0; i < occupiedCount; i++) { - if (rects_overlap(&newRect, &occupiedRects[i])) { - collision = 1; - break; + // Create new cached texture + if (cacheSize < 512) { + SDL_Surface* surface = TTF_RenderText_Blended(font, text, strlen(text), color); + if (surface) { + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + if (texture) { + strncpy(textCache[cacheSize].text, text, sizeof(textCache[cacheSize].text) - 1); + textCache[cacheSize].text[sizeof(textCache[cacheSize].text) - 1] = '\0'; + textCache[cacheSize].texture = texture; + textCache[cacheSize].width = surface->w; + textCache[cacheSize].height = surface->h; + textCache[cacheSize].color = color; + textCache[cacheSize].font = font; + *width = surface->w; + *height = surface->h; + SDL_DestroySurface(surface); + cacheSize++; + return texture; } - } - - // Check collision with glass point - if (!collision && label_collides_with_point(&newRect, glassX, glassY, radius)) { - collision = 1; - } - - // Store the label position (even if collision - for selected items) - PreCalculatedLabel* newLabel = &preCalculatedLabels[preCalculatedLabelCount]; - newLabel->glassIndex = candidate->index; - newLabel->screenX = labelX; - newLabel->screenY = labelY; - newLabel->visible = !collision || candidate->priority >= 500.0f; // Always show selected - strncpy(newLabel->text, candidate->labelText, sizeof(newLabel->text) - 1); - newLabel->text[sizeof(newLabel->text) - 1] = '\0'; - - preCalculatedLabelCount++; - - // Add to occupied list if no collision - if (!collision && occupiedCount < 512) { - occupiedRects[occupiedCount] = newRect; - occupiedCount++; + SDL_DestroySurface(surface); } } + return NULL; } -// Draw pre-calculated labels -void draw_precalculated_labels(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view __attribute__((unused))) { - const SDL_Color blue = {0, 0, 150, 255}; - - for (i32 i = 0; i < preCalculatedLabelCount; i++) { - PreCalculatedLabel* label = &preCalculatedLabels[i]; - if (label->visible) { - draw_text(renderer, labelFont, label->text, label->screenX, label->screenY, blue); +void clear_text_cache(void) { + for (i32 i = 0; i < cacheSize; i++) { + if (textCache[i].texture) { + SDL_DestroyTexture(textCache[i].texture); } } + cacheSize = 0; } -// Check if label recalculation is needed and update tracking values -b32 needs_label_recalculation(ViewState* view) { - const f32 FLOAT_TOLERANCE = 0.001f; - - // Check if any of the factors that affect label positioning have changed - b32 needs_recalc = - (fabsf(view->zoomLevel - view->lastZoomLevel) > FLOAT_TOLERANCE) || - (view->windowWidth != view->lastWindowWidth) || - (view->windowHeight != view->lastWindowHeight) || - (fabsf(view->offsetX - view->lastOffsetX) > FLOAT_TOLERANCE) || - (fabsf(view->offsetY - view->lastOffsetY) > FLOAT_TOLERANCE); - - if (needs_recalc) { - // Update tracking values - view->lastZoomLevel = view->zoomLevel; - view->lastWindowWidth = view->windowWidth; - view->lastWindowHeight = view->windowHeight; - view->lastOffsetX = view->offsetX; - view->lastOffsetY = view->offsetY; +// Draw text helper +void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x, i32 y, SDL_Color color) { + i32 width, height; + SDL_Texture* texture = get_cached_text(renderer, font, text, color, &width, &height); + if (texture) { + SDL_FRect destRect = {(f32)x, (f32)y, (f32)width, (f32)height}; + SDL_RenderTexture(renderer, texture, NULL, &destRect); } - - return needs_recalc; } -// Helper function to calculate adaptive radius based on window size and DPI -static inline i32 get_adaptive_radius(const ViewState* view, i32 baseRadius) { - // Scale based on window size - use average of width and height - f32 sizeScale = (view->windowWidth + view->windowHeight) / 1600.0f; // Reference: 1600 (avg of 1920x1080) - - // Clamp scale factor to reasonable bounds - if (sizeScale < 0.7f) sizeScale = 0.7f; - if (sizeScale > 2.5f) sizeScale = 2.5f; - - i32 scaledRadius = (i32)(baseRadius * sizeScale); - - // Ensure minimum radius for usability - if (scaledRadius < 2) scaledRadius = 2; - if (scaledRadius > 12) scaledRadius = 12; - - return scaledRadius; -} - -// Structure to hold cached text textures -typedef struct { - char text[64]; // The text string - SDL_Color color; // Text color - TTF_Font *font; // Font used - SDL_Texture *texture; // The cached texture - i32 width; // Texture width - i32 height; // Texture height - u32 lastUsed; // Timestamp for LRU eviction -} CachedText; - -// Text cache -#define MAX_TEXT_CACHE 2048 -static CachedText textCache[MAX_TEXT_CACHE] = {0}; -static i32 cacheCount = 0; - -// Function to draw text using SDL3_ttf with caching for improved performance -void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x, i32 y, SDL_Color color) { - // Check if we already have this text cached - for (i32 i = 0; i < cacheCount; i++) { - CachedText *entry = &textCache[i]; - - // Compare text, font, and color to find a match - if (entry->font == font && - strcmp(entry->text, text) == 0 && - entry->color.r == color.r && - entry->color.g == color.g && - entry->color.b == color.b && - entry->color.a == color.a) { - - // Use the cached texture and update LRU timestamp - entry->lastUsed = SDL_GetTicks(); - SDL_FRect rect = {(f32)x, (f32)y, (f32)entry->width, (f32)entry->height}; - SDL_RenderTexture(renderer, entry->texture, NULL, &rect); - return; +// 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); + if (surface) { + SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); + if (texture) { + SDL_FRect destRect = {(f32)x, (f32)y, (f32)surface->w, (f32)surface->h}; + SDL_RenderTexture(renderer, texture, NULL, &destRect); + SDL_DestroyTexture(texture); } - } - - // Text not in cache, create a new texture - SDL_Surface *surface = TTF_RenderText_Blended(font, text, 0, color); - if (!surface) { - printf("TTF_RenderText_Blended failed: %s\n", SDL_GetError()); - return; - } - - SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface); - if (!texture) { - printf("SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError()); SDL_DestroySurface(surface); - return; - } - - // Draw the text - SDL_FRect rect = {(f32)x, (f32)y, (f32)surface->w, (f32)surface->h}; - SDL_RenderTexture(renderer, texture, NULL, &rect); - - // Add to cache or replace LRU entry - CachedText *entry = NULL; - if (cacheCount < MAX_TEXT_CACHE) { - // Cache has space, use new slot - entry = &textCache[cacheCount++]; - } else { - // Cache is full, find LRU entry to replace - u32 oldestTime = SDL_GetTicks(); - i32 oldestIndex = 0; - for (i32 i = 0; i < MAX_TEXT_CACHE; i++) { - if (textCache[i].lastUsed < oldestTime) { - oldestTime = textCache[i].lastUsed; - oldestIndex = i; - } - } - entry = &textCache[oldestIndex]; - - // Clean up old texture - if (entry->texture) { - SDL_DestroyTexture(entry->texture); - } } - - // Store new entry - strncpy(entry->text, text, sizeof(entry->text) - 1); - entry->text[sizeof(entry->text) - 1] = '\0'; - entry->color = color; - entry->font = font; - entry->texture = texture; - entry->width = surface->w; - entry->height = surface->h; - entry->lastUsed = SDL_GetTicks(); - - SDL_DestroySurface(surface); } - -// Function to draw a filled circle using SDL3's primitive functions +// Draw filled circle void draw_filled_circle(SDL_Renderer *renderer, i32 centerX, i32 centerY, i32 radius) { - // Draw a filled circle using a series of horizontal lines - // This is significantly faster than the pixel-by-pixel approach for (i32 y = -radius; y <= radius; y++) { - // Calculate width of the horizontal line at this y position - i32 x_width = (i32)sqrtf((float)(radius * radius - y * y)); - - // Draw a horizontal line from left to right edge of the circle - SDL_RenderLine( - renderer, - (f32)(centerX - x_width), (f32)(centerY + y), - (f32)(centerX + x_width), (f32)(centerY + y) - ); + for (i32 x = -radius; x <= radius; x++) { + if (x*x + y*y <= radius*radius) { + SDL_RenderPoint(renderer, centerX + x, centerY + y); + } + } } } -// UI element rendering - -// Function to draw the axes with correct visible range -void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { - const SDL_Color black = {0, 0, 0, 255}; - const i32 padding = get_adaptive_padding(view); - - // X-axis - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderLine(renderer, (f32)padding, (f32)(view->windowHeight - padding), - (f32)(view->windowWidth - padding), (f32)(view->windowHeight - padding)); - - // Y-axis - SDL_RenderLine(renderer, (f32)padding, (f32)padding, - (f32)padding, (f32)(view->windowHeight - padding)); +// Draw grid +void draw_grid(SDL_Renderer *renderer, const ViewState* view) { + SDL_SetRenderDrawColor(renderer, 230, 230, 230, 255); - // Calculate visible range based on current view - f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; - get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); + const i32 padding = get_adaptive_padding(view); + const i32 plotWidth = view->windowWidth - 2 * padding; + const i32 plotHeight = view->windowHeight - 2 * padding; - // Draw X-axis labels - now using the actual visible range - char label[32]; - for (i32 i = 0; i <= 5; i++) { - // For flipped axis: start with max and go to min - f32 value = visibleMaxAbbe - i * (visibleMaxAbbe - visibleMinAbbe) / 5; - i32 x = padding + i * (view->windowWidth - 2 * padding) / 5; - - SDL_RenderLine(renderer, (f32)x, (f32)(view->windowHeight - padding), - (f32)x, (f32)(view->windowHeight - padding + 5)); - sprintf(label, "%.1f", value); - draw_text(renderer, font, label, x - 15, view->windowHeight - padding + 10, black); + // Draw vertical grid lines (for Abbe numbers) + for (i32 i = 0; i <= 10; i++) { + i32 x = padding + (i * plotWidth) / 10; + SDL_RenderLine(renderer, x, padding, x, view->windowHeight - padding); } - // Draw Y-axis labels - now using the actual visible range - for (i32 i = 0; i <= 5; i++) { - f32 value = visibleMinRI + i * (visibleMaxRI - visibleMinRI) / 5; - i32 y = view->windowHeight - padding - i * (view->windowHeight - 2 * padding) / 5; - - SDL_RenderLine(renderer, (f32)(padding - 5), (f32)y, (f32)padding, (f32)y); - sprintf(label, "%.3f", value); - draw_text(renderer, font, label, padding - 50, y - 10, black); + // Draw horizontal grid lines (for refractive indices) + for (i32 i = 0; i <= 10; i++) { + i32 y = padding + (i * plotHeight) / 10; + SDL_RenderLine(renderer, padding, y, view->windowWidth - padding, y); } - - // Add axis titles with subscripts - // For X axis title (Abbe Number) - draw_text(renderer, titleFont, "Abbe Number (V", view->windowWidth / 2 - 80, view->windowHeight - padding + 30, black); - // Draw the subscript "d" with a smaller font and lowered position - draw_text(renderer, font, "d", view->windowWidth / 2 + 54, view->windowHeight - padding + 38, black); - // Draw the closing parenthesis - draw_text(renderer, titleFont, ")", view->windowWidth / 2 + 63, view->windowHeight - padding + 30, black); - - // For Y axis title (Refractive Index) - draw_text(renderer, titleFont, "Refractive Index (n", padding - 20, padding - 35, black); - // Draw the subscript "d" with a smaller font and lowered position - draw_text(renderer, font, "d", padding + 132, padding - 25, black); - // Draw the closing parenthesis - draw_text(renderer, titleFont, ")", padding + 141, padding - 35, black); } -// Function to draw a grid based on visible data range -void draw_grid(SDL_Renderer *renderer, const ViewState* view) { +// Draw axes +void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { + // Create smaller font for axis scales + TTF_Font *axisFont = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 12); + if (!axisFont) { + axisFont = font; // Fallback to regular font if loading fails + } const i32 padding = get_adaptive_padding(view); + const i32 plotWidth = view->windowWidth - 2 * padding; + const i32 plotHeight = view->windowHeight - 2 * padding; - // Calculate visible range for grid - f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; - get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); + SDL_Color black = {0, 0, 0, 255}; - // Determine grid step sizes based on zoom level - f32 xStep = 5.0f; - f32 yStep = 0.05f; + // Draw X axis (bottom) + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderLine(renderer, padding, view->windowHeight - padding, + view->windowWidth - padding, view->windowHeight - padding); - if (view->zoomLevel > 3.0f) { - xStep = 1.0f; - yStep = 0.01f; - } else if (view->zoomLevel > 7.0f) { - xStep = 0.5f; - yStep = 0.005f; - } + // Draw Y axis (left) + SDL_RenderLine(renderer, padding, padding, padding, view->windowHeight - padding); - // Round start values to nice grid intervals - const f32 xGridStart = floorf(visibleMinAbbe / xStep) * xStep; - const f32 yGridStart = floorf(visibleMinRI / yStep) * yStep; + // Calculate visible range ONCE for stable zoomed axes - with validation + f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; + get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - // Draw vertical grid lines (X-axis) - for (f32 x = xGridStart; x <= visibleMaxAbbe; x += xStep) { - i32 screenX, screenY; - data_to_screen_coords(x, visibleMinRI, view, &screenX, &screenY); - - if (screenX >= padding && screenX <= view->windowWidth - padding) { - SDL_RenderLine(renderer, (f32)screenX, (f32)padding, - (f32)screenX, (f32)(view->windowHeight - padding)); + // Validate the visible range - fallback to full range if invalid + if (!isfinite(visibleMinAbbe) || !isfinite(visibleMaxAbbe) || + !isfinite(visibleMinRI) || !isfinite(visibleMaxRI) || + visibleMaxAbbe <= visibleMinAbbe || visibleMaxRI <= visibleMinRI) { + // Use full data range as fallback + visibleMinAbbe = view->minAbbe; + visibleMaxAbbe = view->maxAbbe; + visibleMinRI = view->minRI; + visibleMaxRI = view->maxRI; + } + + // Use float precision for range calculations + f32 abbeRange = visibleMaxAbbe - visibleMinAbbe; + f32 riRange = visibleMaxRI - visibleMinRI; + + // Draw axis labels + char buffer[32]; + + // X-axis labels (Abbe numbers - STABLE but ZOOMED positions) + for (i32 i = 0; i <= 10; i++) { + f32 normalizedX = (f32)i / 10.0f; + f32 abbeValue = visibleMaxAbbe - normalizedX * abbeRange; + i32 x = padding + (i * plotWidth) / 10; + + // Adaptive precision based on zoom level + if (abbeRange < 5.0f) { + snprintf(buffer, sizeof(buffer), "%.2f", abbeValue); + } else { + snprintf(buffer, sizeof(buffer), "%.1f", abbeValue); } + draw_text_direct(renderer, axisFont, buffer, x - 15, view->windowHeight - padding + 5, black); } - // Draw horizontal grid lines (Y-axis) - for (f32 y = yGridStart; y <= visibleMaxRI; y += yStep) { - i32 screenX, screenY; - data_to_screen_coords(visibleMinAbbe, y, view, &screenX, &screenY); + // Y-axis labels (refractive indices - STABLE but ZOOMED positions) + for (i32 i = 0; i <= 10; i++) { + f32 normalizedY = (f32)i / 10.0f; + f32 riValue = visibleMinRI + normalizedY * riRange; + i32 y = view->windowHeight - padding - (i * plotHeight) / 10; - if (screenY >= padding && screenY <= view->windowHeight - padding) { - SDL_RenderLine(renderer, (f32)padding, (f32)screenY, - (f32)(view->windowWidth - padding), (f32)screenY); + // Adaptive precision based on zoom level + if (riRange < 0.1f) { + snprintf(buffer, sizeof(buffer), "%.4f", riValue); + } else { + snprintf(buffer, sizeof(buffer), "%.3f", riValue); } + draw_text_direct(renderer, axisFont, buffer, padding - 45, y - 8, black); } -} - -// Helper function to find the shortest name in a cluster -static inline const char* get_cluster_shortest_name(const GlassCluster* cluster) { - if (cluster->count == 0) return ""; - - const Glass* shortestGlass = get_glass(cluster->glassIndices[0]); - if (!shortestGlass) return ""; - const char* shortestName = (const char*)shortestGlass->name; - size_t shortestLen = strlen(shortestName); + // Axis titles with subscripts (approximated) + draw_text(renderer, titleFont, "Abbe Number (Vd)", view->windowWidth/2 - 80, view->windowHeight - 25, black); - for (i32 i = 1; i < cluster->count; i++) { - const Glass* glass = get_glass(cluster->glassIndices[i]); - if (!glass) continue; - - const char* name = (const char*)glass->name; - size_t len = strlen(name); - if (len < shortestLen) { - shortestName = name; - shortestLen = len; - } - } + // Vertical text for Y-axis (simplified) + draw_text(renderer, titleFont, "Refractive Index (nd)", 5, 10, black); - return shortestName; -} - - -// Simple label position finder with better offset calculation -static inline i32 find_best_label_position(i32 glassX, i32 glassY, i32 textWidth, i32 textHeight, - LabelRect* existingLabels, i32 labelCount, i32 pointRadius) { - // Fixed offset - always outside the point - const i32 baseOffset = pointRadius + 6; // Always outside point with margin + // 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); - // Try each position in order - for (i32 posIdx = 0; posIdx < (i32)NUM_LABEL_POSITIONS; posIdx++) { - const LabelOffset* pos = &LABEL_POSITIONS[posIdx]; - - // Calculate label position - i32 labelX = glassX + pos->dx * baseOffset; - i32 labelY = glassY + pos->dy * baseOffset; - - // Adjust for text alignment - if (pos->dx < 0) { // Left side - labelX -= textWidth; - } else if (pos->dx == 0) { // Center horizontally - labelX -= textWidth / 2; - } - - if (pos->dy < 0) { // Above - labelY -= textHeight; - } else if (pos->dy == 0) { // Center vertically - labelY -= textHeight / 2; - } - - LabelRect candidate = { - .x = labelX, - .y = labelY, - .width = textWidth, - .height = textHeight - }; - - // Check collision with existing labels - b32 collision = 0; - for (i32 i = 0; i < labelCount; i++) { - if (rects_overlap(&candidate, &existingLabels[i])) { - collision = 1; - break; - } - } - - if (!collision) { - return posIdx; - } + // Clean up axis font if we created it + if (axisFont != font) { + TTF_CloseFont(axisFont); } - - // If all positions have collisions, return -1 to indicate no placement - return -1; } - -// Function to draw glass points and labels with simple collision detection -void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view, GlassCluster* clusters, i32 clusterCount) { - const i32 padding = get_adaptive_padding(view); - const i32 baseRadius = 4; - const i32 radius = get_adaptive_radius(view, baseRadius); +// Draw glass points and labels - simplified version showing all labels +void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view) { + 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 - // Get visible data range for smart culling - f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; - get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - // Track which clusters have been processed - b32* clusterProcessed = (b32*)calloc(clusterCount, sizeof(b32)); - // Draw all glass points first - for (i32 i = 0; i < (i32)get_glass_count(); i++) { + // Draw all glass points and labels + for (u32 i = 0; i < glassCount; i++) { const Glass* glass = get_glass(i); if (!glass) continue; - // Smart culling: Check data bounds before expensive coordinate conversion - if (glass->abbeNumber < visibleMinAbbe || glass->abbeNumber > visibleMaxAbbe || - glass->refractiveIndex < visibleMinRI || glass->refractiveIndex > visibleMaxRI) { - continue; - } - i32 x, y; data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &x, &y); - // Final screen bounds check - if (x < padding || x > view->windowWidth - padding || - y < padding || y > view->windowHeight - padding) { + // Check if point is within the plot area (respecting axes padding) + const i32 padding = get_adaptive_padding(view); + if (x < padding || x >= view->windowWidth - padding || + y < padding || y >= view->windowHeight - padding) { continue; } - // Highlight selected glass only (no clustering) - if (i == view->selectedGlass) { - // Draw a larger highlight circle - SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255); // Light blue highlight - draw_filled_circle(renderer, x, y, radius + 2); + // Set color based on selection + if ((i32)i == view->selectedGlass) { + SDL_SetRenderDrawColor(renderer, selectedColor.r, selectedColor.g, selectedColor.b, selectedColor.a); + } else { + SDL_SetRenderDrawColor(renderer, normalColor.r, normalColor.g, normalColor.b, normalColor.a); } - // Draw filled circle - SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // Red for glass points - draw_filled_circle(renderer, x, y, radius); + // Draw glass point (larger) + draw_filled_circle(renderer, x, y, 5); + + // Draw label only if it should be shown (tight clustering logic) + if (should_show_glass_label((i32)i, view)) { + const char* glassName = (const char*)glass->name; + draw_text(renderer, labelFont, glassName, x + 12, y - 12, labelColor); + } } - - // Draw pre-calculated labels (simple and stable) - draw_precalculated_labels(renderer, labelFont, view); - - if (clusterProcessed) free(clusterProcessed); } -// Smart positioning for properties window (reverted to working version) +// Smart window positioning helper void calculate_smart_window_position(i32 glassX, i32 glassY, i32 windowWidth, i32 windowHeight, const ViewState* view, i32* windowX, i32* windowY) { - const i32 margin = 10; - const i32 baseOffset = 8 + (i32)(4.0f / view->zoomLevel); // Closer at higher zoom - - // Try positions in order of preference: right, left, below, above - typedef struct { - i32 x, y; - f32 distance; // Distance from glass point - } Position; - - Position positions[4] = { - {glassX + baseOffset, glassY - windowHeight / 2, 0}, // Right - {glassX - windowWidth - baseOffset, glassY - windowHeight / 2, 0}, // Left - {glassX - windowWidth / 2, glassY + baseOffset, 0}, // Below - {glassX - windowWidth / 2, glassY - windowHeight - baseOffset, 0} // Above - }; - - // Calculate distances and check bounds - for (i32 i = 0; i < 4; i++) { - Position* pos = &positions[i]; - - // Check if position is within screen bounds - if (pos->x >= margin && pos->x + windowWidth <= view->windowWidth - margin && - pos->y >= margin && pos->y + windowHeight <= view->windowHeight - margin) { - - // Calculate distance from glass point - i32 centerX = pos->x + windowWidth / 2; - i32 centerY = pos->y + windowHeight / 2; - f32 dx = centerX - glassX; - f32 dy = centerY - glassY; - pos->distance = sqrtf(dx * dx + dy * dy); - } else { - pos->distance = 10000.0f; // Invalid position - } - } - - // Find best valid position (shortest distance) - i32 bestIndex = 0; - for (i32 i = 1; i < 4; i++) { - if (positions[i].distance < positions[bestIndex].distance) { - bestIndex = i; - } + // Simple positioning: try right first, then left if doesn't fit + if (glassX + windowWidth + 10 < view->windowWidth) { + *windowX = glassX + 10; + } else { + *windowX = glassX - windowWidth - 10; + if (*windowX < 0) *windowX = 10; } - // If no good position found, use constrained positioning - if (positions[bestIndex].distance > 1000.0f) { - *windowX = glassX + baseOffset; - *windowY = glassY - windowHeight / 2; - - // Constrain to screen bounds - if (*windowX + windowWidth > view->windowWidth - margin) { - *windowX = view->windowWidth - windowWidth - margin; - } - if (*windowX < margin) { - *windowX = margin; - } - if (*windowY + windowHeight > view->windowHeight - margin) { - *windowY = view->windowHeight - windowHeight - margin; - } - if (*windowY < margin) { - *windowY = margin; - } + // Try above first, then below if doesn't fit + if (glassY - windowHeight - 10 > 0) { + *windowY = glassY - windowHeight - 10; } else { - *windowX = positions[bestIndex].x; - *windowY = positions[bestIndex].y; + *windowY = glassY + 10; + if (*windowY + windowHeight > view->windowHeight) { + *windowY = view->windowHeight - windowHeight - 10; + } } } -// Function to draw the properties popup for the selected glass -void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { - if (view->selectedGlass < 0 || view->selectedGlass >= (i32)get_glass_count()) { - return; // No glass selected - } +// Draw cluster properties window (shows all glasses in tight cluster) +void draw_cluster_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { + if (view->selectedCluster < 0 || !view->tightClusters) return; - const Glass* glass = get_glass(view->selectedGlass); - if (!glass) return; // Safety check + const TightCluster* cluster = &view->tightClusters[view->selectedCluster]; + if (cluster->count == 0) return; - // Calculate glass position on screen - i32 glassX, glassY; - data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); + // Calculate cluster position on screen using representative glass + const Glass* repGlass = get_glass(cluster->representativeIndex); + if (!repGlass) return; + + i32 clusterX, clusterY; + data_to_screen_coords(repGlass->abbeNumber, repGlass->refractiveIndex, view, &clusterX, &clusterY); - // Define property window size - const i32 windowWidth = 200; - const i32 windowHeight = 100; - const i32 padding = 10; + // Window properties + const i32 windowWidth = 320; + const i32 windowHeight = 20 + cluster->count * 25; - // Use smart positioning i32 windowX, windowY; - calculate_smart_window_position(glassX, glassY, windowWidth, windowHeight, view, &windowX, &windowY); + calculate_smart_window_position(clusterX, clusterY, windowWidth, windowHeight, view, &windowX, &windowY); - // Draw background with slight transparency - SDL_FRect windowRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 245, 245, 245, 235); - SDL_RenderFillRect(renderer, &windowRect); + // Draw background + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 220); + SDL_FRect bgRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; + SDL_RenderFillRect(renderer, &bgRect); // Draw border - SDL_SetRenderDrawColor(renderer, 40, 40, 40, 255); - SDL_RenderRect(renderer, &windowRect); - - // Draw glass name as title - SDL_Color darkBlue = {0, 0, 120, 255}; - draw_text(renderer, titleFont, (const char*)glass->name, windowX + padding, windowY + padding, darkBlue); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderRect(renderer, &bgRect); - // Draw properties + // Draw text SDL_Color black = {0, 0, 0, 255}; - char buffer[64]; - - // Refractive Index - sprintf(buffer, "n = %.4f", glass->refractiveIndex); - draw_text(renderer, font, buffer, windowX + padding, windowY + padding + 30, black); + char buffer[256]; - // Abbe Number - sprintf(buffer, "V = %.2f", glass->abbeNumber); - draw_text(renderer, font, buffer, windowX + padding, windowY + padding + 55, black); - - // Reset blend mode - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + // Draw each glass in cluster directly (no title) + for (i32 i = 0; i < cluster->count; i++) { + const Glass* glass = get_glass(cluster->glassIndices[i]); + if (glass) { + i32 yPos = windowY + 10 + i * 25; + + snprintf(buffer, sizeof(buffer), "%s: nd=%.4f, vd=%.2f", + (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber); + draw_text(renderer, font, buffer, windowX + 10, yPos, black); + } + } } -// Function to draw properties for a cluster of similar glasses -void draw_cluster_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont __attribute__((unused)), const ViewState* view, GlassCluster* clusters, i32 clusterCount) { - if (view->selectedCluster < 0 || view->selectedCluster >= clusterCount) { - return; // No cluster selected - } +// Draw glass properties window +void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { + if (view->selectedGlass < 0) return; - const GlassCluster* cluster = &clusters[view->selectedCluster]; + const Glass* glass = get_glass(view->selectedGlass); + if (!glass) return; - // Calculate cluster position on screen - i32 clusterX, clusterY; - data_to_screen_coords(cluster->avgAbbeNumber, cluster->avgRefractiveIndex, view, &clusterX, &clusterY); + // Calculate glass position on screen + i32 glassX, glassY; + data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); - // Define property window size (dynamic height based on glass count) - const i32 windowWidth = 280; - const i32 windowHeight = 20 + cluster->count * 25; - const i32 padding = 10; + // Window properties + const i32 windowWidth = 300; + const i32 windowHeight = 120; - // Use smart positioning i32 windowX, windowY; - calculate_smart_window_position(clusterX, clusterY, windowWidth, windowHeight, view, &windowX, &windowY); + calculate_smart_window_position(glassX, glassY, windowWidth, windowHeight, view, &windowX, &windowY); - // Draw background with slight transparency - SDL_FRect windowRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 245, 245, 245, 235); - SDL_RenderFillRect(renderer, &windowRect); + // Draw background + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 200); + SDL_FRect bgRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; + SDL_RenderFillRect(renderer, &bgRect); // Draw border - SDL_SetRenderDrawColor(renderer, 40, 40, 40, 255); - SDL_RenderRect(renderer, &windowRect); + SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); + SDL_RenderRect(renderer, &bgRect); - // Draw properties for each glass in cluster directly (no title) + // Draw text SDL_Color black = {0, 0, 0, 255}; - for (i32 i = 0; i < cluster->count; i++) { - const Glass* glass = get_glass(cluster->glassIndices[i]); - if (!glass) continue; - - char buffer[128]; - i32 yPos = windowY + padding + i * 25; - - sprintf(buffer, "%s: n=%.4f, V=%.2f", (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber); - draw_text(renderer, font, buffer, windowX + padding, yPos, black); - } - // Reset blend mode - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE); + 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); + + snprintf(buffer, sizeof(buffer), "Manufacturer: %s", (const char*)glass->manufacturer); + draw_text(renderer, font, buffer, windowX + 10, windowY + 80, black); } -// Function to draw the help window +// Draw help window void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) { - // Background for help window - i32 width = view->windowWidth - 200; - i32 height = view->windowHeight - 200; - i32 x = (view->windowWidth - width) / 2; - i32 y = (view->windowHeight - height) / 2; + if (!view->showHelp) return; + + const i32 windowWidth = 400; + const i32 windowHeight = 300; + const i32 windowX = (view->windowWidth - windowWidth) / 2; + const i32 windowY = (view->windowHeight - windowHeight) / 2; - SDL_FRect helpRect = {(f32)x, (f32)y, (f32)width, (f32)height}; - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - SDL_SetRenderDrawColor(renderer, 245, 245, 245, 230); - SDL_RenderFillRect(renderer, &helpRect); + // Draw background + SDL_SetRenderDrawColor(renderer, 255, 255, 255, 240); + SDL_FRect bgRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight}; + SDL_RenderFillRect(renderer, &bgRect); // Draw border SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderRect(renderer, &helpRect); + SDL_RenderRect(renderer, &bgRect); - // Draw help title + // Draw help text SDL_Color black = {0, 0, 0, 255}; - draw_text(renderer, titleFont, "Keyboard Shortcuts", x + width/2 - 100, y + 20, black); - - // Define all shortcuts - const char* shortcuts[] = { - "g? - Show/hide this help window", - "ESC, q - Quit application", - "+/- - Zoom in/out", - "h, j, k, l - Pan left, down, up, right", - "Shift+H, Shift+L - Previous/next catalog", - "r - Reset view to original position", - "f - Toggle fullscreen mode", - "Mouse wheel - Zoom in/out at cursor", - "Mouse drag - Pan the view", - "Mouse click - Select glass to see properties" - }; - - const i32 numShortcuts = sizeof(shortcuts) / sizeof(shortcuts[0]); - const i32 lineHeight = 30; - const i32 startY = y + 70; - - // Draw all shortcuts - for (i32 i = 0; i < numShortcuts; i++) { - draw_text(renderer, font, shortcuts[i], x + 50, startY + i * lineHeight, black); - } - - // Draw exit instruction - draw_text(renderer, font, "Press g? again, q or ESC to close this window", - x + width/2 - 150, y + height - 40, black); + i32 yPos = windowY + 20; + + draw_text(renderer, titleFont, "GlaMaC Help", windowX + 20, yPos, black); + yPos += 40; + + draw_text(renderer, font, "Mouse Controls:", windowX + 20, yPos, black); + yPos += 25; + draw_text(renderer, font, " Click: Select glass", windowX + 30, yPos, black); + yPos += 20; + draw_text(renderer, font, " Drag: Pan view", windowX + 30, yPos, black); + yPos += 20; + draw_text(renderer, font, " Wheel: Zoom in/out", windowX + 30, yPos, black); + yPos += 30; + + draw_text(renderer, font, "Keyboard Controls:", windowX + 20, yPos, black); + yPos += 25; + draw_text(renderer, font, " g?: Show/hide this help", windowX + 30, yPos, black); + yPos += 20; + draw_text(renderer, font, " r: Reset view", windowX + 30, yPos, black); + yPos += 20; + draw_text(renderer, font, " ESC: Clear selection or quit", windowX + 30, yPos, black); + yPos += 20; + draw_text(renderer, font, " q: Quit", windowX + 30, yPos, black); + yPos += 30; + + draw_text(renderer, font, "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, GlassCluster* clusters, i32 clusterCount) { - const SDL_Color black = {0, 0, 0, 255}; - - // Only recalculate label positions when view parameters change - if (needs_label_recalculation(view)) { - recalculate_label_positions(view, clusters, clusterCount); - } - - // Clear screen (white background) +void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, ViewState* view) { + // Clear screen SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderClear(renderer); // Draw grid - SDL_SetRenderDrawColor(renderer, 220, 220, 220, 255); draw_grid(renderer, view); - // Draw axes with correct visible range + // Draw axes draw_axes(renderer, font, titleFont, view); // Draw glass points and labels - draw_glass_points(renderer, labelFont, view, clusters, clusterCount); + draw_glass_points(renderer, labelFont, view); - // Draw cluster properties if a cluster is selected - if (view->selectedCluster >= 0) { - draw_cluster_properties(renderer, font, titleFont, view, clusters, clusterCount); - } - // Fallback to individual glass properties for backward compatibility - else if (view->selectedGlass >= 0) { - draw_glass_properties(renderer, font, titleFont, view); - } - - // Add catalog name at top center - const char* catalogName = get_current_catalog_name(); - i32 catalogTextWidth; - TTF_GetStringSize(titleFont, catalogName, 0, &catalogTextWidth, NULL); - draw_text(renderer, titleFont, catalogName, (view->windowWidth - catalogTextWidth) / 2, 20, black); + // Draw glass properties if selected + draw_glass_properties(renderer, font, titleFont, view); - // Add a small hint about help in the corner - draw_text(renderer, labelFont, "Press g? for help", 10, (i32)(0.975*view->windowHeight), black); + // Draw cluster properties if selected + draw_cluster_properties(renderer, font, titleFont, view); - // Draw help window if toggled - if (view->showHelp) { - draw_help_window(renderer, font, titleFont, view); - } + // Draw help window if needed + draw_help_window(renderer, font, titleFont, view); - // Update screen + // Present the rendered frame SDL_RenderPresent(renderer); } -// Font management - -// Helper function to try loading a font with fallbacks -static TTF_Font* try_load_font_from_paths(const char** fontNames, i32 numFonts, i32 size) { - TTF_Font* font = NULL; - - // Try all font paths - for (i32 i = 0; i < numFonts; i++) { - font = TTF_OpenFont(fontNames[i], size); - if (font) { - printf("Successfully loaded font: %s (size %d)\n", fontNames[i], size); - return font; - } - } - - // If all specific paths fail, try some generic system approaches - #ifndef _WIN32 - // Try to find any sans-serif font using common naming patterns - const char* genericFonts[] = { - "/usr/share/fonts/truetype/droid/DroidSans.ttf", - "/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf", - "/usr/share/fonts/TTF/DroidSans.ttf", - "/usr/share/fonts/noto/NotoSans-Regular.ttf", - "/usr/share/fonts/google-noto/NotoSans-Regular.ttf", - }; - - const i32 numGeneric = sizeof(genericFonts) / sizeof(genericFonts[0]); - for (i32 i = 0; i < numGeneric; i++) { - font = TTF_OpenFont(genericFonts[i], size); - if (font) { - printf("Successfully loaded generic font: %s (size %d)\n", genericFonts[i], size); - return font; - } - } - #endif - - return NULL; -} - -// Helper function to calculate DPI-aware font sizes -static void calculate_font_sizes(i32 windowWidth, i32 windowHeight, f32 dpi, i32* regular, i32* title, i32* label) { - // Base sizes for reference resolution (1920x1080) - const i32 baseRegular = 14; - const i32 baseTitle = 18; - const i32 baseLabel = 12; - - // Calculate scale factor based on window size and DPI - f32 sizeScale = (windowWidth + windowHeight) / 1600.0f; // Average of 1920x1080 - f32 dpiScale = dpi / 96.0f; // 96 DPI is standard - f32 totalScale = sizeScale * dpiScale; - - // Clamp scale factor to reasonable bounds - if (totalScale < 0.7f) totalScale = 0.7f; - if (totalScale > 2.5f) totalScale = 2.5f; - - *regular = (i32)(baseRegular * totalScale); - *title = (i32)(baseTitle * totalScale); - - // Apply configurable label scaling - f32 labelScale = totalScale * LABEL_SIZE_SCALE; // Use configurable scale factor - if (labelScale < 0.7f) labelScale = 0.7f; - if (labelScale > 2.0f) labelScale = 2.0f; // Allow larger labels - *label = (i32)(baseLabel * labelScale); +// Font management functions +b32 load_fonts(FontSet *fonts) { + fonts->regular = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 16); + fonts->title = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", 20); + fonts->label = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 14); - // Ensure minimum readable sizes - if (*regular < 10) *regular = 10; - if (*title < 12) *title = 12; - if (*label < 8) *label = 8; // Smaller minimum for labels + return fonts->regular && fonts->title && fonts->label; } -// Load fonts with DPI-aware sizing b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) { - // Common font names to try, in order of preference - const char* fontNames[] = { - #ifdef _WIN32 - // Try bundled font first for Windows builds - "DejaVuSans.ttf", - "./DejaVuSans.ttf", - // Then try Windows system fonts - "C:\\Windows\\Fonts\\arial.ttf", - "C:\\Windows\\Fonts\\tahoma.ttf", - "C:\\Windows\\Fonts\\verdana.ttf", - "C:\\Windows\\Fonts\\calibri.ttf", - #else - // Ubuntu/Debian paths - "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", - "/usr/share/fonts/truetype/freefont/FreeSans.ttf", - // Arch Linux paths - "/usr/share/fonts/TTF/DejaVuSans.ttf", - "/usr/share/fonts/TTF/LiberationSans-Regular.ttf", - "/usr/share/fonts/gnu-free/FreeSans.ttf", - // Fedora/CentOS paths - "/usr/share/fonts/dejavu/DejaVuSans.ttf", - "/usr/share/fonts/liberation/LiberationSans-Regular.ttf", - // OpenSUSE paths - "/usr/share/fonts/truetype/DejaVuSans.ttf", - // Alternative common locations - "/usr/share/fonts/TTF/arial.ttf", - "/usr/share/fonts/truetype/msttcorefonts/arial.ttf", - "/System/Library/Fonts/Arial.ttf", // macOS - // Try local fonts directory - "./fonts/DejaVuSans.ttf", - "./DejaVuSans.ttf", - #endif - }; - - const i32 numFonts = sizeof(fontNames) / sizeof(fontNames[0]); - i32 regularSize, titleSize, labelSize; - calculate_font_sizes(windowWidth, windowHeight, dpi, ®ularSize, &titleSize, &labelSize); - - printf("Adaptive font sizes: regular=%d, title=%d, label=%d\n", regularSize, titleSize, labelSize); - - // Load regular font - fonts->regular = try_load_font_from_paths(fontNames, numFonts, regularSize); - if (!fonts->regular) { - printf("Failed to load any regular font!\n"); - printf("SDL_Error: %s\n", SDL_GetError()); - #ifndef _WIN32 - printf("Suggestion: Install fonts with: sudo apt install fonts-dejavu-core fonts-liberation (Ubuntu/Debian)\n"); - printf(" sudo pacman -S ttf-dejavu ttf-liberation (Arch)\n"); - printf(" sudo dnf install dejavu-sans-fonts liberation-fonts (Fedora)\n"); - #endif - return 0; - } - - // Load title font (try larger size first, fallback to regular font) - fonts->title = try_load_font_from_paths(fontNames, numFonts, titleSize); - if (!fonts->title) { - printf("Warning: Could not load title font, using regular font\n"); - fonts->title = fonts->regular; // Use the same font - } - - // Load label font (try smaller size first, fallback to regular font) - fonts->label = try_load_font_from_paths(fontNames, numFonts, labelSize); - if (!fonts->label) { - printf("Warning: Could not load label font, using regular font\n"); - fonts->label = fonts->regular; // Use the same font - } - - return 1; + (void)windowHeight; // Suppress unused parameter warning + // Calculate font sizes based on window size and DPI (increased sizes) + i32 regularSize = (i32)(16.0f * dpi / 96.0f * windowWidth / 800.0f); + i32 titleSize = (i32)(20.0f * dpi / 96.0f * windowWidth / 800.0f); + i32 labelSize = (i32)(14.0f * dpi / 96.0f * windowWidth / 800.0f); + + // Clamp font sizes + if (regularSize < 12) regularSize = 12; + if (regularSize > 32) regularSize = 32; + if (titleSize < 16) titleSize = 16; + if (titleSize > 36) titleSize = 36; + if (labelSize < 10) labelSize = 10; + if (labelSize > 24) labelSize = 24; + + fonts->regular = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", regularSize); + fonts->title = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", titleSize); + fonts->label = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", labelSize); + + return fonts->regular && fonts->title && fonts->label; } -// Load all required fonts (legacy function for backward compatibility) -b32 load_fonts(FontSet *fonts) { - // Use default sizes - this function is for backward compatibility - return load_adaptive_fonts(fonts, 1920, 1080, 96.0f); -} - -// Free all fonts void free_fonts(FontSet *fonts) { - // Only close fonts that are different from regular font - if (fonts->regular) TTF_CloseFont(fonts->regular); - if (fonts->title && fonts->title != fonts->regular) TTF_CloseFont(fonts->title); - if (fonts->label && fonts->label != fonts->regular) TTF_CloseFont(fonts->label); -} - -// Clear text cache -void clear_text_cache(void) { - for (i32 i = 0; i < cacheCount; i++) { - if (textCache[i].texture) { - SDL_DestroyTexture(textCache[i].texture); - textCache[i].texture = NULL; - } + if (fonts->regular) { + TTF_CloseFont(fonts->regular); + fonts->regular = NULL; } - cacheCount = 0; -} + if (fonts->title) { + TTF_CloseFont(fonts->title); + fonts->title = NULL; + } + if (fonts->label) { + TTF_CloseFont(fonts->label); + fonts->label = NULL; + } +} \ No newline at end of file diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c index ee75762..ff85b71 100644 --- a/src/glamac/glamac_view.c +++ b/src/glamac/glamac_view.c @@ -2,11 +2,26 @@ * glamac_view.c - Source file dealing with view states, filtering, zooming etc. of GlaMaC. */ #include +#include #include +#include #include #include "glamac_view.h" #include "glass_data.h" +// External debug mode function +extern b32 is_debug_mode(void); + +// Global tight cluster threshold parameters +f32 g_tight_cluster_nd_threshold = DEFAULT_TIGHT_CLUSTER_ND_THRESHOLD; +f32 g_tight_cluster_vd_threshold = DEFAULT_TIGHT_CLUSTER_VD_THRESHOLD; + +// Global loose cluster parameters (zoom-dependent) +f32 g_loose_cluster_nd_threshold = DEFAULT_LOOSE_CLUSTER_ND_THRESHOLD; // Base threshold +f32 g_loose_cluster_vd_threshold = DEFAULT_LOOSE_CLUSTER_VD_THRESHOLD; // Base threshold +f32 g_loose_cluster_nd_fraction = DEFAULT_LOOSE_CLUSTER_ND_FRACTION; // Zoom scaling fraction +f32 g_loose_cluster_vd_fraction = DEFAULT_LOOSE_CLUSTER_VD_FRACTION; // Zoom scaling fraction + // Initialize a view state with default values void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { view->zoomLevel = 1.0f; @@ -20,6 +35,14 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) { view->selectedGlass = -1; // No glass selected initially view->selectedCluster = -1; // No cluster selected initially + // Initialize tight cluster data + view->tightClusters = NULL; + view->tightClusterCount = 0; + + // Initialize loose cluster data + view->looseClusters = NULL; + view->looseClusterCount = 0; + // Initialize label recalculation tracking (force initial calculation) view->lastZoomLevel = -1.0f; view->lastWindowWidth = -1; @@ -109,6 +132,9 @@ void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view const i32 padding = get_adaptive_padding(view); view->offsetX += (f32)(mouseX - newMouseScreenX) / (view->windowWidth - 2 * padding); view->offsetY += (f32)(newMouseScreenY - mouseY) / (view->windowHeight - 2 * padding); + + // Recreate loose clusters since zoom changed (they are zoom-dependent) + create_loose_clusters(view); } // Toggle fullscreen @@ -128,57 +154,263 @@ void reset_view(ViewState* view) { view->offsetX = 0.0f; view->offsetY = 0.0f; view->selectedGlass = -1; + + // Recreate loose clusters since zoom changed + create_loose_clusters(view); +} + +// Check if two glasses should be clustered based on separate nd and vd thresholds +static inline b32 should_cluster_glasses(const Glass* glass1, const Glass* glass2) { + f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); + f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); + + return (nd_diff <= g_tight_cluster_nd_threshold) && (vd_diff <= g_tight_cluster_vd_threshold); +} + +// Find glass with shortest name in a list +static i32 find_shortest_name_glass(const i32* glassIndices, i32 count) { + if (count == 0) return -1; + + i32 shortestIndex = glassIndices[0]; + size_t shortestLength = strlen((const char*)get_glass(shortestIndex)->name); + + for (i32 i = 1; i < count; i++) { + const Glass* glass = get_glass(glassIndices[i]); + if (glass) { + size_t nameLength = strlen((const char*)glass->name); + if (nameLength < shortestLength) { + shortestLength = nameLength; + shortestIndex = glassIndices[i]; + } + } + } + + return shortestIndex; +} + +// Create tight clusters for current catalog +void create_tight_clusters(ViewState* view) { + // Free existing clusters + free_tight_clusters(view); + + const u32 glassCount = get_glass_count(); + if (glassCount == 0) return; + + // Allocate temporary arrays + TightCluster* clusters = (TightCluster*)calloc(glassCount, sizeof(TightCluster)); + b32* processed = (b32*)calloc(glassCount, sizeof(b32)); + i32 clusterCount = 0; + + if (!clusters || !processed) { + free(clusters); + free(processed); + return; + } + + printf("Creating tight clusters for %s catalog (%u glasses, nd_threshold=%.6f, vd_threshold=%.3f)...\n", + get_current_catalog_name(), glassCount, g_tight_cluster_nd_threshold, g_tight_cluster_vd_threshold); + + // Simple clustering algorithm + for (u32 i = 0; i < glassCount; i++) { + if (processed[i]) continue; + + const Glass* glass1 = get_glass(i); + if (!glass1) continue; + + // Start new cluster with this glass + TightCluster* cluster = &clusters[clusterCount]; + cluster->glassIndices[0] = (i32)i; + cluster->count = 1; + cluster->avgAbbeNumber = glass1->abbeNumber; + cluster->avgRefractiveIndex = glass1->refractiveIndex; + processed[i] = 1; + + // Find nearby glasses within tight clustering distance + for (u32 j = i + 1; j < glassCount && cluster->count < MAX_CLUSTER_SIZE; j++) { + if (processed[j]) continue; + + const Glass* glass2 = get_glass(j); + if (!glass2) continue; + + if (should_cluster_glasses(glass1, glass2)) { + // Add to cluster + cluster->glassIndices[cluster->count] = (i32)j; + cluster->count++; + + // Update average position + cluster->avgAbbeNumber = (cluster->avgAbbeNumber * (cluster->count - 1) + glass2->abbeNumber) / cluster->count; + cluster->avgRefractiveIndex = (cluster->avgRefractiveIndex * (cluster->count - 1) + glass2->refractiveIndex) / cluster->count; + + processed[j] = 1; + } + } + + // Set representative glass (shortest name) + cluster->representativeIndex = find_shortest_name_glass(cluster->glassIndices, cluster->count); + + clusterCount++; + } + + // Count multi-glass clusters + i32 multiGlassClusters = 0; + for (i32 i = 0; i < clusterCount; i++) { + if (clusters[i].count > 1) { + multiGlassClusters++; + } + } + + printf("Created %d tight clusters (%d with multiple glasses)\n", clusterCount, multiGlassClusters); + + // Debug output: show detailed cluster information + if (is_debug_mode()) { + printf("\n=== TIGHT CLUSTER DEBUG INFO ===\n"); + printf("Catalog: %s, nd_threshold: %.6f, vd_threshold: %.3f\n", + get_current_catalog_name(), g_tight_cluster_nd_threshold, g_tight_cluster_vd_threshold); + + for (i32 i = 0; i < clusterCount; i++) { + const TightCluster* cluster = &clusters[i]; + if (cluster->count > 1) { + printf("\nCluster %d (%d glasses):\n", i, cluster->count); + for (i32 j = 0; j < cluster->count; j++) { + const Glass* glass = get_glass(cluster->glassIndices[j]); + if (glass) { + printf(" %s: nd=%.6f, vd=%.3f", + (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber); + if (cluster->glassIndices[j] == cluster->representativeIndex) { + printf(" [REPRESENTATIVE]"); + } + printf("\n"); + } + } + + // Calculate and display pairwise distances within cluster + printf(" Pairwise distances:\n"); + for (i32 j = 0; j < cluster->count; j++) { + for (i32 k = j + 1; k < cluster->count; k++) { + const Glass* glass1 = get_glass(cluster->glassIndices[j]); + const Glass* glass2 = get_glass(cluster->glassIndices[k]); + if (glass1 && glass2) { + f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); + f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); + printf(" %s <-> %s: nd_diff=%.6f, vd_diff=%.3f\n", + (const char*)glass1->name, (const char*)glass2->name, nd_diff, vd_diff); + } + } + } + } + } + printf("=== END CLUSTER DEBUG INFO ===\n\n"); + } + + // Resize to actual number of clusters and store + if (clusterCount > 0) { + TightCluster* resizedClusters = (TightCluster*)realloc(clusters, clusterCount * sizeof(TightCluster)); + if (!resizedClusters) { + free(clusters); + view->tightClusters = NULL; + view->tightClusterCount = 0; + } else { + view->tightClusters = resizedClusters; + view->tightClusterCount = clusterCount; + } + } else { + free(clusters); + view->tightClusters = NULL; + view->tightClusterCount = 0; + } + + free(processed); +} + +// Free tight clusters +void free_tight_clusters(ViewState* view) { + if (view->tightClusters) { + free(view->tightClusters); + view->tightClusters = NULL; + } + view->tightClusterCount = 0; view->selectedCluster = -1; } -// Create clusters from glasses with similar properties -GlassCluster* create_glass_clusters(i32* clusterCount) { - const f32 SIMILARITY_THRESHOLD = 0.004f; // 0.4% difference threshold - const i32 maxGlasses = get_glass_count(); +// Create loose clusters for current catalog (zoom-dependent) +void create_loose_clusters(ViewState* view) { + // Free existing clusters + free_loose_clusters(view); + + const u32 glassCount = get_glass_count(); + if (glassCount == 0) return; + + // Calculate zoom-dependent thresholds that reach ZERO at maximum zoom + f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; + get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - // Allocate temporary structures - GlassCluster* clusters = (GlassCluster*)calloc(maxGlasses, sizeof(GlassCluster)); - b32* processed = (b32*)calloc(maxGlasses, sizeof(b32)); - i32 numClusters = 0; + // Calculate zoom factor based on zoom level (0.0 at max zoom, 1.0 at min zoom) + // At MAX_ZOOM, factor = 0; At MIN_ZOOM, factor = 1 + f32 zoomRange = MAX_ZOOM - MIN_ZOOM; + f32 currentZoomNormalized = (view->zoomLevel - MIN_ZOOM) / zoomRange; + + // Invert so that factor = 0 at max zoom, factor = 1 at min zoom + f32 zoomFactor = 1.0f - (currentZoomNormalized > 1.0f ? 1.0f : currentZoomNormalized); + + // Ensure we reach exactly zero at maximum zoom + if (view->zoomLevel >= MAX_ZOOM) { + zoomFactor = 0.0f; + } + + // Calculate thresholds: ZERO at max zoom, base*fraction at min zoom + f32 ndThreshold = zoomFactor * g_loose_cluster_nd_threshold * g_loose_cluster_nd_fraction; + f32 vdThreshold = zoomFactor * g_loose_cluster_vd_threshold * g_loose_cluster_vd_fraction; + + // Allocate temporary arrays + LooseCluster* clusters = (LooseCluster*)calloc(glassCount, sizeof(LooseCluster)); + b32* processed = (b32*)calloc(glassCount, sizeof(b32)); + i32 clusterCount = 0; if (!clusters || !processed) { - if (clusters) free(clusters); - if (processed) free(processed); - *clusterCount = 0; - return NULL; + free(clusters); + free(processed); + return; } - for (i32 i = 0; i < (i32)maxGlasses; i++) { + printf("Creating loose clusters for %s catalog (%u glasses)...\n", get_current_catalog_name(), glassCount); + printf(" zoom level: %.2f, zoom factor: %.4f\n", view->zoomLevel, zoomFactor); + printf(" nd: %.3f * %.1f * %.4f = %.6f\n", + g_loose_cluster_nd_threshold, g_loose_cluster_nd_fraction, zoomFactor, ndThreshold); + printf(" vd: %.2f * %.1f * %.4f = %.3f\n", + g_loose_cluster_vd_threshold, g_loose_cluster_vd_fraction, zoomFactor, vdThreshold); + + // Simple clustering algorithm (same as tight clustering but with zoom-dependent thresholds) + for (u32 i = 0; i < glassCount; i++) { if (processed[i]) continue; const Glass* glass1 = get_glass(i); if (!glass1) continue; // Start new cluster with this glass - GlassCluster* cluster = &clusters[numClusters]; - cluster->glassIndices[0] = i; + LooseCluster* cluster = &clusters[clusterCount]; + cluster->glassIndices[0] = (i32)i; cluster->count = 1; cluster->avgAbbeNumber = glass1->abbeNumber; cluster->avgRefractiveIndex = glass1->refractiveIndex; processed[i] = 1; - // Find similar glasses to add to this cluster - for (i32 j = i + 1; j < (i32)maxGlasses && cluster->count < MAX_CLUSTER_SIZE; j++) { + // Find nearby glasses within loose clustering distance + for (u32 j = i + 1; j < glassCount && cluster->count < MAX_CLUSTER_SIZE; j++) { if (processed[j]) continue; const Glass* glass2 = get_glass(j); if (!glass2) continue; - // Check if glasses are similar (within 1% difference) - f32 abbeDiff = fabsf(glass1->abbeNumber - glass2->abbeNumber) / glass1->abbeNumber; - f32 riDiff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex) / glass1->refractiveIndex; + // Check if glasses should be clustered (separate nd/vd thresholds) + f32 nd_diff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex); + f32 vd_diff = fabsf(glass1->abbeNumber - glass2->abbeNumber); - if (abbeDiff < SIMILARITY_THRESHOLD && riDiff < SIMILARITY_THRESHOLD) { - // Add to cluster and update averages - cluster->glassIndices[cluster->count] = j; + if (nd_diff <= ndThreshold && vd_diff <= vdThreshold) { + // Add to cluster + cluster->glassIndices[cluster->count] = (i32)j; cluster->count++; - // Update running average + // Update average position cluster->avgAbbeNumber = (cluster->avgAbbeNumber * (cluster->count - 1) + glass2->abbeNumber) / cluster->count; cluster->avgRefractiveIndex = (cluster->avgRefractiveIndex * (cluster->count - 1) + glass2->refractiveIndex) / cluster->count; @@ -186,50 +418,110 @@ GlassCluster* create_glass_clusters(i32* clusterCount) { } } - numClusters++; + // Set representative glass (shortest name) + cluster->representativeIndex = find_shortest_name_glass(cluster->glassIndices, cluster->count); + + clusterCount++; } - free(processed); + // Count multi-glass clusters + i32 multiGlassClusters = 0; + for (i32 i = 0; i < clusterCount; i++) { + if (clusters[i].count > 1) { + multiGlassClusters++; + } + } - // Resize to actual number of clusters - GlassCluster* finalClusters = (GlassCluster*)realloc(clusters, numClusters * sizeof(GlassCluster)); - if (!finalClusters) { + printf("Created %d loose clusters (%d with multiple glasses)\n", clusterCount, multiGlassClusters); + + // Resize to actual number of clusters and store + if (clusterCount > 0) { + LooseCluster* resizedClusters = (LooseCluster*)realloc(clusters, clusterCount * sizeof(LooseCluster)); + if (!resizedClusters) { + free(clusters); + view->looseClusters = NULL; + view->looseClusterCount = 0; + } else { + view->looseClusters = resizedClusters; + view->looseClusterCount = clusterCount; + } + } else { free(clusters); - *clusterCount = 0; - return NULL; + view->looseClusters = NULL; + view->looseClusterCount = 0; } - *clusterCount = numClusters; - return finalClusters; + free(processed); } -// Free cluster memory -void free_glass_clusters(GlassCluster* clusters) { - if (clusters) { - free(clusters); +// Free loose clusters +void free_loose_clusters(ViewState* view) { + if (view->looseClusters) { + free(view->looseClusters); + view->looseClusters = NULL; } + view->looseClusterCount = 0; } -// Find cluster at screen position -i32 find_cluster_at_position(GlassCluster* clusters, i32 clusterCount, i32 x, i32 y, const ViewState* view) { - const f32 maxDistance = 15.0f; // Maximum distance in pixels - i32 nearest = -1; - f32 minDist = maxDistance; +// Find tight cluster that contains a specific glass +i32 find_tight_cluster_for_glass(i32 glassIndex, const ViewState* view) { + if (!view->tightClusters) return -1; - for (i32 i = 0; i < clusterCount; i++) { - i32 clusterX, clusterY; - data_to_screen_coords(clusters[i].avgAbbeNumber, clusters[i].avgRefractiveIndex, view, &clusterX, &clusterY); + for (i32 i = 0; i < view->tightClusterCount; i++) { + for (i32 j = 0; j < view->tightClusters[i].count; j++) { + if (view->tightClusters[i].glassIndices[j] == glassIndex) { + return i; // Return cluster index + } + } + } + + return -1; // Not in any cluster +} + +// Find loose cluster that contains a specific glass +i32 find_loose_cluster_for_glass(i32 glassIndex, const ViewState* view) { + if (!view->looseClusters) return -1; + + for (i32 i = 0; i < view->looseClusterCount; i++) { + for (i32 j = 0; j < view->looseClusters[i].count; j++) { + if (view->looseClusters[i].glassIndices[j] == glassIndex) { + return i; // Return cluster index + } + } + } + + return -1; // Not in any cluster +} + +// Check if a glass label should be shown (combined tight and loose clustering logic) +b32 should_show_glass_label(i32 glassIndex, const ViewState* view) { + // First check tight clusters (they have priority) + i32 tightClusterIndex = find_tight_cluster_for_glass(glassIndex, view); + + if (tightClusterIndex >= 0) { + // Glass is in a tight cluster + const TightCluster* cluster = &view->tightClusters[tightClusterIndex]; - // Calculate distance - const f32 dx = x - clusterX; - const f32 dy = y - clusterY; - const f32 dist = sqrtf(dx*dx + dy*dy); + if (cluster->count > 1) { + // Multi-glass tight cluster: only show representative (shortest name) + return glassIndex == cluster->representativeIndex; + } + } + + // Then check loose clusters (only if not in tight cluster) + i32 looseClusterIndex = find_loose_cluster_for_glass(glassIndex, view); + + if (looseClusterIndex >= 0) { + // Glass is in a loose cluster + const LooseCluster* cluster = &view->looseClusters[looseClusterIndex]; - if (dist < minDist) { - minDist = dist; - nearest = i; + if (cluster->count > 1) { + // Multi-glass loose cluster: only show representative (shortest name) + return glassIndex == cluster->representativeIndex; } } - return nearest; + // Single glass or not in any cluster: always show + return 1; } + diff --git a/src/glamac/glass_data.c b/src/glamac/glass_data.c index bcead81..32077de 100644 --- a/src/glamac/glass_data.c +++ b/src/glamac/glass_data.c @@ -374,7 +374,7 @@ static GlamacResult load_all_manufacturers_to_catalogs(const char* json_content, GlamacResult result = load_single_manufacturer_to_catalog(json_content, json_len, manufacturers[i], catalog); if (result == GLAMAC_SUCCESS) { - printf("Loaded %d glasses from %s\n", catalog->count, catalog->name); + // printf("Loaded %d glasses from %s\n", catalog->count, catalog->name); g_glass_context.catalog_count++; // Set first successful catalog as current @@ -631,4 +631,4 @@ void cleanup_glass_data(void) { g_glass_context.catalog_count = 0; g_glass_context.current_catalog = -1; g_glass_context.using_json_data = 0; -} \ No newline at end of file +} diff --git a/src/glautils/fgla.c b/src/glautils/fgla.c index f2ed8eb..baf2fef 100644 --- a/src/glautils/fgla.c +++ b/src/glautils/fgla.c @@ -442,54 +442,66 @@ int main(int argc, char* argv[]) { // Load all glasses first, then filter by catalog during display loaded = load_glasses_from_json((const byte*)successful_path, NULL); - u32 glass_count = get_glass_count(); + // Search across all catalogs + u32 total_catalog_count = get_catalog_count(); u32 found_count = 0; // Determine search type int is_glass_code_search = is_glass_code_pattern(search_term); - // Pre-allocate matches array for performance (avoids double iteration) - u32* matching_indices = malloc(glass_count * sizeof(u32)); - if (!matching_indices) { + // Pre-allocate matches array for performance - estimate max size across all catalogs + u32* matching_indices = malloc(10000 * sizeof(u32)); // Generous allocation for all glasses + u32* matching_catalogs = malloc(10000 * sizeof(u32)); // Track which catalog each match is from + if (!matching_indices || !matching_catalogs) { fprintf(stderr, "Error: Memory allocation failed\n"); + free(matching_indices); + free(matching_catalogs); cleanup_glass_data(); return FGLA_ERROR_MEMORY; } - // Single pass: find and store matches + // Search through all catalogs found_count = 0; - for (u32 i = 0; i < glass_count; i++) { - const Glass* glass = get_glass(i); - if (!glass) continue; - - const char* glass_name = (const char*)get_glass_name(i); - if (!glass_name) continue; - - int matches = 0; + for (u32 catalog_idx = 0; catalog_idx < total_catalog_count; catalog_idx++) { + set_current_catalog(catalog_idx); + u32 glass_count = get_glass_count(); - if (is_glass_code_search) { - // Glass code pattern search - const char* glass_code = (const char*)glass->glass_code; - if (glass_code && matches_glass_code_pattern_safe(glass_code, search_term)) { - matches = 1; + for (u32 i = 0; i < glass_count; i++) { + const Glass* glass = get_glass(i); + if (!glass) continue; + + const char* glass_name = (const char*)get_glass_name(i); + if (!glass_name) continue; + + int matches = 0; + + if (is_glass_code_search) { + // Glass code pattern search + const char* glass_code = (const char*)glass->glass_code; + if (glass_code && matches_glass_code_pattern_safe(glass_code, search_term)) { + matches = 1; + } + } else { + // Name search + if (contains_substring_safe(glass_name, search_term)) { + matches = 1; + } } - } else { - // Name search - if (contains_substring_safe(glass_name, search_term)) { - matches = 1; + + // Check if this glass matches the search term and catalog filter + if (matches && matches_catalog((const char*)glass->manufacturer, catalog_list, catalog_count)) { + matching_indices[found_count] = i; + matching_catalogs[found_count] = catalog_idx; + found_count++; } } - - // Check if this glass matches the search term and catalog filter - if (matches && matches_catalog((const char*)glass->manufacturer, catalog_list, catalog_count)) { - matching_indices[found_count++] = i; - } } // Output results if (found_count == 0) { print_error_with_suggestion(FGLA_ERROR_NO_MATCHES, search_term); free(matching_indices); + free(matching_catalogs); cleanup_glass_data(); return FGLA_ERROR_NO_MATCHES; } @@ -500,6 +512,11 @@ int main(int argc, char* argv[]) { // Display matches from stored indices for (u32 j = 0; j < found_count; j++) { u32 i = matching_indices[j]; + u32 catalog_idx = matching_catalogs[j]; + + // Set the correct catalog for this glass + set_current_catalog(catalog_idx); + const Glass* glass = get_glass(i); const char* glass_name = (const char*)get_glass_name(i); @@ -512,6 +529,7 @@ int main(int argc, char* argv[]) { print_footer(output_format); free(matching_indices); + free(matching_catalogs); // Cleanup cleanup_glass_data(); diff --git a/test.txt b/test.txt new file mode 100644 index 0000000..df03815 --- /dev/null +++ b/test.txt @@ -0,0 +1,302 @@ +Debug mode enabled +Running in debug mode (no GUI) +Trying JSON path: data/json/glasses.json +Loaded 156 glasses from SCHOTT +Loaded 211 glasses from HOYA +Loaded 324 glasses from CDGM +Loaded 136 glasses from Ohara +Using catalog: CDGM +Creating clusters for CDGM catalog (324 glasses)... + → ADDING [2] H-FK61B to TIGHT cluster 1 (distance=0.000000 from [1] H-FK61) + → ADDING [243] D-FK61 to TIGHT cluster 1 (distance=0.000000 from [1] H-FK61) + → ADDING [245] D-FK61A to TIGHT cluster 1 (distance=0.000000 from [1] H-FK61) +Cluster 1 (TIGHT, 4 members): H-FK61, H-FK61B, D-FK61... + → ADDING [247] D-FK95 to TIGHT cluster 3 (distance=0.000000 from [4] H-FK95N) +Cluster 3 (TIGHT, 2 members): H-FK95N, D-FK95 + → ADDING [249] D-QK3L to TIGHT cluster 5 (distance=0.000000 from [6] H-QK3L) + → ADDING [322] FC5 to TIGHT cluster 5 (distance=0.000000 from [6] H-QK3L) +Cluster 5 (TIGHT, 3 members): H-QK3L, D-QK3L, FC5 + → ADDING D-ZK3 to LOOSE cluster (max distance=0.128761) + → ADDING D-ZK3A to LOOSE cluster (max distance=0.095283) + → ADDING D-ZK3A-25 to LOOSE cluster (max distance=0.140006) +Cluster 9 (LOOSE, 4 members): K4A, D-ZK3, D-ZK3A... + → ADDING H-ZPK1A to LOOSE cluster (max distance=0.112022) + → ADDING H-BaK4 to LOOSE cluster (max distance=0.068505) + → ADDING D-K59 to LOOSE cluster (max distance=0.148930) +Cluster 10 (LOOSE, 4 members): H-K5, H-ZPK1A, H-BaK4... + → ADDING H-BaK1 to LOOSE cluster (max distance=0.021614) + → ADDING H-ZK7A to LOOSE cluster (max distance=0.142820) +Cluster 11 (LOOSE, 3 members): H-K6, H-BaK1, H-ZK7A + → ADDING H-BaK6 to LOOSE cluster (max distance=0.138961) + → REJECTING H-ZK7 from LOOSE cluster (distance=0.186602 from H-BaK6 > 0.150000) + → REJECTING H-ZK14 from LOOSE cluster (distance=0.164739 from H-BaK6 > 0.150000) + → ADDING D-ZK3L to LOOSE cluster (max distance=0.104003) + → ADDING D-ZK3L-25 to LOOSE cluster (max distance=0.142583) +Cluster 12 (LOOSE, 4 members): H-K7, H-BaK6, D-ZK3L... + → ADDING H-ZK10 to LOOSE cluster (max distance=0.132866) +Cluster 13 (LOOSE, 2 members): H-K8, H-ZK10 + → ADDING [16] H-K9LGT to TIGHT cluster 14 (distance=0.000000 from [15] H-K9L) + → ADDING [17] H-K9L* to TIGHT cluster 14 (distance=0.000000 from [15] H-K9L) + → ADDING [18] H-K9LA to TIGHT cluster 14 (distance=0.000000 from [15] H-K9L) +Cluster 14 (TIGHT, 4 members): H-K9L, H-K9LGT, H-K9L*... + → ADDING H-ZK4 to LOOSE cluster (max distance=0.127726) +Cluster 15 (LOOSE, 2 members): H-K10, H-ZK4 + → ADDING H-LaK4L to LOOSE cluster (max distance=0.120456) +Cluster 16 (LOOSE, 2 members): H-K11, H-LaK4L + → ADDING H-ZK11 to LOOSE cluster (max distance=0.106839) +Cluster 17 (LOOSE, 2 members): H-K12, H-ZK11 + → ADDING H-ZK2 to LOOSE cluster (max distance=0.063853) + → ADDING D-ZK2 to LOOSE cluster (max distance=0.116948) + → REJECTING D-ZK2A from LOOSE cluster (distance=0.160000 from D-ZK2 > 0.150000) + → ADDING D-ZK2A-25 to LOOSE cluster (max distance=0.070005) +Cluster 18 (LOOSE, 4 members): H-K50, H-ZK2, D-ZK2... + → ADDING H-ZK6 to LOOSE cluster (max distance=0.107874) +Cluster 19 (LOOSE, 2 members): H-K51, H-ZK6 + → ADDING D-ZPK3 to LOOSE cluster (max distance=0.010027) + → ADDING D-ZPK3-25 to LOOSE cluster (max distance=0.070006) +Cluster 21 (LOOSE, 3 members): H-ZPK3, D-ZPK3, D-ZPK3-25 + → ADDING [263] D-ZPK5 to TIGHT cluster 22 (distance=0.000000 from [27] H-ZPK5) +Cluster 22 (TIGHT, 2 members): H-ZPK5, D-ZPK5 + → ADDING [265] D-ZPK7 to TIGHT cluster 23 (distance=0.000000 from [28] H-ZPK7) +Cluster 23 (TIGHT, 2 members): H-ZPK7, D-ZPK7 + → ADDING D-ZK2L to LOOSE cluster (max distance=0.128893) +Cluster 24 (LOOSE, 2 members): H-BaK2, D-ZK2L + → ADDING H-LaK50A to LOOSE cluster (max distance=0.108926) + → REJECTING D-ZK21 from LOOSE cluster (distance=0.191931 from H-LaK50A > 0.150000) +Cluster 26 (LOOSE, 2 members): H-BaK5, H-LaK50A + → ADDING [36] H-BaK7GT to TIGHT cluster 27 (distance=0.000000 from [35] H-BaK7) +Cluster 27 (TIGHT, 2 members): H-BaK7, H-BaK7GT + → ADDING H-LaK12 to LOOSE cluster (max distance=0.141338) +Cluster 28 (LOOSE, 2 members): H-BaK7A, H-LaK12 + → ADDING [42] H-ZK3A to TIGHT cluster 31 (distance=0.000000 from [41] H-ZK3) +Cluster 31 (TIGHT, 2 members): H-ZK3, H-ZK3A + → ADDING H-LaK10 to LOOSE cluster (max distance=0.126477) +Cluster 32 (LOOSE, 2 members): H-ZK5, H-LaK10 + → ADDING H-ZK14 to LOOSE cluster (max distance=0.022349) +Cluster 33 (LOOSE, 2 members): H-ZK7, H-ZK14 + → ADDING [50] H-ZK9A to TIGHT cluster 35 (distance=0.000000 from [49] H-ZK9B) +Cluster 35 (TIGHT, 2 members): H-ZK9B, H-ZK9A + → ADDING H-LaK7A to LOOSE cluster (max distance=0.124809) + → ADDING D-LaK52 to LOOSE cluster (max distance=0.117204) + → ADDING D-LaK52-25 to LOOSE cluster (max distance=0.127095) +Cluster 37 (LOOSE, 4 members): H-ZK20, H-LaK7A, D-LaK52... + → ADDING D-ZK21 to LOOSE cluster (max distance=0.090012) + → ADDING [280] D-ZK21-25 to TIGHT cluster 38 (distance=0.000470 from [56] H-ZK21) +Cluster 38 (TIGHT, 3 members): H-ZK21, D-ZK21, D-ZK21-25 + → ADDING [58] H-ZK50GT to TIGHT cluster 39 (distance=0.000000 from [57] H-ZK50) +Cluster 39 (TIGHT, 2 members): H-ZK50, H-ZK50GT + → ADDING H-LaK11 to LOOSE cluster (max distance=0.075208) + → ADDING H-LaK52 to LOOSE cluster (max distance=0.144819) +Cluster 41 (LOOSE, 3 members): H-LaK2A, H-LaK11, H-LaK52 + → ADDING H-ZBaF50 to LOOSE cluster (max distance=0.133533) +Cluster 42 (LOOSE, 2 members): H-LaK3, H-ZBaF50 + → ADDING H-LaK51A to LOOSE cluster (max distance=0.021382) + → ADDING D-LaK70 to LOOSE cluster (max distance=0.142713) +Cluster 43 (LOOSE, 3 members): H-LaK5A, H-LaK51A, D-LaK70 + → ADDING H-LaK8A to LOOSE cluster (max distance=0.060001) +Cluster 45 (LOOSE, 2 members): H-LaK8B, H-LaK8A + → ADDING [75] H-LaK53A to TIGHT cluster 46 (distance=0.000000 from [74] H-LaK53B) +Cluster 46 (TIGHT, 2 members): H-LaK53B, H-LaK53A + → ADDING D-LaK5 to LOOSE cluster (max distance=0.071215) + → ADDING D-LaK5-25 to LOOSE cluster (max distance=0.080005) +Cluster 48 (LOOSE, 3 members): H-LaK59A, D-LaK5, D-LaK5-25 + → ADDING H-LaK72 to LOOSE cluster (max distance=0.082152) +Cluster 50 (LOOSE, 2 members): H-LaK67, H-LaK72 + → ADDING QF1 to LOOSE cluster (max distance=0.049999) +Cluster 54 (LOOSE, 2 members): H-QF1, QF1 + → ADDING ZBaF17 to LOOSE cluster (max distance=0.131502) +Cluster 57 (LOOSE, 2 members): QF5, ZBaF17 + → ADDING QF6 to LOOSE cluster (max distance=0.080002) +Cluster 58 (LOOSE, 2 members): H-QF6A, QF6 + → ADDING QF8 to LOOSE cluster (max distance=0.029999) + → REJECTING H-ZBaF5 from LOOSE cluster (distance=0.158369 from QF8 > 0.150000) + → ADDING H-ZBaF52 to LOOSE cluster (max distance=0.132744) +Cluster 59 (LOOSE, 3 members): H-QF8, QF8, H-ZBaF52 + → ADDING H-QF50A to LOOSE cluster (max distance=0.139999) + → ADDING [96] QF50 to TIGHT cluster 61 (distance=0.000000 from [94] H-QF50) +Cluster 61 (TIGHT, 3 members): H-QF50, H-QF50A, QF50 + → ADDING [99] F1 to TIGHT cluster 63 (distance=0.000000 from [98] H-F1) +Cluster 63 (TIGHT, 2 members): H-F1, F1 + → ADDING F2 to LOOSE cluster (max distance=0.040001) +Cluster 64 (LOOSE, 2 members): H-F2, F2 + → ADDING F3 to LOOSE cluster (max distance=0.040001) +Cluster 65 (LOOSE, 2 members): H-F3, F3 + → ADDING [105] F4 to TIGHT cluster 66 (distance=0.000000 from [104] H-F4) +Cluster 66 (TIGHT, 2 members): H-F4, F4 + → ADDING [107] F5 to TIGHT cluster 67 (distance=0.000000 from [106] H-F5) +Cluster 67 (TIGHT, 2 members): H-F5, F5 + → ADDING F6 to LOOSE cluster (max distance=0.020000) + → ADDING H-F13 to LOOSE cluster (max distance=0.130004) + → ADDING F13 to LOOSE cluster (max distance=0.130004) +Cluster 68 (LOOSE, 4 members): H-F6, F6, H-F13... + → ADDING H-ZBaF4 to LOOSE cluster (max distance=0.132961) +Cluster 69 (LOOSE, 2 members): F7, H-ZBaF4 + → ADDING H-F52 to LOOSE cluster (max distance=0.142384) +Cluster 70 (LOOSE, 2 members): H-F51, H-F52 + → ADDING BaF4 to LOOSE cluster (max distance=0.129997) +Cluster 73 (LOOSE, 2 members): H-BaF4, BaF4 + → ADDING BaF7 to LOOSE cluster (max distance=0.039997) +Cluster 76 (LOOSE, 2 members): H-BaF7, BaF7 + → ADDING D-LaK6 to LOOSE cluster (max distance=0.081962) + → ADDING D-LaK6-25 to LOOSE cluster (max distance=0.139181) +Cluster 78 (LOOSE, 3 members): H-ZBaF1, D-LaK6, D-LaK6-25 +>>> Starting cluster 79 with glass [125] ZBaF2 (nd=1.63962, vd=48.27) + → ADDING H-ZF1A to LOOSE cluster (max distance=0.049999) + → ADDING [138] ZF1 to TIGHT cluster 86 (distance=0.000000 from [136] H-ZF1) +Cluster 86 (TIGHT, 3 members): H-ZF1, H-ZF1A, ZF1 + → ADDING [140] ZF2 to TIGHT cluster 87 (distance=0.000000 from [139] H-ZF2) +Cluster 87 (TIGHT, 2 members): H-ZF2, ZF2 + → ADDING [142] ZF3 to TIGHT cluster 88 (distance=0.000000 from [141] H-ZF3) +Cluster 88 (TIGHT, 2 members): H-ZF3, ZF3 + → ADDING [144] H-ZF4AGT to TIGHT cluster 89 (distance=0.000000 from [143] H-ZF4A) + → ADDING [145] ZF4 to TIGHT cluster 89 (distance=0.000000 from [143] H-ZF4A) +Cluster 89 (TIGHT, 3 members): H-ZF4A, H-ZF4AGT, ZF4 + → ADDING ZF5 to LOOSE cluster (max distance=0.059999) +Cluster 90 (LOOSE, 2 members): H-ZF5, ZF5 + → ADDING [149] ZF6 to TIGHT cluster 91 (distance=0.000000 from [148] H-ZF6) +Cluster 91 (TIGHT, 2 members): H-ZF6, ZF6 + → ADDING [151] H-ZF7LAGT to TIGHT cluster 92 (distance=0.000000 from [150] H-ZF7LA) + → ADDING [153] ZF7L to TIGHT cluster 92 (distance=0.000000 from [150] H-ZF7LA) + → ADDING [154] ZF7LGT to TIGHT cluster 92 (distance=0.000000 from [150] H-ZF7LA) +Cluster 92 (TIGHT, 4 members): H-ZF7LA, H-ZF7LAGT, ZF7L... + → ADDING ZF7LTT to LOOSE cluster (max distance=0.090005) +Cluster 93 (LOOSE, 2 members): ZF7, ZF7LTT + → ADDING ZF10 to LOOSE cluster (max distance=0.020000) + → ADDING D-ZF10 to LOOSE cluster (max distance=0.100000) + → ADDING D-ZF10-25 to LOOSE cluster (max distance=0.100014) +Cluster 95 (LOOSE, 4 members): H-ZF10, ZF10, D-ZF10... + → ADDING ZF11 to LOOSE cluster (max distance=0.020000) +Cluster 96 (LOOSE, 2 members): H-ZF11, ZF11 + → ADDING ZF12 to LOOSE cluster (max distance=0.060001) +Cluster 97 (LOOSE, 2 members): H-ZF12, ZF12 + → ADDING [164] H-ZF13GT to TIGHT cluster 98 (distance=0.000000 from [163] H-ZF13) +Cluster 98 (TIGHT, 2 members): H-ZF13, H-ZF13GT + → ADDING [168] ZF50 to TIGHT cluster 101 (distance=0.000000 from [167] H-ZF50) +Cluster 101 (TIGHT, 2 members): H-ZF50, ZF50 + → ADDING [171] H-ZF52GT to TIGHT cluster 103 (distance=0.000000 from [170] H-ZF52) + → ADDING [172] H-ZF52TT to TIGHT cluster 103 (distance=0.000000 from [170] H-ZF52) + → ADDING [173] H-ZF52A to TIGHT cluster 103 (distance=0.000000 from [170] H-ZF52) +Cluster 103 (TIGHT, 4 members): H-ZF52, H-ZF52GT, H-ZF52TT... + → ADDING [176] H-ZF62GT to TIGHT cluster 105 (distance=0.000000 from [175] H-ZF62) +Cluster 105 (TIGHT, 2 members): H-ZF62, H-ZF62GT + → ADDING [178] H-ZF71GT to TIGHT cluster 106 (distance=0.000000 from [177] H-ZF71) +Cluster 106 (TIGHT, 2 members): H-ZF71, H-ZF71GT + → ADDING [180] H-ZF72AGT to TIGHT cluster 107 (distance=0.000000 from [179] H-ZF72A) +Cluster 107 (TIGHT, 2 members): H-ZF72A, H-ZF72AGT + → ADDING [182] H-ZF73GT to TIGHT cluster 108 (distance=0.000000 from [181] H-ZF73) +Cluster 108 (TIGHT, 2 members): H-ZF73, H-ZF73GT + → ADDING [184] H-ZF88GT to TIGHT cluster 109 (distance=0.000000 from [183] H-ZF88) +Cluster 109 (TIGHT, 2 members): H-ZF88, H-ZF88GT + → ADDING H-LaF53 to LOOSE cluster (max distance=0.058037) + → ADDING D-LaF050 to LOOSE cluster (max distance=0.117026) + → ADDING D-LaF050-25 to LOOSE cluster (max distance=0.080009) +Cluster 110 (LOOSE, 4 members): H-LaF1, H-LaF53, D-LaF050... + → ADDING [189] H-LaF4GT to TIGHT cluster 113 (distance=0.000000 from [188] H-LaF4) +Cluster 113 (TIGHT, 2 members): H-LaF4, H-LaF4GT + → ADDING H-ZLaF53B to LOOSE cluster (max distance=0.130865) + → ADDING H-ZLaF53BGT to LOOSE cluster (max distance=0.130865) + → REJECTING H-ZLaF78B from LOOSE cluster (distance=0.173342 from H-ZLaF53B > 0.150000) + → ADDING D-ZLaF67-25 to LOOSE cluster (max distance=0.141987) +Cluster 116 (LOOSE, 4 members): H-LaF7, H-ZLaF53B, H-ZLaF53BGT... + → ADDING D-LaF50 to LOOSE cluster (max distance=0.010110) + → ADDING D-LaF50-25 to LOOSE cluster (max distance=0.079999) +Cluster 118 (LOOSE, 3 members): H-LaF50B, D-LaF50, D-LaF50-25 + → ADDING H-ZLaF1 to LOOSE cluster (max distance=0.071752) +Cluster 120 (LOOSE, 2 members): H-LaF52, H-ZLaF1 + → ADDING H-ZLaF73 to LOOSE cluster (max distance=0.129077) + → ADDING D-ZLaF85A to LOOSE cluster (max distance=0.090220) + → ADDING D-ZLaF85A-25 to LOOSE cluster (max distance=0.104373) +Cluster 122 (LOOSE, 4 members): H-LaF55, H-ZLaF73, D-ZLaF85A... + → ADDING H-ZLaF4LB to LOOSE cluster (max distance=0.029999) +Cluster 126 (LOOSE, 2 members): H-ZLaF4LA, H-ZLaF4LB + → ADDING H-ZLaF50D to LOOSE cluster (max distance=0.010002) + → ADDING H-ZLaF69 to LOOSE cluster (max distance=0.032313) + → ADDING H-ZLaF69A to LOOSE cluster (max distance=0.023324) +Cluster 127 (LOOSE, 4 members): H-ZLaF50E, H-ZLaF50D, H-ZLaF69... + → ADDING H-ZLaF52 to LOOSE cluster (max distance=0.070000) + → ADDING D-ZLaF52LA to LOOSE cluster (max distance=0.040191) + → ADDING D-ZLaF52LA-25 to LOOSE cluster (max distance=0.050073) +Cluster 129 (LOOSE, 4 members): H-ZLaF52A, H-ZLaF52, D-ZLaF52LA... + → ADDING H-ZLaF55C to LOOSE cluster (max distance=0.020000) + → ADDING D-ZLaF61 to LOOSE cluster (max distance=0.023533) + → ADDING D-ZLaF61-25 to LOOSE cluster (max distance=0.061501) +Cluster 130 (LOOSE, 4 members): H-ZLaF55D, H-ZLaF55C, D-ZLaF61... + → ADDING [217] H-ZLaF66GT to TIGHT cluster 132 (distance=0.000000 from [216] H-ZLaF66) +Cluster 132 (TIGHT, 2 members): H-ZLaF66, H-ZLaF66GT + → ADDING H-ZLaF68B to LOOSE cluster (max distance=0.059998) + → REJECTING D-ZLaF81-25 from LOOSE cluster (distance=0.195166 from H-ZLaF68B > 0.150000) +Cluster 133 (LOOSE, 2 members): H-ZLaF68C, H-ZLaF68B + → ADDING H-ZLaF71AGT to LOOSE cluster (max distance=0.079998) + → ADDING H-ZLaF89L to LOOSE cluster (max distance=0.137150) + → ADDING H-ZLaF89LA to LOOSE cluster (max distance=0.119624) +Cluster 135 (LOOSE, 4 members): H-ZLaF71, H-ZLaF71AGT, H-ZLaF89L... + → ADDING H-ZLaF75B to LOOSE cluster (max distance=0.100000) + → ADDING H-ZLaF75C to LOOSE cluster (max distance=0.059999) +Cluster 136 (LOOSE, 3 members): H-ZLaF75A, H-ZLaF75B, H-ZLaF75C + → ADDING H-ZLaF76A to LOOSE cluster (max distance=0.049999) +Cluster 137 (LOOSE, 2 members): H-ZLaF76, H-ZLaF76A + → ADDING H-ZLaF90A to LOOSE cluster (max distance=0.029999) +Cluster 139 (LOOSE, 2 members): H-ZLaF90, H-ZLaF90A + → ADDING H-ZLaF92A to LOOSE cluster (max distance=0.039999) +Cluster 141 (LOOSE, 2 members): H-ZLaF92, H-ZLaF92A + → ADDING TF3 to LOOSE cluster (max distance=0.020024) +Cluster 143 (LOOSE, 2 members): H-TF3L, TF3 + → ADDING D-FK61A-25 to LOOSE cluster (max distance=0.020005) + → ADDING S-FPL51 to LOOSE cluster (max distance=0.070001) +Cluster 145 (LOOSE, 3 members): D-FK61-25, D-FK61A-25, S-FPL51 + → ADDING D-PK3 to LOOSE cluster (max distance=0.062876) +Cluster 147 (LOOSE, 2 members): D-QK3L-25, D-PK3 + → ADDING D-K9-25 to LOOSE cluster (max distance=0.069998) + → ADDING [253] D-K9GT to TIGHT cluster 148 (distance=0.000000 from [251] D-K9) +Cluster 148 (TIGHT, 3 members): D-K9, D-K9-25, D-K9GT + → ADDING D-ZPK1A-25 to LOOSE cluster (max distance=0.070009) +Cluster 152 (LOOSE, 2 members): D-ZPK1A, D-ZPK1A-25 + → ADDING D-ZK2L-25 to LOOSE cluster (max distance=0.020242) +Cluster 156 (LOOSE, 2 members): D-ZK2A, D-ZK2L-25 + → ADDING D-ZK79-25 to LOOSE cluster (max distance=0.080006) +Cluster 158 (LOOSE, 2 members): D-ZK79, D-ZK79-25 + → ADDING D-ZF93-25 to LOOSE cluster (max distance=0.010179) +Cluster 160 (LOOSE, 2 members): D-ZF93, D-ZF93-25 + → ADDING D-LaF53-25 to LOOSE cluster (max distance=0.080011) +Cluster 161 (LOOSE, 2 members): D-LaF53, D-LaF53-25 + → ADDING D-LaF79-25 to LOOSE cluster (max distance=0.010040) + → ADDING D-ZLaF85L to LOOSE cluster (max distance=0.147434) + → ADDING D-ZLaF85L-25 to LOOSE cluster (max distance=0.126353) +Cluster 162 (LOOSE, 4 members): D-LaF79, D-LaF79-25, D-ZLaF85L... + → ADDING D-ZLaF50-25 to LOOSE cluster (max distance=0.040017) +Cluster 163 (LOOSE, 2 members): D-ZLaF50, D-ZLaF50-25 + → ADDING D-ZLaF81-25 to LOOSE cluster (max distance=0.030024) +Cluster 165 (LOOSE, 2 members): D-ZLaF81, D-ZLaF81-25 + → ADDING D-ZLaF85LN-25 to LOOSE cluster (max distance=0.020033) +Cluster 166 (LOOSE, 2 members): D-ZLaF85LN, D-ZLaF85LN-25 + → ADDING D-ZLaF85LS-25 to LOOSE cluster (max distance=0.030018) +Cluster 167 (LOOSE, 2 members): D-ZLaF85LS, D-ZLaF85LS-25 +Created 33 tight clusters, 68 loose clusters (169 total) + +Final cluster assignments: +>>> H-ZK2 is in cluster 18 (LOOSE, 4 members) +>>> ZBaF2 is in cluster 79 (LOOSE, 1 members) +Glass data loaded: 324 glasses +Created 169 clusters +Glass 0: H-FK55 (nd=1.5502, vd=75.23, mfg=CDGM) +Glass 1: H-FK61 (nd=1.4970, vd=81.61, mfg=CDGM) +Glass 2: H-FK61B (nd=1.4970, vd=81.61, mfg=CDGM) +Glass 3: H-FK71 (nd=1.4565, vd=90.27, mfg=CDGM) +Glass 4: H-FK95N (nd=1.4378, vd=94.52, mfg=CDGM) +Glass 5: H-QK1 (nd=1.4705, vd=66.88, mfg=CDGM) +Glass 6: H-QK3L (nd=1.4875, vd=70.44, mfg=CDGM) +Glass 7: H-K1 (nd=1.4997, vd=62.07, mfg=CDGM) +Glass 8: H-K2 (nd=1.5005, vd=66.02, mfg=CDGM) +Glass 9: H-K3 (nd=1.5046, vd=64.72, mfg=CDGM) + +Checking problematic glass indices: +Glass [40]: H-ZK2 (nd=1.58313, vd=59.46) +Glass [125]: ZBaF2 (nd=1.63962, vd=48.27) + +Testing collision detection... +Rect1 vs Rect2 overlap: YES +Rect1 vs Rect3 overlap: NO +Label collision with point test: +Label at point collision: YES +Label far from point collision: NO +Debug mode complete -- cgit v1.2.3