From b59995f9732720e8c82e22f44c0c8cb3efe2c708 Mon Sep 17 00:00:00 2001 From: admin Date: Mon, 4 Aug 2025 18:10:36 +0200 Subject: changes to label visibility --- include/glamac_render.h | 33 ++- include/glamac_view.h | 10 + include/glass_data.h | 7 + src/glamac/glamac.c | 59 +++- src/glamac/glamac_events.c | 56 ++-- src/glamac/glamac_render.c | 678 +++++++++++++++++++++++---------------------- src/glamac/glamac_view.c | 15 + src/glamac/glass_data.c | 304 +++++++++++--------- 8 files changed, 660 insertions(+), 502 deletions(-) diff --git a/include/glamac_render.h b/include/glamac_render.h index 546bafe..a07e055 100644 --- a/include/glamac_render.h +++ b/include/glamac_render.h @@ -21,7 +21,7 @@ void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *tit 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, const ViewState* view, GlassCluster* clusters, i32 clusterCount); +void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, ViewState* view, GlassCluster* clusters, i32 clusterCount); // Font management typedef struct { @@ -40,8 +40,33 @@ b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 d void free_fonts(FontSet *fonts); void clear_text_cache(void); -// Static label positioning -void calculate_static_label_positions(GlassCluster* clusters, i32 clusterCount, const ViewState* view); -void cleanup_static_label_positions(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 +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); #endif /* GLAMAC_RENDER_H */ diff --git a/include/glamac_view.h b/include/glamac_view.h index 21f273f..9eec63a 100644 --- a/include/glamac_view.h +++ b/include/glamac_view.h @@ -45,11 +45,21 @@ typedef struct { 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) + + // Label recalculation tracking + f32 lastZoomLevel; // Last zoom level for which labels were calculated + i32 lastWindowWidth; // Last window width for labels + i32 lastWindowHeight; // Last window height for labels + f32 lastOffsetX; // Last X offset for labels + f32 lastOffsetY; // Last Y offset for labels } ViewState; // Initialize a view state with default values void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight); +// Refresh view state data range when catalog changes +void refresh_view_data_range(ViewState* view); + // Helper function to calculate adaptive padding static inline i32 get_adaptive_padding(const ViewState* view) { i32 padding = (i32)(view->windowWidth * MAX_PADDING_PERCENT); diff --git a/include/glass_data.h b/include/glass_data.h index 75aedde..56c2c7f 100644 --- a/include/glass_data.h +++ b/include/glass_data.h @@ -46,4 +46,11 @@ b32 load_glasses_from_json(const byte* json_path, const byte* manufacturer_filte // Cleanup glass data resources void cleanup_glass_data(void); +// Catalog management +u32 get_catalog_count(void); +const char* get_catalog_name(u32 catalog_index); +const char* get_current_catalog_name(void); +void set_current_catalog(u32 catalog_index); +void cycle_catalog(i32 direction); // +1 for next, -1 for previous + #endif /* GLASS_DATA_H */ diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c index 7988cc7..4b2f0fa 100644 --- a/src/glamac/glamac.c +++ b/src/glamac/glamac.c @@ -48,7 +48,62 @@ b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) { #define INITIAL_WIDTH 800 #define INITIAL_HEIGHT 600 -int main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) { +// Debug mode flag +static b32 g_debug_mode = 0; + +int main(int argc, char* argv[]) { + // Check for debug flag + for (int 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}; @@ -161,7 +216,7 @@ int main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) cleanup_glass_data(); free_fonts(&fonts); clear_text_cache(); - cleanup_static_label_positions(); + // 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 63bb07c..73d1133 100644 --- a/src/glamac/glamac_events.c +++ b/src/glamac/glamac_events.c @@ -2,7 +2,9 @@ * glamac_events.c - source file detailing keyboard and mouse events used in GlaMaC. */ #include +#include #include "glamac_view.h" +#include "glass_data.h" // Process key event b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *window, b32 *quit) { @@ -78,14 +80,32 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo // Vim-like navigation case SDLK_H: - // Move left - view->offsetX += PAN_STEP; + if (key->mod & SDL_KMOD_SHIFT) { + // Shift+H: Previous catalog + 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()); + } else { + // Move left + view->offsetX += PAN_STEP; + } view->gKeyPressed = 0; // Reset g key state return 1; case SDLK_L: - // Move right - view->offsetX -= PAN_STEP; + if (key->mod & SDL_KMOD_SHIFT) { + // Shift+L: Next catalog + 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()); + } else { + // Move right + view->offsetX -= PAN_STEP; + } view->gKeyPressed = 0; // Reset g key state return 1; @@ -114,32 +134,14 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las if (button->type == SDL_EVENT_MOUSE_BUTTON_DOWN) { SDL_GetMouseState(&mouseX, &mouseY); - // First find the nearest glass point + // Find the nearest glass point (no clustering) i32 nearestGlass = find_nearest_glass((i32)mouseX, (i32)mouseY, view, 15); if (nearestGlass >= 0) { - // Check if this glass is part of a cluster - i32 glassClusterIndex = -1; - for (i32 i = 0; i < clusterCount; i++) { - for (i32 j = 0; j < clusters[i].count; j++) { - if (clusters[i].glassIndices[j] == nearestGlass) { - glassClusterIndex = i; - break; - } - } - if (glassClusterIndex >= 0) break; - } - - if (glassClusterIndex >= 0) { - // This glass is part of a cluster - select the cluster - view->selectedCluster = glassClusterIndex; - view->selectedGlass = -1; - } else { - // Individual glass not in any cluster - view->selectedGlass = nearestGlass; - view->selectedCluster = -1; - } + // Select the individual glass + view->selectedGlass = nearestGlass; + view->selectedCluster = -1; } else { - // If clicking outside any point, clear all selections + // If clicking outside any point, clear selection view->selectedGlass = -1; view->selectedCluster = -1; // Start dragging for panning diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c index 9d03645..8536218 100644 --- a/src/glamac/glamac_render.c +++ b/src/glamac/glamac_render.c @@ -12,91 +12,240 @@ // Drawing primitives -// Smart label positioning +// 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 typedef struct { i32 dx, dy; } LabelOffset; +// Simplified positions - only 4 basic directions static const LabelOffset LABEL_POSITIONS[] = { - {0, -1}, // Up - closest to point - {-1, 0}, // Left - second closest - {0, 1}, // Down - third closest - {1, 0}, // Right - fourth closest - {-1, -1}, // Top-left - further away - {1, -1}, // Top-right - further away - {-1, 1}, // Bottom-left - further away - {1, 1}, // Bottom-right - furthest away - // Additional positions for very crowded areas - {0, -2}, // Far up - {-2, 0}, // Far left - {0, 2}, // Far down - {2, 0}, // Far right - {-2, -2}, // Far top-left - {2, -2}, // Far top-right - {-2, 2}, // Far bottom-left - {2, 2}, // Far bottom-right - // Even more distant positions for extremely crowded areas - {0, -3}, // Very far up - {-3, 0}, // Very far left - {0, 3}, // Very far down - {3, 0}, // Very far right - {-3, -3}, // Very far top-left - {3, -3}, // Very far top-right - {-3, 3}, // Very far bottom-left - {3, 3} // Very far bottom-right + {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 18 // Distance from glass point (increased to clear red dots) -#define LABEL_COLLISION_PADDING 2 // Extra padding around labels (reduced from 5 - less aggressive) +#define LABEL_OFFSET_DISTANCE 8 // Reduced distance from glass point +#define LABEL_COLLISION_PADDING 1 // Minimal padding -// Structure to store static label positions (calculated once, zoom-independent) -typedef struct { - i32 glassIndex; // Index of the glass this label belongs to - i32 positionIndex; // Which position from LABEL_POSITIONS was chosen - const char* text; // Label text (pointer to glass name or cluster name) - b32 isCluster; // True if this is a cluster label - i32 clusterIndex; // Cluster index if isCluster is true -} StaticLabelPosition; -// Global static label positions (calculated once at startup) -static StaticLabelPosition* g_staticLabels = NULL; -static i32 g_staticLabelCount = 0; +// 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); -// Helper function to calculate local glass density for label filtering -static inline f32 get_local_density(const Glass* targetGlass, const ViewState* view __attribute__((unused))) { - const f32 DENSITY_RADIUS = 0.02f; // Radius in data space (nd/vd units) - i32 nearbyCount = 0; +// 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; + } - for (i32 i = 0; i < (i32)get_glass_count(); i++) { + // 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; +} + +// 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 || glass == targetGlass) continue; + 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; + } - // Calculate distance in data space - f32 dx = glass->abbeNumber - targetGlass->abbeNumber; - f32 dy = glass->refractiveIndex - targetGlass->refractiveIndex; - f32 distance = sqrtf(dx*dx + dy*dy); + // No clustering - just use glass name + const char* labelText = (const char*)glass->name; - if (distance < DENSITY_RADIUS) { - nearbyCount++; + 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; + } } } - return (f32)nearbyCount; + // 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; + } + } + + // 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++; + } + } } -// Helper function to determine if a glass label should be shown -static inline b32 should_show_label(const Glass* glass, const ViewState* view, i32 glassIndex) { - // Always show selected glass label - if (glassIndex == view->selectedGlass) { - return 1; - } +// 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}; - // Calculate density-based threshold - f32 density = get_local_density(glass, view); - const f32 DENSITY_THRESHOLD = 3.0f; // Maximum nearby glasses before hiding labels + 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); + } + } +} + +// 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; + } - // Show label if density is low enough - return density < DENSITY_THRESHOLD; + return needs_recalc; } // Helper function to calculate adaptive radius based on window size and DPI @@ -333,18 +482,6 @@ void draw_grid(SDL_Renderer *renderer, const ViewState* view) { } } -// 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 -} - // 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 ""; @@ -370,217 +507,62 @@ static inline const char* get_cluster_shortest_name(const GlassCluster* cluster) return shortestName; } -// Helper function to check if two label rectangles overlap in screen space -static inline b32 labels_overlap_in_screen(i32 x1, i32 y1, i32 w1, i32 h1, i32 x2, i32 y2, i32 w2, i32 h2) { - return !(x1 + w1 + LABEL_COLLISION_PADDING < x2 || - x2 + w2 + LABEL_COLLISION_PADDING < x1 || - y1 + h1 + LABEL_COLLISION_PADDING < y2 || - y2 + h2 + LABEL_COLLISION_PADDING < y1); -} -// Function to find best position for a label avoiding collisions with existing labels -static inline i32 find_best_static_position(i32 glassIndex, const char* labelText, - const ViewState* view, TTF_Font* labelFont) { - const Glass* glass = get_glass(glassIndex); - if (!glass) return 0; - - // Convert glass position to screen coordinates (using current view for calculation) - i32 glassX, glassY; - data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); +// 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 - // Get text dimensions - int textWidth, textHeight; - if (!TTF_GetStringSize(labelFont, labelText, 0, &textWidth, &textHeight)) { - textWidth = strlen(labelText) * 8; // Fallback estimate - textHeight = 14; - } - - // Try each position in order of preference + // Try each position in order for (i32 posIdx = 0; posIdx < (i32)NUM_LABEL_POSITIONS; posIdx++) { const LabelOffset* pos = &LABEL_POSITIONS[posIdx]; - i32 labelX = glassX + pos->dx * LABEL_OFFSET_DISTANCE; - i32 labelY = glassY + pos->dy * LABEL_OFFSET_DISTANCE; - // Check for collisions with existing labels - b32 collision = 0; - for (i32 j = 0; j < g_staticLabelCount; j++) { - const StaticLabelPosition* existingLabel = &g_staticLabels[j]; - const Glass* existingGlass = get_glass(existingLabel->glassIndex); - if (!existingGlass) continue; - - // Calculate existing label position - i32 existingGlassX, existingGlassY; - data_to_screen_coords(existingGlass->abbeNumber, existingGlass->refractiveIndex, - view, &existingGlassX, &existingGlassY); - - const LabelOffset* existingPos = &LABEL_POSITIONS[existingLabel->positionIndex]; - i32 existingLabelX = existingGlassX + existingPos->dx * LABEL_OFFSET_DISTANCE; - i32 existingLabelY = existingGlassY + existingPos->dy * LABEL_OFFSET_DISTANCE; - - // Get existing label dimensions - int existingWidth, existingHeight; - if (!TTF_GetStringSize(labelFont, existingLabel->text, 0, &existingWidth, &existingHeight)) { - existingWidth = strlen(existingLabel->text) * 8; - existingHeight = 14; - } - - // Check for overlap - if (labels_overlap_in_screen(labelX, labelY, textWidth, textHeight, - existingLabelX, existingLabelY, existingWidth, existingHeight)) { - collision = 1; - break; - } + // 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 (!collision) { - return posIdx; // Found a good position + if (pos->dy < 0) { // Above + labelY -= textHeight; + } else if (pos->dy == 0) { // Center vertically + labelY -= textHeight / 2; } - } - - // If we get here, all positions had collisions - implement emergency positioning - // Find which position has the least overlap and use that as a starting point - i32 bestPosition = 0; - f32 minOverlap = 1000000.0f; // Large number - - for (i32 posIdx = 0; posIdx < (i32)NUM_LABEL_POSITIONS; posIdx++) { - const LabelOffset* pos = &LABEL_POSITIONS[posIdx]; - i32 labelX = glassX + pos->dx * LABEL_OFFSET_DISTANCE; - i32 labelY = glassY + pos->dy * LABEL_OFFSET_DISTANCE; - f32 totalOverlap = 0.0f; + LabelRect candidate = { + .x = labelX, + .y = labelY, + .width = textWidth, + .height = textHeight + }; - // Calculate total overlap area with all existing labels - for (i32 j = 0; j < g_staticLabelCount; j++) { - const StaticLabelPosition* existingLabel = &g_staticLabels[j]; - const Glass* existingGlass = get_glass(existingLabel->glassIndex); - if (!existingGlass) continue; - - i32 existingGlassX, existingGlassY; - data_to_screen_coords(existingGlass->abbeNumber, existingGlass->refractiveIndex, - view, &existingGlassX, &existingGlassY); - - const LabelOffset* existingPos = &LABEL_POSITIONS[existingLabel->positionIndex]; - i32 existingLabelX = existingGlassX + existingPos->dx * LABEL_OFFSET_DISTANCE; - i32 existingLabelY = existingGlassY + existingPos->dy * LABEL_OFFSET_DISTANCE; - - int existingWidth, existingHeight; - if (!TTF_GetStringSize(labelFont, existingLabel->text, 0, &existingWidth, &existingHeight)) { - existingWidth = strlen(existingLabel->text) * 8; - existingHeight = 14; - } - - // Calculate overlap area - i32 overlapLeft = (labelX > existingLabelX) ? labelX : existingLabelX; - i32 overlapRight = ((labelX + textWidth) < (existingLabelX + existingWidth)) ? - (labelX + textWidth) : (existingLabelX + existingWidth); - i32 overlapTop = (labelY > existingLabelY) ? labelY : existingLabelY; - i32 overlapBottom = ((labelY + textHeight) < (existingLabelY + existingHeight)) ? - (labelY + textHeight) : (existingLabelY + existingHeight); - - if (overlapLeft < overlapRight && overlapTop < overlapBottom) { - f32 overlapArea = (overlapRight - overlapLeft) * (overlapBottom - overlapTop); - totalOverlap += overlapArea; + // 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 (totalOverlap < minOverlap) { - minOverlap = totalOverlap; - bestPosition = posIdx; - } - } - - // Debug trace for problematic labels - if (strstr(labelText, "P-LASF47") || strstr(labelText, "N-LASF43")) { - printf("Label %s had collisions in all positions, using best position %d with overlap %.1f\n", - labelText, bestPosition, minOverlap); - } - - return bestPosition; -} - -// Function to calculate static label positions once at startup -void calculate_static_label_positions(GlassCluster* clusters, i32 clusterCount, const ViewState* view) { - // Clean up any existing static labels - if (g_staticLabels) { - free(g_staticLabels); - g_staticLabels = NULL; - g_staticLabelCount = 0; - } - - // Allocate for maximum possible labels - g_staticLabels = (StaticLabelPosition*)calloc(get_glass_count(), sizeof(StaticLabelPosition)); - if (!g_staticLabels) return; - - // We need a font for text size calculation - use a reasonable default size - TTF_Font* tempFont = TTF_OpenFont("/usr/share/fonts/TTF/DejaVuSans.ttf", 12); - if (!tempFont) { - // Try alternative paths - tempFont = TTF_OpenFont("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 12); - } - - // Track which clusters have been processed - b32* clusterProcessed = (b32*)calloc(clusterCount, sizeof(b32)); - if (!clusterProcessed) { - free(g_staticLabels); - g_staticLabels = NULL; - if (tempFont) TTF_CloseFont(tempFont); - return; - } - - // Process each glass and determine label positions with collision avoidance - for (i32 i = 0; i < (i32)get_glass_count(); i++) { - const Glass* glass = get_glass(i); - if (!glass) continue; - - // Skip if this glass shouldn't show a label - if (!should_show_label(glass, view, i)) continue; - - // Check if this glass is part of a cluster - i32 clusterIndex = find_glass_cluster(i, clusters, clusterCount); - - if (clusterIndex >= 0) { - // This glass is part of a cluster - if (!clusterProcessed[clusterIndex]) { - // This is the first glass from this cluster, create cluster label - const char* clusterText = get_cluster_shortest_name(&clusters[clusterIndex]); - - g_staticLabels[g_staticLabelCount].glassIndex = i; - g_staticLabels[g_staticLabelCount].positionIndex = tempFont ? - find_best_static_position(i, clusterText, view, tempFont) : 0; - g_staticLabels[g_staticLabelCount].text = clusterText; - g_staticLabels[g_staticLabelCount].isCluster = 1; - g_staticLabels[g_staticLabelCount].clusterIndex = clusterIndex; - g_staticLabelCount++; - clusterProcessed[clusterIndex] = 1; - } - } else { - // Individual glass, not part of a cluster - g_staticLabels[g_staticLabelCount].glassIndex = i; - g_staticLabels[g_staticLabelCount].positionIndex = tempFont ? - find_best_static_position(i, (const char*)glass->name, view, tempFont) : 0; - g_staticLabels[g_staticLabelCount].text = (const char*)glass->name; - g_staticLabels[g_staticLabelCount].isCluster = 0; - g_staticLabels[g_staticLabelCount].clusterIndex = -1; - g_staticLabelCount++; + if (!collision) { + return posIdx; } } - free(clusterProcessed); - if (tempFont) TTF_CloseFont(tempFont); + // If all positions have collisions, return -1 to indicate no placement + return -1; } -// Function to clean up static label positions -void cleanup_static_label_positions(void) { - if (g_staticLabels) { - free(g_staticLabels); - g_staticLabels = NULL; - g_staticLabelCount = 0; - } -} -// Function to draw glass points and labels using static positions +// 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 SDL_Color blue = {0, 0, 150, 255}; const i32 padding = get_adaptive_padding(view); const i32 baseRadius = 4; const i32 radius = get_adaptive_radius(view, baseRadius); @@ -589,12 +571,10 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI; get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI); - // If static positions haven't been calculated yet, calculate them - if (!g_staticLabels) { - calculate_static_label_positions(clusters, clusterCount, view); - } + // Track which clusters have been processed + b32* clusterProcessed = (b32*)calloc(clusterCount, sizeof(b32)); - // Draw all glass points + // Draw all glass points first for (i32 i = 0; i < (i32)get_glass_count(); i++) { const Glass* glass = get_glass(i); if (!glass) continue; @@ -614,11 +594,8 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt continue; } - // Check if this glass is part of a cluster - i32 clusterIndex = find_glass_cluster(i, clusters, clusterCount); - - // Highlight selected glass or cluster - if (i == view->selectedGlass || (clusterIndex >= 0 && clusterIndex == view->selectedCluster)) { + // 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); @@ -629,34 +606,79 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt draw_filled_circle(renderer, x, y, radius); } - // Draw labels using static positions - for (i32 i = 0; i < g_staticLabelCount; i++) { - const StaticLabelPosition* label = &g_staticLabels[i]; - const Glass* glass = get_glass(label->glassIndex); - if (!glass) continue; + // 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) +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 this glass is visible - if (glass->abbeNumber < visibleMinAbbe || glass->abbeNumber > visibleMaxAbbe || - glass->refractiveIndex < visibleMinRI || glass->refractiveIndex > visibleMaxRI) { - continue; + // 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 } - - i32 glassX, glassY; - data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); - - // Check if glass point is on screen - if (glassX < padding || glassX > view->windowWidth - padding || - glassY < padding || glassY > view->windowHeight - padding) { - continue; + } + + // 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; } + } + + // If no good position found, use constrained positioning + if (positions[bestIndex].distance > 1000.0f) { + *windowX = glassX + baseOffset; + *windowY = glassY - windowHeight / 2; - // Calculate label position using static offset - const LabelOffset* pos = &LABEL_POSITIONS[label->positionIndex]; - i32 labelX = glassX + pos->dx * LABEL_OFFSET_DISTANCE; - i32 labelY = glassY + pos->dy * LABEL_OFFSET_DISTANCE; - - // Draw the label - draw_text(renderer, labelFont, label->text, labelX, labelY, blue); + // 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; + } + } else { + *windowX = positions[bestIndex].x; + *windowY = positions[bestIndex].y; } } @@ -673,25 +695,14 @@ void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *tit i32 glassX, glassY; data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY); - // Define property window size and position + // Define property window size const i32 windowWidth = 200; const i32 windowHeight = 100; const i32 padding = 10; - // Position the window near the glass point but ensure it stays within screen bounds - i32 windowX = glassX + 15; // Offset from glass point - i32 windowY = glassY - windowHeight / 2; - - // Adjust if window would go off-screen - if (windowX + windowWidth > view->windowWidth) { - windowX = glassX - windowWidth - 15; - } - if (windowY < 0) { - windowY = 0; - } - if (windowY + windowHeight > view->windowHeight) { - windowY = view->windowHeight - windowHeight; - } + // Use smart positioning + i32 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}; @@ -735,25 +746,14 @@ void draw_cluster_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *t i32 clusterX, clusterY; data_to_screen_coords(cluster->avgAbbeNumber, cluster->avgRefractiveIndex, view, &clusterX, &clusterY); - // Define property window size (no title, just properties) + // Define property window size (dynamic height based on glass count) const i32 windowWidth = 280; - const i32 windowHeight = 20 + cluster->count * 25; // Dynamic height based on glass count (no title) + const i32 windowHeight = 20 + cluster->count * 25; const i32 padding = 10; - // Position the window near the cluster point but ensure it stays within screen bounds - i32 windowX = clusterX + 15; - i32 windowY = clusterY - windowHeight / 2; - - // Adjust if window would go off-screen - if (windowX + windowWidth > view->windowWidth) { - windowX = clusterX - windowWidth - 15; - } - if (windowY < 0) { - windowY = 0; - } - if (windowY + windowHeight > view->windowHeight) { - windowY = view->windowHeight - windowHeight; - } + // Use smart positioning + i32 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}; @@ -809,6 +809,7 @@ void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFon "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", @@ -831,9 +832,14 @@ void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFon } // Main render function -void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, const ViewState* view, GlassCluster* clusters, i32 clusterCount) { +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) SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255); SDL_RenderClear(renderer); @@ -857,6 +863,12 @@ void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Fon 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); + // Add a small hint about help in the corner draw_text(renderer, labelFont, "Press g? for help", 10, (i32)(0.975*view->windowHeight), black); diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c index 67763a8..ee75762 100644 --- a/src/glamac/glamac_view.c +++ b/src/glamac/glamac_view.c @@ -20,10 +20,25 @@ 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 label recalculation tracking (force initial calculation) + view->lastZoomLevel = -1.0f; + view->lastWindowWidth = -1; + view->lastWindowHeight = -1; + view->lastOffsetX = -999.0f; + view->lastOffsetY = -999.0f; + // Calculate data range find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI); } +// Refresh view state data range when catalog changes +void refresh_view_data_range(ViewState* view) { + find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI); + + // Force label recalculation on catalog change + view->lastZoomLevel = -1.0f; // Invalid values to force recalc +} + // Calculate visible data range void get_visible_data_range(const ViewState* view, f32 *visibleMinAbbe, f32 *visibleMaxAbbe, f32 *visibleMinRI, f32 *visibleMaxRI) { diff --git a/src/glamac/glass_data.c b/src/glamac/glass_data.c index 28bd963..bcead81 100644 --- a/src/glamac/glass_data.c +++ b/src/glamac/glass_data.c @@ -19,15 +19,23 @@ #include #include -// Glass data context structure +// Individual catalog structure typedef struct { Glass* glasses; u32 count; u32 capacity; + char name[32]; +} GlassCatalog; + +// Glass data context structure with multiple catalogs +typedef struct { + GlassCatalog catalogs[8]; // Support up to 8 manufacturers + u32 catalog_count; + i32 current_catalog; // Index of currently active catalog b32 using_json_data; } GlassDataContext; -// Global context (will be refactored to parameter-based later) +// Global context static GlassDataContext g_glass_context = {0}; // Fallback sample glass data (SCHOTT glasses for testing) @@ -183,40 +191,35 @@ static double safe_find_json_number(const char* json, const char* key, size_t js return atof(num_str); } -// Safe memory management -static void cleanup_glass_context(GlassDataContext* ctx) { - if (ctx && ctx->glasses) { - free(ctx->glasses); - ctx->glasses = NULL; - ctx->count = 0; - ctx->capacity = 0; - ctx->using_json_data = 0; - } -} - -static GlamacResult allocate_glasses(GlassDataContext* ctx, u32 count) { - if (!ctx || count == 0) return GLAMAC_ERROR_INVALID_ARGUMENT; +// Safe memory management for individual catalogs +static GlamacResult allocate_catalog_glasses(GlassCatalog* catalog, u32 count, const char* name) { + if (!catalog || count == 0) return GLAMAC_ERROR_INVALID_ARGUMENT; - // Prevent integer overflow - only check if count is very large + // Prevent integer overflow if (count > UINT32_MAX / sizeof(Glass)) { return GLAMAC_ERROR_MEMORY; } - cleanup_glass_context(ctx); + // Clean up existing data + if (catalog->glasses) { + free(catalog->glasses); + } - ctx->glasses = (Glass*)calloc(count, sizeof(Glass)); - if (!ctx->glasses) { + catalog->glasses = (Glass*)calloc(count, sizeof(Glass)); + if (!catalog->glasses) { return GLAMAC_ERROR_MEMORY; } - ctx->capacity = count; - ctx->count = 0; + catalog->capacity = count; + catalog->count = 0; + strncpy(catalog->name, name ? name : "Unknown", sizeof(catalog->name) - 1); + catalog->name[sizeof(catalog->name) - 1] = '\0'; return GLAMAC_SUCCESS; } -// Helper function to parse glasses from a manufacturer section -static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, size_t section_len, const char* manufacturer_name) { +// Helper function to parse glasses from a manufacturer section into a catalog +static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, size_t section_len, GlassCatalog* catalog) { // Find glasses array const char* glasses_array = strstr(manufacturer_section, "\"glasses\":"); if (!glasses_array) { @@ -264,56 +267,45 @@ static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, if (name && nd > 0.0 && vd > 0.0 && nd < 10.0 && vd < 200.0) { // Expand glasses array if needed - if (g_glass_context.count >= g_glass_context.capacity) { - u32 new_capacity = g_glass_context.capacity * 2; + if (catalog->count >= catalog->capacity) { + u32 new_capacity = catalog->capacity * 2; if (new_capacity < 100) new_capacity = 100; - Glass* new_glasses = (Glass*)realloc(g_glass_context.glasses, new_capacity * sizeof(Glass)); + Glass* new_glasses = (Glass*)realloc(catalog->glasses, new_capacity * sizeof(Glass)); if (!new_glasses) { if (name) free(name); if (glass_code) free(glass_code); return GLAMAC_ERROR_MEMORY; } - g_glass_context.glasses = new_glasses; - g_glass_context.capacity = new_capacity; + catalog->glasses = new_glasses; + catalog->capacity = new_capacity; } // Copy glass data safely size_t name_len = strlen(name); - if (name_len < sizeof(g_glass_context.glasses[g_glass_context.count].name)) { - strcpy((char*)g_glass_context.glasses[g_glass_context.count].name, name); - g_glass_context.glasses[g_glass_context.count].abbeNumber = (f32)vd; - g_glass_context.glasses[g_glass_context.count].refractiveIndex = (f32)nd; + if (name_len < sizeof(catalog->glasses[catalog->count].name)) { + strcpy((char*)catalog->glasses[catalog->count].name, name); + catalog->glasses[catalog->count].abbeNumber = (f32)vd; + catalog->glasses[catalog->count].refractiveIndex = (f32)nd; // Copy glass code safely if (glass_code) { size_t code_len = strlen(glass_code); - if (code_len < sizeof(g_glass_context.glasses[g_glass_context.count].glass_code)) { - strcpy((char*)g_glass_context.glasses[g_glass_context.count].glass_code, glass_code); + if (code_len < sizeof(catalog->glasses[catalog->count].glass_code)) { + strcpy((char*)catalog->glasses[catalog->count].glass_code, glass_code); } else { - strncpy((char*)g_glass_context.glasses[g_glass_context.count].glass_code, glass_code, - sizeof(g_glass_context.glasses[g_glass_context.count].glass_code) - 1); - g_glass_context.glasses[g_glass_context.count].glass_code[sizeof(g_glass_context.glasses[g_glass_context.count].glass_code) - 1] = '\0'; + strncpy((char*)catalog->glasses[catalog->count].glass_code, glass_code, + sizeof(catalog->glasses[catalog->count].glass_code) - 1); + catalog->glasses[catalog->count].glass_code[sizeof(catalog->glasses[catalog->count].glass_code) - 1] = '\0'; } } else { - strcpy((char*)g_glass_context.glasses[g_glass_context.count].glass_code, "N/A"); + strcpy((char*)catalog->glasses[catalog->count].glass_code, "N/A"); } // Copy manufacturer name - if (manufacturer_name) { - size_t mfg_len = strlen(manufacturer_name); - if (mfg_len < sizeof(g_glass_context.glasses[g_glass_context.count].manufacturer)) { - strcpy((char*)g_glass_context.glasses[g_glass_context.count].manufacturer, manufacturer_name); - } else { - strncpy((char*)g_glass_context.glasses[g_glass_context.count].manufacturer, manufacturer_name, - sizeof(g_glass_context.glasses[g_glass_context.count].manufacturer) - 1); - g_glass_context.glasses[g_glass_context.count].manufacturer[sizeof(g_glass_context.glasses[g_glass_context.count].manufacturer) - 1] = '\0'; - } - } else { - strcpy((char*)g_glass_context.glasses[g_glass_context.count].manufacturer, "Unknown"); - } + strcpy((char*)catalog->glasses[catalog->count].manufacturer, catalog->name); - g_glass_context.count++; + catalog->count++; } } @@ -334,10 +326,11 @@ static GlamacResult parse_manufacturer_glasses(const char* manufacturer_section, return GLAMAC_SUCCESS; } -// Load specific manufacturer -static GlamacResult load_single_manufacturer(const char* json_content, size_t json_len, const char* manufacturer_filter) { +// Load single manufacturer into a specific catalog +static GlamacResult load_single_manufacturer_to_catalog(const char* json_content, size_t json_len, + const char* manufacturer_name, GlassCatalog* catalog) { char manufacturer_key[MAX_MANUFACTURER_NAME_LEN + 16]; - int ret = snprintf(manufacturer_key, sizeof(manufacturer_key), "\"%s\":", manufacturer_filter); + int ret = snprintf(manufacturer_key, sizeof(manufacturer_key), "\"%s\":", manufacturer_name); if (ret < 0 || ret >= (int)sizeof(manufacturer_key)) { return GLAMAC_ERROR_INVALID_ARGUMENT; } @@ -348,81 +341,52 @@ static GlamacResult load_single_manufacturer(const char* json_content, size_t js } // For single manufacturer loading, use the entire remaining JSON - // This ensures we don't miss any glasses due to boundary detection issues size_t section_len = json_len - (mfg_section - json_content); - // Initialize with full capacity to avoid realloc during parsing - GlamacResult result = allocate_glasses(&g_glass_context, 5000); + // Initialize catalog with full capacity to avoid realloc during parsing + GlamacResult result = allocate_catalog_glasses(catalog, 2000, manufacturer_name); if (result != GLAMAC_SUCCESS) { return result; } - result = parse_manufacturer_glasses(mfg_section, section_len, manufacturer_filter); + result = parse_manufacturer_glasses(mfg_section, section_len, catalog); if (result != GLAMAC_SUCCESS) { return result; } - if (g_glass_context.count == 0) { - cleanup_glass_context(&g_glass_context); + if (catalog->count == 0) { return GLAMAC_ERROR_NO_GLASSES_FOUND; } - g_glass_context.using_json_data = 1; return GLAMAC_SUCCESS; } -// Load all manufacturers -static GlamacResult load_all_manufacturers(const char* json_content, size_t json_len) { +// Load all manufacturers into separate catalogs +static GlamacResult load_all_manufacturers_to_catalogs(const char* json_content, size_t json_len) { const char* manufacturers[] = {"SCHOTT", "HOYA", "CDGM", "Ohara", NULL}; - // Initialize with larger capacity for all manufacturers - GlamacResult result = allocate_glasses(&g_glass_context, 5000); - if (result != GLAMAC_SUCCESS) { - return result; - } - - b32 found_any = 0; + g_glass_context.catalog_count = 0; + g_glass_context.current_catalog = -1; - for (int i = 0; manufacturers[i] != NULL; i++) { - char manufacturer_key[MAX_MANUFACTURER_NAME_LEN + 16]; - int ret = snprintf(manufacturer_key, sizeof(manufacturer_key), "\"%s\":", manufacturers[i]); - if (ret < 0 || ret >= (int)sizeof(manufacturer_key)) { - continue; // Skip this manufacturer - } - - const char* mfg_section = strstr(json_content, manufacturer_key); - if (!mfg_section) { - continue; // This manufacturer not found in JSON - } + for (int i = 0; manufacturers[i] != NULL && g_glass_context.catalog_count < 8; i++) { + GlassCatalog* catalog = &g_glass_context.catalogs[g_glass_context.catalog_count]; - // Find the end of this manufacturer section - const char* section_end = json_content + json_len; - const char* next_mfg = mfg_section + strlen(manufacturer_key); - - // Look for the next manufacturer key - for (int j = 0; manufacturers[j] != NULL; j++) { - if (j == i) continue; // Skip current manufacturer - - char next_key[MAX_MANUFACTURER_NAME_LEN + 16]; - ret = snprintf(next_key, sizeof(next_key), "\"%s\":", manufacturers[j]); - if (ret < 0 || ret >= (int)sizeof(next_key)) continue; + 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); + g_glass_context.catalog_count++; - const char* next_section = strstr(next_mfg, next_key); - if (next_section && next_section < section_end) { - section_end = next_section; + // Set first successful catalog as current + if (g_glass_context.current_catalog == -1) { + g_glass_context.current_catalog = (i32)(g_glass_context.catalog_count - 1); } - } - - size_t section_len = section_end - mfg_section; - - GlamacResult parse_result = parse_manufacturer_glasses(mfg_section, section_len, manufacturers[i]); - if (parse_result == GLAMAC_SUCCESS) { - found_any = 1; + } else { + printf("Failed to load %s: %s\n", manufacturers[i], glamac_error_string(result)); } } - if (!found_any || g_glass_context.count == 0) { - cleanup_glass_context(&g_glass_context); + if (g_glass_context.catalog_count == 0) { return GLAMAC_ERROR_NO_GLASSES_FOUND; } @@ -478,11 +442,14 @@ static GlamacResult load_glasses_from_json_secure(const char* json_path, const c // Handle both single manufacturer and all manufacturers cases if (manufacturer_filter) { - // Load specific manufacturer - result = load_single_manufacturer(json_content, bytes_read, manufacturer_filter); + // Load specific manufacturer into first catalog + g_glass_context.catalog_count = 1; + g_glass_context.current_catalog = 0; + result = load_single_manufacturer_to_catalog(json_content, bytes_read, + manufacturer_filter, &g_glass_context.catalogs[0]); } else { - // Load all manufacturers - result = load_all_manufacturers(json_content, bytes_read); + // Load all manufacturers into separate catalogs + result = load_all_manufacturers_to_catalogs(json_content, bytes_read); } free(json_content); @@ -517,45 +484,57 @@ static GlamacResult try_load_json_with_fallbacks_secure(const char* manufacturer // Public API implementation u32 get_glass_count(void) { - return g_glass_context.count; + if (g_glass_context.current_catalog < 0 || g_glass_context.current_catalog >= (i32)g_glass_context.catalog_count) { + return 0; + } + return g_glass_context.catalogs[g_glass_context.current_catalog].count; } const Glass* get_glass(u32 index) { - if (index < g_glass_context.count && g_glass_context.glasses) { - return &g_glass_context.glasses[index]; + if (g_glass_context.current_catalog < 0 || g_glass_context.current_catalog >= (i32)g_glass_context.catalog_count) { + return NULL; + } + GlassCatalog* catalog = &g_glass_context.catalogs[g_glass_context.current_catalog]; + if (index < catalog->count && catalog->glasses) { + return &catalog->glasses[index]; } return NULL; } const byte* get_glass_name(u32 index) { - if (index < g_glass_context.count && g_glass_context.glasses) { - return g_glass_context.glasses[index].name; - } - return NULL; + const Glass* glass = get_glass(index); + return glass ? glass->name : NULL; } void find_glass_data_range(f32 *minAbbe, f32 *maxAbbe, f32 *minRI, f32 *maxRI) { if (!minAbbe || !maxAbbe || !minRI || !maxRI) return; - if (g_glass_context.count == 0 || !g_glass_context.glasses) { + if (g_glass_context.current_catalog < 0 || g_glass_context.current_catalog >= (i32)g_glass_context.catalog_count) { + *minAbbe = *maxAbbe = 50.0f; + *minRI = *maxRI = 1.5f; + return; + } + + GlassCatalog* catalog = &g_glass_context.catalogs[g_glass_context.current_catalog]; + if (catalog->count == 0 || !catalog->glasses) { *minAbbe = *maxAbbe = 50.0f; *minRI = *maxRI = 1.5f; return; } - *minAbbe = g_glass_context.glasses[0].abbeNumber; - *maxAbbe = g_glass_context.glasses[0].abbeNumber; - *minRI = g_glass_context.glasses[0].refractiveIndex; - *maxRI = g_glass_context.glasses[0].refractiveIndex; + *minAbbe = catalog->glasses[0].abbeNumber; + *maxAbbe = catalog->glasses[0].abbeNumber; + *minRI = catalog->glasses[0].refractiveIndex; + *maxRI = catalog->glasses[0].refractiveIndex; - for (u32 i = 1; i < g_glass_context.count; i++) { - if (g_glass_context.glasses[i].abbeNumber < *minAbbe) *minAbbe = g_glass_context.glasses[i].abbeNumber; - if (g_glass_context.glasses[i].abbeNumber > *maxAbbe) *maxAbbe = g_glass_context.glasses[i].abbeNumber; - if (g_glass_context.glasses[i].refractiveIndex < *minRI) *minRI = g_glass_context.glasses[i].refractiveIndex; - if (g_glass_context.glasses[i].refractiveIndex > *maxRI) *maxRI = g_glass_context.glasses[i].refractiveIndex; + for (u32 i = 1; i < catalog->count; i++) { + if (catalog->glasses[i].abbeNumber < *minAbbe) *minAbbe = catalog->glasses[i].abbeNumber; + if (catalog->glasses[i].abbeNumber > *maxAbbe) *maxAbbe = catalog->glasses[i].abbeNumber; + if (catalog->glasses[i].refractiveIndex < *minRI) *minRI = catalog->glasses[i].refractiveIndex; + if (catalog->glasses[i].refractiveIndex > *maxRI) *maxRI = catalog->glasses[i].refractiveIndex; } - // Add exactly 5% margin to the range as requested + // Add exactly 5% margin to the range const f32 abbeMargin = (*maxAbbe - *minAbbe) * 0.05f; const f32 riMargin = (*maxRI - *minRI) * 0.05f; *minAbbe -= abbeMargin; @@ -570,33 +549,86 @@ b32 load_glasses_from_json(const byte* json_path, const byte* manufacturer_filte } void initialize_glass_data(void) { - // Load only SCHOTT catalog for now - GlamacResult result = try_load_json_with_fallbacks_secure("SCHOTT"); + // Load all manufacturers into memory + GlamacResult result = try_load_json_with_fallbacks_secure(NULL); // NULL = load all if (result != GLAMAC_SUCCESS) { - // Fallback to default static data + // Fallback to default static data in a single catalog printf("JSON loading failed (%s), using fallback glass data\n", glamac_error_string(result)); - cleanup_glass_context(&g_glass_context); + cleanup_glass_data(); - GlamacResult alloc_result = allocate_glasses(&g_glass_context, DEFAULT_GLASS_COUNT); + GlamacResult alloc_result = allocate_catalog_glasses(&g_glass_context.catalogs[0], DEFAULT_GLASS_COUNT, "SCHOTT"); if (alloc_result == GLAMAC_SUCCESS) { - memcpy(g_glass_context.glasses, default_glasses, DEFAULT_GLASS_COUNT * sizeof(Glass)); - g_glass_context.count = DEFAULT_GLASS_COUNT; + memcpy(g_glass_context.catalogs[0].glasses, default_glasses, DEFAULT_GLASS_COUNT * sizeof(Glass)); + g_glass_context.catalogs[0].count = DEFAULT_GLASS_COUNT; + g_glass_context.catalog_count = 1; + g_glass_context.current_catalog = 0; g_glass_context.using_json_data = 0; // Set manufacturer for default glasses for (u32 i = 0; i < DEFAULT_GLASS_COUNT; i++) { - strcpy((char*)g_glass_context.glasses[i].manufacturer, "SCHOTT"); + strcpy((char*)g_glass_context.catalogs[0].glasses[i].manufacturer, "SCHOTT"); } } else { printf("Critical error: Failed to allocate fallback glass data\n"); - g_glass_context.count = 0; + g_glass_context.catalog_count = 0; + g_glass_context.current_catalog = -1; } } } +// Catalog management functions +u32 get_catalog_count(void) { + return g_glass_context.catalog_count; +} + +const char* get_catalog_name(u32 catalog_index) { + if (catalog_index >= g_glass_context.catalog_count) { + return NULL; + } + return g_glass_context.catalogs[catalog_index].name; +} + +const char* get_current_catalog_name(void) { + if (g_glass_context.current_catalog < 0 || g_glass_context.current_catalog >= (i32)g_glass_context.catalog_count) { + return "Unknown"; + } + return g_glass_context.catalogs[g_glass_context.current_catalog].name; +} + +void set_current_catalog(u32 catalog_index) { + if (catalog_index < g_glass_context.catalog_count) { + g_glass_context.current_catalog = (i32)catalog_index; + } +} + +void cycle_catalog(i32 direction) { + if (g_glass_context.catalog_count == 0) return; + + i32 new_catalog = g_glass_context.current_catalog + direction; + + // Wrap around + if (new_catalog < 0) { + new_catalog = (i32)g_glass_context.catalog_count - 1; + } else if (new_catalog >= (i32)g_glass_context.catalog_count) { + new_catalog = 0; + } + + g_glass_context.current_catalog = new_catalog; +} + // Cleanup function for proper resource management void cleanup_glass_data(void) { - cleanup_glass_context(&g_glass_context); + for (u32 i = 0; i < g_glass_context.catalog_count; i++) { + if (g_glass_context.catalogs[i].glasses) { + free(g_glass_context.catalogs[i].glasses); + g_glass_context.catalogs[i].glasses = NULL; + g_glass_context.catalogs[i].count = 0; + g_glass_context.catalogs[i].capacity = 0; + } + } + 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 -- cgit v1.2.3