summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoradmin <admin@optics-design.com>2025-08-04 12:37:12 +0200
committeradmin <admin@optics-design.com>2025-08-04 12:37:12 +0200
commit8e1ae9c581ea51bfef6e531865f75453180f31b6 (patch)
treed90b301180f55f88aa022ce5421dbd3a0b61bcd1 /src
parent4910356db2585e55d2876001e40b21e9b148bcc4 (diff)
changes to glamac rendering
Diffstat (limited to 'src')
-rw-r--r--src/glamac/glamac.c70
-rw-r--r--src/glamac/glamac_events.c49
-rw-r--r--src/glamac/glamac_render.c603
-rw-r--r--src/glamac/glamac_view.c114
-rw-r--r--src/glamac/glass_data.c61
-rw-r--r--src/glautils/fgla.c7
-rw-r--r--src/glautils/gla.c139
7 files changed, 801 insertions, 242 deletions
diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c
index 60d0a1e..7988cc7 100644
--- a/src/glamac/glamac.c
+++ b/src/glamac/glamac.c
@@ -4,6 +4,7 @@
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <stdio.h>
+#include <stdlib.h>
#include "glamacdef.h" // Type definitions
#include "glass_data.h" // Glass catalog
#include "glamac_view.h" // View management
@@ -11,17 +12,48 @@
// External function declarations from glamac_events.c
extern b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window,
- i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit);
+ i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit,
+ GlassCluster *clusters, i32 clusterCount);
+
+// Function to reload fonts on window resize
+b32 reload_fonts_if_needed(FontSet *fonts, ViewState *view, f32 dpi) {
+ static i32 lastWidth = 0;
+ static i32 lastHeight = 0;
+
+ // Check if window size changed significantly (more than 50% change)
+ if (abs(view->windowWidth - lastWidth) > view->windowWidth * 0.5 ||
+ abs(view->windowHeight - lastHeight) > view->windowHeight * 0.5) {
+
+ // Clear text cache first
+ clear_text_cache();
+
+ // Free existing fonts
+ free_fonts(fonts);
+
+ // Reload with new size
+ if (!load_adaptive_fonts(fonts, view->windowWidth, view->windowHeight, dpi)) {
+ printf("Failed to reload fonts after window resize!\n");
+ return 0;
+ }
+
+ lastWidth = view->windowWidth;
+ lastHeight = view->windowHeight;
+ printf("Fonts reloaded for new window size: %dx%d\n", view->windowWidth, view->windowHeight);
+ }
+
+ return 1;
+}
// Initial window dimensions
#define INITIAL_WIDTH 800
#define INITIAL_HEIGHT 600
-int main(int argc, char* argv[]) {
+int main(int argc __attribute__((unused)), char* argv[] __attribute__((unused))) {
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
FontSet fonts = {0};
+
// Initialize SDL3
if (!SDL_Init(SDL_INIT_VIDEO)) {
printf("SDL3 could not initialize! SDL_Error: %s\n", SDL_GetError());
@@ -58,8 +90,21 @@ int main(int argc, char* argv[]) {
// Enable vsync
SDL_SetRenderVSync(renderer, 1);
- // Load fonts
- if (!load_fonts(&fonts)) {
+ // Get display DPI (fallback to default if not available)
+ f32 dpi = 96.0f; // Default DPI
+ #if SDL_VERSION_ATLEAST(3, 0, 0)
+ // SDL3 uses different display API
+ SDL_DisplayID* displays = SDL_GetDisplays(NULL);
+ if (displays) {
+ if (SDL_GetDisplayContentScale(displays[0]) > 0) {
+ dpi = 96.0f * SDL_GetDisplayContentScale(displays[0]);
+ }
+ SDL_free(displays);
+ }
+ #endif
+
+ // Load fonts with adaptive sizing
+ if (!load_adaptive_fonts(&fonts, INITIAL_WIDTH, INITIAL_HEIGHT, dpi)) {
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
@@ -74,6 +119,11 @@ int main(int argc, char* argv[]) {
ViewState view;
init_view_state(&view, INITIAL_WIDTH, INITIAL_HEIGHT);
+ // Create glass clusters for similar glasses
+ i32 clusterCount = 0;
+ GlassCluster* clusters = create_glass_clusters(&clusterCount);
+ printf("Created %d glass clusters from %d glasses\n", clusterCount, get_glass_count());
+
// Main loop flag
b32 quit = 0;
@@ -81,7 +131,6 @@ int main(int argc, char* argv[]) {
SDL_Event e;
// Mouse state
- i32 mouseX = 0, mouseY = 0;
i32 lastMouseX = 0, lastMouseY = 0;
b32 dragging = 0;
@@ -92,20 +141,27 @@ int main(int argc, char* argv[]) {
while (!quit) {
// Handle events on queue
while (SDL_PollEvent(&e)) {
- process_events(&e, &view, window, &lastMouseX, &lastMouseY, &dragging, &quit);
+ process_events(&e, &view, window, &lastMouseX, &lastMouseY, &dragging, &quit, clusters, clusterCount);
+ }
+
+ // Reload fonts if window size changed significantly
+ if (!reload_fonts_if_needed(&fonts, &view, dpi)) {
+ quit = 1; // Exit if font reloading fails
}
// Render everything
- render(renderer, fonts.regular, fonts.title, fonts.label, &view);
+ render(renderer, fonts.regular, fonts.title, fonts.label, &view, clusters, clusterCount);
}
// Stop text input
SDL_StopTextInput(window);
// Clean up resources
+ free_glass_clusters(clusters);
cleanup_glass_data();
free_fonts(&fonts);
clear_text_cache();
+ cleanup_static_label_positions();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
TTF_Quit();
diff --git a/src/glamac/glamac_events.c b/src/glamac/glamac_events.c
index e5c706c..63bb07c 100644
--- a/src/glamac/glamac_events.c
+++ b/src/glamac/glamac_events.c
@@ -28,9 +28,10 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
break;
case SDLK_ESCAPE:
- // ESC closes glass selection first, then help window, finally quits
- if (view->selectedGlass >= 0) {
+ // ESC closes glass/cluster selection first, then help window, finally quits
+ if (view->selectedGlass >= 0 || view->selectedCluster >= 0) {
view->selectedGlass = -1; // Clear selection
+ view->selectedCluster = -1;
} else if (view->showHelp) {
view->showHelp = 0;
} else {
@@ -105,7 +106,7 @@ b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *windo
}
// Process mouse button event
-b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging) {
+b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, GlassCluster *clusters, i32 clusterCount) {
f32 mouseX, mouseY;
switch (button->button) {
@@ -113,14 +114,34 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las
if (button->type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
SDL_GetMouseState(&mouseX, &mouseY);
- // Check if click is near a glass point
- i32 nearestGlass = find_nearest_glass((i32)mouseX, (i32)mouseY, view, 15); // 15 pixels max distance
+ // First find the nearest glass point
+ i32 nearestGlass = find_nearest_glass((i32)mouseX, (i32)mouseY, view, 15);
if (nearestGlass >= 0) {
- view->selectedGlass = nearestGlass;
- // Don't start dragging when selecting a glass
+ // 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;
+ }
} else {
- // If clicking outside any glass point, clear selection
+ // If clicking outside any point, clear all selections
view->selectedGlass = -1;
+ view->selectedCluster = -1;
// Start dragging for panning
*dragging = 1;
*lastMouseX = (i32)mouseX;
@@ -138,14 +159,14 @@ b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *las
}
// Process mouse motion event
-b32 process_mouse_motion(SDL_MouseMotionEvent *motion, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 dragging) {
+b32 process_mouse_motion(SDL_MouseMotionEvent *motion __attribute__((unused)), ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 dragging) {
if (dragging) {
// Get current mouse position
f32 mouseX, mouseY;
SDL_GetMouseState(&mouseX, &mouseY);
// Calculate normalized delta
- const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 padding = get_adaptive_padding(view);
const f32 dx = (mouseX - *lastMouseX) / (view->windowWidth - 2 * padding);
const f32 dy = (mouseY - *lastMouseY) / (view->windowHeight - 2 * padding);
@@ -170,6 +191,9 @@ b32 process_window_event(SDL_WindowEvent *window_event, ViewState *view) {
view->windowWidth = (i32)window_event->data1;
view->windowHeight = (i32)window_event->data2;
return 1;
+ default:
+ // Ignore other window events
+ break;
}
return 0;
@@ -177,7 +201,8 @@ b32 process_window_event(SDL_WindowEvent *window_event, ViewState *view) {
// Process all events on queue
b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window,
- i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit) {
+ i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit,
+ GlassCluster *clusters, i32 clusterCount) {
switch (event->type) {
case SDL_EVENT_QUIT:
*quit = 1;
@@ -195,7 +220,7 @@ b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window,
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
- return process_mouse_button(&event->button, view, lastMouseX, lastMouseY, dragging);
+ return process_mouse_button(&event->button, view, lastMouseX, lastMouseY, dragging, clusters, clusterCount);
case SDL_EVENT_MOUSE_MOTION:
return process_mouse_motion(&event->motion, view, lastMouseX, lastMouseY, *dragging);
diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c
index 619a6e0..9d03645 100644
--- a/src/glamac/glamac_render.c
+++ b/src/glamac/glamac_render.c
@@ -4,6 +4,7 @@
#include <stdio.h>
#include <math.h>
#include <string.h>
+#include <stdlib.h>
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "glamac_render.h"
@@ -11,6 +12,111 @@
// Drawing primitives
+// Smart label positioning
+typedef struct {
+ i32 dx, dy;
+} LabelOffset;
+
+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
+};
+#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)
+
+// 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;
+
+// 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;
+
+ for (i32 i = 0; i < (i32)get_glass_count(); i++) {
+ const Glass* glass = get_glass(i);
+ if (!glass || glass == targetGlass) 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);
+
+ if (distance < DENSITY_RADIUS) {
+ nearbyCount++;
+ }
+ }
+
+ return (f32)nearbyCount;
+}
+
+// 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;
+ }
+
+ // Calculate density-based threshold
+ f32 density = get_local_density(glass, view);
+ const f32 DENSITY_THRESHOLD = 3.0f; // Maximum nearby glasses before hiding labels
+
+ // Show label if density is low enough
+ return density < DENSITY_THRESHOLD;
+}
+
+// Helper function to calculate adaptive radius based on window size and DPI
+static inline i32 get_adaptive_radius(const ViewState* view, i32 baseRadius) {
+ // Scale based on window size - use average of width and height
+ f32 sizeScale = (view->windowWidth + view->windowHeight) / 1600.0f; // Reference: 1600 (avg of 1920x1080)
+
+ // Clamp scale factor to reasonable bounds
+ if (sizeScale < 0.7f) sizeScale = 0.7f;
+ if (sizeScale > 2.5f) sizeScale = 2.5f;
+
+ i32 scaledRadius = (i32)(baseRadius * sizeScale);
+
+ // Ensure minimum radius for usability
+ if (scaledRadius < 2) scaledRadius = 2;
+ if (scaledRadius > 12) scaledRadius = 12;
+
+ return scaledRadius;
+}
+
// Structure to hold cached text textures
typedef struct {
char text[64]; // The text string
@@ -19,10 +125,11 @@ typedef struct {
SDL_Texture *texture; // The cached texture
i32 width; // Texture width
i32 height; // Texture height
+ u32 lastUsed; // Timestamp for LRU eviction
} CachedText;
// Text cache
-#define MAX_TEXT_CACHE 128
+#define MAX_TEXT_CACHE 2048
static CachedText textCache[MAX_TEXT_CACHE] = {0};
static i32 cacheCount = 0;
@@ -40,7 +147,8 @@ void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x,
entry->color.b == color.b &&
entry->color.a == color.a) {
- // Use the cached texture
+ // Use the cached texture and update LRU timestamp
+ entry->lastUsed = SDL_GetTicks();
SDL_FRect rect = {(f32)x, (f32)y, (f32)entry->width, (f32)entry->height};
SDL_RenderTexture(renderer, entry->texture, NULL, &rect);
return;
@@ -65,24 +173,39 @@ void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x,
SDL_FRect rect = {(f32)x, (f32)y, (f32)surface->w, (f32)surface->h};
SDL_RenderTexture(renderer, texture, NULL, &rect);
- // Add to cache if there is space
+ // Add to cache or replace LRU entry
+ CachedText *entry = NULL;
if (cacheCount < MAX_TEXT_CACHE) {
- CachedText *entry = &textCache[cacheCount++];
-
- // Copy text (with bounds checking)
- strncpy(entry->text, text, sizeof(entry->text) - 1);
- entry->text[sizeof(entry->text) - 1] = '\0';
-
- entry->color = color;
- entry->font = font;
- entry->texture = texture;
- entry->width = surface->w;
- entry->height = surface->h;
+ // Cache has space, use new slot
+ entry = &textCache[cacheCount++];
} else {
- // Cache is full, just clean up the texture
- SDL_DestroyTexture(texture);
+ // Cache is full, find LRU entry to replace
+ u32 oldestTime = SDL_GetTicks();
+ i32 oldestIndex = 0;
+ for (i32 i = 0; i < MAX_TEXT_CACHE; i++) {
+ if (textCache[i].lastUsed < oldestTime) {
+ oldestTime = textCache[i].lastUsed;
+ oldestIndex = i;
+ }
+ }
+ entry = &textCache[oldestIndex];
+
+ // Clean up old texture
+ if (entry->texture) {
+ SDL_DestroyTexture(entry->texture);
+ }
}
+ // Store new entry
+ strncpy(entry->text, text, sizeof(entry->text) - 1);
+ entry->text[sizeof(entry->text) - 1] = '\0';
+ entry->color = color;
+ entry->font = font;
+ entry->texture = texture;
+ entry->width = surface->w;
+ entry->height = surface->h;
+ entry->lastUsed = SDL_GetTicks();
+
SDL_DestroySurface(surface);
}
@@ -109,7 +232,7 @@ void draw_filled_circle(SDL_Renderer *renderer, i32 centerX, i32 centerY, i32 ra
// Function to draw the axes with correct visible range
void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
const SDL_Color black = {0, 0, 0, 255};
- const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 padding = get_adaptive_padding(view);
// X-axis
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
@@ -165,7 +288,7 @@ void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, cons
// Function to draw a grid based on visible data range
void draw_grid(SDL_Renderer *renderer, const ViewState* view) {
- const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 padding = get_adaptive_padding(view);
// Calculate visible range for grid
f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI;
@@ -210,25 +333,292 @@ void draw_grid(SDL_Renderer *renderer, const ViewState* view) {
}
}
-// Function to draw glass points and labels
-void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, 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 "";
+
+ const Glass* shortestGlass = get_glass(cluster->glassIndices[0]);
+ if (!shortestGlass) return "";
+
+ const char* shortestName = (const char*)shortestGlass->name;
+ size_t shortestLen = strlen(shortestName);
+
+ for (i32 i = 1; i < cluster->count; i++) {
+ const Glass* glass = get_glass(cluster->glassIndices[i]);
+ if (!glass) continue;
+
+ const char* name = (const char*)glass->name;
+ size_t len = strlen(name);
+ if (len < shortestLen) {
+ shortestName = name;
+ shortestLen = len;
+ }
+ }
+
+ 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);
+
+ // 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
+ 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;
+ }
+ }
+
+ if (!collision) {
+ return posIdx; // Found a good position
+ }
+ }
+
+ // 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;
+
+ // 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;
+ }
+ }
+
+ 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++;
+ }
+ }
+
+ free(clusterProcessed);
+ if (tempFont) TTF_CloseFont(tempFont);
+}
+
+// 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
+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 = (i32)(view->windowWidth * PADDING_PERCENT);
- const i32 radius = 4;
+ const i32 padding = get_adaptive_padding(view);
+ const i32 baseRadius = 4;
+ const i32 radius = get_adaptive_radius(view, baseRadius);
+
+ // Get visible data range for smart culling
+ f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI;
+ get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI);
- for (i32 i = 0; i < get_glass_count(); i++) {
+ // If static positions haven't been calculated yet, calculate them
+ if (!g_staticLabels) {
+ calculate_static_label_positions(clusters, clusterCount, view);
+ }
+
+ // Draw all glass points
+ for (i32 i = 0; i < (i32)get_glass_count(); i++) {
const Glass* glass = get_glass(i);
+ if (!glass) continue;
+
+ // Smart culling: Check data bounds before expensive coordinate conversion
+ if (glass->abbeNumber < visibleMinAbbe || glass->abbeNumber > visibleMaxAbbe ||
+ glass->refractiveIndex < visibleMinRI || glass->refractiveIndex > visibleMaxRI) {
+ continue;
+ }
+
i32 x, y;
data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &x, &y);
- // Skip if outside visible area
+ // Final screen bounds check
if (x < padding || x > view->windowWidth - padding ||
y < padding || y > view->windowHeight - padding) {
continue;
}
- // Highlight selected glass
- if (i == view->selectedGlass) {
+ // 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)) {
// Draw a larger highlight circle
SDL_SetRenderDrawColor(renderer, 100, 100, 255, 255); // Light blue highlight
draw_filled_circle(renderer, x, y, radius + 2);
@@ -237,19 +627,47 @@ void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewSt
// Draw filled circle
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // Red for glass points
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;
+
+ // Check if this glass is visible
+ 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);
+
+ // Check if glass point is on screen
+ if (glassX < padding || glassX > view->windowWidth - padding ||
+ glassY < padding || glassY > view->windowHeight - padding) {
+ continue;
+ }
+
+ // 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 glass name
- draw_text(renderer, labelFont, (const char*)glass->name, x + 6, y - 10, blue);
+ // Draw the label
+ draw_text(renderer, labelFont, label->text, labelX, labelY, blue);
}
}
// Function to draw the properties popup for the selected glass
void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
- if (view->selectedGlass < 0 || view->selectedGlass >= get_glass_count()) {
+ if (view->selectedGlass < 0 || view->selectedGlass >= (i32)get_glass_count()) {
return; // No glass selected
}
const Glass* glass = get_glass(view->selectedGlass);
+ if (!glass) return; // Safety check
// Calculate glass position on screen
i32 glassX, glassY;
@@ -305,6 +723,65 @@ void draw_glass_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *tit
SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
}
+// Function to draw properties for a cluster of similar glasses
+void draw_cluster_properties(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont __attribute__((unused)), const ViewState* view, GlassCluster* clusters, i32 clusterCount) {
+ if (view->selectedCluster < 0 || view->selectedCluster >= clusterCount) {
+ return; // No cluster selected
+ }
+
+ const GlassCluster* cluster = &clusters[view->selectedCluster];
+
+ // Calculate cluster position on screen
+ i32 clusterX, clusterY;
+ data_to_screen_coords(cluster->avgAbbeNumber, cluster->avgRefractiveIndex, view, &clusterX, &clusterY);
+
+ // Define property window size (no title, just properties)
+ const i32 windowWidth = 280;
+ const i32 windowHeight = 20 + cluster->count * 25; // Dynamic height based on glass count (no title)
+ 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;
+ }
+
+ // Draw background with slight transparency
+ SDL_FRect windowRect = {(f32)windowX, (f32)windowY, (f32)windowWidth, (f32)windowHeight};
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
+ SDL_SetRenderDrawColor(renderer, 245, 245, 245, 235);
+ SDL_RenderFillRect(renderer, &windowRect);
+
+ // Draw border
+ SDL_SetRenderDrawColor(renderer, 40, 40, 40, 255);
+ SDL_RenderRect(renderer, &windowRect);
+
+ // Draw properties for each glass in cluster directly (no title)
+ SDL_Color black = {0, 0, 0, 255};
+ for (i32 i = 0; i < cluster->count; i++) {
+ const Glass* glass = get_glass(cluster->glassIndices[i]);
+ if (!glass) continue;
+
+ char buffer[128];
+ i32 yPos = windowY + padding + i * 25;
+
+ sprintf(buffer, "%s: n=%.4f, V=%.2f", (const char*)glass->name, glass->refractiveIndex, glass->abbeNumber);
+ draw_text(renderer, font, buffer, windowX + padding, yPos, black);
+ }
+
+ // Reset blend mode
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
+}
+
// Function to draw the help window
void draw_help_window(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
// Background for help window
@@ -354,7 +831,7 @@ 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) {
+void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, const ViewState* view, GlassCluster* clusters, i32 clusterCount) {
const SDL_Color black = {0, 0, 0, 255};
// Clear screen (white background)
@@ -369,10 +846,14 @@ void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Fon
draw_axes(renderer, font, titleFont, view);
// Draw glass points and labels
- draw_glass_points(renderer, labelFont, view);
+ draw_glass_points(renderer, labelFont, view, clusters, clusterCount);
- // Draw glass properties if a glass is selected
- if (view->selectedGlass >= 0) {
+ // Draw cluster properties if a cluster is selected
+ if (view->selectedCluster >= 0) {
+ draw_cluster_properties(renderer, font, titleFont, view, clusters, clusterCount);
+ }
+ // Fallback to individual glass properties for backward compatibility
+ else if (view->selectedGlass >= 0) {
draw_glass_properties(renderer, font, titleFont, view);
}
@@ -427,11 +908,46 @@ static TTF_Font* try_load_font_from_paths(const char** fontNames, i32 numFonts,
return NULL;
}
-// Load all required fonts
-b32 load_fonts(FontSet *fonts) {
+// Helper function to calculate DPI-aware font sizes
+static void calculate_font_sizes(i32 windowWidth, i32 windowHeight, f32 dpi, i32* regular, i32* title, i32* label) {
+ // Base sizes for reference resolution (1920x1080)
+ const i32 baseRegular = 14;
+ const i32 baseTitle = 18;
+ const i32 baseLabel = 12;
+
+ // Calculate scale factor based on window size and DPI
+ f32 sizeScale = (windowWidth + windowHeight) / 1600.0f; // Average of 1920x1080
+ f32 dpiScale = dpi / 96.0f; // 96 DPI is standard
+ f32 totalScale = sizeScale * dpiScale;
+
+ // Clamp scale factor to reasonable bounds
+ if (totalScale < 0.7f) totalScale = 0.7f;
+ if (totalScale > 2.5f) totalScale = 2.5f;
+
+ *regular = (i32)(baseRegular * totalScale);
+ *title = (i32)(baseTitle * totalScale);
+
+ // Apply configurable label scaling
+ f32 labelScale = totalScale * LABEL_SIZE_SCALE; // Use configurable scale factor
+ if (labelScale < 0.7f) labelScale = 0.7f;
+ if (labelScale > 2.0f) labelScale = 2.0f; // Allow larger labels
+ *label = (i32)(baseLabel * labelScale);
+
+ // Ensure minimum readable sizes
+ if (*regular < 10) *regular = 10;
+ if (*title < 12) *title = 12;
+ if (*label < 8) *label = 8; // Smaller minimum for labels
+}
+
+// Load fonts with DPI-aware sizing
+b32 load_adaptive_fonts(FontSet *fonts, i32 windowWidth, i32 windowHeight, f32 dpi) {
// Common font names to try, in order of preference
const char* fontNames[] = {
#ifdef _WIN32
+ // Try bundled font first for Windows builds
+ "DejaVuSans.ttf",
+ "./DejaVuSans.ttf",
+ // Then try Windows system fonts
"C:\\Windows\\Fonts\\arial.ttf",
"C:\\Windows\\Fonts\\tahoma.ttf",
"C:\\Windows\\Fonts\\verdana.ttf",
@@ -461,10 +977,13 @@ b32 load_fonts(FontSet *fonts) {
};
const i32 numFonts = sizeof(fontNames) / sizeof(fontNames[0]);
- i32 fontSizes[] = {14, 18, 12}; // regular, title, label
+ i32 regularSize, titleSize, labelSize;
+ calculate_font_sizes(windowWidth, windowHeight, dpi, &regularSize, &titleSize, &labelSize);
+
+ printf("Adaptive font sizes: regular=%d, title=%d, label=%d\n", regularSize, titleSize, labelSize);
// Load regular font
- fonts->regular = try_load_font_from_paths(fontNames, numFonts, fontSizes[0]);
+ fonts->regular = try_load_font_from_paths(fontNames, numFonts, regularSize);
if (!fonts->regular) {
printf("Failed to load any regular font!\n");
printf("SDL_Error: %s\n", SDL_GetError());
@@ -477,14 +996,14 @@ b32 load_fonts(FontSet *fonts) {
}
// Load title font (try larger size first, fallback to regular font)
- fonts->title = try_load_font_from_paths(fontNames, numFonts, fontSizes[1]);
+ fonts->title = try_load_font_from_paths(fontNames, numFonts, titleSize);
if (!fonts->title) {
printf("Warning: Could not load title font, using regular font\n");
fonts->title = fonts->regular; // Use the same font
}
// Load label font (try smaller size first, fallback to regular font)
- fonts->label = try_load_font_from_paths(fontNames, numFonts, fontSizes[2]);
+ fonts->label = try_load_font_from_paths(fontNames, numFonts, labelSize);
if (!fonts->label) {
printf("Warning: Could not load label font, using regular font\n");
fonts->label = fonts->regular; // Use the same font
@@ -493,6 +1012,12 @@ b32 load_fonts(FontSet *fonts) {
return 1;
}
+// Load all required fonts (legacy function for backward compatibility)
+b32 load_fonts(FontSet *fonts) {
+ // Use default sizes - this function is for backward compatibility
+ return load_adaptive_fonts(fonts, 1920, 1080, 96.0f);
+}
+
// Free all fonts
void free_fonts(FontSet *fonts) {
// Only close fonts that are different from regular font
diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c
index 22d6401..67763a8 100644
--- a/src/glamac/glamac_view.c
+++ b/src/glamac/glamac_view.c
@@ -2,6 +2,7 @@
* glamac_view.c - Source file dealing with view states, filtering, zooming etc. of GlaMaC.
*/
#include <math.h>
+#include <stdlib.h>
#include <SDL3/SDL.h>
#include "glamac_view.h"
#include "glass_data.h"
@@ -17,6 +18,7 @@ void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) {
view->gKeyPressed = 0;
view->gKeyTime = 0;
view->selectedGlass = -1; // No glass selected initially
+ view->selectedCluster = -1; // No cluster selected initially
// Calculate data range
find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI);
@@ -27,7 +29,7 @@ void get_visible_data_range(const ViewState* view, f32 *visibleMinAbbe, f32 *vis
f32 *visibleMinRI, f32 *visibleMaxRI) {
// Convert screen corners to data values
f32 topLeftAbbe, topLeftRI, bottomRightAbbe, bottomRightRI;
- const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 padding = get_adaptive_padding(view);
screen_to_data_coords(padding, padding, view, &topLeftAbbe, &topLeftRI);
screen_to_data_coords(view->windowWidth - padding, view->windowHeight - padding,
@@ -45,8 +47,9 @@ i32 find_nearest_glass(i32 x, i32 y, const ViewState* view, f32 maxDistance) {
i32 nearest = -1;
f32 minDist = maxDistance;
- for (i32 i = 0; i < get_glass_count(); i++) {
+ for (i32 i = 0; i < (i32)get_glass_count(); i++) {
const Glass* glass = get_glass(i);
+ if (!glass) continue; // Safety check
i32 glassX, glassY;
data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY);
@@ -88,7 +91,7 @@ void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view
data_to_screen_coords(mouseDataX, mouseDataY, view, &newMouseScreenX, &newMouseScreenY);
// Adjust offset to keep mouse position fixed
- const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 padding = get_adaptive_padding(view);
view->offsetX += (f32)(mouseX - newMouseScreenX) / (view->windowWidth - 2 * padding);
view->offsetY += (f32)(newMouseScreenY - mouseY) / (view->windowHeight - 2 * padding);
}
@@ -109,4 +112,109 @@ void reset_view(ViewState* view) {
view->zoomLevel = 1.0f;
view->offsetX = 0.0f;
view->offsetY = 0.0f;
+ view->selectedGlass = -1;
+ view->selectedCluster = -1;
+}
+
+// Create clusters from glasses with similar properties
+GlassCluster* create_glass_clusters(i32* clusterCount) {
+ const f32 SIMILARITY_THRESHOLD = 0.004f; // 0.4% difference threshold
+ const i32 maxGlasses = get_glass_count();
+
+ // Allocate temporary structures
+ GlassCluster* clusters = (GlassCluster*)calloc(maxGlasses, sizeof(GlassCluster));
+ b32* processed = (b32*)calloc(maxGlasses, sizeof(b32));
+ i32 numClusters = 0;
+
+ if (!clusters || !processed) {
+ if (clusters) free(clusters);
+ if (processed) free(processed);
+ *clusterCount = 0;
+ return NULL;
+ }
+
+ for (i32 i = 0; i < (i32)maxGlasses; i++) {
+ if (processed[i]) continue;
+
+ const Glass* glass1 = get_glass(i);
+ if (!glass1) continue;
+
+ // Start new cluster with this glass
+ GlassCluster* cluster = &clusters[numClusters];
+ cluster->glassIndices[0] = i;
+ cluster->count = 1;
+ cluster->avgAbbeNumber = glass1->abbeNumber;
+ cluster->avgRefractiveIndex = glass1->refractiveIndex;
+ processed[i] = 1;
+
+ // Find similar glasses to add to this cluster
+ for (i32 j = i + 1; j < (i32)maxGlasses && cluster->count < MAX_CLUSTER_SIZE; j++) {
+ if (processed[j]) continue;
+
+ const Glass* glass2 = get_glass(j);
+ if (!glass2) continue;
+
+ // Check if glasses are similar (within 1% difference)
+ f32 abbeDiff = fabsf(glass1->abbeNumber - glass2->abbeNumber) / glass1->abbeNumber;
+ f32 riDiff = fabsf(glass1->refractiveIndex - glass2->refractiveIndex) / glass1->refractiveIndex;
+
+ if (abbeDiff < SIMILARITY_THRESHOLD && riDiff < SIMILARITY_THRESHOLD) {
+ // Add to cluster and update averages
+ cluster->glassIndices[cluster->count] = j;
+ cluster->count++;
+
+ // Update running average
+ cluster->avgAbbeNumber = (cluster->avgAbbeNumber * (cluster->count - 1) + glass2->abbeNumber) / cluster->count;
+ cluster->avgRefractiveIndex = (cluster->avgRefractiveIndex * (cluster->count - 1) + glass2->refractiveIndex) / cluster->count;
+
+ processed[j] = 1;
+ }
+ }
+
+ numClusters++;
+ }
+
+ free(processed);
+
+ // Resize to actual number of clusters
+ GlassCluster* finalClusters = (GlassCluster*)realloc(clusters, numClusters * sizeof(GlassCluster));
+ if (!finalClusters) {
+ free(clusters);
+ *clusterCount = 0;
+ return NULL;
+ }
+
+ *clusterCount = numClusters;
+ return finalClusters;
+}
+
+// Free cluster memory
+void free_glass_clusters(GlassCluster* clusters) {
+ if (clusters) {
+ free(clusters);
+ }
+}
+
+// Find cluster at screen position
+i32 find_cluster_at_position(GlassCluster* clusters, i32 clusterCount, i32 x, i32 y, const ViewState* view) {
+ const f32 maxDistance = 15.0f; // Maximum distance in pixels
+ i32 nearest = -1;
+ f32 minDist = maxDistance;
+
+ for (i32 i = 0; i < clusterCount; i++) {
+ i32 clusterX, clusterY;
+ data_to_screen_coords(clusters[i].avgAbbeNumber, clusters[i].avgRefractiveIndex, view, &clusterX, &clusterY);
+
+ // Calculate distance
+ const f32 dx = x - clusterX;
+ const f32 dy = y - clusterY;
+ const f32 dist = sqrtf(dx*dx + dy*dy);
+
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = i;
+ }
+ }
+
+ return nearest;
}
diff --git a/src/glamac/glass_data.c b/src/glamac/glass_data.c
index 40f7ea3..28bd963 100644
--- a/src/glamac/glass_data.c
+++ b/src/glamac/glass_data.c
@@ -32,17 +32,17 @@ static GlassDataContext g_glass_context = {0};
// Fallback sample glass data (SCHOTT glasses for testing)
static const Glass default_glasses[] = {
- {"FK51A", 81.61f, 1.4866f},
- {"F2", 36.37f, 1.6200f},
- {"SF10", 28.41f, 1.7283f},
- {"SF11", 25.76f, 1.7847f},
- {"SK16", 60.32f, 1.6204f},
- {"LASF9", 32.17f, 1.8503f},
- {"N-SF6", 25.36f, 1.8050f},
- {"LaK9", 59.46f, 1.6910f},
- {"N-FK51A", 84.47f, 1.4875f},
- {"N-SF14", 27.38f, 1.7617f},
- {"N-BK7", 64.17f, 1.5168f}
+ {.name = "FK51A", .abbeNumber = 81.61f, .refractiveIndex = 1.4866f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "F2", .abbeNumber = 36.37f, .refractiveIndex = 1.6200f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "SF10", .abbeNumber = 28.41f, .refractiveIndex = 1.7283f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "SF11", .abbeNumber = 25.76f, .refractiveIndex = 1.7847f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "SK16", .abbeNumber = 60.32f, .refractiveIndex = 1.6204f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "LASF9", .abbeNumber = 32.17f, .refractiveIndex = 1.8503f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "N-SF6", .abbeNumber = 25.36f, .refractiveIndex = 1.8050f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "LaK9", .abbeNumber = 59.46f, .refractiveIndex = 1.6910f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "N-FK51A", .abbeNumber = 84.47f, .refractiveIndex = 1.4875f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "N-SF14", .abbeNumber = 27.38f, .refractiveIndex = 1.7617f, .glass_code = "", .manufacturer = "SCHOTT"},
+ {.name = "N-BK7", .abbeNumber = 64.17f, .refractiveIndex = 1.5168f, .glass_code = "", .manufacturer = "SCHOTT"}
};
#define DEFAULT_GLASS_COUNT (sizeof(default_glasses) / sizeof(default_glasses[0]))
@@ -197,8 +197,8 @@ static void cleanup_glass_context(GlassDataContext* ctx) {
static GlamacResult allocate_glasses(GlassDataContext* ctx, u32 count) {
if (!ctx || count == 0) return GLAMAC_ERROR_INVALID_ARGUMENT;
- // Prevent integer overflow
- if (count > (SIZE_MAX / sizeof(Glass))) {
+ // Prevent integer overflow - only check if count is very large
+ if (count > UINT32_MAX / sizeof(Glass)) {
return GLAMAC_ERROR_MEMORY;
}
@@ -347,27 +347,12 @@ static GlamacResult load_single_manufacturer(const char* json_content, size_t js
return GLAMAC_ERROR_MANUFACTURER_NOT_FOUND;
}
- // Find the end of this manufacturer section (next manufacturer or end of object)
- const char* section_end = json_content + json_len;
- const char* next_mfg = mfg_section + strlen(manufacturer_key);
+ // 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);
- // Look for the next manufacturer key or end of object
- while (next_mfg < json_content + json_len) {
- if (*next_mfg == '"' && next_mfg[1] != 'g') { // Not "glasses"
- // Check if this looks like another manufacturer key
- const char* colon = strchr(next_mfg, ':');
- if (colon && colon < next_mfg + 50) { // Reasonable manufacturer name length
- section_end = next_mfg;
- break;
- }
- }
- next_mfg++;
- }
-
- size_t section_len = section_end - mfg_section;
-
- // Initialize with estimated capacity
- GlamacResult result = allocate_glasses(&g_glass_context, 1000);
+ // Initialize with full capacity to avoid realloc during parsing
+ GlamacResult result = allocate_glasses(&g_glass_context, 5000);
if (result != GLAMAC_SUCCESS) {
return result;
}
@@ -570,9 +555,9 @@ void find_glass_data_range(f32 *minAbbe, f32 *maxAbbe, f32 *minRI, f32 *maxRI) {
if (g_glass_context.glasses[i].refractiveIndex > *maxRI) *maxRI = g_glass_context.glasses[i].refractiveIndex;
}
- // Add a small margin to the range
- const f32 abbeMargin = (*maxAbbe - *minAbbe) * 0.1f;
- const f32 riMargin = (*maxRI - *minRI) * 0.1f;
+ // Add exactly 5% margin to the range as requested
+ const f32 abbeMargin = (*maxAbbe - *minAbbe) * 0.05f;
+ const f32 riMargin = (*maxRI - *minRI) * 0.05f;
*minAbbe -= abbeMargin;
*maxAbbe += abbeMargin;
*minRI -= riMargin;
@@ -585,8 +570,8 @@ b32 load_glasses_from_json(const byte* json_path, const byte* manufacturer_filte
}
void initialize_glass_data(void) {
- // Try to load all manufacturers from JSON by default
- GlamacResult result = try_load_json_with_fallbacks_secure(NULL);
+ // Load only SCHOTT catalog for now
+ GlamacResult result = try_load_json_with_fallbacks_secure("SCHOTT");
if (result != GLAMAC_SUCCESS) {
// Fallback to default static data
diff --git a/src/glautils/fgla.c b/src/glautils/fgla.c
index 68232fd..f2ed8eb 100644
--- a/src/glautils/fgla.c
+++ b/src/glautils/fgla.c
@@ -15,7 +15,6 @@
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
-#include <errno.h>
#include "glass_data.h"
#include "glamacdef.h"
@@ -286,7 +285,7 @@ void print_header(OutputFormat format, u32 found_count, const char* search_term,
}
}
-void print_glass_result(OutputFormat format, const Glass* glass, const char* glass_name, int is_first, int is_last) {
+void print_glass_result(OutputFormat format, const Glass* glass, const char* glass_name, int is_first) {
switch (format) {
case OUTPUT_CSV:
printf("%s,%.5f,%.2f,%s,%s\n",
@@ -505,7 +504,7 @@ int main(int argc, char* argv[]) {
const char* glass_name = (const char*)get_glass_name(i);
if (glass && glass_name) {
- print_glass_result(output_format, glass, glass_name, j == 0, j == found_count - 1);
+ print_glass_result(output_format, glass, glass_name, j == 0);
}
}
@@ -518,4 +517,4 @@ int main(int argc, char* argv[]) {
cleanup_glass_data();
return FGLA_SUCCESS;
-} \ No newline at end of file
+}
diff --git a/src/glautils/gla.c b/src/glautils/gla.c
deleted file mode 100644
index 7922c4f..0000000
--- a/src/glautils/gla.c
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * gla.c - main source program file for displaying the glass map.
- *
- * Copyright (C) 2025 https://optics-design.com
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * See the COPYING file for the full license text.
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-int main(int argc, char* argv[]){
- int filecount = 0;
- char rfilename[FILENAME_MAX];
- char glassname[50];
-
- FILE *readfile;
-
- if (argc==1){
- printf("This program is designed to work with glass data from the manufacturers\n");
- return 1;
- }
- //Argument parsing
- while (--argc>0) {
- if ((strstr((++argv)[0], ".xml"))){
- filecount++;
- strcpy(rfilename, argv[0]);
- }
- else {
- strcpy(glassname, argv[0]);
- for (int i =0; i<50; i++) {
- if (!glassname[i]){
- break;
- }
- if (glassname[i] >= 'a' && glassname[i] <= 'z'){
- glassname[i] = glassname[i] - ('a'- 'A');
- }
-
- }
- // while(*glassname){
- // if (*glassname >= 'a' && *glassname <= 'z'){
- // *glassname = *glassname - ('a'- 'A');
- // }
- // glassname++;
- // }
- // glassname = glassname -1;
-
- }
-
- }
- // printf("%s\n", rfilename);
- readfile = fopen(rfilename, "rb");
- if (readfile == NULL) {
- perror("Failed to open file");
- return 1;
- }
-
- fseek(readfile, 0, SEEK_END);
-
- long file_size = ftell(readfile);
- if (file_size == -1) {
- perror("ftell failed");
- fclose(readfile);
- return 1;
- }
- char* buffer = malloc(file_size + 1);
-
- fseek(readfile, 0, SEEK_SET);
-
- if (buffer == NULL) {
- perror("Failed to allocate memory");
- fclose(readfile);
- return 1;
- }
- // Read the file into the buffer
- size_t read_size = fread(buffer, 1, file_size, readfile);
- if (read_size != file_size) {
- perror("Failed to read the complete file");
- free(buffer);
- fclose(readfile);
- return 1;
- }
- // Null terminate the buffer (in case it's treated as a string)
- buffer[file_size] = '\0';
- fclose(readfile);
-
- // char *newline_ptr = NULL;
- // for (int i = 0; i < file_size; i++) {
- // if (buffer[i] == '\n') {
- // newline_ptr = &buffer[i];
- // break;
- // }
- // }
- // fwrite(buffer, 1, newline_ptr - buffer, stdout);
- char* line_start = buffer;
- char* line_end = NULL;
- int line_length;
- int glafound = 0;
-
- while ((line_end=strchr(line_start, '\n'))) {
- *line_end = '\0';
- if (!glafound){
- if (strstr(line_start, glassname) && strstr(line_start, "<GlassName>")) {
- line_length = strlen(line_start)-27; // 2*Length_of_tag+1 + 4
- printf("Name: %.*s, ", line_length, line_start+14); // Length_of_tag+3
- glafound = 1;
- }
- }
-
- else{
- if (strstr(line_start, "</NumericName>")) {
- line_length = strlen(line_start)-31; // 2*Length_of_tag+1 + 4
- printf("Code: %.*s", line_length, line_start+16); // Length_of_tag+3
- }
- // if (strstr(line_start, "</EquationType>")) {
- // line_length = strlen(line_start)-33; // 2*Length_of_tag+1 + 4
- // printf("Equation: %.*s\n", line_length, line_start+17); // Length_of_tag+3
- // }
- // if (strstr(line_start, "</Coefficient>")) {
- // line_length = strlen(line_start)-32; // 2*Length_of_tag+1 + 4
- // printf("Coefficient: %.*s\n", line_length, line_start+17); // Length_of_tag+3
- // }
- if (strstr(line_start, "</Glass>")) {
- printf("\n");
- glafound = 0;
- }
- }
- line_start = line_end+1;
-
- }
-
- free(buffer);
- return 0;
-}
Back to https://optics-design.com