summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/glamac_render.h34
-rw-r--r--include/glamac_view.h66
-rw-r--r--src/glamac/glamac.c74
-rw-r--r--src/glamac/glamac_events.c54
-rw-r--r--src/glamac/glamac_render.c1246
-rw-r--r--src/glamac/glamac_view.c398
-rw-r--r--src/glamac/glass_data.c4
-rw-r--r--src/glautils/fgla.c72
-rw-r--r--test.txt302
9 files changed, 1162 insertions, 1088 deletions
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 <SDL3/SDL.h>
#include <stdio.h>
+#include <math.h>
#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, &regularSize, &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 <math.h>
+#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <SDL3/SDL.h>
#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
Back to https://optics-design.com