From 6be3193593d6fe6dc36e584a44ec629a44fc394b Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 25 Apr 2025 21:19:44 +0200 Subject: First Commit --- src/glamac/glamac.c | 121 +++++++++++++ src/glamac/glamac_events.c | 218 +++++++++++++++++++++++ src/glamac/glamac_render.c | 427 +++++++++++++++++++++++++++++++++++++++++++++ src/glamac/glamac_view.c | 119 +++++++++++++ src/glamac/glass_data.c | 81 +++++++++ src/glautils/gla.c | 139 +++++++++++++++ 6 files changed, 1105 insertions(+) create mode 100644 src/glamac/glamac.c create mode 100644 src/glamac/glamac_events.c create mode 100644 src/glamac/glamac_render.c create mode 100644 src/glamac/glamac_view.c create mode 100644 src/glamac/glass_data.c create mode 100644 src/glautils/gla.c (limited to 'src') 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 +#include +#include +#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 +#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 +#include +#include +#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 +#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 +#include +#include + +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, "")) { + 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, "")) { + 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, "")) { + // 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, "")) { + // 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, "")) { + printf("\n"); + glafound = 0; + } + } + line_start = line_end+1; + + } + + free(buffer); + return 0; +} -- cgit v1.2.3