summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/glamac_render.h33
-rw-r--r--include/glamac_view.h10
-rw-r--r--include/glass_data.h7
-rw-r--r--src/glamac/glamac.c59
-rw-r--r--src/glamac/glamac_events.c56
-rw-r--r--src/glamac/glamac_render.c678
-rw-r--r--src/glamac/glamac_view.c15
-rw-r--r--src/glamac/glass_data.c304
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 <SDL3/SDL.h>
+#include <stdio.h>
#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 <ctype.h>
#include <limits.h>
-// 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
Back to https://optics-design.com