diff --git a/CMakeLists.txt b/CMakeLists.txt index 265f910..faff68c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,22 +11,10 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) # Headless build option (no SFML, no graphics - for server/testing/Emscripten prep) option(MCRF_HEADLESS "Build without graphics dependencies (SFML, ImGui)" OFF) -# SDL2 backend option (SDL2 + OpenGL ES 2 - for Emscripten/WebGL, Android, cross-platform) -option(MCRF_SDL2 "Build with SDL2+OpenGL ES 2 backend instead of SFML" OFF) - -# Emscripten builds: use SDL2 if specified, otherwise fall back to headless +# Emscripten builds are always headless (no SFML yet - using stubs) if(EMSCRIPTEN) - if(MCRF_SDL2) - message(STATUS "Emscripten detected - using SDL2 backend") - set(MCRF_HEADLESS OFF) - else() - set(MCRF_HEADLESS ON) - message(STATUS "Emscripten detected - forcing HEADLESS mode (use -DMCRF_SDL2=ON for graphics)") - endif() -endif() - -if(MCRF_SDL2) - message(STATUS "Building with SDL2 backend - SDL2+OpenGL ES 2") + set(MCRF_HEADLESS ON) + message(STATUS "Emscripten detected - forcing HEADLESS mode") endif() if(MCRF_HEADLESS) @@ -48,13 +36,9 @@ if(EMSCRIPTEN) # Emscripten build: use Python headers compiled for wasm32-emscripten # The pyconfig.h from cross-build has correct LONG_BIT and other settings set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python") - # Force-include wasm pyconfig.h BEFORE anything else to set correct platform defines add_compile_options(-include ${PYTHON_WASM_BUILD}/pyconfig.h) - # Override LONG_BIT - Emscripten's limits.h incorrectly defines it as 64 for wasm32 - add_compile_definitions(LONG_BIT=32) - # Include wasm build directory FIRST so its pyconfig.h is found by #include "pyconfig.h" - include_directories(BEFORE ${PYTHON_WASM_BUILD}) include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include) + include_directories(${PYTHON_WASM_BUILD}) # For generated headers message(STATUS "Using Emscripten Python from: ${PYTHON_WASM_BUILD}") elseif(MCRF_CROSS_WINDOWS) # Windows cross-compilation: use cpython headers with PC/pyconfig.h @@ -73,9 +57,8 @@ else() include_directories(${CMAKE_SOURCE_DIR}/deps/Python) endif() -# ImGui and ImGui-SFML include directories (not needed in headless or SDL2 mode) -# SDL2 builds will use ImGui with SDL2 backend later; for now, no ImGui -if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) +# ImGui and ImGui-SFML include directories (not needed in headless mode) +if(NOT MCRF_HEADLESS) include_directories(${CMAKE_SOURCE_DIR}/modules/imgui) include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml) @@ -92,14 +75,13 @@ endif() # Collect all the source files file(GLOB_RECURSE SOURCES "src/*.cpp") -# Add ImGui sources to the build (only if using SFML) -if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) +# Add ImGui sources to the build (only if not headless) +if(NOT MCRF_HEADLESS) list(APPEND SOURCES ${IMGUI_SOURCES}) endif() # Find OpenGL (required by ImGui-SFML) - not needed in headless mode -# SDL2 builds handle OpenGL ES 2 differently (via SDL2 or Emscripten) -if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2) +if(NOT MCRF_HEADLESS) if(MCRF_CROSS_WINDOWS) # For cross-compilation, OpenGL is provided by MinGW set(OPENGL_LIBRARIES opengl32) @@ -127,27 +109,8 @@ if(EMSCRIPTEN) ${LIBTCOD_WASM_BUILD}/_deps/lodepng-c-build/liblodepng-c.a ${LIBTCOD_WASM_BUILD}/_deps/utf8proc-build/libutf8proc.a) include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) # Use Linux platform stubs for now - # For SDL2 builds, add stb headers for image/font loading - if(MCRF_SDL2) - include_directories(${CMAKE_SOURCE_DIR}/deps/stb) - endif() message(STATUS "Linking Emscripten Python: ${PYTHON_WASM_BUILD}/libpython3.14.a") message(STATUS "Linking Emscripten libtcod: ${LIBTCOD_WASM_BUILD}/libtcod.a") -elseif(MCRF_SDL2) - # SDL2 build (non-Emscripten): link against SDL2 and system libraries - # Note: For desktop SDL2 builds in the future - find_package(SDL2 REQUIRED) - find_package(OpenGL REQUIRED) - set(LINK_LIBS - SDL2::SDL2 - OpenGL::GL - tcod - python3.14 - m dl util pthread) - include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) - include_directories(${CMAKE_SOURCE_DIR}/deps/stb) # stb_image.h, stb_truetype.h - link_directories(${CMAKE_SOURCE_DIR}/__lib) - message(STATUS "Building with SDL2 backend (desktop)") elseif(MCRF_HEADLESS) # Headless build: no SFML, no OpenGL if(WIN32 OR MCRF_CROSS_WINDOWS) @@ -232,8 +195,6 @@ endif() add_executable(mcrogueface ${SOURCES}) # Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code) -# We ALWAYS need this because libtcod headers expect SDL3, not SDL2 -# Our SDL2 backend is separate from libtcod's SDL3 renderer target_compile_definitions(mcrogueface PRIVATE NO_SDL) # Define MCRF_HEADLESS for headless builds (excludes SFML/ImGui code) @@ -241,15 +202,9 @@ if(MCRF_HEADLESS) target_compile_definitions(mcrogueface PRIVATE MCRF_HEADLESS) endif() -# Define MCRF_SDL2 for SDL2 builds (uses SDL2+OpenGL ES 2 instead of SFML) -if(MCRF_SDL2) - target_compile_definitions(mcrogueface PRIVATE MCRF_SDL2) -endif() - # Emscripten-specific link options (use ports for zlib, bzip2, sqlite3) if(EMSCRIPTEN) - # Base Emscripten options - set(EMSCRIPTEN_LINK_OPTIONS + target_link_options(mcrogueface PRIVATE -sUSE_ZLIB=1 -sUSE_BZIP2=1 -sUSE_SQLITE3=1 @@ -260,9 +215,6 @@ if(EMSCRIPTEN) -sSTACK_OVERFLOW_CHECK=2 -fexceptions -sNO_DISABLE_EXCEPTION_CATCHING - # Disable features that require dynamic linking support - -sERROR_ON_UNDEFINED_SYMBOLS=0 - -sALLOW_UNIMPLEMENTED_SYSCALLS=1 # Preload Python stdlib into virtual filesystem at /lib/python3.14 --preload-file=${CMAKE_SOURCE_DIR}/wasm_stdlib/lib@/lib # Preload game scripts into /scripts @@ -270,24 +222,6 @@ if(EMSCRIPTEN) # Preload assets --preload-file=${CMAKE_SOURCE_DIR}/assets@/assets ) - - # Add SDL2 options if using SDL2 backend - if(MCRF_SDL2) - list(APPEND EMSCRIPTEN_LINK_OPTIONS - -sUSE_SDL=2 - -sFULL_ES2=1 - -sMIN_WEBGL_VERSION=2 - -sMAX_WEBGL_VERSION=2 - ) - # SDL2 flags are also needed at compile time for headers - target_compile_options(mcrogueface PRIVATE - -sUSE_SDL=2 - ) - message(STATUS "Emscripten SDL2 options enabled: -sUSE_SDL=2 -sFULL_ES2=1") - endif() - - target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS}) - # Set Python home for the embedded interpreter target_compile_definitions(mcrogueface PRIVATE MCRF_WASM_PYTHON_HOME="/lib/python3.14" diff --git a/src/Common.h b/src/Common.h index b77c7be..2827d94 100644 --- a/src/Common.h +++ b/src/Common.h @@ -10,15 +10,11 @@ // ============================================================================= #ifdef MCRF_HEADLESS - // Use headless type stubs instead of SFML (no graphics, for CI/testing) + // Use headless type stubs instead of SFML #include "platform/HeadlessTypes.h" #define MCRF_GRAPHICS_BACKEND "headless" -#elif defined(MCRF_SDL2) - // Use SDL2 + OpenGL ES 2 backend (for Emscripten/WebGL, Android, cross-platform) - #include "platform/SDL2Types.h" - #define MCRF_GRAPHICS_BACKEND "sdl2" #else - // Use SFML for graphics and audio (default desktop build) + // Use SFML for graphics and audio #include #include #define MCRF_GRAPHICS_BACKEND "sfml" diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index 68c3f8e..774ed6a 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -7,8 +7,7 @@ #include "Animation.h" #include "Timer.h" #include "BenchmarkLogger.h" -// ImGui is only available for SFML builds (not headless, not SDL2) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS #include "imgui.h" #include "imgui-SFML.h" #endif @@ -87,8 +86,8 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg) window->setFramerateLimit(60); render_target = window.get(); -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) - // Initialize ImGui for the window (SFML builds only) +#ifndef MCRF_HEADLESS + // Initialize ImGui for the window if (ImGui::SFML::Init(*window)) { imguiInitialized = true; // Register settings handler before .ini is loaded (happens on first frame) @@ -200,7 +199,7 @@ void GameEngine::cleanup() } // Shutdown ImGui AFTER window is closed to avoid X11 BadCursor errors -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS if (imguiInitialized) { ImGui::SFML::Shutdown(); imguiInitialized = false; @@ -362,8 +361,8 @@ void GameEngine::doFrame() if (!headless) { sUserInput(); -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) - // Update ImGui (SFML builds only) +#ifndef MCRF_HEADLESS + // Update ImGui if (imguiInitialized) { ImGui::SFML::Update(*window, clock.getElapsedTime()); } @@ -406,8 +405,8 @@ void GameEngine::doFrame() profilerOverlay->render(*render_target); } -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) - // Render ImGui overlays (console and scene explorer) - SFML builds only +#ifndef MCRF_HEADLESS + // Render ImGui overlays (console and scene explorer) if (imguiInitialized && !headless) { console.render(); sceneExplorer.render(*this); @@ -594,8 +593,8 @@ void GameEngine::sUserInput() sf::Event event; while (window && window->pollEvent(event)) { -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) - // Process event through ImGui first (SFML builds only) +#ifndef MCRF_HEADLESS + // Process event through ImGui first if (imguiInitialized) { ImGui::SFML::ProcessEvent(*window, event); } diff --git a/src/GameEngine.h b/src/GameEngine.h index 36d507c..f9d7a40 100644 --- a/src/GameEngine.h +++ b/src/GameEngine.h @@ -10,8 +10,7 @@ #include "HeadlessRenderer.h" #include "SceneTransition.h" #include "Profiler.h" -// ImGui is only available for SFML builds (not headless, not SDL2) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS #include "ImGuiConsole.h" #include "ImGuiSceneExplorer.h" #endif @@ -197,8 +196,7 @@ private: int overlayUpdateCounter = 0; // Only update overlay every N frames ProfilerOverlay* profilerOverlay = nullptr; // The actual overlay renderer -// ImGui is only available for SFML builds -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS // ImGui console overlay ImGuiConsole console; ImGuiSceneExplorer sceneExplorer; diff --git a/src/ImGuiConsole.cpp b/src/ImGuiConsole.cpp index 284f8d0..6f5f149 100644 --- a/src/ImGuiConsole.cpp +++ b/src/ImGuiConsole.cpp @@ -1,7 +1,7 @@ // ImGuiConsole.cpp - Debug console using ImGui -// This file is excluded from headless and SDL2 builds (ImGui-SFML only) +// This file is excluded from headless builds (no GUI/debug interface needed) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS #include "ImGuiConsole.h" #include "imgui.h" diff --git a/src/ImGuiConsole.h b/src/ImGuiConsole.h index 0184bb4..e705936 100644 --- a/src/ImGuiConsole.h +++ b/src/ImGuiConsole.h @@ -1,7 +1,7 @@ #pragma once -// ImGuiConsole - excluded from headless and SDL2 builds (ImGui-SFML only) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +// ImGuiConsole - excluded from headless builds (no GUI/debug interface) +#ifndef MCRF_HEADLESS #include #include diff --git a/src/ImGuiSceneExplorer.cpp b/src/ImGuiSceneExplorer.cpp index e255bbf..0eb7c03 100644 --- a/src/ImGuiSceneExplorer.cpp +++ b/src/ImGuiSceneExplorer.cpp @@ -1,7 +1,7 @@ // ImGuiSceneExplorer.cpp - Debug scene hierarchy explorer using ImGui -// This file is excluded from headless and SDL2 builds (ImGui-SFML only) +// This file is excluded from headless builds (no GUI/debug interface needed) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS #include "ImGuiSceneExplorer.h" #include "imgui.h" diff --git a/src/ImGuiSceneExplorer.h b/src/ImGuiSceneExplorer.h index 76ef22e..7293ed3 100644 --- a/src/ImGuiSceneExplorer.h +++ b/src/ImGuiSceneExplorer.h @@ -1,7 +1,7 @@ #pragma once -// ImGuiSceneExplorer - excluded from headless and SDL2 builds (ImGui-SFML only) -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +// ImGuiSceneExplorer - excluded from headless builds (no GUI/debug interface) +#ifndef MCRF_HEADLESS #include #include diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 292236d..c06eee3 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -32,8 +32,7 @@ #include "PyUniformCollection.h" // Shader uniform collection (#106) #include "McRogueFaceVersion.h" #include "GameEngine.h" -// ImGui is only available for SFML builds -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS #include "ImGuiConsole.h" #endif #include "BenchmarkLogger.h" @@ -1628,7 +1627,7 @@ PyObject* McRFPy_API::_setDevConsole(PyObject* self, PyObject* args) { return NULL; } -#if !defined(MCRF_HEADLESS) && !defined(MCRF_SDL2) +#ifndef MCRF_HEADLESS ImGuiConsole::setEnabled(enabled); #endif Py_RETURN_NONE; diff --git a/src/platform/EmscriptenStubs.cpp b/src/platform/EmscriptenStubs.cpp deleted file mode 100644 index 105c79d..0000000 --- a/src/platform/EmscriptenStubs.cpp +++ /dev/null @@ -1,38 +0,0 @@ -// EmscriptenStubs.cpp - Stub implementations for missing POSIX functions in Emscripten -// These functions are referenced by Python's posixmodule and timemodule but not available in WASM - -#ifdef __EMSCRIPTEN__ - -#include -#include -#include -#include - -extern "C" { - -// wait3 and wait4 are BSD-style wait functions not available in Emscripten -// Python's posixmodule references them but they're not critical for WASM -pid_t wait3(int *status, int options, struct rusage *rusage) { - errno = ENOSYS; - return -1; -} - -pid_t wait4(pid_t pid, int *status, int options, struct rusage *rusage) { - errno = ENOSYS; - return -1; -} - -// wcsftime - wide character strftime, used by Python's timemodule -// Provide a minimal implementation that returns 0 (no characters written) -size_t wcsftime(wchar_t *wcs, size_t maxsize, const wchar_t *format, const struct tm *timeptr) { - // In a full implementation, this would format time as wide chars - // For WASM, just return 0 to indicate nothing written - if (maxsize > 0) { - wcs[0] = L'\0'; - } - return 0; -} - -} // extern "C" - -#endif // __EMSCRIPTEN__ diff --git a/src/platform/SDL2Renderer.cpp b/src/platform/SDL2Renderer.cpp deleted file mode 100644 index 158018c..0000000 --- a/src/platform/SDL2Renderer.cpp +++ /dev/null @@ -1,1875 +0,0 @@ -// SDL2Renderer.cpp - OpenGL ES 2 rendering implementation for SDL2 backend -// Implements the SDL2 types defined in SDL2Types.h using SDL2 and OpenGL ES 2 - -#ifdef MCRF_SDL2 - -#include "SDL2Renderer.h" -#include "SDL2Types.h" - -#include -#include -#include -#include - -// SDL2 and OpenGL ES 2 headers -#ifdef __EMSCRIPTEN__ -#include -#include -// Emscripten's USE_SDL=2 port puts headers directly in include path -#include -#include -#else -#include -#include -// Desktop OpenGL - we'll use GL 2.1 compatible subset that matches GLES2 -#define GL_GLEXT_PROTOTYPES -#include -#endif - -// stb libraries for image/font loading (from deps/stb/) -#define STB_IMAGE_IMPLEMENTATION -#include - -#define STB_TRUETYPE_IMPLEMENTATION -#include - -namespace sf { - -// ============================================================================= -// Built-in Shaders (GLSL ES 2.0 / GLSL 1.20 compatible) -// ============================================================================= - -static const char* SHAPE_VERTEX_SHADER = R"( -#ifdef GL_ES -precision mediump float; -#endif -attribute vec2 a_position; -attribute vec4 a_color; -uniform mat4 u_projection; -varying vec4 v_color; - -void main() { - gl_Position = u_projection * vec4(a_position, 0.0, 1.0); - v_color = a_color; -} -)"; - -static const char* SHAPE_FRAGMENT_SHADER = R"( -#ifdef GL_ES -precision mediump float; -#endif -varying vec4 v_color; - -void main() { - gl_FragColor = v_color; -} -)"; - -static const char* SPRITE_VERTEX_SHADER = R"( -#ifdef GL_ES -precision mediump float; -#endif -attribute vec2 a_position; -attribute vec4 a_color; -attribute vec2 a_texcoord; -uniform mat4 u_projection; -varying vec4 v_color; -varying vec2 v_texcoord; - -void main() { - gl_Position = u_projection * vec4(a_position, 0.0, 1.0); - v_color = a_color; - v_texcoord = a_texcoord; -} -)"; - -static const char* SPRITE_FRAGMENT_SHADER = R"( -#ifdef GL_ES -precision mediump float; -#endif -varying vec4 v_color; -varying vec2 v_texcoord; -uniform sampler2D u_texture; - -void main() { - gl_FragColor = texture2D(u_texture, v_texcoord) * v_color; -} -)"; - -// Text shader is same as sprite for now -static const char* TEXT_VERTEX_SHADER = SPRITE_VERTEX_SHADER; -static const char* TEXT_FRAGMENT_SHADER = R"( -#ifdef GL_ES -precision mediump float; -#endif -varying vec4 v_color; -varying vec2 v_texcoord; -uniform sampler2D u_texture; - -void main() { - // Text rendering: use texture alpha as coverage - float alpha = texture2D(u_texture, v_texcoord).a; - gl_FragColor = vec4(v_color.rgb, v_color.a * alpha); -} -)"; - -// ============================================================================= -// SDL2Renderer Implementation -// ============================================================================= - -SDL2Renderer& SDL2Renderer::getInstance() { - static SDL2Renderer instance; - return instance; -} - -bool SDL2Renderer::init() { - if (initialized_) return true; - - // Initialize SDL2 if not already done - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS) < 0) { - std::cerr << "SDL2Renderer: Failed to initialize SDL: " << SDL_GetError() << std::endl; - return false; - } - - // Note: Shaders are initialized in initGL() after GL context is created - - // Set up initial projection matrix (identity) - memset(projectionMatrix_, 0, sizeof(projectionMatrix_)); - projectionMatrix_[0] = 1.0f; - projectionMatrix_[5] = 1.0f; - projectionMatrix_[10] = 1.0f; - projectionMatrix_[15] = 1.0f; - - initialized_ = true; - return true; -} - -bool SDL2Renderer::initGL() { - if (glInitialized_) return true; - - // Initialize built-in shaders (requires active GL context) - initBuiltinShaders(); - - glInitialized_ = true; - return true; -} - -void SDL2Renderer::shutdown() { - if (!initialized_) return; - - // Delete built-in shader programs - if (shapeProgram_) glDeleteProgram(shapeProgram_); - if (spriteProgram_) glDeleteProgram(spriteProgram_); - if (textProgram_) glDeleteProgram(textProgram_); - - shapeProgram_ = spriteProgram_ = textProgram_ = 0; - - SDL_Quit(); - initialized_ = false; -} - -void SDL2Renderer::initBuiltinShaders() { - // Compile shape shader - if (!compileAndLinkProgram(SHAPE_VERTEX_SHADER, SHAPE_FRAGMENT_SHADER, shapeProgram_)) { - std::cerr << "SDL2Renderer: Failed to compile shape shader" << std::endl; - } - - // Compile sprite shader - if (!compileAndLinkProgram(SPRITE_VERTEX_SHADER, SPRITE_FRAGMENT_SHADER, spriteProgram_)) { - std::cerr << "SDL2Renderer: Failed to compile sprite shader" << std::endl; - } - - // Compile text shader - if (!compileAndLinkProgram(TEXT_VERTEX_SHADER, TEXT_FRAGMENT_SHADER, textProgram_)) { - std::cerr << "SDL2Renderer: Failed to compile text shader" << std::endl; - } -} - -unsigned int SDL2Renderer::compileShaderStage(unsigned int type, const char* source) { - unsigned int shader = glCreateShader(type); - glShaderSource(shader, 1, &source, nullptr); - glCompileShader(shader); - - int success; - glGetShaderiv(shader, GL_COMPILE_STATUS, &success); - if (!success) { - char infoLog[512]; - glGetShaderInfoLog(shader, 512, nullptr, infoLog); - std::cerr << "SDL2Renderer: Shader compilation failed: " << infoLog << std::endl; - glDeleteShader(shader); - return 0; - } - - return shader; -} - -bool SDL2Renderer::compileAndLinkProgram(const char* vertexSrc, const char* fragmentSrc, unsigned int& programOut) { - unsigned int vertexShader = compileShaderStage(GL_VERTEX_SHADER, vertexSrc); - if (!vertexShader) return false; - - unsigned int fragmentShader = compileShaderStage(GL_FRAGMENT_SHADER, fragmentSrc); - if (!fragmentShader) { - glDeleteShader(vertexShader); - return false; - } - - unsigned int program = glCreateProgram(); - glAttachShader(program, vertexShader); - glAttachShader(program, fragmentShader); - - // Bind attribute locations before linking - glBindAttribLocation(program, 0, "a_position"); - glBindAttribLocation(program, 1, "a_color"); - glBindAttribLocation(program, 2, "a_texcoord"); - - glLinkProgram(program); - - // Shaders can be deleted after linking - glDeleteShader(vertexShader); - glDeleteShader(fragmentShader); - - int success; - glGetProgramiv(program, GL_LINK_STATUS, &success); - if (!success) { - char infoLog[512]; - glGetProgramInfoLog(program, 512, nullptr, infoLog); - std::cerr << "SDL2Renderer: Program linking failed: " << infoLog << std::endl; - glDeleteProgram(program); - return false; - } - - programOut = program; - return true; -} - -unsigned int SDL2Renderer::getShaderProgram(ShaderType type) const { - switch (type) { - case ShaderType::Shape: return shapeProgram_; - case ShaderType::Sprite: return spriteProgram_; - case ShaderType::Text: return textProgram_; - default: return 0; - } -} - -unsigned int SDL2Renderer::compileShader(const std::string& vertexSource, const std::string& fragmentSource) { - unsigned int program = 0; - if (compileAndLinkProgram(vertexSource.c_str(), fragmentSource.c_str(), program)) { - return program; - } - return 0; -} - -void SDL2Renderer::deleteShaderProgram(unsigned int programId) { - if (programId) { - glDeleteProgram(programId); - } -} - -unsigned int SDL2Renderer::createTexture(unsigned int width, unsigned int height, const unsigned char* pixels) { - unsigned int textureId; - glGenTextures(1, &textureId); - glBindTexture(GL_TEXTURE_2D, textureId); - - // Set default texture parameters - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - - // Upload pixel data (RGBA format) - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - return textureId; -} - -void SDL2Renderer::updateTexture(unsigned int textureId, unsigned int x, unsigned int y, - unsigned int width, unsigned int height, const unsigned char* pixels) { - glBindTexture(GL_TEXTURE_2D, textureId); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels); -} - -void SDL2Renderer::deleteTexture(unsigned int textureId) { - if (textureId) { - glDeleteTextures(1, &textureId); - } -} - -void SDL2Renderer::setTextureSmooth(unsigned int textureId, bool smooth) { - glBindTexture(GL_TEXTURE_2D, textureId); - GLint filter = smooth ? GL_LINEAR : GL_NEAREST; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); -} - -void SDL2Renderer::setTextureRepeated(unsigned int textureId, bool repeated) { - glBindTexture(GL_TEXTURE_2D, textureId); - GLint wrap = repeated ? GL_REPEAT : GL_CLAMP_TO_EDGE; - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap); -} - -unsigned int SDL2Renderer::createFBO(unsigned int width, unsigned int height, unsigned int& colorTexture) { - // Create color texture - colorTexture = createTexture(width, height, nullptr); - - // Create FBO - unsigned int fbo; - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - - // Attach color texture - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0); - - // Check completeness - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { - std::cerr << "SDL2Renderer: FBO is not complete" << std::endl; - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glDeleteFramebuffers(1, &fbo); - deleteTexture(colorTexture); - return 0; - } - - glBindFramebuffer(GL_FRAMEBUFFER, 0); - return fbo; -} - -void SDL2Renderer::deleteFBO(unsigned int fboId) { - if (fboId) { - glDeleteFramebuffers(1, &fboId); - } -} - -void SDL2Renderer::bindFBO(unsigned int fboId) { - fboStack_.push_back(fboId); - glBindFramebuffer(GL_FRAMEBUFFER, fboId); -} - -void SDL2Renderer::unbindFBO() { - if (!fboStack_.empty()) { - fboStack_.pop_back(); - } - unsigned int fbo = fboStack_.empty() ? 0 : fboStack_.back(); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); -} - -void SDL2Renderer::setViewport(int x, int y, unsigned int width, unsigned int height) { - glViewport(x, y, width, height); -} - -void SDL2Renderer::setProjection(float left, float right, float bottom, float top) { - // Build orthographic projection matrix - float near = -1.0f; - float far = 1.0f; - - memset(projectionMatrix_, 0, sizeof(projectionMatrix_)); - projectionMatrix_[0] = 2.0f / (right - left); - projectionMatrix_[5] = 2.0f / (top - bottom); - projectionMatrix_[10] = -2.0f / (far - near); - projectionMatrix_[12] = -(right + left) / (right - left); - projectionMatrix_[13] = -(top + bottom) / (top - bottom); - projectionMatrix_[14] = -(far + near) / (far - near); - projectionMatrix_[15] = 1.0f; -} - -static int clearCount = 0; -void SDL2Renderer::clear(float r, float g, float b, float a) { - glClearColor(r, g, b, a); - glClear(GL_COLOR_BUFFER_BIT); - - // Debug: Log first few clears to confirm render loop is running - if (clearCount < 5) { - std::cout << "SDL2Renderer::clear(" << r << ", " << g << ", " << b << ", " << a << ") #" << clearCount << std::endl; - clearCount++; - } -} - -void SDL2Renderer::drawTriangles(const float* vertices, size_t vertexCount, - const float* colors, const float* texCoords, - unsigned int textureId) { - if (vertexCount == 0) return; - - unsigned int program = textureId ? spriteProgram_ : shapeProgram_; - glUseProgram(program); - - // Set projection uniform - int projLoc = glGetUniformLocation(program, "u_projection"); - glUniformMatrix4fv(projLoc, 1, GL_FALSE, projectionMatrix_); - - // Enable blending - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Set up vertex attributes - glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices); - - if (colors) { - glEnableVertexAttribArray(1); - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, colors); - } - - if (texCoords && textureId) { - glEnableVertexAttribArray(2); - glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 0, texCoords); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, textureId); - int texLoc = glGetUniformLocation(program, "u_texture"); - glUniform1i(texLoc, 0); - } - - // Draw - glDrawArrays(GL_TRIANGLES, 0, vertexCount); - - // Clean up - glDisableVertexAttribArray(0); - if (colors) glDisableVertexAttribArray(1); - if (texCoords && textureId) glDisableVertexAttribArray(2); -} - -// ============================================================================= -// sf::RenderWindow Implementation -// ============================================================================= - -RenderWindow::~RenderWindow() { - close(); -} - -void RenderWindow::create(VideoMode mode, const std::string& title, uint32_t style) { - // Close any existing window - close(); - - // Initialize SDL2 renderer - if (!SDL2Renderer::getInstance().init()) { - std::cerr << "RenderWindow: Failed to initialize SDL2Renderer" << std::endl; - return; - } - - // Set OpenGL attributes for ES2/WebGL compatibility - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#ifdef __EMSCRIPTEN__ - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); -#else - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); -#endif - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0); - - // Convert sf::Style to SDL window flags - Uint32 sdlFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; - if (style & Style::Fullscreen) { - sdlFlags |= SDL_WINDOW_FULLSCREEN; - } - if (style & Style::Resize) { - sdlFlags |= SDL_WINDOW_RESIZABLE; - } - if (!(style & Style::Titlebar)) { - sdlFlags |= SDL_WINDOW_BORDERLESS; - } - -#ifdef __EMSCRIPTEN__ - // For Emscripten, tell SDL2 which canvas element to use - // SDL_HINT_EMSCRIPTEN_CANVAS_SELECTOR = "SDL_EMSCRIPTEN_CANVAS_SELECTOR" - SDL_SetHint("SDL_EMSCRIPTEN_CANVAS_SELECTOR", "#canvas"); - - // Set the canvas size explicitly before creating the window - emscripten_set_canvas_element_size("#canvas", mode.width, mode.height); - - std::cout << "Emscripten: Setting canvas to " << mode.width << "x" << mode.height << std::endl; -#endif - - // Create window - SDL_Window* window = SDL_CreateWindow( - title.c_str(), - SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - mode.width, mode.height, - sdlFlags - ); - - if (!window) { - std::cerr << "RenderWindow: Failed to create window: " << SDL_GetError() << std::endl; - return; - } - - // Create OpenGL context - SDL_GLContext context = SDL_GL_CreateContext(window); - if (!context) { - std::cerr << "RenderWindow: Failed to create GL context: " << SDL_GetError() << std::endl; - SDL_DestroyWindow(window); - return; - } - - sdlWindow_ = window; - glContext_ = context; - size_ = Vector2u(mode.width, mode.height); - title_ = title; - open_ = true; - -#ifdef __EMSCRIPTEN__ - // Force canvas size AFTER SDL window creation (SDL may have reset it) - emscripten_set_canvas_element_size("#canvas", mode.width, mode.height); - - // Also set the CSS size to match - EM_ASM({ - var canvas = document.getElementById('canvas'); - if (canvas) { - canvas.width = $0; - canvas.height = $1; - canvas.style.width = $0 + 'px'; - canvas.style.height = $1 + 'px'; - console.log('EM_ASM: Set canvas to ' + $0 + 'x' + $1); - } else { - console.error('EM_ASM: Canvas element not found!'); - } - }, mode.width, mode.height); - - // Re-make context current after canvas resize - SDL_GL_MakeCurrent(window, context); -#endif - - // Initialize OpenGL resources now that we have a context - if (!SDL2Renderer::getInstance().initGL()) { - std::cerr << "RenderWindow: Failed to initialize OpenGL resources" << std::endl; - } - - // Set up initial view - view_ = View(FloatRect(0, 0, static_cast(mode.width), static_cast(mode.height))); - defaultView_ = view_; - - // Set up OpenGL state - glViewport(0, 0, mode.width, mode.height); - std::cout << "GL viewport set to " << mode.width << "x" << mode.height << std::endl; - - GLenum err = glGetError(); - if (err != GL_NO_ERROR) { - std::cerr << "GL error after viewport: " << err << std::endl; - } - - SDL2Renderer::getInstance().setProjection(0, mode.width, mode.height, 0); - - // Enable blending for transparency - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - - // Initial clear to a visible color to confirm GL is working - glClearColor(0.2f, 0.3f, 0.4f, 1.0f); // Blue-gray - glClear(GL_COLOR_BUFFER_BIT); - - err = glGetError(); - if (err != GL_NO_ERROR) { - std::cerr << "GL error after clear: " << err << std::endl; - } - - SDL_GL_SwapWindow(window); - - err = glGetError(); - if (err != GL_NO_ERROR) { - std::cerr << "GL error after swap: " << err << std::endl; - } - - std::cout << "RenderWindow: Created " << mode.width << "x" << mode.height << " window" << std::endl; - std::cout << "WebGL context should now show blue-gray" << std::endl; -} - -void RenderWindow::close() { - if (glContext_) { - SDL_GL_DeleteContext(static_cast(glContext_)); - glContext_ = nullptr; - } - if (sdlWindow_) { - SDL_DestroyWindow(static_cast(sdlWindow_)); - sdlWindow_ = nullptr; - } - open_ = false; -} - -void RenderWindow::clear(const Color& color) { - SDL2Renderer::getInstance().clear( - color.r / 255.0f, - color.g / 255.0f, - color.b / 255.0f, - color.a / 255.0f - ); -} - -void RenderWindow::display() { - SDL_GL_SwapWindow(static_cast(sdlWindow_)); -} - -void RenderWindow::setTitle(const std::string& title) { - title_ = title; - if (sdlWindow_) { - SDL_SetWindowTitle(static_cast(sdlWindow_), title.c_str()); - } -} - -void RenderWindow::setFramerateLimit(unsigned int limit) { - // SDL2 doesn't have built-in framerate limiting - // We'd need to implement this manually with timing - // For now, VSync is the recommended approach -} - -void RenderWindow::setVerticalSyncEnabled(bool enabled) { - SDL_GL_SetSwapInterval(enabled ? 1 : 0); -} - -void RenderWindow::setVisible(bool visible) { - if (sdlWindow_) { - if (visible) { - SDL_ShowWindow(static_cast(sdlWindow_)); - } else { - SDL_HideWindow(static_cast(sdlWindow_)); - } - } -} - -void RenderWindow::setMouseCursorVisible(bool visible) { - SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); -} - -void RenderWindow::setMouseCursorGrabbed(bool grabbed) { - if (sdlWindow_) { - SDL_SetWindowGrab(static_cast(sdlWindow_), grabbed ? SDL_TRUE : SDL_FALSE); - } -} - -Vector2i RenderWindow::getPosition() const { - int x = 0, y = 0; - if (sdlWindow_) { - SDL_GetWindowPosition(static_cast(sdlWindow_), &x, &y); - } - return Vector2i(x, y); -} - -void RenderWindow::setPosition(const Vector2i& position) { - if (sdlWindow_) { - SDL_SetWindowPosition(static_cast(sdlWindow_), position.x, position.y); - } -} - -void RenderWindow::setSize(const Vector2u& size) { - size_ = size; - if (sdlWindow_) { - SDL_SetWindowSize(static_cast(sdlWindow_), size.x, size.y); - glViewport(0, 0, size.x, size.y); - } -} - -// Event polling - translate SDL events to sf::Event -bool RenderWindow::pollEvent(Event& event) { - SDL_Event sdlEvent; - while (SDL_PollEvent(&sdlEvent)) { - if (translateSDLEvent(&sdlEvent, &event)) { - return true; - } - } - return false; -} - -bool RenderWindow::waitEvent(Event& event) { - SDL_Event sdlEvent; - if (SDL_WaitEvent(&sdlEvent)) { - return translateSDLEvent(&sdlEvent, &event); - } - return false; -} - -// ============================================================================= -// VideoMode Implementation -// ============================================================================= - -VideoMode VideoMode::getDesktopMode() { - SDL_DisplayMode mode; - if (SDL_GetDesktopDisplayMode(0, &mode) == 0) { - return VideoMode(mode.w, mode.h, SDL_BITSPERPIXEL(mode.format)); - } - return VideoMode(1920, 1080, 32); -} - -const std::vector& VideoMode::getFullscreenModes() { - static std::vector modes; - static bool initialized = false; - - if (!initialized) { - int numModes = SDL_GetNumDisplayModes(0); - for (int i = 0; i < numModes; ++i) { - SDL_DisplayMode mode; - if (SDL_GetDisplayMode(0, i, &mode) == 0) { - modes.push_back(VideoMode(mode.w, mode.h, SDL_BITSPERPIXEL(mode.format))); - } - } - initialized = true; - } - - return modes; -} - -// ============================================================================= -// Event Translation -// ============================================================================= - -// SDL scancode to sf::Keyboard::Key mapping table -static const Keyboard::Key SDL_SCANCODE_TO_SF_KEY[] = { - // This is a simplified mapping - full implementation would have all keys - Keyboard::Unknown // Placeholder -}; - -bool translateSDLEvent(const void* sdlEventPtr, void* sfEventPtr) { - const SDL_Event& sdlEvent = *static_cast(sdlEventPtr); - Event& sfEvent = *static_cast(sfEventPtr); - - switch (sdlEvent.type) { - case SDL_QUIT: - sfEvent.type = Event::Closed; - return true; - - case SDL_WINDOWEVENT: - switch (sdlEvent.window.event) { - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_SIZE_CHANGED: - sfEvent.type = Event::Resized; - sfEvent.size.width = sdlEvent.window.data1; - sfEvent.size.height = sdlEvent.window.data2; - return true; - case SDL_WINDOWEVENT_FOCUS_GAINED: - sfEvent.type = Event::GainedFocus; - return true; - case SDL_WINDOWEVENT_FOCUS_LOST: - sfEvent.type = Event::LostFocus; - return true; - case SDL_WINDOWEVENT_ENTER: - sfEvent.type = Event::MouseEntered; - return true; - case SDL_WINDOWEVENT_LEAVE: - sfEvent.type = Event::MouseLeft; - return true; - } - break; - - case SDL_KEYDOWN: - case SDL_KEYUP: - sfEvent.type = sdlEvent.type == SDL_KEYDOWN ? Event::KeyPressed : Event::KeyReleased; - sfEvent.key.code = static_cast(sdlScancodeToSfKey(sdlEvent.key.keysym.scancode)); - sfEvent.key.alt = (sdlEvent.key.keysym.mod & KMOD_ALT) != 0; - sfEvent.key.control = (sdlEvent.key.keysym.mod & KMOD_CTRL) != 0; - sfEvent.key.shift = (sdlEvent.key.keysym.mod & KMOD_SHIFT) != 0; - sfEvent.key.system = (sdlEvent.key.keysym.mod & KMOD_GUI) != 0; - return true; - - case SDL_TEXTINPUT: - sfEvent.type = Event::TextEntered; - // Convert UTF-8 to single codepoint (simplified - only handles ASCII and simple UTF-8) - sfEvent.text.unicode = static_cast(sdlEvent.text.text[0]); - return true; - - case SDL_MOUSEMOTION: - sfEvent.type = Event::MouseMoved; - sfEvent.mouseMove.x = sdlEvent.motion.x; - sfEvent.mouseMove.y = sdlEvent.motion.y; - return true; - - case SDL_MOUSEBUTTONDOWN: - case SDL_MOUSEBUTTONUP: - sfEvent.type = sdlEvent.type == SDL_MOUSEBUTTONDOWN ? Event::MouseButtonPressed : Event::MouseButtonReleased; - sfEvent.mouseButton.button = static_cast(sdlButtonToSfButton(sdlEvent.button.button)); - sfEvent.mouseButton.x = sdlEvent.button.x; - sfEvent.mouseButton.y = sdlEvent.button.y; - return true; - - case SDL_MOUSEWHEEL: - sfEvent.type = Event::MouseWheelScrolled; - sfEvent.mouseWheelScroll.wheel = sdlEvent.wheel.x != 0 ? Mouse::HorizontalWheel : Mouse::VerticalWheel; - sfEvent.mouseWheelScroll.delta = sdlEvent.wheel.x != 0 ? sdlEvent.wheel.x : sdlEvent.wheel.y; - // Get current mouse position - SDL_GetMouseState(&sfEvent.mouseWheelScroll.x, &sfEvent.mouseWheelScroll.y); - return true; - } - - return false; -} - -// ============================================================================= -// Keyboard/Mouse Implementation -// ============================================================================= - -int sdlScancodeToSfKey(int sdlScancode) { - // Simplified mapping - covers most common keys - switch (sdlScancode) { - case SDL_SCANCODE_A: return Keyboard::A; - case SDL_SCANCODE_B: return Keyboard::B; - case SDL_SCANCODE_C: return Keyboard::C; - case SDL_SCANCODE_D: return Keyboard::D; - case SDL_SCANCODE_E: return Keyboard::E; - case SDL_SCANCODE_F: return Keyboard::F; - case SDL_SCANCODE_G: return Keyboard::G; - case SDL_SCANCODE_H: return Keyboard::H; - case SDL_SCANCODE_I: return Keyboard::I; - case SDL_SCANCODE_J: return Keyboard::J; - case SDL_SCANCODE_K: return Keyboard::K; - case SDL_SCANCODE_L: return Keyboard::L; - case SDL_SCANCODE_M: return Keyboard::M; - case SDL_SCANCODE_N: return Keyboard::N; - case SDL_SCANCODE_O: return Keyboard::O; - case SDL_SCANCODE_P: return Keyboard::P; - case SDL_SCANCODE_Q: return Keyboard::Q; - case SDL_SCANCODE_R: return Keyboard::R; - case SDL_SCANCODE_S: return Keyboard::S; - case SDL_SCANCODE_T: return Keyboard::T; - case SDL_SCANCODE_U: return Keyboard::U; - case SDL_SCANCODE_V: return Keyboard::V; - case SDL_SCANCODE_W: return Keyboard::W; - case SDL_SCANCODE_X: return Keyboard::X; - case SDL_SCANCODE_Y: return Keyboard::Y; - case SDL_SCANCODE_Z: return Keyboard::Z; - case SDL_SCANCODE_0: return Keyboard::Num0; - case SDL_SCANCODE_1: return Keyboard::Num1; - case SDL_SCANCODE_2: return Keyboard::Num2; - case SDL_SCANCODE_3: return Keyboard::Num3; - case SDL_SCANCODE_4: return Keyboard::Num4; - case SDL_SCANCODE_5: return Keyboard::Num5; - case SDL_SCANCODE_6: return Keyboard::Num6; - case SDL_SCANCODE_7: return Keyboard::Num7; - case SDL_SCANCODE_8: return Keyboard::Num8; - case SDL_SCANCODE_9: return Keyboard::Num9; - case SDL_SCANCODE_ESCAPE: return Keyboard::Escape; - case SDL_SCANCODE_LCTRL: return Keyboard::LControl; - case SDL_SCANCODE_LSHIFT: return Keyboard::LShift; - case SDL_SCANCODE_LALT: return Keyboard::LAlt; - case SDL_SCANCODE_LGUI: return Keyboard::LSystem; - case SDL_SCANCODE_RCTRL: return Keyboard::RControl; - case SDL_SCANCODE_RSHIFT: return Keyboard::RShift; - case SDL_SCANCODE_RALT: return Keyboard::RAlt; - case SDL_SCANCODE_RGUI: return Keyboard::RSystem; - case SDL_SCANCODE_SPACE: return Keyboard::Space; - case SDL_SCANCODE_RETURN: return Keyboard::Enter; - case SDL_SCANCODE_BACKSPACE: return Keyboard::Backspace; - case SDL_SCANCODE_TAB: return Keyboard::Tab; - case SDL_SCANCODE_LEFT: return Keyboard::Left; - case SDL_SCANCODE_RIGHT: return Keyboard::Right; - case SDL_SCANCODE_UP: return Keyboard::Up; - case SDL_SCANCODE_DOWN: return Keyboard::Down; - case SDL_SCANCODE_F1: return Keyboard::F1; - case SDL_SCANCODE_F2: return Keyboard::F2; - case SDL_SCANCODE_F3: return Keyboard::F3; - case SDL_SCANCODE_F4: return Keyboard::F4; - case SDL_SCANCODE_F5: return Keyboard::F5; - case SDL_SCANCODE_F6: return Keyboard::F6; - case SDL_SCANCODE_F7: return Keyboard::F7; - case SDL_SCANCODE_F8: return Keyboard::F8; - case SDL_SCANCODE_F9: return Keyboard::F9; - case SDL_SCANCODE_F10: return Keyboard::F10; - case SDL_SCANCODE_F11: return Keyboard::F11; - case SDL_SCANCODE_F12: return Keyboard::F12; - default: return Keyboard::Unknown; - } -} - -int sfKeyToSdlScancode(int sfKey) { - // Reverse mapping (simplified) - switch (sfKey) { - case Keyboard::A: return SDL_SCANCODE_A; - case Keyboard::W: return SDL_SCANCODE_W; - case Keyboard::S: return SDL_SCANCODE_S; - case Keyboard::D: return SDL_SCANCODE_D; - case Keyboard::Space: return SDL_SCANCODE_SPACE; - case Keyboard::Escape: return SDL_SCANCODE_ESCAPE; - // Add more as needed - default: return SDL_SCANCODE_UNKNOWN; - } -} - -int sdlButtonToSfButton(int sdlButton) { - switch (sdlButton) { - case SDL_BUTTON_LEFT: return Mouse::Left; - case SDL_BUTTON_RIGHT: return Mouse::Right; - case SDL_BUTTON_MIDDLE: return Mouse::Middle; - case SDL_BUTTON_X1: return Mouse::XButton1; - case SDL_BUTTON_X2: return Mouse::XButton2; - default: return Mouse::Left; - } -} - -int sfButtonToSdlButton(int sfButton) { - switch (sfButton) { - case Mouse::Left: return SDL_BUTTON_LEFT; - case Mouse::Right: return SDL_BUTTON_RIGHT; - case Mouse::Middle: return SDL_BUTTON_MIDDLE; - case Mouse::XButton1: return SDL_BUTTON_X1; - case Mouse::XButton2: return SDL_BUTTON_X2; - default: return SDL_BUTTON_LEFT; - } -} - -bool Keyboard::isKeyPressed(Key key) { - const Uint8* state = SDL_GetKeyboardState(nullptr); - int scancode = sfKeyToSdlScancode(key); - return scancode != SDL_SCANCODE_UNKNOWN && state[scancode]; -} - -bool Mouse::isButtonPressed(Button button) { - Uint32 state = SDL_GetMouseState(nullptr, nullptr); - return state & SDL_BUTTON(sfButtonToSdlButton(button)); -} - -Vector2i Mouse::getPosition() { - int x, y; - SDL_GetMouseState(&x, &y); - return Vector2i(x, y); -} - -Vector2i Mouse::getPosition(const RenderWindow& relativeTo) { - // For now, same as global position (would need window-relative in multi-window setup) - return getPosition(); -} - -void Mouse::setPosition(const Vector2i& position) { - SDL_WarpMouseGlobal(position.x, position.y); -} - -void Mouse::setPosition(const Vector2i& position, const RenderWindow& relativeTo) { - SDL_WarpMouseInWindow( - static_cast(relativeTo.getNativeWindowHandle()), - position.x, position.y - ); -} - -// ============================================================================= -// RenderTarget Implementation -// ============================================================================= - -void RenderTarget::clear(const Color& color) { - SDL2Renderer::getInstance().clear( - color.r / 255.0f, - color.g / 255.0f, - color.b / 255.0f, - color.a / 255.0f - ); -} - -void RenderTarget::draw(const Vertex* vertices, size_t vertexCount, PrimitiveType type, const RenderStates& states) { - // TODO: Implement with proper vertex buffer handling -} - -void RenderTarget::draw(const VertexArray& vertices, const RenderStates& states) { - draw(&vertices[0], vertices.getVertexCount(), vertices.getPrimitiveType(), states); -} - -// ============================================================================= -// RenderTexture Implementation -// ============================================================================= - -RenderTexture::~RenderTexture() { - if (fboId_) { - SDL2Renderer::getInstance().deleteFBO(fboId_); - } -} - -bool RenderTexture::create(unsigned int width, unsigned int height) { - size_ = Vector2u(width, height); - - unsigned int colorTexture = 0; - fboId_ = SDL2Renderer::getInstance().createFBO(width, height, colorTexture); - - if (!fboId_) { - return false; - } - - // Set up internal texture to point to FBO color attachment - texture_.setNativeHandle(colorTexture); - - view_ = View(FloatRect(0, 0, static_cast(width), static_cast(height))); - defaultView_ = view_; - - return true; -} - -void RenderTexture::clear(const Color& color) { - SDL2Renderer::getInstance().bindFBO(fboId_); - RenderTarget::clear(color); -} - -void RenderTexture::display() { - SDL2Renderer::getInstance().unbindFBO(); -} - -// ============================================================================= -// Texture Implementation -// ============================================================================= - -Texture::~Texture() { - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } -} - -Texture::Texture(const Texture& other) - : size_(other.size_), smooth_(other.smooth_), repeated_(other.repeated_) { - if (other.textureId_) { - // Create new texture with same properties - textureId_ = SDL2Renderer::getInstance().createTexture(size_.x, size_.y, nullptr); - // Note: Would need to copy pixel data for full implementation - } -} - -Texture& Texture::operator=(const Texture& other) { - if (this != &other) { - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } - size_ = other.size_; - smooth_ = other.smooth_; - repeated_ = other.repeated_; - if (other.textureId_) { - textureId_ = SDL2Renderer::getInstance().createTexture(size_.x, size_.y, nullptr); - } - } - return *this; -} - -bool Texture::create(unsigned int width, unsigned int height) { - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } - size_ = Vector2u(width, height); - textureId_ = SDL2Renderer::getInstance().createTexture(width, height, nullptr); - return textureId_ != 0; -} - -bool Texture::loadFromFile(const std::string& filename) { - int width, height, channels; - unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4); - - if (!data) { - std::cerr << "Texture: Failed to load " << filename << ": " << stbi_failure_reason() << std::endl; - return false; - } - - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } - - size_ = Vector2u(width, height); - textureId_ = SDL2Renderer::getInstance().createTexture(width, height, data); - - stbi_image_free(data); - return textureId_ != 0; -} - -bool Texture::loadFromMemory(const void* data, size_t size) { - int width, height, channels; - unsigned char* pixels = stbi_load_from_memory( - static_cast(data), size, &width, &height, &channels, 4); - - if (!pixels) { - return false; - } - - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } - - size_ = Vector2u(width, height); - textureId_ = SDL2Renderer::getInstance().createTexture(width, height, pixels); - - stbi_image_free(pixels); - return textureId_ != 0; -} - -void Texture::setSmooth(bool smooth) { - smooth_ = smooth; - if (textureId_) { - SDL2Renderer::getInstance().setTextureSmooth(textureId_, smooth); - } -} - -void Texture::setRepeated(bool repeated) { - repeated_ = repeated; - if (textureId_) { - SDL2Renderer::getInstance().setTextureRepeated(textureId_, repeated); - } -} - -Image Texture::copyToImage() const { - Image img; - img.create(size_.x, size_.y); - // TODO: Read back from GPU texture - return img; -} - -void Texture::update(const RenderWindow& window) { - // TODO: Copy window contents to texture -} - -void Texture::update(const Uint8* pixels) { - if (textureId_ && pixels) { - SDL2Renderer::getInstance().updateTexture(textureId_, 0, 0, size_.x, size_.y, pixels); - } -} - -void Texture::update(const Uint8* pixels, unsigned int width, unsigned int height, unsigned int x, unsigned int y) { - if (textureId_ && pixels) { - SDL2Renderer::getInstance().updateTexture(textureId_, x, y, width, height, pixels); - } -} - -// ============================================================================= -// Image Implementation -// ============================================================================= - -bool Image::loadFromFile(const std::string& filename) { - int width, height, channels; - unsigned char* data = stbi_load(filename.c_str(), &width, &height, &channels, 4); - - if (!data) { - return false; - } - - size_ = Vector2u(width, height); - pixels_.resize(width * height * 4); - memcpy(pixels_.data(), data, pixels_.size()); - - stbi_image_free(data); - return true; -} - -bool Image::saveToFile(const std::string& filename) const { - // TODO: Use stb_image_write - return false; -} - -// ============================================================================= -// Font Implementation -// ============================================================================= - -bool Font::loadFromFile(const std::string& filename) { - FILE* file = fopen(filename.c_str(), "rb"); - if (!file) { - return false; - } - - fseek(file, 0, SEEK_END); - size_t size = ftell(file); - fseek(file, 0, SEEK_SET); - - fontData_.resize(size); - fread(fontData_.data(), 1, size, file); - fclose(file); - - loaded_ = true; - return true; -} - -bool Font::loadFromMemory(const void* data, size_t sizeInBytes) { - fontData_.resize(sizeInBytes); - memcpy(fontData_.data(), data, sizeInBytes); - loaded_ = true; - return true; -} - -// ============================================================================= -// Shape Drawing (Stubs - implement with vertex generation) -// ============================================================================= - -void Shape::draw(RenderTarget& target, RenderStates states) const { - size_t pointCount = getPointCount(); - if (pointCount < 3) return; - - // Get the combined transform - Transform combinedTransform = states.transform * getTransform(); - - // Build vertex data for fill (triangle fan from center) - std::vector vertices; - std::vector colors; - - // Calculate center point - Vector2f center(0, 0); - for (size_t i = 0; i < pointCount; ++i) { - center.x += getPoint(i).x; - center.y += getPoint(i).y; - } - center.x /= pointCount; - center.y /= pointCount; - - // Transform center - Vector2f transformedCenter = combinedTransform.transformPoint(center); - - // Build triangles (fan from center) - Color fill = getFillColor(); - float fr = fill.r / 255.0f; - float fg = fill.g / 255.0f; - float fb = fill.b / 255.0f; - float fa = fill.a / 255.0f; - - for (size_t i = 0; i < pointCount; ++i) { - size_t next = (i + 1) % pointCount; - - Vector2f p1 = combinedTransform.transformPoint(getPoint(i)); - Vector2f p2 = combinedTransform.transformPoint(getPoint(next)); - - // Triangle: center, p1, p2 - vertices.push_back(transformedCenter.x); - vertices.push_back(transformedCenter.y); - vertices.push_back(p1.x); - vertices.push_back(p1.y); - vertices.push_back(p2.x); - vertices.push_back(p2.y); - - // Colors for each vertex - for (int v = 0; v < 3; ++v) { - colors.push_back(fr); - colors.push_back(fg); - colors.push_back(fb); - colors.push_back(fa); - } - } - - // Draw fill - if (fill.a > 0 && !vertices.empty()) { - SDL2Renderer::getInstance().drawTriangles( - vertices.data(), vertices.size() / 2, - colors.data(), nullptr, 0 - ); - } - - // Draw outline if thickness > 0 - float outlineThickness = getOutlineThickness(); - if (outlineThickness > 0) { - Color outline = getOutlineColor(); - if (outline.a > 0) { - float or_ = outline.r / 255.0f; - float og = outline.g / 255.0f; - float ob = outline.b / 255.0f; - float oa = outline.a / 255.0f; - - // Build outline as quads (two triangles per edge) - vertices.clear(); - colors.clear(); - - for (size_t i = 0; i < pointCount; ++i) { - size_t next = (i + 1) % pointCount; - - Vector2f p1 = combinedTransform.transformPoint(getPoint(i)); - Vector2f p2 = combinedTransform.transformPoint(getPoint(next)); - - // Calculate normal direction - Vector2f dir(p2.x - p1.x, p2.y - p1.y); - float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); - if (len > 0) { - dir.x /= len; - dir.y /= len; - } - Vector2f normal(-dir.y * outlineThickness, dir.x * outlineThickness); - - // Outer points - Vector2f p1o(p1.x + normal.x, p1.y + normal.y); - Vector2f p2o(p2.x + normal.x, p2.y + normal.y); - - // Two triangles for quad - // Triangle 1: p1, p2, p1o - vertices.push_back(p1.x); vertices.push_back(p1.y); - vertices.push_back(p2.x); vertices.push_back(p2.y); - vertices.push_back(p1o.x); vertices.push_back(p1o.y); - // Triangle 2: p2, p2o, p1o - vertices.push_back(p2.x); vertices.push_back(p2.y); - vertices.push_back(p2o.x); vertices.push_back(p2o.y); - vertices.push_back(p1o.x); vertices.push_back(p1o.y); - - for (int v = 0; v < 6; ++v) { - colors.push_back(or_); - colors.push_back(og); - colors.push_back(ob); - colors.push_back(oa); - } - } - - if (!vertices.empty()) { - SDL2Renderer::getInstance().drawTriangles( - vertices.data(), vertices.size() / 2, - colors.data(), nullptr, 0 - ); - } - } - } -} - -void VertexArray::draw(RenderTarget& target, RenderStates states) const { - if (vertices_.empty()) return; - - // Convert vertex array to flat arrays based on primitive type - std::vector positions; - std::vector colors; - std::vector texcoords; - - auto addVertex = [&](const Vertex& v) { - Vector2f p = states.transform.transformPoint(v.position); - positions.push_back(p.x); - positions.push_back(p.y); - colors.push_back(v.color.r / 255.0f); - colors.push_back(v.color.g / 255.0f); - colors.push_back(v.color.b / 255.0f); - colors.push_back(v.color.a / 255.0f); - texcoords.push_back(v.texCoords.x); - texcoords.push_back(v.texCoords.y); - }; - - switch (primitiveType_) { - case Triangles: - // Already in triangle format - for (size_t i = 0; i < vertices_.size(); ++i) { - addVertex(vertices_[i]); - } - break; - - case TriangleFan: - // Convert fan to triangles: v0, v1, v2, then v0, v2, v3, etc. - if (vertices_.size() >= 3) { - for (size_t i = 1; i < vertices_.size() - 1; ++i) { - addVertex(vertices_[0]); - addVertex(vertices_[i]); - addVertex(vertices_[i + 1]); - } - } - break; - - case TriangleStrip: - // Convert strip to triangles - if (vertices_.size() >= 3) { - for (size_t i = 0; i < vertices_.size() - 2; ++i) { - if (i % 2 == 0) { - addVertex(vertices_[i]); - addVertex(vertices_[i + 1]); - addVertex(vertices_[i + 2]); - } else { - // Flip winding for odd triangles - addVertex(vertices_[i + 1]); - addVertex(vertices_[i]); - addVertex(vertices_[i + 2]); - } - } - } - break; - - case Quads: - // Convert quads to triangles (4 vertices -> 2 triangles) - for (size_t i = 0; i + 3 < vertices_.size(); i += 4) { - // Triangle 1: v0, v1, v2 - addVertex(vertices_[i]); - addVertex(vertices_[i + 1]); - addVertex(vertices_[i + 2]); - // Triangle 2: v0, v2, v3 - addVertex(vertices_[i]); - addVertex(vertices_[i + 2]); - addVertex(vertices_[i + 3]); - } - break; - - case Lines: - // Draw lines as thin quads (2 triangles per line) - for (size_t i = 0; i + 1 < vertices_.size(); i += 2) { - Vector2f p1 = states.transform.transformPoint(vertices_[i].position); - Vector2f p2 = states.transform.transformPoint(vertices_[i + 1].position); - - // Calculate perpendicular for line thickness (1 pixel) - Vector2f dir(p2.x - p1.x, p2.y - p1.y); - float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); - if (len > 0) { - dir.x /= len; - dir.y /= len; - } - Vector2f perp(-dir.y * 0.5f, dir.x * 0.5f); - - // Build thin quad - Vector2f v0(p1.x - perp.x, p1.y - perp.y); - Vector2f v1(p1.x + perp.x, p1.y + perp.y); - Vector2f v2(p2.x + perp.x, p2.y + perp.y); - Vector2f v3(p2.x - perp.x, p2.y - perp.y); - - const Vertex& vert1 = vertices_[i]; - const Vertex& vert2 = vertices_[i + 1]; - - // Triangle 1 - positions.insert(positions.end(), {v0.x, v0.y, v1.x, v1.y, v2.x, v2.y}); - for (int j = 0; j < 2; ++j) { - colors.insert(colors.end(), {vert1.color.r/255.f, vert1.color.g/255.f, vert1.color.b/255.f, vert1.color.a/255.f}); - } - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - texcoords.insert(texcoords.end(), {0, 0, 0, 0, 0, 0}); - - // Triangle 2 - positions.insert(positions.end(), {v0.x, v0.y, v2.x, v2.y, v3.x, v3.y}); - colors.insert(colors.end(), {vert1.color.r/255.f, vert1.color.g/255.f, vert1.color.b/255.f, vert1.color.a/255.f}); - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - texcoords.insert(texcoords.end(), {0, 0, 0, 0, 0, 0}); - } - break; - - case LineStrip: - // Similar to Lines but connected - for (size_t i = 0; i + 1 < vertices_.size(); ++i) { - Vector2f p1 = states.transform.transformPoint(vertices_[i].position); - Vector2f p2 = states.transform.transformPoint(vertices_[i + 1].position); - - Vector2f dir(p2.x - p1.x, p2.y - p1.y); - float len = std::sqrt(dir.x * dir.x + dir.y * dir.y); - if (len > 0) { - dir.x /= len; - dir.y /= len; - } - Vector2f perp(-dir.y * 0.5f, dir.x * 0.5f); - - Vector2f v0(p1.x - perp.x, p1.y - perp.y); - Vector2f v1(p1.x + perp.x, p1.y + perp.y); - Vector2f v2(p2.x + perp.x, p2.y + perp.y); - Vector2f v3(p2.x - perp.x, p2.y - perp.y); - - const Vertex& vert1 = vertices_[i]; - const Vertex& vert2 = vertices_[i + 1]; - - positions.insert(positions.end(), {v0.x, v0.y, v1.x, v1.y, v2.x, v2.y}); - for (int j = 0; j < 2; ++j) { - colors.insert(colors.end(), {vert1.color.r/255.f, vert1.color.g/255.f, vert1.color.b/255.f, vert1.color.a/255.f}); - } - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - texcoords.insert(texcoords.end(), {0, 0, 0, 0, 0, 0}); - - positions.insert(positions.end(), {v0.x, v0.y, v2.x, v2.y, v3.x, v3.y}); - colors.insert(colors.end(), {vert1.color.r/255.f, vert1.color.g/255.f, vert1.color.b/255.f, vert1.color.a/255.f}); - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - colors.insert(colors.end(), {vert2.color.r/255.f, vert2.color.g/255.f, vert2.color.b/255.f, vert2.color.a/255.f}); - texcoords.insert(texcoords.end(), {0, 0, 0, 0, 0, 0}); - } - break; - - case Points: - // Draw points as small quads - for (size_t i = 0; i < vertices_.size(); ++i) { - Vector2f p = states.transform.transformPoint(vertices_[i].position); - const Vertex& v = vertices_[i]; - - // 2x2 pixel quad centered on point - positions.insert(positions.end(), { - p.x - 1, p.y - 1, p.x + 1, p.y - 1, p.x + 1, p.y + 1, - p.x - 1, p.y - 1, p.x + 1, p.y + 1, p.x - 1, p.y + 1 - }); - for (int j = 0; j < 6; ++j) { - colors.insert(colors.end(), {v.color.r/255.f, v.color.g/255.f, v.color.b/255.f, v.color.a/255.f}); - } - texcoords.insert(texcoords.end(), {0,0, 0,0, 0,0, 0,0, 0,0, 0,0}); - } - break; - } - - if (!positions.empty()) { - // Use shape shader (no texture) - glUseProgram(SDL2Renderer::getInstance().getShaderProgram(SDL2Renderer::ShaderType::Shape)); - SDL2Renderer::getInstance().drawTriangles( - positions.data(), positions.size() / 2, - colors.data(), nullptr, 0 - ); - } -} - -void Sprite::draw(RenderTarget& target, RenderStates states) const { - if (!texture_) return; - - Transform combined = states.transform * getTransform(); - - // Get texture rectangle (use full texture if not set) - IntRect rect = textureRect_; - if (rect.width == 0 || rect.height == 0) { - rect = IntRect(0, 0, texture_->getSize().x, texture_->getSize().y); - } - - // Four corners of sprite in local space - Vector2f p0 = combined.transformPoint(0, 0); - Vector2f p1 = combined.transformPoint(static_cast(rect.width), 0); - Vector2f p2 = combined.transformPoint(static_cast(rect.width), static_cast(rect.height)); - Vector2f p3 = combined.transformPoint(0, static_cast(rect.height)); - - // Texture coordinates (normalized) - Vector2u texSize = texture_->getSize(); - if (texSize.x == 0 || texSize.y == 0) return; - - float u0 = rect.left / static_cast(texSize.x); - float v0 = rect.top / static_cast(texSize.y); - float u1 = (rect.left + rect.width) / static_cast(texSize.x); - float v1 = (rect.top + rect.height) / static_cast(texSize.y); - - // Two triangles forming a quad (6 vertices) - float vertices[] = { - p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, // Triangle 1 - p0.x, p0.y, p2.x, p2.y, p3.x, p3.y // Triangle 2 - }; - - float texcoords[] = { - u0, v0, u1, v0, u1, v1, // Triangle 1 - u0, v0, u1, v1, u0, v1 // Triangle 2 - }; - - // Color tint for all 6 vertices - float colors[24]; - float r = color_.r / 255.0f; - float g = color_.g / 255.0f; - float b = color_.b / 255.0f; - float a = color_.a / 255.0f; - for (int i = 0; i < 6; ++i) { - colors[i * 4 + 0] = r; - colors[i * 4 + 1] = g; - colors[i * 4 + 2] = b; - colors[i * 4 + 3] = a; - } - - // Use sprite shader and draw - glUseProgram(SDL2Renderer::getInstance().getShaderProgram(SDL2Renderer::ShaderType::Sprite)); - SDL2Renderer::getInstance().drawTriangles(vertices, 6, colors, texcoords, texture_->getNativeHandle()); -} - -// Static cache for font atlases - keyed by (font data pointer, character size) -static std::map, FontAtlas> s_fontAtlasCache; - -void Text::draw(RenderTarget& target, RenderStates states) const { - if (!font_ || string_.empty() || !font_->isLoaded()) return; - - // Get or create font atlas for this font + size combination - auto key = std::make_pair(font_, characterSize_); - auto it = s_fontAtlasCache.find(key); - if (it == s_fontAtlasCache.end()) { - FontAtlas atlas; - if (!atlas.load(font_->getData(), font_->getDataSize(), static_cast(characterSize_))) { - return; // Failed to create atlas - } - it = s_fontAtlasCache.emplace(key, std::move(atlas)).first; - } - const FontAtlas& atlas = it->second; - - Transform combined = states.transform * getTransform(); - - // Build vertex data for all glyphs - std::vector vertices; - std::vector texcoords; - std::vector colors; - - float x = 0; - float y = atlas.getAscent(); // Start at baseline - - float r = fillColor_.r / 255.0f; - float g = fillColor_.g / 255.0f; - float b = fillColor_.b / 255.0f; - float a = fillColor_.a / 255.0f; - - for (size_t i = 0; i < string_.size(); ++i) { - char c = string_[i]; - - // Handle newlines - if (c == '\n') { - x = 0; - y += atlas.getLineHeight(); - continue; - } - - FontAtlas::GlyphInfo glyph; - if (!atlas.getGlyph(static_cast(c), glyph)) { - // Try space for unknown glyphs - if (!atlas.getGlyph(' ', glyph)) { - continue; - } - } - - // Calculate quad corners in local space - float x0 = x + glyph.xoff; - float y0 = y + glyph.yoff; - float x1 = x0 + glyph.width; - float y1 = y0 + glyph.height; - - // Transform to world space - Vector2f p0 = combined.transformPoint(x0, y0); - Vector2f p1 = combined.transformPoint(x1, y0); - Vector2f p2 = combined.transformPoint(x1, y1); - Vector2f p3 = combined.transformPoint(x0, y1); - - // Two triangles for quad - vertices.insert(vertices.end(), { - p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, // Triangle 1 - p0.x, p0.y, p2.x, p2.y, p3.x, p3.y // Triangle 2 - }); - - texcoords.insert(texcoords.end(), { - glyph.u0, glyph.v0, glyph.u1, glyph.v0, glyph.u1, glyph.v1, // Triangle 1 - glyph.u0, glyph.v0, glyph.u1, glyph.v1, glyph.u0, glyph.v1 // Triangle 2 - }); - - // 6 vertices * 4 color components - for (int v = 0; v < 6; ++v) { - colors.insert(colors.end(), {r, g, b, a}); - } - - x += glyph.xadvance; - } - - if (!vertices.empty()) { - // Use text shader (uses alpha from texture) - glUseProgram(SDL2Renderer::getInstance().getShaderProgram(SDL2Renderer::ShaderType::Text)); - SDL2Renderer::getInstance().drawTriangles( - vertices.data(), vertices.size() / 2, - colors.data(), texcoords.data(), - atlas.getTextureId() - ); - } -} - -FloatRect Text::getLocalBounds() const { - if (!font_ || string_.empty() || !font_->isLoaded()) { - return FloatRect(0, 0, 0, 0); - } - - // Get or create font atlas for this font + size combination - auto key = std::make_pair(font_, characterSize_); - auto it = s_fontAtlasCache.find(key); - if (it == s_fontAtlasCache.end()) { - FontAtlas atlas; - if (!atlas.load(font_->getData(), font_->getDataSize(), static_cast(characterSize_))) { - return FloatRect(0, 0, 0, 0); - } - it = s_fontAtlasCache.emplace(key, std::move(atlas)).first; - } - const FontAtlas& atlas = it->second; - - float x = 0; - float maxX = 0; - float minY = 0; - float maxY = atlas.getLineHeight(); - int lineCount = 1; - - for (size_t i = 0; i < string_.size(); ++i) { - char c = string_[i]; - - if (c == '\n') { - maxX = std::max(maxX, x); - x = 0; - lineCount++; - continue; - } - - FontAtlas::GlyphInfo glyph; - if (atlas.getGlyph(static_cast(c), glyph)) { - x += glyph.xadvance; - } - } - - maxX = std::max(maxX, x); - maxY = atlas.getLineHeight() * lineCount; - - return FloatRect(0, 0, maxX, maxY); -} - -FloatRect Text::getGlobalBounds() const { - FloatRect local = getLocalBounds(); - Transform t = getTransform(); - return t.transformRect(local); -} - -// ============================================================================= -// Shader Implementation -// ============================================================================= - -Shader::~Shader() { - if (programId_) { - SDL2Renderer::getInstance().deleteShaderProgram(programId_); - } -} - -bool Shader::loadFromFile(const std::string& filename, Type type) { - // TODO: Load shader from file - return false; -} - -bool Shader::loadFromFile(const std::string& vertexFile, const std::string& fragmentFile) { - // TODO: Load shaders from files - return false; -} - -bool Shader::loadFromMemory(const std::string& shader, Type type) { - // For fragment-only shaders, use default vertex shader - if (type == Fragment) { - std::string defaultVertex = R"( - attribute vec2 a_position; - attribute vec4 a_color; - attribute vec2 a_texcoord; - uniform mat4 u_projection; - varying vec4 v_color; - varying vec2 v_texcoord; - void main() { - gl_Position = u_projection * vec4(a_position, 0.0, 1.0); - v_color = a_color; - v_texcoord = a_texcoord; - } - )"; - programId_ = SDL2Renderer::getInstance().compileShader(defaultVertex, shader); - loaded_ = programId_ != 0; - return loaded_; - } - return false; -} - -void Shader::setUniform(const std::string& name, float x) { - if (programId_) { - glUseProgram(programId_); - int loc = glGetUniformLocation(programId_, name.c_str()); - if (loc >= 0) glUniform1f(loc, x); - } -} - -void Shader::setUniform(const std::string& name, const Vector2f& v) { - if (programId_) { - glUseProgram(programId_); - int loc = glGetUniformLocation(programId_, name.c_str()); - if (loc >= 0) glUniform2f(loc, v.x, v.y); - } -} - -void Shader::setUniform(const std::string& name, const Color& color) { - if (programId_) { - glUseProgram(programId_); - int loc = glGetUniformLocation(programId_, name.c_str()); - if (loc >= 0) glUniform4f(loc, color.r/255.f, color.g/255.f, color.b/255.f, color.a/255.f); - } -} - -void Shader::setUniform(const std::string& name, const Texture& texture) { - // Texture binding is handled during draw -} - -void Shader::setUniform(const std::string& name, const Glsl::Vec3& v) { - if (programId_) { - glUseProgram(programId_); - int loc = glGetUniformLocation(programId_, name.c_str()); - if (loc >= 0) glUniform3f(loc, v.x, v.y, v.z); - } -} - -void Shader::setUniform(const std::string& name, const Glsl::Vec4& v) { - if (programId_) { - glUseProgram(programId_); - int loc = glGetUniformLocation(programId_, name.c_str()); - if (loc >= 0) glUniform4f(loc, v.x, v.y, v.z, v.w); - } -} - -void Shader::setUniform(const std::string& name, CurrentTextureType) { - // Handled during draw -} - -bool Shader::isAvailable() { - return SDL2Renderer::getInstance().isInitialized(); -} - -// ============================================================================= -// FontAtlas Implementation -// ============================================================================= - -FontAtlas::FontAtlas() = default; - -FontAtlas::~FontAtlas() { - if (textureId_) { - SDL2Renderer::getInstance().deleteTexture(textureId_); - } - if (stbFontInfo_) { - delete static_cast(stbFontInfo_); - } -} - -bool FontAtlas::load(const unsigned char* fontData, size_t dataSize, float fontSize) { - fontSize_ = fontSize; - - stbtt_fontinfo* info = new stbtt_fontinfo(); - if (!stbtt_InitFont(info, fontData, 0)) { - delete info; - return false; - } - - stbFontInfo_ = info; - - // Get font metrics - int ascent, descent, lineGap; - stbtt_GetFontVMetrics(info, &ascent, &descent, &lineGap); - - float scale = stbtt_ScaleForPixelHeight(info, fontSize); - ascent_ = ascent * scale; - descent_ = descent * scale; - lineHeight_ = (ascent - descent + lineGap) * scale; - - // Create glyph atlas (simple ASCII for now) - const int atlasSize = 512; - std::vector atlasPixels(atlasSize * atlasSize, 0); - - int x = 1, y = 1; - int rowHeight = 0; - - for (uint32_t c = 32; c < 128; ++c) { - int advance, lsb; - stbtt_GetCodepointHMetrics(info, c, &advance, &lsb); - - int x0, y0, x1, y1; - stbtt_GetCodepointBitmapBox(info, c, scale, scale, &x0, &y0, &x1, &y1); - - int w = x1 - x0; - int h = y1 - y0; - - if (x + w + 1 >= atlasSize) { - x = 1; - y += rowHeight + 1; - rowHeight = 0; - } - - if (y + h + 1 >= atlasSize) { - break; // Atlas full - } - - // Render glyph to atlas - stbtt_MakeCodepointBitmap(info, &atlasPixels[y * atlasSize + x], w, h, atlasSize, scale, scale, c); - - GlyphInfo glyph; - glyph.u0 = x / (float)atlasSize; - glyph.v0 = y / (float)atlasSize; - glyph.u1 = (x + w) / (float)atlasSize; - glyph.v1 = (y + h) / (float)atlasSize; - glyph.xoff = x0; - glyph.yoff = y0; - glyph.xadvance = advance * scale; - glyph.width = w; - glyph.height = h; - - glyphCache_[c] = glyph; - - x += w + 1; - rowHeight = std::max(rowHeight, h); - } - - // Convert single-channel to RGBA - std::vector rgbaPixels(atlasSize * atlasSize * 4); - for (int i = 0; i < atlasSize * atlasSize; ++i) { - rgbaPixels[i * 4 + 0] = 255; - rgbaPixels[i * 4 + 1] = 255; - rgbaPixels[i * 4 + 2] = 255; - rgbaPixels[i * 4 + 3] = atlasPixels[i]; - } - - textureId_ = SDL2Renderer::getInstance().createTexture(atlasSize, atlasSize, rgbaPixels.data()); - - return true; -} - -bool FontAtlas::getGlyph(uint32_t codepoint, GlyphInfo& info) const { - auto it = glyphCache_.find(codepoint); - if (it != glyphCache_.end()) { - info = it->second; - return true; - } - return false; -} - -} // namespace sf - -#endif // MCRF_SDL2 diff --git a/src/platform/SDL2Renderer.h b/src/platform/SDL2Renderer.h deleted file mode 100644 index f5c3e30..0000000 --- a/src/platform/SDL2Renderer.h +++ /dev/null @@ -1,182 +0,0 @@ -// SDL2Renderer.h - OpenGL ES 2 rendering implementation for SDL2 backend -// This file provides the actual rendering implementation for the SDL2 types -// defined in SDL2Types.h. It handles: -// - OpenGL ES 2 context management -// - Shader compilation and management -// - Texture loading (via stb_image) -// - Font rendering (via stb_truetype) -// - Framebuffer object management -// -// Part of the renderer abstraction layer - -#pragma once - -#ifdef MCRF_SDL2 - -#include -#include -#include -#include - -// Forward declarations to avoid SDL header in this header -struct SDL_Window; -typedef void* SDL_GLContext; - -namespace sf { - -// Forward declarations -class RenderTarget; -class Texture; -class Shader; - -// ============================================================================= -// SDL2 Renderer - Singleton managing OpenGL ES 2 state -// ============================================================================= - -class SDL2Renderer { -public: - // Singleton access - static SDL2Renderer& getInstance(); - - // Initialization/shutdown - bool init(); // Initialize SDL2 (call before window creation) - bool initGL(); // Initialize OpenGL resources (call after GL context exists) - void shutdown(); - bool isInitialized() const { return initialized_; } - bool isGLInitialized() const { return glInitialized_; } - - // Built-in shader programs - enum class ShaderType { - Shape, // For RectangleShape, CircleShape, etc. - Sprite, // For textured sprites - Text, // For text rendering - Custom // User-provided shaders - }; - - // Get a built-in shader program - unsigned int getShaderProgram(ShaderType type) const; - - // Compile a custom shader program - unsigned int compileShader(const std::string& vertexSource, const std::string& fragmentSource); - void deleteShaderProgram(unsigned int programId); - - // Texture management - unsigned int createTexture(unsigned int width, unsigned int height, const unsigned char* pixels = nullptr); - void updateTexture(unsigned int textureId, unsigned int x, unsigned int y, - unsigned int width, unsigned int height, const unsigned char* pixels); - void deleteTexture(unsigned int textureId); - void setTextureSmooth(unsigned int textureId, bool smooth); - void setTextureRepeated(unsigned int textureId, bool repeated); - - // FBO management - unsigned int createFBO(unsigned int width, unsigned int height, unsigned int& colorTexture); - void deleteFBO(unsigned int fboId); - void bindFBO(unsigned int fboId); - void unbindFBO(); - - // Render state management - void setViewport(int x, int y, unsigned int width, unsigned int height); - void setProjection(float left, float right, float bottom, float top); - void clear(float r, float g, float b, float a); - - // Drawing primitives - void drawTriangles(const float* vertices, size_t vertexCount, - const float* colors, const float* texCoords, - unsigned int textureId = 0); - - // Projection matrix access (for shaders) - const float* getProjectionMatrix() const { return projectionMatrix_; } - -private: - SDL2Renderer() = default; - ~SDL2Renderer() = default; - SDL2Renderer(const SDL2Renderer&) = delete; - SDL2Renderer& operator=(const SDL2Renderer&) = delete; - - bool initialized_ = false; - bool glInitialized_ = false; - - // Built-in shader programs - unsigned int shapeProgram_ = 0; - unsigned int spriteProgram_ = 0; - unsigned int textProgram_ = 0; - - // Current projection matrix (4x4 orthographic) - float projectionMatrix_[16] = {0}; - - // FBO stack for nested render-to-texture - std::vector fboStack_; - - // Helper functions - bool compileAndLinkProgram(const char* vertexSrc, const char* fragmentSrc, unsigned int& programOut); - unsigned int compileShaderStage(unsigned int type, const char* source); - void initBuiltinShaders(); -}; - -// ============================================================================= -// Keyboard/Mouse SDL2 Implementation helpers -// ============================================================================= - -// SDL2 scancode to sf::Keyboard::Key mapping -int sdlScancodeToSfKey(int sdlScancode); -int sfKeyToSdlScancode(int sfKey); - -// SDL2 mouse button to sf::Mouse::Button mapping -int sdlButtonToSfButton(int sdlButton); -int sfButtonToSdlButton(int sfButton); - -// ============================================================================= -// Event translation -// ============================================================================= - -// Translate SDL_Event to sf::Event -// Returns true if the event was translated, false if it should be ignored -bool translateSDLEvent(const void* sdlEvent, void* sfEvent); - -// ============================================================================= -// Font Atlas for text rendering -// ============================================================================= - -class FontAtlas { -public: - FontAtlas(); - ~FontAtlas(); - - // Load font data - bool load(const unsigned char* fontData, size_t dataSize, float fontSize); - - // Get texture atlas - unsigned int getTextureId() const { return textureId_; } - - // Get glyph info for rendering - struct GlyphInfo { - float u0, v0, u1, v1; // Texture coordinates - float xoff, yoff; // Offset from cursor - float xadvance; // How far to advance cursor - float width, height; // Glyph dimensions in pixels - }; - - bool getGlyph(uint32_t codepoint, GlyphInfo& info) const; - - // Get font metrics - float getAscent() const { return ascent_; } - float getDescent() const { return descent_; } - float getLineHeight() const { return lineHeight_; } - -private: - unsigned int textureId_ = 0; - float fontSize_ = 0; - float ascent_ = 0; - float descent_ = 0; - float lineHeight_ = 0; - - // Glyph cache - maps codepoint to glyph info - std::unordered_map glyphCache_; - - // stb_truetype font info (opaque pointer to avoid header inclusion) - void* stbFontInfo_ = nullptr; -}; - -} // namespace sf - -#endif // MCRF_SDL2 diff --git a/src/platform/SDL2Types.h b/src/platform/SDL2Types.h deleted file mode 100644 index d91a473..0000000 --- a/src/platform/SDL2Types.h +++ /dev/null @@ -1,1116 +0,0 @@ -// SDL2Types.h - SFML type stubs for SDL2 + OpenGL ES 2 backend -// This file provides type definitions that compile against SDL2 instead of SFML, -// enabling cross-platform builds for Web (Emscripten/WebGL), Android, and desktop. -// -// Part of the renderer abstraction layer (issue #158 continuation) -// -// Architecture: -// - Game code uses sf:: namespace types (unchanged from SFML builds) -// - This header provides sf:: types implemented using SDL2 + OpenGL ES 2 -// - Compile-time selection via MCRF_SDL2 define -// -// Phases: -// - Phase 1: Skeleton (stubs matching HeadlessTypes.h) -// - Phase 2: Window + GL context -// - Phase 3: Shape rendering (rectangles, circles) -// - Phase 4: Texture + Sprites -// - Phase 5: Text rendering -// - Phase 6: RenderTexture (FBO) -// - Phase 7: Custom shaders - -#pragma once - -#include -#include -#include -#include -#include - -// SDL2 headers - conditionally included when actually implementing -// For now, forward declare what we need -#ifdef MCRF_SDL2_IMPL -#include -#include -#endif - -namespace sf { - -// Forward declarations (needed for RenderWindow) -struct Event; -class Keyboard; -class Mouse; - -// ============================================================================= -// Type Aliases (SFML compatibility) -// ============================================================================= - -using Uint8 = uint8_t; -using Uint16 = uint16_t; -using Uint32 = uint32_t; -using Uint64 = uint64_t; -using Int8 = int8_t; -using Int16 = int16_t; -using Int32 = int32_t; -using Int64 = int64_t; - -// ============================================================================= -// Vector Types -// ============================================================================= - -template -struct Vector2 { - T x = 0; - T y = 0; - - Vector2() = default; - Vector2(T x_, T y_) : x(x_), y(y_) {} - - template - explicit Vector2(const Vector2& other) : x(static_cast(other.x)), y(static_cast(other.y)) {} - - Vector2 operator+(const Vector2& rhs) const { return Vector2(x + rhs.x, y + rhs.y); } - Vector2 operator-(const Vector2& rhs) const { return Vector2(x - rhs.x, y - rhs.y); } - Vector2 operator*(T scalar) const { return Vector2(x * scalar, y * scalar); } - Vector2 operator/(T scalar) const { return Vector2(x / scalar, y / scalar); } - Vector2& operator+=(const Vector2& rhs) { x += rhs.x; y += rhs.y; return *this; } - Vector2& operator-=(const Vector2& rhs) { x -= rhs.x; y -= rhs.y; return *this; } - Vector2& operator*=(T scalar) { x *= scalar; y *= scalar; return *this; } - Vector2& operator/=(T scalar) { x /= scalar; y /= scalar; return *this; } - bool operator==(const Vector2& rhs) const { return x == rhs.x && y == rhs.y; } - bool operator!=(const Vector2& rhs) const { return !(*this == rhs); } - Vector2 operator-() const { return Vector2(-x, -y); } -}; - -using Vector2f = Vector2; -using Vector2i = Vector2; -using Vector2u = Vector2; - -template -Vector2 operator*(T scalar, const Vector2& vec) { return vec * scalar; } - -// ============================================================================= -// Color Type -// ============================================================================= - -struct Color { - uint8_t r = 0; - uint8_t g = 0; - uint8_t b = 0; - uint8_t a = 255; - - Color() = default; - Color(uint8_t r_, uint8_t g_, uint8_t b_, uint8_t a_ = 255) : r(r_), g(g_), b(b_), a(a_) {} - - bool operator==(const Color& rhs) const { return r == rhs.r && g == rhs.g && b == rhs.b && a == rhs.a; } - bool operator!=(const Color& rhs) const { return !(*this == rhs); } - - // Standard colors - static const Color Black; - static const Color White; - static const Color Red; - static const Color Green; - static const Color Blue; - static const Color Yellow; - static const Color Magenta; - static const Color Cyan; - static const Color Transparent; -}; - -// Static color definitions -inline const Color Color::Black(0, 0, 0); -inline const Color Color::White(255, 255, 255); -inline const Color Color::Red(255, 0, 0); -inline const Color Color::Green(0, 255, 0); -inline const Color Color::Blue(0, 0, 255); -inline const Color Color::Yellow(255, 255, 0); -inline const Color Color::Magenta(255, 0, 255); -inline const Color Color::Cyan(0, 255, 255); -inline const Color Color::Transparent(0, 0, 0, 0); - -// ============================================================================= -// Rectangle Types -// ============================================================================= - -template -struct Rect { - T left = 0; - T top = 0; - T width = 0; - T height = 0; - - Rect() = default; - Rect(T left_, T top_, T width_, T height_) : left(left_), top(top_), width(width_), height(height_) {} - Rect(const Vector2& position, const Vector2& size) - : left(position.x), top(position.y), width(size.x), height(size.y) {} - - bool contains(T x, T y) const { - return x >= left && x < left + width && y >= top && y < top + height; - } - bool contains(const Vector2& point) const { return contains(point.x, point.y); } - - bool intersects(const Rect& other) const { - return left < other.left + other.width && left + width > other.left && - top < other.top + other.height && top + height > other.top; - } - - Vector2 getPosition() const { return Vector2(left, top); } - Vector2 getSize() const { return Vector2(width, height); } -}; - -using FloatRect = Rect; -using IntRect = Rect; - -// ============================================================================= -// Time Types -// ============================================================================= - -class Time { - int64_t microseconds_ = 0; -public: - Time() = default; - float asSeconds() const { return microseconds_ / 1000000.0f; } - int32_t asMilliseconds() const { return static_cast(microseconds_ / 1000); } - int64_t asMicroseconds() const { return microseconds_; } - - static Time Zero; - - friend Time seconds(float amount); - friend Time milliseconds(int32_t amount); - friend Time microseconds(int64_t amount); -}; - -inline Time Time::Zero; - -inline Time seconds(float amount) { Time t; t.microseconds_ = static_cast(amount * 1000000); return t; } -inline Time milliseconds(int32_t amount) { Time t; t.microseconds_ = amount * 1000; return t; } -inline Time microseconds(int64_t amount) { Time t; t.microseconds_ = amount; return t; } - -class Clock { - int64_t start_time_ = 0; - - static int64_t now_microseconds() { - // Use C++11 chrono for portable timing - auto now = std::chrono::high_resolution_clock::now(); - auto duration = now.time_since_epoch(); - return std::chrono::duration_cast(duration).count(); - } -public: - Clock() : start_time_(now_microseconds()) {} - - Time getElapsedTime() const { - return microseconds(now_microseconds() - start_time_); - } - - Time restart() { - int64_t now = now_microseconds(); - int64_t elapsed = now - start_time_; - start_time_ = now; - return microseconds(elapsed); - } -}; - -// ============================================================================= -// Transform -// ============================================================================= -// SDL2 backend will implement actual matrix math for transforms -// For now, stub implementation matching headless - -class Transform { - // 3x3 matrix stored as column-major for OpenGL - // [ a c tx ] [ m[0] m[3] m[6] ] - // [ b d ty ] -> [ m[1] m[4] m[7] ] - // [ 0 0 1 ] [ m[2] m[5] m[8] ] - float m[9] = {1,0,0, 0,1,0, 0,0,1}; - -public: - Transform() = default; - - Transform& translate(float x, float y) { - // Combine with translation matrix - m[6] += m[0] * x + m[3] * y; - m[7] += m[1] * x + m[4] * y; - return *this; - } - Transform& translate(const Vector2f& offset) { return translate(offset.x, offset.y); } - - Transform& rotate(float angle) { - float rad = angle * 3.14159265f / 180.0f; - float cos_a = std::cos(rad); - float sin_a = std::sin(rad); - - float new_m0 = m[0] * cos_a + m[3] * sin_a; - float new_m1 = m[1] * cos_a + m[4] * sin_a; - float new_m3 = m[0] * -sin_a + m[3] * cos_a; - float new_m4 = m[1] * -sin_a + m[4] * cos_a; - - m[0] = new_m0; m[1] = new_m1; - m[3] = new_m3; m[4] = new_m4; - return *this; - } - Transform& rotate(float angle, const Vector2f& center) { - translate(center.x, center.y); - rotate(angle); - translate(-center.x, -center.y); - return *this; - } - - Transform& scale(float factorX, float factorY) { - m[0] *= factorX; m[1] *= factorX; - m[3] *= factorY; m[4] *= factorY; - return *this; - } - Transform& scale(const Vector2f& factors) { return scale(factors.x, factors.y); } - - Vector2f transformPoint(float x, float y) const { - return Vector2f(m[0] * x + m[3] * y + m[6], - m[1] * x + m[4] * y + m[7]); - } - Vector2f transformPoint(const Vector2f& point) const { - return transformPoint(point.x, point.y); - } - - FloatRect transformRect(const FloatRect& rect) const { - // Transform all four corners and compute bounding box - Vector2f p1 = transformPoint(rect.left, rect.top); - Vector2f p2 = transformPoint(rect.left + rect.width, rect.top); - Vector2f p3 = transformPoint(rect.left, rect.top + rect.height); - Vector2f p4 = transformPoint(rect.left + rect.width, rect.top + rect.height); - - float minX = std::min({p1.x, p2.x, p3.x, p4.x}); - float maxX = std::max({p1.x, p2.x, p3.x, p4.x}); - float minY = std::min({p1.y, p2.y, p3.y, p4.y}); - float maxY = std::max({p1.y, p2.y, p3.y, p4.y}); - - return FloatRect(minX, minY, maxX - minX, maxY - minY); - } - - Transform getInverse() const { - // Compute inverse of 3x3 affine matrix - float det = m[0] * m[4] - m[1] * m[3]; - if (std::abs(det) < 1e-7f) return Transform(); - - float invDet = 1.0f / det; - Transform inv; - inv.m[0] = m[4] * invDet; - inv.m[1] = -m[1] * invDet; - inv.m[3] = -m[3] * invDet; - inv.m[4] = m[0] * invDet; - inv.m[6] = (m[3] * m[7] - m[4] * m[6]) * invDet; - inv.m[7] = (m[1] * m[6] - m[0] * m[7]) * invDet; - return inv; - } - - Transform operator*(const Transform& rhs) const { - Transform result; - result.m[0] = m[0] * rhs.m[0] + m[3] * rhs.m[1]; - result.m[1] = m[1] * rhs.m[0] + m[4] * rhs.m[1]; - result.m[3] = m[0] * rhs.m[3] + m[3] * rhs.m[4]; - result.m[4] = m[1] * rhs.m[3] + m[4] * rhs.m[4]; - result.m[6] = m[0] * rhs.m[6] + m[3] * rhs.m[7] + m[6]; - result.m[7] = m[1] * rhs.m[6] + m[4] * rhs.m[7] + m[7]; - return result; - } - Vector2f operator*(const Vector2f& point) const { return transformPoint(point); } - - static const Transform Identity; - - // SDL2-specific: Get raw matrix for OpenGL - const float* getMatrix() const { return m; } -}; - -inline const Transform Transform::Identity; - -// ============================================================================= -// Vertex (for custom geometry) -// ============================================================================= - -struct Vertex { - Vector2f position; - Color color; - Vector2f texCoords; - - Vertex() = default; - Vertex(const Vector2f& pos) : position(pos), color(Color::White) {} - Vertex(const Vector2f& pos, const Color& col) : position(pos), color(col) {} - Vertex(const Vector2f& pos, const Vector2f& tex) : position(pos), color(Color::White), texCoords(tex) {} - Vertex(const Vector2f& pos, const Color& col, const Vector2f& tex) : position(pos), color(col), texCoords(tex) {} -}; - -// ============================================================================= -// View (camera) -// ============================================================================= - -class View { - Vector2f center_; - Vector2f size_; - float rotation_ = 0.0f; - FloatRect viewport_{0, 0, 1, 1}; -public: - View() : center_(0, 0), size_(1000, 1000) {} - View(const FloatRect& rect) : center_(rect.left + rect.width/2, rect.top + rect.height/2), size_(rect.width, rect.height) {} - View(const Vector2f& center, const Vector2f& size) : center_(center), size_(size) {} - - void setCenter(float x, float y) { center_ = Vector2f(x, y); } - void setCenter(const Vector2f& center) { center_ = center; } - void setSize(float width, float height) { size_ = Vector2f(width, height); } - void setSize(const Vector2f& size) { size_ = size; } - void setRotation(float angle) { rotation_ = angle; } - void setViewport(const FloatRect& viewport) { viewport_ = viewport; } - - const Vector2f& getCenter() const { return center_; } - const Vector2f& getSize() const { return size_; } - float getRotation() const { return rotation_; } - const FloatRect& getViewport() const { return viewport_; } - - void move(float offsetX, float offsetY) { center_.x += offsetX; center_.y += offsetY; } - void move(const Vector2f& offset) { center_ += offset; } - void rotate(float angle) { rotation_ += angle; } - void zoom(float factor) { size_ *= factor; } - - Transform getTransform() const { - // View transform: translates world coords to view coords (NDC) - // Order: Scale to NDC, then Rotate, then Translate center to origin - Transform t; - t.translate(-center_.x, -center_.y); // Move center to origin - t.rotate(-rotation_); // Rotate around origin (negative for view) - t.scale(2.0f / size_.x, 2.0f / size_.y); // Scale to NDC [-1,1] - return t; - } - - Transform getInverseTransform() const { - return getTransform().getInverse(); - } -}; - -// ============================================================================= -// Rendering Types -// ============================================================================= - -enum PrimitiveType { - Points, - Lines, - LineStrip, - Triangles, - TriangleStrip, - TriangleFan, - Quads // Deprecated in SFML 3.0, but we'll support it via triangulation -}; - -// BlendMode stub -struct BlendMode { - BlendMode() = default; - static const BlendMode Alpha; - static const BlendMode Add; - static const BlendMode Multiply; - static const BlendMode None; -}; -inline const BlendMode BlendMode::Alpha{}; -inline const BlendMode BlendMode::Add{}; -inline const BlendMode BlendMode::Multiply{}; -inline const BlendMode BlendMode::None{}; - -// Forward declare Shader for RenderStates -class Shader; - -class RenderStates { -public: - Transform transform; - BlendMode blendMode; - const Shader* shader = nullptr; - - RenderStates() = default; - RenderStates(const Transform& t) : transform(t) {} - RenderStates(const BlendMode& mode) : blendMode(mode) {} - RenderStates(const Shader* s) : shader(s) {} - static const RenderStates Default; -}; - -inline const RenderStates RenderStates::Default; - -// Forward declarations for rendering types -class RenderTarget; -class RenderTexture; -class RenderWindow; -class Texture; -class Font; -class Shader; - -// Drawable base class -class Drawable { -public: - virtual ~Drawable() = default; -protected: - friend class RenderTarget; - virtual void draw(RenderTarget& target, RenderStates states) const = 0; -}; - -// Transformable base class -class Transformable { -protected: - Vector2f position_; - float rotation_ = 0.0f; - Vector2f scale_{1.0f, 1.0f}; - Vector2f origin_; -public: - virtual ~Transformable() = default; - - void setPosition(float x, float y) { position_ = Vector2f(x, y); } - void setPosition(const Vector2f& position) { position_ = position; } - void setRotation(float angle) { rotation_ = angle; } - void setScale(float factorX, float factorY) { scale_ = Vector2f(factorX, factorY); } - void setScale(const Vector2f& factors) { scale_ = factors; } - void setOrigin(float x, float y) { origin_ = Vector2f(x, y); } - void setOrigin(const Vector2f& origin) { origin_ = origin; } - - const Vector2f& getPosition() const { return position_; } - float getRotation() const { return rotation_; } - const Vector2f& getScale() const { return scale_; } - const Vector2f& getOrigin() const { return origin_; } - - void move(float offsetX, float offsetY) { position_.x += offsetX; position_.y += offsetY; } - void move(const Vector2f& offset) { position_ += offset; } - void rotate(float angle) { rotation_ += angle; } - void scale(float factorX, float factorY) { scale_.x *= factorX; scale_.y *= factorY; } - void scale(const Vector2f& factor) { scale_.x *= factor.x; scale_.y *= factor.y; } - - Transform getTransform() const { - Transform transform; - // Apply transformations: translate to position, rotate, scale, translate by -origin - transform.translate(position_.x, position_.y); - transform.rotate(rotation_); - transform.scale(scale_.x, scale_.y); - transform.translate(-origin_.x, -origin_.y); - return transform; - } - Transform getInverseTransform() const { - return getTransform().getInverse(); - } -}; - -// ============================================================================= -// Shape Classes -// ============================================================================= - -class Shape : public Drawable, public Transformable { -protected: - Color fillColor_ = Color::White; - Color outlineColor_ = Color::White; - float outlineThickness_ = 0.0f; -public: - void setFillColor(const Color& color) { fillColor_ = color; } - void setOutlineColor(const Color& color) { outlineColor_ = color; } - void setOutlineThickness(float thickness) { outlineThickness_ = thickness; } - - const Color& getFillColor() const { return fillColor_; } - const Color& getOutlineColor() const { return outlineColor_; } - float getOutlineThickness() const { return outlineThickness_; } - - virtual FloatRect getLocalBounds() const { return FloatRect(); } - virtual FloatRect getGlobalBounds() const { return FloatRect(); } - - // Virtual methods for shape points (implemented by derived classes) - virtual size_t getPointCount() const = 0; - virtual Vector2f getPoint(size_t index) const = 0; - -protected: - void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp -}; - -class RectangleShape : public Shape { - Vector2f size_; -public: - RectangleShape(const Vector2f& size = Vector2f(0, 0)) : size_(size) {} - void setSize(const Vector2f& size) { size_ = size; } - const Vector2f& getSize() const { return size_; } - FloatRect getLocalBounds() const override { return FloatRect(0, 0, size_.x, size_.y); } - FloatRect getGlobalBounds() const override { return FloatRect(position_.x, position_.y, size_.x, size_.y); } - - size_t getPointCount() const override { return 4; } - Vector2f getPoint(size_t index) const override { - switch (index) { - case 0: return Vector2f(0, 0); - case 1: return Vector2f(size_.x, 0); - case 2: return Vector2f(size_.x, size_.y); - case 3: return Vector2f(0, size_.y); - default: return Vector2f(); - } - } -}; - -class CircleShape : public Shape { - float radius_ = 0.0f; - size_t pointCount_ = 30; -public: - CircleShape(float radius = 0, size_t pointCount = 30) : radius_(radius), pointCount_(pointCount) {} - void setRadius(float radius) { radius_ = radius; } - float getRadius() const { return radius_; } - void setPointCount(size_t count) { pointCount_ = count; } - size_t getPointCount() const override { return pointCount_; } - FloatRect getLocalBounds() const override { return FloatRect(0, 0, radius_ * 2, radius_ * 2); } - - Vector2f getPoint(size_t index) const override { - float angle = static_cast(index) / pointCount_ * 2.0f * 3.14159265f; - return Vector2f(radius_ + radius_ * std::cos(angle), radius_ + radius_ * std::sin(angle)); - } -}; - -class ConvexShape : public Shape { - std::vector points_; -public: - ConvexShape(size_t pointCount = 0) : points_(pointCount) {} - void setPointCount(size_t count) { points_.resize(count); } - size_t getPointCount() const override { return points_.size(); } - void setPoint(size_t index, const Vector2f& point) { if (index < points_.size()) points_[index] = point; } - Vector2f getPoint(size_t index) const override { return index < points_.size() ? points_[index] : Vector2f(); } -}; - -// ============================================================================= -// VertexArray -// ============================================================================= - -class VertexArray : public Drawable { - std::vector vertices_; - PrimitiveType primitiveType_ = Points; -public: - VertexArray() = default; - VertexArray(PrimitiveType type, size_t vertexCount = 0) : vertices_(vertexCount), primitiveType_(type) {} - - size_t getVertexCount() const { return vertices_.size(); } - Vertex& operator[](size_t index) { return vertices_[index]; } - const Vertex& operator[](size_t index) const { return vertices_[index]; } - - void clear() { vertices_.clear(); } - void resize(size_t vertexCount) { vertices_.resize(vertexCount); } - void append(const Vertex& vertex) { vertices_.push_back(vertex); } - - void setPrimitiveType(PrimitiveType type) { primitiveType_ = type; } - PrimitiveType getPrimitiveType() const { return primitiveType_; } - - FloatRect getBounds() const { return FloatRect(); } // TODO: Implement - -protected: - void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp -}; - -// ============================================================================= -// Image -// ============================================================================= - -class Image { - Vector2u size_; - std::vector pixels_; -public: - Image() = default; - - void create(unsigned int width, unsigned int height, const Color& color = Color::Black) { - size_ = Vector2u(width, height); - pixels_.resize(width * height * 4, 0); - // Fill with color - for (unsigned int y = 0; y < height; ++y) { - for (unsigned int x = 0; x < width; ++x) { - setPixel(x, y, color); - } - } - } - - bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp (uses stb_image) - bool saveToFile(const std::string& filename) const; // Implemented in SDL2Renderer.cpp (uses stb_image_write) - - Vector2u getSize() const { return size_; } - - void setPixel(unsigned int x, unsigned int y, const Color& color) { - if (x < size_.x && y < size_.y) { - size_t idx = (y * size_.x + x) * 4; - pixels_[idx] = color.r; - pixels_[idx + 1] = color.g; - pixels_[idx + 2] = color.b; - pixels_[idx + 3] = color.a; - } - } - - Color getPixel(unsigned int x, unsigned int y) const { - if (x < size_.x && y < size_.y) { - size_t idx = (y * size_.x + x) * 4; - return Color(pixels_[idx], pixels_[idx + 1], pixels_[idx + 2], pixels_[idx + 3]); - } - return Color::Black; - } - - const Uint8* getPixelsPtr() const { return pixels_.data(); } - - // SDL2-specific: Allow direct pixel access for texture upload - Uint8* getPixelsPtr() { return pixels_.data(); } - void setSize(unsigned int w, unsigned int h) { size_ = Vector2u(w, h); pixels_.resize(w * h * 4); } -}; - -// ============================================================================= -// Texture -// ============================================================================= - -// Forward declare RenderWindow for Texture::update -class RenderWindow; - -class Texture { - Vector2u size_; - // SDL2-specific: OpenGL texture handle (0 = not created) - unsigned int textureId_ = 0; - bool smooth_ = false; - bool repeated_ = false; - -public: - Texture() = default; - ~Texture(); // Implemented in SDL2Renderer.cpp (deletes GL texture) - - // Copy constructor/assignment - needed for proper GL resource management - Texture(const Texture& other); - Texture& operator=(const Texture& other); - - bool create(unsigned int width, unsigned int height); // Implemented in SDL2Renderer.cpp - bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp - bool loadFromMemory(const void* data, size_t size); // Implemented in SDL2Renderer.cpp - - Vector2u getSize() const { return size_; } - void setSmooth(bool smooth); // Implemented in SDL2Renderer.cpp - bool isSmooth() const { return smooth_; } - void setRepeated(bool repeated); // Implemented in SDL2Renderer.cpp - bool isRepeated() const { return repeated_; } - - Image copyToImage() const; // Implemented in SDL2Renderer.cpp - void update(const RenderWindow& window); // Implemented in SDL2Renderer.cpp - void update(const Uint8* pixels); // Implemented in SDL2Renderer.cpp - void update(const Uint8* pixels, unsigned int width, unsigned int height, unsigned int x, unsigned int y); - - // SDL2-specific: Get GL texture handle - unsigned int getNativeHandle() const { return textureId_; } - void setNativeHandle(unsigned int id) { textureId_ = id; } -}; - -// ============================================================================= -// Sprite -// ============================================================================= - -class Sprite : public Drawable, public Transformable { - const Texture* texture_ = nullptr; - IntRect textureRect_; - Color color_ = Color::White; -public: - Sprite() = default; - Sprite(const Texture& texture) : texture_(&texture) {} - Sprite(const Texture& texture, const IntRect& rectangle) : texture_(&texture), textureRect_(rectangle) {} - - void setTexture(const Texture& texture, bool resetRect = false) { texture_ = &texture; } - void setTextureRect(const IntRect& rectangle) { textureRect_ = rectangle; } - void setColor(const Color& color) { color_ = color; } - - const Texture* getTexture() const { return texture_; } - const IntRect& getTextureRect() const { return textureRect_; } - const Color& getColor() const { return color_; } - - FloatRect getLocalBounds() const { return FloatRect(0, 0, static_cast(textureRect_.width), static_cast(textureRect_.height)); } - FloatRect getGlobalBounds() const { return FloatRect(position_.x, position_.y, static_cast(textureRect_.width), static_cast(textureRect_.height)); } - -protected: - void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp -}; - -// ============================================================================= -// Text and Font -// ============================================================================= - -class Font { - // SDL2-specific: font data for stb_truetype - std::vector fontData_; - bool loaded_ = false; - -public: - struct Info { - std::string family; - }; - - Font() = default; - bool loadFromFile(const std::string& filename); // Implemented in SDL2Renderer.cpp - bool loadFromMemory(const void* data, size_t sizeInBytes); // Implemented in SDL2Renderer.cpp - const Info& getInfo() const { static Info info; return info; } - - // SDL2-specific: Access font data - const unsigned char* getData() const { return fontData_.data(); } - size_t getDataSize() const { return fontData_.size(); } - bool isLoaded() const { return loaded_; } -}; - -class Text : public Drawable, public Transformable { - std::string string_; - const Font* font_ = nullptr; - unsigned int characterSize_ = 30; - Color fillColor_ = Color::White; - Color outlineColor_ = Color::Black; - float outlineThickness_ = 0.0f; - uint32_t style_ = 0; -public: - enum Style { Regular = 0, Bold = 1, Italic = 2, Underlined = 4, StrikeThrough = 8 }; - - Text() = default; - Text(const std::string& string, const Font& font, unsigned int characterSize = 30) - : string_(string), font_(&font), characterSize_(characterSize) {} - - void setString(const std::string& string) { string_ = string; } - void setFont(const Font& font) { font_ = &font; } - void setCharacterSize(unsigned int size) { characterSize_ = size; } - void setStyle(uint32_t style) { style_ = style; } - void setFillColor(const Color& color) { fillColor_ = color; } - void setOutlineColor(const Color& color) { outlineColor_ = color; } - void setOutlineThickness(float thickness) { outlineThickness_ = thickness; } - - const std::string& getString() const { return string_; } - const Font* getFont() const { return font_; } - unsigned int getCharacterSize() const { return characterSize_; } - uint32_t getStyle() const { return style_; } - const Color& getFillColor() const { return fillColor_; } - const Color& getOutlineColor() const { return outlineColor_; } - float getOutlineThickness() const { return outlineThickness_; } - - FloatRect getLocalBounds() const; // Implemented in SDL2Renderer.cpp - FloatRect getGlobalBounds() const; // Implemented in SDL2Renderer.cpp - -protected: - void draw(RenderTarget& target, RenderStates states) const override; // Implemented in SDL2Renderer.cpp -}; - -// ============================================================================= -// RenderTarget (base class for rendering) -// ============================================================================= - -class RenderTarget { -protected: - Vector2u size_; - View view_; - View defaultView_; -public: - virtual ~RenderTarget() = default; - - virtual Vector2u getSize() const { return size_; } - virtual void clear(const Color& color = Color::Black); // Implemented in SDL2Renderer.cpp - - void draw(const Drawable& drawable, const RenderStates& states = RenderStates::Default) { - drawable.draw(*this, states); - } - void draw(const Vertex* vertices, size_t vertexCount, PrimitiveType type, const RenderStates& states = RenderStates::Default); - void draw(const VertexArray& vertices, const RenderStates& states = RenderStates::Default); - - void setView(const View& view) { view_ = view; } - const View& getView() const { return view_; } - const View& getDefaultView() const { return defaultView_; } - - IntRect getViewport(const View& view) const { return IntRect(0, 0, size_.x, size_.y); } - - Vector2f mapPixelToCoords(const Vector2i& point) const { return Vector2f(static_cast(point.x), static_cast(point.y)); } - Vector2f mapPixelToCoords(const Vector2i& point, const View& view) const { return Vector2f(static_cast(point.x), static_cast(point.y)); } - Vector2i mapCoordsToPixel(const Vector2f& point) const { return Vector2i(static_cast(point.x), static_cast(point.y)); } - Vector2i mapCoordsToPixel(const Vector2f& point, const View& view) const { return Vector2i(static_cast(point.x), static_cast(point.y)); } -}; - -// ============================================================================= -// RenderTexture (Framebuffer Object) -// ============================================================================= - -class RenderTexture : public RenderTarget { - Texture texture_; - unsigned int fboId_ = 0; // OpenGL FBO handle -public: - RenderTexture() = default; - ~RenderTexture(); // Implemented in SDL2Renderer.cpp - - bool create(unsigned int width, unsigned int height); // Implemented in SDL2Renderer.cpp - - void clear(const Color& color = Color::Black) override; // Implemented in SDL2Renderer.cpp - void display(); // Implemented in SDL2Renderer.cpp - - const Texture& getTexture() const { return texture_; } - void setSmooth(bool smooth) { texture_.setSmooth(smooth); } - bool isSmooth() const { return texture_.isSmooth(); } - - // SDL2-specific: Get FBO handle - unsigned int getNativeHandle() const { return fboId_; } -}; - -// ============================================================================= -// RenderWindow -// ============================================================================= - -namespace Style { - enum { - None = 0, - Titlebar = 1 << 0, - Resize = 1 << 1, - Close = 1 << 2, - Fullscreen = 1 << 3, - Default = Titlebar | Resize | Close - }; -} - -class VideoMode { -public: - unsigned int width = 0; - unsigned int height = 0; - unsigned int bitsPerPixel = 32; - - VideoMode() = default; - VideoMode(unsigned int w, unsigned int h, unsigned int bpp = 32) : width(w), height(h), bitsPerPixel(bpp) {} - - static VideoMode getDesktopMode(); // Implemented in SDL2Renderer.cpp - static const std::vector& getFullscreenModes(); // Implemented in SDL2Renderer.cpp -}; - -class RenderWindow : public RenderTarget { - bool open_ = false; - std::string title_; - - // SDL2-specific handles (void* to avoid SDL header dependency) - void* sdlWindow_ = nullptr; - void* glContext_ = nullptr; - -public: - RenderWindow() = default; - RenderWindow(VideoMode mode, const std::string& title, uint32_t style = Style::Default) { - create(mode, title, style); - } - ~RenderWindow(); // Implemented in SDL2Renderer.cpp - - void create(VideoMode mode, const std::string& title, uint32_t style = Style::Default); // Implemented in SDL2Renderer.cpp - - void close(); // Implemented in SDL2Renderer.cpp - bool isOpen() const { return open_; } - - void clear(const Color& color = Color::Black) override; // Implemented in SDL2Renderer.cpp - void display(); // Implemented in SDL2Renderer.cpp - - void setTitle(const std::string& title); // Implemented in SDL2Renderer.cpp - void setFramerateLimit(unsigned int limit); // Implemented in SDL2Renderer.cpp - void setVerticalSyncEnabled(bool enabled); // Implemented in SDL2Renderer.cpp - void setVisible(bool visible); // Implemented in SDL2Renderer.cpp - void setMouseCursorVisible(bool visible); // Implemented in SDL2Renderer.cpp - void setMouseCursorGrabbed(bool grabbed); // Implemented in SDL2Renderer.cpp - void setKeyRepeatEnabled(bool enabled) {} // No-op for now - - Vector2i getPosition() const; // Implemented in SDL2Renderer.cpp - void setPosition(const Vector2i& position); // Implemented in SDL2Renderer.cpp - Vector2u getSize() const override { return size_; } - void setSize(const Vector2u& size); // Implemented in SDL2Renderer.cpp - - bool pollEvent(Event& event); // Implemented in SDL2Renderer.cpp - bool waitEvent(Event& event); // Implemented in SDL2Renderer.cpp - - // SDL2-specific: Access native handles - void* getNativeWindowHandle() const { return sdlWindow_; } - void* getGLContext() const { return glContext_; } -}; - -// ============================================================================= -// Audio Stubs (SDL2_mixer could implement these later) -// ============================================================================= - -class SoundBuffer { -public: - SoundBuffer() = default; - bool loadFromFile(const std::string& filename) { return true; } // Stub - bool loadFromMemory(const void* data, size_t sizeInBytes) { return true; } // Stub - Time getDuration() const { return Time(); } -}; - -class Sound { -public: - enum Status { Stopped, Paused, Playing }; - - Sound() = default; - Sound(const SoundBuffer& buffer) {} - - void setBuffer(const SoundBuffer& buffer) {} - void play() {} - void pause() {} - void stop() {} - - Status getStatus() const { return Stopped; } - void setVolume(float volume) {} - float getVolume() const { return 100.0f; } - void setLoop(bool loop) {} - bool getLoop() const { return false; } -}; - -class Music { -public: - enum Status { Stopped, Paused, Playing }; - - Music() = default; - bool openFromFile(const std::string& filename) { return true; } // Stub - - void play() {} - void pause() {} - void stop() {} - - Status getStatus() const { return Stopped; } - void setVolume(float volume) {} - float getVolume() const { return 100.0f; } - void setLoop(bool loop) {} - bool getLoop() const { return false; } - Time getDuration() const { return Time(); } - Time getPlayingOffset() const { return Time(); } - void setPlayingOffset(Time offset) {} -}; - -// ============================================================================= -// Input (Keyboard and Mouse) -// ============================================================================= - -class Keyboard { -public: - enum Key { - Unknown = -1, - A = 0, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, - Num0, Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, - Escape, LControl, LShift, LAlt, LSystem, RControl, RShift, RAlt, RSystem, - Menu, LBracket, RBracket, Semicolon, Comma, Period, Apostrophe, Slash, Backslash, - Grave, Equal, Hyphen, Space, Enter, Backspace, Tab, PageUp, PageDown, End, Home, - Insert, Delete, Add, Subtract, Multiply, Divide, - Left, Right, Up, Down, - Numpad0, Numpad1, Numpad2, Numpad3, Numpad4, Numpad5, Numpad6, Numpad7, Numpad8, Numpad9, - F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, - Pause, - KeyCount, - // Deprecated aliases (SFML 2.x compatibility) - Tilde = Grave, - Quote = Apostrophe, - BackSpace = Backspace, - BackSlash = Backslash, - SemiColon = Semicolon, - Dash = Hyphen - }; - - static bool isKeyPressed(Key key); // Implemented in SDL2Renderer.cpp -}; - -class Mouse { -public: - enum Button { Left, Right, Middle, XButton1, XButton2, ButtonCount }; - enum Wheel { VerticalWheel, HorizontalWheel }; - - static bool isButtonPressed(Button button); // Implemented in SDL2Renderer.cpp - static Vector2i getPosition(); // Implemented in SDL2Renderer.cpp - static Vector2i getPosition(const RenderWindow& relativeTo); // Implemented in SDL2Renderer.cpp - static void setPosition(const Vector2i& position); // Implemented in SDL2Renderer.cpp - static void setPosition(const Vector2i& position, const RenderWindow& relativeTo); // Implemented in SDL2Renderer.cpp -}; - -// ============================================================================= -// Event System -// ============================================================================= - -struct Event { - enum EventType { - Closed, - Resized, - LostFocus, - GainedFocus, - TextEntered, - KeyPressed, - KeyReleased, - MouseWheelMoved, // Deprecated - MouseWheelScrolled, - MouseButtonPressed, - MouseButtonReleased, - MouseMoved, - MouseEntered, - MouseLeft, - Count - }; - - struct SizeEvent { unsigned int width, height; }; - struct KeyEvent { Keyboard::Key code; bool alt, control, shift, system; }; - struct TextEvent { uint32_t unicode; }; - struct MouseMoveEvent { int x, y; }; - struct MouseButtonEvent { Mouse::Button button; int x, y; }; - struct MouseWheelScrollEvent { Mouse::Wheel wheel; float delta; int x, y; }; - - EventType type; - union { - SizeEvent size; - KeyEvent key; - TextEvent text; - MouseMoveEvent mouseMove; - MouseButtonEvent mouseButton; - MouseWheelScrollEvent mouseWheelScroll; - }; -}; - -// ============================================================================= -// GLSL Types (for shader uniforms) -// ============================================================================= - -namespace Glsl { - using Vec2 = Vector2f; - - struct Vec3 { - float x = 0, y = 0, z = 0; - Vec3() = default; - Vec3(float x_, float y_, float z_) : x(x_), y(y_), z(z_) {} - }; - - struct Vec4 { - float x = 0, y = 0, z = 0, w = 0; - Vec4() = default; - Vec4(float x_, float y_, float z_, float w_) : x(x_), y(y_), z(z_), w(w_) {} - Vec4(const Color& c) : x(c.r/255.f), y(c.g/255.f), z(c.b/255.f), w(c.a/255.f) {} - }; -} // namespace Glsl - -// Forward declaration for CurrentTexture -struct CurrentTextureType {}; - -// ============================================================================= -// Shader -// ============================================================================= - -class Shader { - unsigned int programId_ = 0; // OpenGL shader program handle - bool loaded_ = false; - -public: - enum Type { Vertex, Geometry, Fragment }; - static const CurrentTextureType CurrentTexture; - - Shader() = default; - ~Shader(); // Implemented in SDL2Renderer.cpp - - bool loadFromFile(const std::string& filename, Type type); // Implemented in SDL2Renderer.cpp - bool loadFromFile(const std::string& vertexFile, const std::string& fragmentFile); // Implemented in SDL2Renderer.cpp - bool loadFromMemory(const std::string& shader, Type type); // Implemented in SDL2Renderer.cpp - - void setUniform(const std::string& name, float x); // Implemented in SDL2Renderer.cpp - void setUniform(const std::string& name, const Vector2f& v); - void setUniform(const std::string& name, const Color& color); - void setUniform(const std::string& name, const Texture& texture); - void setUniform(const std::string& name, const Glsl::Vec3& v); - void setUniform(const std::string& name, const Glsl::Vec4& v); - void setUniform(const std::string& name, CurrentTextureType); - - static bool isAvailable(); // Implemented in SDL2Renderer.cpp - - // SDL2-specific: Get program handle - unsigned int getNativeHandle() const { return programId_; } - bool isLoaded() const { return loaded_; } -}; - -inline const CurrentTextureType Shader::CurrentTexture{}; - -// ============================================================================= -// Error stream -// ============================================================================= - -#include -#include - -inline std::ostream& err() { - static std::stringstream dummy; - return dummy; -} - -} // namespace sf