summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authoradmin <contact@optics-design.com>2025-04-25 21:19:44 +0200
committeradmin <contact@optics-design.com>2025-04-25 21:19:44 +0200
commit6be3193593d6fe6dc36e584a44ec629a44fc394b (patch)
treeeec627ebb2abc443ce44b8ce8b9180f2225e13ec /src
First Commit
Diffstat (limited to 'src')
-rw-r--r--src/glamac/glamac.c121
-rw-r--r--src/glamac/glamac_events.c218
-rw-r--r--src/glamac/glamac_render.c427
-rw-r--r--src/glamac/glamac_view.c119
-rw-r--r--src/glamac/glass_data.c81
-rw-r--r--src/glautils/gla.c139
6 files changed, 1105 insertions, 0 deletions
diff --git a/src/glamac/glamac.c b/src/glamac/glamac.c
new file mode 100644
index 0000000..955d515
--- /dev/null
+++ b/src/glamac/glamac.c
@@ -0,0 +1,121 @@
+/**
+ * glamac.c - main source file for the GlaMaC.
+ *
+ * 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 <SDL.h>
+#include <SDL_ttf.h>
+#include <stdio.h>
+#include "glamacdef.h" // Type definitions
+#include "glass_data.h" // Glass catalog
+#include "glamac_view.h" // View management
+#include "glamac_render.h" // Rendering
+
+// 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);
+
+// Initial window dimensions
+#define INITIAL_WIDTH 800
+#define INITIAL_HEIGHT 600
+
+int main(int argc, char* argv[]) {
+ SDL_Window* window = NULL;
+ SDL_Renderer* renderer = NULL;
+ FontSet fonts = {0};
+
+ // Initialize SDL2
+ if (SDL_Init(SDL_INIT_VIDEO) < 0) {
+ printf("SDL2 could not initialize! SDL_Error: %s\n", SDL_GetError());
+ return 1;
+ }
+
+ // Initialize SDL_ttf
+ if (TTF_Init() < 0) {
+ printf("SDL_ttf could not initialize! TTF_Error: %s\n", TTF_GetError());
+ SDL_Quit();
+ return 1;
+ }
+
+ // Create window with SDL2
+ window = SDL_CreateWindow("Optical Glass Map", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
+ INITIAL_WIDTH, INITIAL_HEIGHT,
+ SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);
+ if (window == NULL) {
+ printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
+ TTF_Quit();
+ SDL_Quit();
+ return 1;
+ }
+
+ // Create renderer with hardware acceleration and vsync
+ renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
+ if (renderer == NULL) {
+ printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
+ SDL_DestroyWindow(window);
+ TTF_Quit();
+ SDL_Quit();
+ return 1;
+ }
+
+ // Load fonts
+ if (!load_fonts(&fonts)) {
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ TTF_Quit();
+ SDL_Quit();
+ return 1;
+ }
+
+ // Initialize glass data
+ initialize_glass_data();
+
+ // Initialize view state
+ ViewState view;
+ init_view_state(&view, INITIAL_WIDTH, INITIAL_HEIGHT);
+
+ // Main loop flag
+ b32 quit = 0;
+
+ // Event handler
+ SDL_Event e;
+
+ // Mouse state
+ i32 mouseX = 0, mouseY = 0;
+ i32 lastMouseX = 0, lastMouseY = 0;
+ b32 dragging = 0;
+
+ // Enable text input
+ SDL_StartTextInput();
+
+ // Main loop
+ while (!quit) {
+ // Handle events on queue
+ while (SDL_PollEvent(&e) != 0) {
+ process_events(&e, &view, window, &lastMouseX, &lastMouseY, &dragging, &quit);
+ }
+
+ // Render everything
+ render(renderer, fonts.regular, fonts.title, fonts.label, &view);
+ }
+
+ // Stop text input
+ SDL_StopTextInput();
+
+ // Clean up resources
+ free_fonts(&fonts);
+ clear_text_cache();
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ TTF_Quit();
+ SDL_Quit();
+
+ return 0;
+}
diff --git a/src/glamac/glamac_events.c b/src/glamac/glamac_events.c
new file mode 100644
index 0000000..29b7232
--- /dev/null
+++ b/src/glamac/glamac_events.c
@@ -0,0 +1,218 @@
+/**
+ * glamac_events.c - source file detailing keyboard and mouse events used in GlaMaC.
+ *
+ * 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 <SDL.h>
+#include "glamac_view.h"
+
+// Process key event
+b32 process_key_event(SDL_KeyboardEvent *key, ViewState *view, SDL_Window *window, b32 *quit) {
+ switch (key->keysym.sym) {
+ case SDLK_g:
+ // First key in sequence
+ view->gKeyPressed = 1;
+ // Store the time when 'g' was pressed
+ view->gKeyTime = SDL_GetTicks();
+ return 1;
+
+ case SDLK_SLASH:
+ case SDLK_QUESTION:
+ // Check if this is part of "g?" sequence (with shift for '?')
+ if (view->gKeyPressed && (SDL_GetTicks() - view->gKeyTime < 1000)) {
+ // If Shift is held (for '?' on most layouts) or it's a direct '?' key
+ if ((key->keysym.mod & KMOD_SHIFT) || key->keysym.sym == SDLK_QUESTION) {
+ view->showHelp = !view->showHelp;
+ }
+ view->gKeyPressed = 0;
+ return 1;
+ }
+ break;
+
+ case SDLK_ESCAPE:
+ // ESC closes glass selection first, then help window, finally quits
+ if (view->selectedGlass >= 0) {
+ view->selectedGlass = -1; // Clear selection
+ } else if (view->showHelp) {
+ view->showHelp = 0;
+ } else {
+ *quit = 1;
+ }
+ view->gKeyPressed = 0;
+ return 1;
+
+ case SDLK_q:
+ if (view->showHelp) {
+ view->showHelp = 0;
+ } else {
+ *quit = 1;
+ }
+ view->gKeyPressed = 0;
+ return 1;
+
+ case SDLK_PLUS:
+ case SDLK_EQUALS:
+ view->gKeyPressed = 0; // Reset g key state
+ // Zoom in at center
+ view->zoomLevel *= ZOOM_FACTOR;
+ if (view->zoomLevel > MAX_ZOOM) view->zoomLevel = MAX_ZOOM;
+ return 1;
+
+ case SDLK_MINUS:
+ // Zoom out at center
+ view->zoomLevel /= ZOOM_FACTOR;
+ if (view->zoomLevel < MIN_ZOOM) view->zoomLevel = MIN_ZOOM;
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ case SDLK_r:
+ // Reset view
+ reset_view(view);
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ case SDLK_f:
+ // Toggle fullscreen
+ toggle_fullscreen(window);
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ // Vim-like navigation
+ case SDLK_h:
+ // Move left
+ view->offsetX += PAN_STEP;
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ case SDLK_l:
+ // Move right
+ view->offsetX -= PAN_STEP;
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ case SDLK_k:
+ // Move up
+ view->offsetY -= PAN_STEP;
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+
+ case SDLK_j:
+ // Move down
+ view->offsetY += PAN_STEP;
+ view->gKeyPressed = 0; // Reset g key state
+ return 1;
+ }
+
+ return 0;
+}
+
+// Process mouse button event
+b32 process_mouse_button(SDL_MouseButtonEvent *button, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 *dragging) {
+ i32 mouseX, mouseY;
+
+ switch (button->button) {
+ case SDL_BUTTON_LEFT:
+ if (button->type == SDL_MOUSEBUTTONDOWN) {
+ SDL_GetMouseState(&mouseX, &mouseY);
+
+ // Check if click is near a glass point
+ i32 nearestGlass = find_nearest_glass(mouseX, mouseY, view, 15); // 15 pixels max distance
+ if (nearestGlass >= 0) {
+ view->selectedGlass = nearestGlass;
+ // Don't start dragging when selecting a glass
+ } else {
+ // If clicking outside any glass point, clear selection
+ view->selectedGlass = -1;
+ // Start dragging for panning
+ *dragging = 1;
+ *lastMouseX = mouseX;
+ *lastMouseY = mouseY;
+ }
+ return 1;
+ } else if (button->type == SDL_MOUSEBUTTONUP) {
+ *dragging = 0;
+ return 1;
+ }
+ break;
+ }
+
+ return 0;
+}
+
+// Process mouse motion event
+b32 process_mouse_motion(SDL_MouseMotionEvent *motion, ViewState *view, i32 *lastMouseX, i32 *lastMouseY, b32 dragging) {
+ if (dragging) {
+ // Get current mouse position
+ i32 mouseX, mouseY;
+ SDL_GetMouseState(&mouseX, &mouseY);
+
+ // Calculate normalized delta
+ const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const f32 dx = (f32)(mouseX - *lastMouseX) / (view->windowWidth - 2 * padding);
+ const f32 dy = (f32)(mouseY - *lastMouseY) / (view->windowHeight - 2 * padding);
+
+ // Update offset - inverted movement for natural feel
+ view->offsetX += dx;
+ view->offsetY -= dy;
+
+ // Update last mouse position
+ *lastMouseX = mouseX;
+ *lastMouseY = mouseY;
+
+ return 1;
+ }
+
+ return 0;
+}
+
+// Process window event
+b32 process_window_event(SDL_WindowEvent *window_event, ViewState *view) {
+ switch (window_event->event) {
+ case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_SIZE_CHANGED:
+ view->windowWidth = window_event->data1;
+ view->windowHeight = window_event->data2;
+ return 1;
+ }
+
+ return 0;
+}
+
+// Process all events on queue
+b32 process_events(SDL_Event *event, ViewState *view, SDL_Window *window,
+ i32 *lastMouseX, i32 *lastMouseY, b32 *dragging, b32 *quit) {
+ switch (event->type) {
+ case SDL_QUIT:
+ *quit = 1;
+ return 1;
+
+ case SDL_KEYDOWN:
+ return process_key_event(&event->key, view, window, quit);
+
+ case SDL_MOUSEWHEEL:
+ // Get mouse position for centered zoom
+ i32 mouseX, mouseY;
+ SDL_GetMouseState(&mouseX, &mouseY);
+ handle_mouse_wheel_zoom(event->wheel.y, mouseX, mouseY, view);
+ return 1;
+
+ case SDL_MOUSEBUTTONDOWN:
+ case SDL_MOUSEBUTTONUP:
+ return process_mouse_button(&event->button, view, lastMouseX, lastMouseY, dragging);
+
+ case SDL_MOUSEMOTION:
+ return process_mouse_motion(&event->motion, view, lastMouseX, lastMouseY, *dragging);
+
+ case SDL_WINDOWEVENT:
+ return process_window_event(&event->window, view);
+ }
+
+ return 0;
+}
diff --git a/src/glamac/glamac_render.c b/src/glamac/glamac_render.c
new file mode 100644
index 0000000..201738b
--- /dev/null
+++ b/src/glamac/glamac_render.c
@@ -0,0 +1,427 @@
+/**
+ * glamac_render.c - source file dealing with the rendering of GlaMaC using SDL2.
+ *
+ * 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 <math.h>
+#include <string.h>
+#include "glamac_render.h"
+#include "glass_data.h"
+
+// Drawing primitives
+
+// Structure to hold cached text textures
+typedef struct {
+ char text[64]; // The text string
+ SDL_Color color; // Text color
+ TTF_Font *font; // Font used
+ SDL_Texture *texture; // The cached texture
+ i32 width; // Texture width
+ i32 height; // Texture height
+} CachedText;
+
+// Text cache
+#define MAX_TEXT_CACHE 128
+static CachedText textCache[MAX_TEXT_CACHE] = {0};
+static i32 cacheCount = 0;
+
+// Function to draw text using SDL_ttf with caching for improved performance
+void draw_text(SDL_Renderer *renderer, TTF_Font *font, const char *text, i32 x, i32 y, SDL_Color color) {
+ // Check if we already have this text cached
+ for (i32 i = 0; i < cacheCount; i++) {
+ CachedText *entry = &textCache[i];
+
+ // Compare text, font, and color to find a match
+ if (entry->font == font &&
+ strcmp(entry->text, text) == 0 &&
+ entry->color.r == color.r &&
+ entry->color.g == color.g &&
+ entry->color.b == color.b &&
+ entry->color.a == color.a) {
+
+ // Use the cached texture
+ SDL_Rect rect = {x, y, entry->width, entry->height};
+ SDL_RenderCopy(renderer, entry->texture, NULL, &rect);
+ return;
+ }
+ }
+
+ // Text not in cache, create a new texture
+ SDL_Surface *surface = TTF_RenderText_Blended(font, text, color);
+ if (!surface) {
+ printf("TTF_RenderText_Blended failed: %s\n", TTF_GetError());
+ return;
+ }
+
+ SDL_Texture *texture = SDL_CreateTextureFromSurface(renderer, surface);
+ if (!texture) {
+ printf("SDL_CreateTextureFromSurface failed: %s\n", SDL_GetError());
+ SDL_FreeSurface(surface);
+ return;
+ }
+
+ // Draw the text
+ SDL_Rect rect = {x, y, surface->w, surface->h};
+ SDL_RenderCopy(renderer, texture, NULL, &rect);
+
+ // Add to cache if there is space
+ 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;
+ } else {
+ // Cache is full, just clean up the texture
+ SDL_DestroyTexture(texture);
+ }
+
+ SDL_FreeSurface(surface);
+}
+
+// Clear the text cache - should be called on shutdown
+void clear_text_cache(void) {
+ for (i32 i = 0; i < cacheCount; i++) {
+ if (textCache[i].texture) {
+ SDL_DestroyTexture(textCache[i].texture);
+ textCache[i].texture = NULL;
+ }
+ }
+ cacheCount = 0;
+}
+
+// Function to draw a filled circle using SDL2's primitive functions
+void draw_filled_circle(SDL_Renderer *renderer, i32 centerX, i32 centerY, i32 radius) {
+ // Draw a filled circle using a series of horizontal lines
+ // This is significantly faster than the pixel-by-pixel approach
+ for (i32 y = -radius; y <= radius; y++) {
+ // Calculate width of the horizontal line at this y position
+ i32 x_width = (i32)sqrtf((float)(radius * radius - y * y));
+
+ // Draw a horizontal line from left to right edge of the circle
+ SDL_RenderDrawLine(
+ renderer,
+ centerX - x_width, centerY + y,
+ centerX + x_width, centerY + y
+ );
+ }
+}
+
+// UI element rendering
+
+// Function to draw the axes with correct visible range
+void draw_axes(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, const ViewState* view) {
+ const SDL_Color black = {0, 0, 0, 255};
+ const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+
+ // X-axis
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderDrawLine(renderer, padding, view->windowHeight - padding,
+ view->windowWidth - padding, view->windowHeight - padding);
+
+ // Y-axis
+ SDL_RenderDrawLine(renderer, padding, padding,
+ padding, view->windowHeight - padding);
+
+ // Calculate visible range based on current view
+ f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI;
+ get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI);
+
+ // Draw X-axis labels - now using the actual visible range
+ char label[32];
+ for (i32 i = 0; i <= 5; i++) {
+ // For flipped axis: start with max and go to min
+ f32 value = visibleMaxAbbe - i * (visibleMaxAbbe - visibleMinAbbe) / 5;
+ i32 x = padding + i * (view->windowWidth - 2 * padding) / 5;
+
+ SDL_RenderDrawLine(renderer, x, view->windowHeight - padding, x, view->windowHeight - padding + 5);
+ sprintf(label, "%.1f", value);
+ draw_text(renderer, font, label, x - 15, view->windowHeight - padding + 10, black);
+ }
+
+ // Draw Y-axis labels - now using the actual visible range
+ for (i32 i = 0; i <= 5; i++) {
+ f32 value = visibleMinRI + i * (visibleMaxRI - visibleMinRI) / 5;
+ i32 y = view->windowHeight - padding - i * (view->windowHeight - 2 * padding) / 5;
+
+ SDL_RenderDrawLine(renderer, padding - 5, y, padding, y);
+ sprintf(label, "%.3f", value);
+ draw_text(renderer, font, label, padding - 50, y - 10, black);
+ }
+
+ // Add axis titles
+ draw_text(renderer, titleFont, "Abbe Number", view->windowWidth / 2 - 50, view->windowHeight - padding + 30, black);
+ draw_text(renderer, titleFont, "Refractive Index", padding - 20, padding - 30, black);
+}
+
+// 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);
+
+ // Calculate visible range for grid
+ f32 visibleMinAbbe, visibleMaxAbbe, visibleMinRI, visibleMaxRI;
+ get_visible_data_range(view, &visibleMinAbbe, &visibleMaxAbbe, &visibleMinRI, &visibleMaxRI);
+
+ // Determine grid step sizes based on zoom level
+ f32 xStep = 5.0f;
+ f32 yStep = 0.05f;
+
+ if (view->zoomLevel > 3.0f) {
+ xStep = 1.0f;
+ yStep = 0.01f;
+ } else if (view->zoomLevel > 7.0f) {
+ xStep = 0.5f;
+ yStep = 0.005f;
+ }
+
+ // Round start values to nice grid intervals
+ const f32 xGridStart = floorf(visibleMinAbbe / xStep) * xStep;
+ const f32 yGridStart = floorf(visibleMinRI / yStep) * yStep;
+
+ // Draw vertical grid lines (X-axis)
+ for (f32 x = xGridStart; x <= visibleMaxAbbe; x += xStep) {
+ i32 screenX, screenY;
+ data_to_screen_coords(x, visibleMinRI, view, &screenX, &screenY);
+
+ if (screenX >= padding && screenX <= view->windowWidth - padding) {
+ SDL_RenderDrawLine(renderer, screenX, padding, screenX, view->windowHeight - padding);
+ }
+ }
+
+ // Draw horizontal grid lines (Y-axis)
+ for (f32 y = yGridStart; y <= visibleMaxRI; y += yStep) {
+ i32 screenX, screenY;
+ data_to_screen_coords(visibleMinAbbe, y, view, &screenX, &screenY);
+
+ if (screenY >= padding && screenY <= view->windowHeight - padding) {
+ SDL_RenderDrawLine(renderer, padding, screenY, view->windowWidth - padding, screenY);
+ }
+ }
+}
+
+// Function to draw glass points and labels
+void draw_glass_points(SDL_Renderer *renderer, TTF_Font *labelFont, const ViewState* view) {
+ const SDL_Color blue = {0, 0, 150, 255};
+ const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ const i32 radius = 4;
+
+ for (i32 i = 0; i < get_glass_count(); i++) {
+ const Glass* glass = get_glass(i);
+ i32 x, y;
+ data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &x, &y);
+
+ // Skip if outside visible area
+ if (x < padding || x > view->windowWidth - padding ||
+ y < padding || y > view->windowHeight - padding) {
+ continue;
+ }
+
+ // Highlight selected glass
+ 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);
+ }
+
+ // Draw filled circle
+ SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255); // Red for glass points
+ draw_filled_circle(renderer, x, y, radius);
+
+ // Draw glass name
+ draw_text(renderer, labelFont, (const char*)glass->name, x + 6, y - 10, 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()) {
+ return; // No glass selected
+ }
+
+ const Glass* glass = get_glass(view->selectedGlass);
+
+ // Calculate glass position on screen
+ i32 glassX, glassY;
+ data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY);
+
+ // Define property window size and position
+ 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;
+ }
+
+ // Draw background with slight transparency
+ SDL_Rect windowRect = {windowX, windowY, windowWidth, 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_RenderDrawRect(renderer, &windowRect);
+
+ // Draw glass name as title
+ SDL_Color darkBlue = {0, 0, 120, 255};
+ draw_text(renderer, titleFont, (const char*)glass->name, windowX + padding, windowY + padding, darkBlue);
+
+ // Draw properties
+ SDL_Color black = {0, 0, 0, 255};
+ char buffer[64];
+
+ // Refractive Index
+ sprintf(buffer, "n = %.4f", glass->refractiveIndex);
+ draw_text(renderer, font, buffer, windowX + padding, windowY + padding + 30, black);
+
+ // Abbe Number
+ sprintf(buffer, "V = %.2f", glass->abbeNumber);
+ draw_text(renderer, font, buffer, windowX + padding, windowY + padding + 55, black);
+
+ // Reset blend mode
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_NONE);
+}
+
+// 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
+ i32 width = view->windowWidth - 200;
+ i32 height = view->windowHeight - 200;
+ i32 x = (view->windowWidth - width) / 2;
+ i32 y = (view->windowHeight - height) / 2;
+
+ SDL_Rect helpRect = {x, y, width, height};
+ SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
+ SDL_SetRenderDrawColor(renderer, 245, 245, 245, 230);
+ SDL_RenderFillRect(renderer, &helpRect);
+
+ // Draw border
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
+ SDL_RenderDrawRect(renderer, &helpRect);
+
+ // Draw help title
+ SDL_Color black = {0, 0, 0, 255};
+ draw_text(renderer, titleFont, "Keyboard Shortcuts", x + width/2 - 100, y + 20, black);
+
+ // Define all shortcuts
+ const char* shortcuts[] = {
+ "g? - Show/hide this help window",
+ "ESC, q - Quit application",
+ "+/- - Zoom in/out",
+ "h, j, k, l - Pan left, down, up, right",
+ "r - Reset view to original position",
+ "f - Toggle fullscreen mode",
+ "Mouse wheel - Zoom in/out at cursor",
+ "Mouse drag - Pan the view",
+ "Mouse click - Select glass to see properties"
+ };
+
+ const i32 numShortcuts = sizeof(shortcuts) / sizeof(shortcuts[0]);
+ const i32 lineHeight = 30;
+ const i32 startY = y + 70;
+
+ // Draw all shortcuts
+ for (i32 i = 0; i < numShortcuts; i++) {
+ draw_text(renderer, font, shortcuts[i], x + 50, startY + i * lineHeight, black);
+ }
+
+ // Draw exit instruction
+ draw_text(renderer, font, "Press g? again, q or ESC to close this window",
+ x + width/2 - 150, y + height - 40, black);
+}
+
+// Main render function
+void render(SDL_Renderer *renderer, TTF_Font *font, TTF_Font *titleFont, TTF_Font *labelFont, const ViewState* view) {
+ const SDL_Color black = {0, 0, 0, 255};
+
+ // Clear screen (white background)
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+ SDL_RenderClear(renderer);
+
+ // Draw grid
+ SDL_SetRenderDrawColor(renderer, 220, 220, 220, 255);
+ draw_grid(renderer, view);
+
+ // Draw axes with correct visible range
+ draw_axes(renderer, font, titleFont, view);
+
+ // Draw glass points and labels
+ draw_glass_points(renderer, labelFont, view);
+
+ // Draw glass properties if a glass is selected
+ if (view->selectedGlass >= 0) {
+ draw_glass_properties(renderer, font, titleFont, view);
+ }
+
+ // Add a small hint about help in the corner
+ draw_text(renderer, labelFont, "Press g? for help", 10, 0.975*view->windowHeight, black);
+
+ // Draw help window if toggled
+ if (view->showHelp) {
+ draw_help_window(renderer, font, titleFont, view);
+ }
+
+ // Update screen
+ SDL_RenderPresent(renderer);
+}
+
+// Font management
+
+// Load all required fonts
+b32 load_fonts(FontSet *fonts) {
+ fonts->regular = TTF_OpenFont("C:\\Windows\\Fonts\\arial.ttf", 14);
+ if (!fonts->regular) {
+ printf("Failed to load regular font! TTF_Error: %s\n", TTF_GetError());
+ return 0;
+ }
+
+ fonts->title = TTF_OpenFont("C:\\Windows\\Fonts\\arial.ttf", 18);
+ if (!fonts->title) {
+ printf("Failed to load title font! TTF_Error: %s\n", TTF_GetError());
+ TTF_CloseFont(fonts->regular);
+ return 0;
+ }
+
+ fonts->label = TTF_OpenFont("C:\\Windows\\Fonts\\arial.ttf", 12);
+ if (!fonts->label) {
+ printf("Failed to load label font! TTF_Error: %s\n", TTF_GetError());
+ TTF_CloseFont(fonts->title);
+ TTF_CloseFont(fonts->regular);
+ return 0;
+ }
+
+ return 1;
+}
+
+// Free all fonts
+void free_fonts(FontSet *fonts) {
+ if (fonts->regular) TTF_CloseFont(fonts->regular);
+ if (fonts->title) TTF_CloseFont(fonts->title);
+ if (fonts->label) TTF_CloseFont(fonts->label);
+}
diff --git a/src/glamac/glamac_view.c b/src/glamac/glamac_view.c
new file mode 100644
index 0000000..3078ae8
--- /dev/null
+++ b/src/glamac/glamac_view.c
@@ -0,0 +1,119 @@
+/**
+ * glamac_view.c - Source file dealing with view states, filtering, zooming etc. of GlaMaC.
+ *
+ * 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 <math.h>
+#include "glamac_view.h"
+#include "glass_data.h"
+
+// Initialize a view state with default values
+void init_view_state(ViewState* view, i32 windowWidth, i32 windowHeight) {
+ view->zoomLevel = 1.0f;
+ view->offsetX = 0.0f;
+ view->offsetY = 0.0f;
+ view->windowWidth = windowWidth;
+ view->windowHeight = windowHeight;
+ view->showHelp = 0;
+ view->gKeyPressed = 0;
+ view->gKeyTime = 0;
+ view->selectedGlass = -1; // No glass selected initially
+
+ // Calculate data range
+ find_glass_data_range(&view->minAbbe, &view->maxAbbe, &view->minRI, &view->maxRI);
+}
+
+// Calculate visible data range
+void get_visible_data_range(const ViewState* view, f32 *visibleMinAbbe, f32 *visibleMaxAbbe,
+ f32 *visibleMinRI, f32 *visibleMaxRI) {
+ // Convert screen corners to data values
+ f32 topLeftAbbe, topLeftRI, bottomRightAbbe, bottomRightRI;
+ const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+
+ screen_to_data_coords(padding, padding, view, &topLeftAbbe, &topLeftRI);
+ screen_to_data_coords(view->windowWidth - padding, view->windowHeight - padding,
+ view, &bottomRightAbbe, &bottomRightRI);
+
+ // NOTE: With flipped axis, topLeftAbbe is now the maximum and bottomRightAbbe is minimum
+ *visibleMaxAbbe = topLeftAbbe;
+ *visibleMinAbbe = bottomRightAbbe;
+ *visibleMinRI = bottomRightRI; // Remember y is inverted
+ *visibleMaxRI = topLeftRI;
+}
+
+// Find the nearest glass to a given screen position
+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++) {
+ const Glass* glass = get_glass(i);
+ i32 glassX, glassY;
+ data_to_screen_coords(glass->abbeNumber, glass->refractiveIndex, view, &glassX, &glassY);
+
+ // Calculate distance
+ const f32 dx = x - glassX;
+ const f32 dy = y - glassY;
+ const f32 dist = sqrtf(dx*dx + dy*dy);
+
+ if (dist < minDist) {
+ minDist = dist;
+ nearest = i;
+ }
+ }
+
+ return nearest;
+}
+
+// Handle mouse wheel zoom
+void handle_mouse_wheel_zoom(i32 wheelY, i32 mouseX, i32 mouseY, ViewState* view) {
+ f32 zoomFactor = (wheelY > 0) ? ZOOM_FACTOR : 1.0f/ZOOM_FACTOR;
+
+ // Convert mouse position to data coordinates before zoom
+ f32 mouseDataX, mouseDataY;
+ screen_to_data_coords(mouseX, mouseY, view, &mouseDataX, &mouseDataY);
+
+ // Adjust zoom level
+ f32 oldZoom = view->zoomLevel;
+ view->zoomLevel *= zoomFactor;
+
+ // Clamp zoom level
+ if (view->zoomLevel > MAX_ZOOM) view->zoomLevel = MAX_ZOOM;
+ if (view->zoomLevel < MIN_ZOOM) view->zoomLevel = MIN_ZOOM;
+
+ // If zoom didn't change, don't recalculate
+ if (oldZoom == view->zoomLevel) return;
+
+ // Calculate where the mouse point would be after zoom
+ i32 newMouseScreenX, newMouseScreenY;
+ data_to_screen_coords(mouseDataX, mouseDataY, view, &newMouseScreenX, &newMouseScreenY);
+
+ // Adjust offset to keep mouse position fixed
+ const i32 padding = (i32)(view->windowWidth * PADDING_PERCENT);
+ view->offsetX += (f32)(mouseX - newMouseScreenX) / (view->windowWidth - 2 * padding);
+ view->offsetY += (f32)(newMouseScreenY - mouseY) / (view->windowHeight - 2 * padding);
+}
+
+// Toggle fullscreen
+void toggle_fullscreen(SDL_Window* window) {
+ const u32 flags = SDL_GetWindowFlags(window);
+ if ((flags & SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
+ SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
+ } else {
+ SDL_SetWindowFullscreen(window, 0);
+ }
+}
+
+// Reset view to default
+void reset_view(ViewState* view) {
+ view->zoomLevel = 1.0f;
+ view->offsetX = 0.0f;
+ view->offsetY = 0.0f;
+}
diff --git a/src/glamac/glass_data.c b/src/glamac/glass_data.c
new file mode 100644
index 0000000..7c52d08
--- /dev/null
+++ b/src/glamac/glass_data.c
@@ -0,0 +1,81 @@
+/**
+ * glass_data.c - Source file featuring some input data for the GlaMaC. Will be changed in the future.
+ *
+ * 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 "glamacdef.h"
+#include "glass_data.h"
+
+// Sample glass data
+static const Glass 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}
+};
+
+#define GLASS_COUNT (sizeof(glasses) / sizeof(glasses[0]))
+
+// Get number of glasses in the catalog
+u32 get_glass_count(void) {
+ return GLASS_COUNT;
+}
+
+// Get glass at index
+const Glass* get_glass(u32 index) {
+ if (index < GLASS_COUNT) {
+ return &glasses[index];
+ }
+ return NULL;
+}
+
+// Get glass name
+const byte* get_glass_name(u32 index) {
+ if (index < GLASS_COUNT) {
+ return glasses[index].name;
+ }
+ return NULL;
+}
+
+// Find data range in glass catalog
+void find_glass_data_range(f32 *minAbbe, f32 *maxAbbe, f32 *minRI, f32 *maxRI) {
+ *minAbbe = glasses[0].abbeNumber;
+ *maxAbbe = glasses[0].abbeNumber;
+ *minRI = glasses[0].refractiveIndex;
+ *maxRI = glasses[0].refractiveIndex;
+
+ for (u32 i = 1; i < GLASS_COUNT; i++) {
+ if (glasses[i].abbeNumber < *minAbbe) *minAbbe = glasses[i].abbeNumber;
+ if (glasses[i].abbeNumber > *maxAbbe) *maxAbbe = glasses[i].abbeNumber;
+ if (glasses[i].refractiveIndex < *minRI) *minRI = glasses[i].refractiveIndex;
+ if (glasses[i].refractiveIndex > *maxRI) *maxRI = glasses[i].refractiveIndex;
+ }
+
+ // Add a small margin to the range
+ const f32 abbeMargin = (*maxAbbe - *minAbbe) * 0.1f;
+ const f32 riMargin = (*maxRI - *minRI) * 0.1f;
+ *minAbbe -= abbeMargin;
+ *maxAbbe += abbeMargin;
+ *minRI -= riMargin;
+ *maxRI += riMargin;
+}
+
+// Initialize glass data - placeholder for future expansion
+void initialize_glass_data(void) {
+ // Currently using static data, but this could be expanded
+ // to load data from files in the future
+}
diff --git a/src/glautils/gla.c b/src/glautils/gla.c
new file mode 100644
index 0000000..7922c4f
--- /dev/null
+++ b/src/glautils/gla.c
@@ -0,0 +1,139 @@
+/**
+ * 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