diff options
| author | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-21 11:09:56 +0200 |
|---|---|---|
| committer | Sebastiano Tronto <sebastiano@tronto.net> | 2025-04-21 11:09:56 +0200 |
| commit | 123144c93bfc77883c8fb517828b47bbe13b8671 (patch) | |
| tree | 762739afedb5f3f168051367515cb9b9a06ec68d /raylib/src/rcore.c | |
| download | minesweeper-master.tar.gz minesweeper-master.zip | |
Diffstat (limited to 'raylib/src/rcore.c')
| -rw-r--r-- | raylib/src/rcore.c | 4070 |
1 files changed, 4070 insertions, 0 deletions
diff --git a/raylib/src/rcore.c b/raylib/src/rcore.c new file mode 100644 index 0000000..ba23df7 --- /dev/null +++ b/raylib/src/rcore.c | |||
| @@ -0,0 +1,4070 @@ | |||
| 1 | /********************************************************************************************** | ||
| 2 | * | ||
| 3 | * rcore - Window/display management, Graphic device/context management and input management | ||
| 4 | * | ||
| 5 | * PLATFORMS SUPPORTED: | ||
| 6 | * > PLATFORM_DESKTOP_GLFW (GLFW backend): | ||
| 7 | * - Windows (Win32, Win64) | ||
| 8 | * - Linux (X11/Wayland desktop mode) | ||
| 9 | * - macOS/OSX (x64, arm64) | ||
| 10 | * - FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop) | ||
| 11 | * > PLATFORM_DESKTOP_SDL (SDL backend): | ||
| 12 | * - Windows (Win32, Win64) | ||
| 13 | * - Linux (X11/Wayland desktop mode) | ||
| 14 | * - Others (not tested) | ||
| 15 | * > PLATFORM_WEB: | ||
| 16 | * - HTML5 (WebAssembly) | ||
| 17 | * > PLATFORM_DRM: | ||
| 18 | * - Raspberry Pi 0-5 (DRM/KMS) | ||
| 19 | * - Linux DRM subsystem (KMS mode) | ||
| 20 | * > PLATFORM_ANDROID: | ||
| 21 | * - Android (ARM, ARM64) | ||
| 22 | * | ||
| 23 | * CONFIGURATION: | ||
| 24 | * #define SUPPORT_DEFAULT_FONT (default) | ||
| 25 | * Default font is loaded on window initialization to be available for the user to render simple text. | ||
| 26 | * NOTE: If enabled, uses external module functions to load default raylib font (module: text) | ||
| 27 | * | ||
| 28 | * #define SUPPORT_CAMERA_SYSTEM | ||
| 29 | * Camera module is included (rcamera.h) and multiple predefined cameras are available: | ||
| 30 | * free, 1st/3rd person, orbital, custom | ||
| 31 | * | ||
| 32 | * #define SUPPORT_GESTURES_SYSTEM | ||
| 33 | * Gestures module is included (rgestures.h) to support gestures detection: tap, hold, swipe, drag | ||
| 34 | * | ||
| 35 | * #define SUPPORT_MOUSE_GESTURES | ||
| 36 | * Mouse gestures are directly mapped like touches and processed by gestures system. | ||
| 37 | * | ||
| 38 | * #define SUPPORT_BUSY_WAIT_LOOP | ||
| 39 | * Use busy wait loop for timing sync, if not defined, a high-resolution timer is setup and used | ||
| 40 | * | ||
| 41 | * #define SUPPORT_PARTIALBUSY_WAIT_LOOP | ||
| 42 | * Use a partial-busy wait loop, in this case frame sleeps for most of the time and runs a busy-wait-loop at the end | ||
| 43 | * | ||
| 44 | * #define SUPPORT_SCREEN_CAPTURE | ||
| 45 | * Allow automatic screen capture of current screen pressing F12, defined in KeyCallback() | ||
| 46 | * | ||
| 47 | * #define SUPPORT_GIF_RECORDING | ||
| 48 | * Allow automatic gif recording of current screen pressing CTRL+F12, defined in KeyCallback() | ||
| 49 | * | ||
| 50 | * #define SUPPORT_COMPRESSION_API | ||
| 51 | * Support CompressData() and DecompressData() functions, those functions use zlib implementation | ||
| 52 | * provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module | ||
| 53 | * for linkage | ||
| 54 | * | ||
| 55 | * #define SUPPORT_AUTOMATION_EVENTS | ||
| 56 | * Support automatic events recording and playing, useful for automated testing systems or AI based game playing | ||
| 57 | * | ||
| 58 | * DEPENDENCIES: | ||
| 59 | * raymath - 3D math functionality (Vector2, Vector3, Matrix, Quaternion) | ||
| 60 | * camera - Multiple 3D camera modes (free, orbital, 1st person, 3rd person) | ||
| 61 | * gestures - Gestures system for touch-ready devices (or simulated from mouse inputs) | ||
| 62 | * | ||
| 63 | * | ||
| 64 | * LICENSE: zlib/libpng | ||
| 65 | * | ||
| 66 | * Copyright (c) 2013-2024 Ramon Santamaria (@raysan5) and contributors | ||
| 67 | * | ||
| 68 | * This software is provided "as-is", without any express or implied warranty. In no event | ||
| 69 | * will the authors be held liable for any damages arising from the use of this software. | ||
| 70 | * | ||
| 71 | * Permission is granted to anyone to use this software for any purpose, including commercial | ||
| 72 | * applications, and to alter it and redistribute it freely, subject to the following restrictions: | ||
| 73 | * | ||
| 74 | * 1. The origin of this software must not be misrepresented; you must not claim that you | ||
| 75 | * wrote the original software. If you use this software in a product, an acknowledgment | ||
| 76 | * in the product documentation would be appreciated but is not required. | ||
| 77 | * | ||
| 78 | * 2. Altered source versions must be plainly marked as such, and must not be misrepresented | ||
| 79 | * as being the original software. | ||
| 80 | * | ||
| 81 | * 3. This notice may not be removed or altered from any source distribution. | ||
| 82 | * | ||
| 83 | **********************************************************************************************/ | ||
| 84 | |||
| 85 | //---------------------------------------------------------------------------------- | ||
| 86 | // Feature Test Macros required for this module | ||
| 87 | //---------------------------------------------------------------------------------- | ||
| 88 | #if (defined(__linux__) || defined(PLATFORM_WEB)) && (_XOPEN_SOURCE < 500) | ||
| 89 | #undef _XOPEN_SOURCE | ||
| 90 | #define _XOPEN_SOURCE 500 // Required for: readlink if compiled with c99 without gnu ext. | ||
| 91 | #endif | ||
| 92 | |||
| 93 | #if (defined(__linux__) || defined(PLATFORM_WEB)) && (_POSIX_C_SOURCE < 199309L) | ||
| 94 | #undef _POSIX_C_SOURCE | ||
| 95 | #define _POSIX_C_SOURCE 199309L // Required for: CLOCK_MONOTONIC if compiled with c99 without gnu ext. | ||
| 96 | #endif | ||
| 97 | |||
| 98 | #include "raylib.h" // Declares module functions | ||
| 99 | |||
| 100 | // Check if config flags have been externally provided on compilation line | ||
| 101 | #if !defined(EXTERNAL_CONFIG_FLAGS) | ||
| 102 | #include "config.h" // Defines module configuration flags | ||
| 103 | #endif | ||
| 104 | |||
| 105 | #include "utils.h" // Required for: TRACELOG() macros | ||
| 106 | |||
| 107 | #include <stdlib.h> // Required for: srand(), rand(), atexit() | ||
| 108 | #include <stdio.h> // Required for: sprintf() [Used in OpenURL()] | ||
| 109 | #include <string.h> // Required for: strrchr(), strcmp(), strlen(), memset() | ||
| 110 | #include <time.h> // Required for: time() [Used in InitTimer()] | ||
| 111 | #include <math.h> // Required for: tan() [Used in BeginMode3D()], atan2f() [Used in LoadVrStereoConfig()] | ||
| 112 | |||
| 113 | #define RLGL_IMPLEMENTATION | ||
| 114 | #include "rlgl.h" // OpenGL abstraction layer to OpenGL 1.1, 3.3+ or ES2 | ||
| 115 | |||
| 116 | #define RAYMATH_IMPLEMENTATION | ||
| 117 | #include "raymath.h" // Vector2, Vector3, Quaternion and Matrix functionality | ||
| 118 | |||
| 119 | #if defined(SUPPORT_GESTURES_SYSTEM) | ||
| 120 | #define RGESTURES_IMPLEMENTATION | ||
| 121 | #include "rgestures.h" // Gestures detection functionality | ||
| 122 | #endif | ||
| 123 | |||
| 124 | #if defined(SUPPORT_CAMERA_SYSTEM) | ||
| 125 | #define RCAMERA_IMPLEMENTATION | ||
| 126 | #include "rcamera.h" // Camera system functionality | ||
| 127 | #endif | ||
| 128 | |||
| 129 | #if defined(SUPPORT_GIF_RECORDING) | ||
| 130 | #define MSF_GIF_MALLOC(contextPointer, newSize) RL_MALLOC(newSize) | ||
| 131 | #define MSF_GIF_REALLOC(contextPointer, oldMemory, oldSize, newSize) RL_REALLOC(oldMemory, newSize) | ||
| 132 | #define MSF_GIF_FREE(contextPointer, oldMemory, oldSize) RL_FREE(oldMemory) | ||
| 133 | |||
| 134 | #define MSF_GIF_IMPL | ||
| 135 | #include "external/msf_gif.h" // GIF recording functionality | ||
| 136 | #endif | ||
| 137 | |||
| 138 | #if defined(SUPPORT_COMPRESSION_API) | ||
| 139 | #define SINFL_IMPLEMENTATION | ||
| 140 | #define SINFL_NO_SIMD | ||
| 141 | #include "external/sinfl.h" // Deflate (RFC 1951) decompressor | ||
| 142 | |||
| 143 | #define SDEFL_IMPLEMENTATION | ||
| 144 | #include "external/sdefl.h" // Deflate (RFC 1951) compressor | ||
| 145 | #endif | ||
| 146 | |||
| 147 | #if defined(SUPPORT_RPRAND_GENERATOR) | ||
| 148 | #define RPRAND_IMPLEMENTATION | ||
| 149 | #include "external/rprand.h" | ||
| 150 | #endif | ||
| 151 | |||
| 152 | #if defined(__linux__) && !defined(_GNU_SOURCE) | ||
| 153 | #define _GNU_SOURCE | ||
| 154 | #endif | ||
| 155 | |||
| 156 | // Platform specific defines to handle GetApplicationDirectory() | ||
| 157 | #if (defined(_WIN32) && !defined(PLATFORM_DESKTOP_RGFW)) || (defined(_MSC_VER) && defined(PLATFORM_DESKTOP_RGFW)) | ||
| 158 | #ifndef MAX_PATH | ||
| 159 | #define MAX_PATH 1025 | ||
| 160 | #endif | ||
| 161 | __declspec(dllimport) unsigned long __stdcall GetModuleFileNameA(void *hModule, void *lpFilename, unsigned long nSize); | ||
| 162 | __declspec(dllimport) unsigned long __stdcall GetModuleFileNameW(void *hModule, void *lpFilename, unsigned long nSize); | ||
| 163 | __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, void *widestr, int cchwide, void *str, int cbmb, void *defchar, int *used_default); | ||
| 164 | __declspec(dllimport) unsigned int __stdcall timeBeginPeriod(unsigned int uPeriod); | ||
| 165 | __declspec(dllimport) unsigned int __stdcall timeEndPeriod(unsigned int uPeriod); | ||
| 166 | #elif defined(__linux__) | ||
| 167 | #include <unistd.h> | ||
| 168 | #elif defined(__FreeBSD__) | ||
| 169 | #include <sys/types.h> | ||
| 170 | #include <sys/sysctl.h> | ||
| 171 | #include <unistd.h> | ||
| 172 | #elif defined(__APPLE__) | ||
| 173 | #include <sys/syslimits.h> | ||
| 174 | #include <mach-o/dyld.h> | ||
| 175 | #endif // OSs | ||
| 176 | |||
| 177 | #define _CRT_INTERNAL_NONSTDC_NAMES 1 | ||
| 178 | #include <sys/stat.h> // Required for: stat(), S_ISREG [Used in GetFileModTime(), IsFilePath()] | ||
| 179 | |||
| 180 | #if !defined(S_ISREG) && defined(S_IFMT) && defined(S_IFREG) | ||
| 181 | #define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) | ||
| 182 | #endif | ||
| 183 | |||
| 184 | #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) | ||
| 185 | #define DIRENT_MALLOC RL_MALLOC | ||
| 186 | #define DIRENT_FREE RL_FREE | ||
| 187 | |||
| 188 | #include "external/dirent.h" // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] | ||
| 189 | #else | ||
| 190 | #include <dirent.h> // Required for: DIR, opendir(), closedir() [Used in LoadDirectoryFiles()] | ||
| 191 | #endif | ||
| 192 | |||
| 193 | #if defined(_WIN32) | ||
| 194 | #include <io.h> // Required for: _access() [Used in FileExists()] | ||
| 195 | #include <direct.h> // Required for: _getch(), _chdir(), _mkdir() | ||
| 196 | #define GETCWD _getcwd // NOTE: MSDN recommends not to use getcwd(), chdir() | ||
| 197 | #define CHDIR _chdir | ||
| 198 | #define MKDIR(dir) _mkdir(dir) | ||
| 199 | #else | ||
| 200 | #include <unistd.h> // Required for: getch(), chdir(), mkdir(), access() | ||
| 201 | #define GETCWD getcwd | ||
| 202 | #define CHDIR chdir | ||
| 203 | #define MKDIR(dir) mkdir(dir, 0777) | ||
| 204 | #endif | ||
| 205 | |||
| 206 | //---------------------------------------------------------------------------------- | ||
| 207 | // Defines and Macros | ||
| 208 | //---------------------------------------------------------------------------------- | ||
| 209 | #ifndef MAX_FILEPATH_CAPACITY | ||
| 210 | #define MAX_FILEPATH_CAPACITY 8192 // Maximum capacity for filepath | ||
| 211 | #endif | ||
| 212 | #ifndef MAX_FILEPATH_LENGTH | ||
| 213 | #if defined(_WIN32) | ||
| 214 | #define MAX_FILEPATH_LENGTH 256 // On Win32, MAX_PATH = 260 (limits.h) but Windows 10, Version 1607 enables long paths... | ||
| 215 | #else | ||
| 216 | #define MAX_FILEPATH_LENGTH 4096 // On Linux, PATH_MAX = 4096 by default (limits.h) | ||
| 217 | #endif | ||
| 218 | #endif | ||
| 219 | |||
| 220 | #ifndef MAX_KEYBOARD_KEYS | ||
| 221 | #define MAX_KEYBOARD_KEYS 512 // Maximum number of keyboard keys supported | ||
| 222 | #endif | ||
| 223 | #ifndef MAX_MOUSE_BUTTONS | ||
| 224 | #define MAX_MOUSE_BUTTONS 8 // Maximum number of mouse buttons supported | ||
| 225 | #endif | ||
| 226 | #ifndef MAX_GAMEPADS | ||
| 227 | #define MAX_GAMEPADS 4 // Maximum number of gamepads supported | ||
| 228 | #endif | ||
| 229 | #ifndef MAX_GAMEPAD_AXIS | ||
| 230 | #define MAX_GAMEPAD_AXIS 8 // Maximum number of axis supported (per gamepad) | ||
| 231 | #endif | ||
| 232 | #ifndef MAX_GAMEPAD_BUTTONS | ||
| 233 | #define MAX_GAMEPAD_BUTTONS 32 // Maximum number of buttons supported (per gamepad) | ||
| 234 | #endif | ||
| 235 | #ifndef MAX_GAMEPAD_VIBRATION_TIME | ||
| 236 | #define MAX_GAMEPAD_VIBRATION_TIME 2.0f // Maximum vibration time in seconds | ||
| 237 | #endif | ||
| 238 | #ifndef MAX_TOUCH_POINTS | ||
| 239 | #define MAX_TOUCH_POINTS 8 // Maximum number of touch points supported | ||
| 240 | #endif | ||
| 241 | #ifndef MAX_KEY_PRESSED_QUEUE | ||
| 242 | #define MAX_KEY_PRESSED_QUEUE 16 // Maximum number of keys in the key input queue | ||
| 243 | #endif | ||
| 244 | #ifndef MAX_CHAR_PRESSED_QUEUE | ||
| 245 | #define MAX_CHAR_PRESSED_QUEUE 16 // Maximum number of characters in the char input queue | ||
| 246 | #endif | ||
| 247 | |||
| 248 | #ifndef MAX_DECOMPRESSION_SIZE | ||
| 249 | #define MAX_DECOMPRESSION_SIZE 64 // Maximum size allocated for decompression in MB | ||
| 250 | #endif | ||
| 251 | |||
| 252 | #ifndef MAX_AUTOMATION_EVENTS | ||
| 253 | #define MAX_AUTOMATION_EVENTS 16384 // Maximum number of automation events to record | ||
| 254 | #endif | ||
| 255 | |||
| 256 | #ifndef DIRECTORY_FILTER_TAG | ||
| 257 | #define DIRECTORY_FILTER_TAG "DIR" // Name tag used to request directory inclusion on directory scan | ||
| 258 | #endif // NOTE: Used in ScanDirectoryFiles(), ScanDirectoryFilesRecursively() and LoadDirectoryFilesEx() | ||
| 259 | |||
| 260 | // Flags operation macros | ||
| 261 | #define FLAG_SET(n, f) ((n) |= (f)) | ||
| 262 | #define FLAG_CLEAR(n, f) ((n) &= ~(f)) | ||
| 263 | #define FLAG_TOGGLE(n, f) ((n) ^= (f)) | ||
| 264 | #define FLAG_CHECK(n, f) ((n) & (f)) | ||
| 265 | |||
| 266 | //---------------------------------------------------------------------------------- | ||
| 267 | // Types and Structures Definition | ||
| 268 | //---------------------------------------------------------------------------------- | ||
| 269 | typedef struct { int x; int y; } Point; | ||
| 270 | typedef struct { unsigned int width; unsigned int height; } Size; | ||
| 271 | |||
| 272 | // Core global state context data | ||
| 273 | typedef struct CoreData { | ||
| 274 | struct { | ||
| 275 | const char *title; // Window text title const pointer | ||
| 276 | unsigned int flags; // Configuration flags (bit based), keeps window state | ||
| 277 | bool ready; // Check if window has been initialized successfully | ||
| 278 | bool fullscreen; // Check if fullscreen mode is enabled | ||
| 279 | bool shouldClose; // Check if window set for closing | ||
| 280 | bool resizedLastFrame; // Check if window has been resized last frame | ||
| 281 | bool eventWaiting; // Wait for events before ending frame | ||
| 282 | bool usingFbo; // Using FBO (RenderTexture) for rendering instead of default framebuffer | ||
| 283 | |||
| 284 | Point position; // Window position (required on fullscreen toggle) | ||
| 285 | Point previousPosition; // Window previous position (required on borderless windowed toggle) | ||
| 286 | Size display; // Display width and height (monitor, device-screen, LCD, ...) | ||
| 287 | Size screen; // Screen width and height (used render area) | ||
| 288 | Size previousScreen; // Screen previous width and height (required on borderless windowed toggle) | ||
| 289 | Size currentFbo; // Current render width and height (depends on active fbo) | ||
| 290 | Size render; // Framebuffer width and height (render area, including black bars if required) | ||
| 291 | Point renderOffset; // Offset from render area (must be divided by 2) | ||
| 292 | Size screenMin; // Screen minimum width and height (for resizable window) | ||
| 293 | Size screenMax; // Screen maximum width and height (for resizable window) | ||
| 294 | Matrix screenScale; // Matrix to scale screen (framebuffer rendering) | ||
| 295 | |||
| 296 | char **dropFilepaths; // Store dropped files paths pointers (provided by GLFW) | ||
| 297 | unsigned int dropFileCount; // Count dropped files strings | ||
| 298 | |||
| 299 | } Window; | ||
| 300 | struct { | ||
| 301 | const char *basePath; // Base path for data storage | ||
| 302 | |||
| 303 | } Storage; | ||
| 304 | struct { | ||
| 305 | struct { | ||
| 306 | int exitKey; // Default exit key | ||
| 307 | char currentKeyState[MAX_KEYBOARD_KEYS]; // Registers current frame key state | ||
| 308 | char previousKeyState[MAX_KEYBOARD_KEYS]; // Registers previous frame key state | ||
| 309 | |||
| 310 | // NOTE: Since key press logic involves comparing prev vs cur key state, we need to handle key repeats specially | ||
| 311 | char keyRepeatInFrame[MAX_KEYBOARD_KEYS]; // Registers key repeats for current frame | ||
| 312 | |||
| 313 | int keyPressedQueue[MAX_KEY_PRESSED_QUEUE]; // Input keys queue | ||
| 314 | int keyPressedQueueCount; // Input keys queue count | ||
| 315 | |||
| 316 | int charPressedQueue[MAX_CHAR_PRESSED_QUEUE]; // Input characters queue (unicode) | ||
| 317 | int charPressedQueueCount; // Input characters queue count | ||
| 318 | |||
| 319 | } Keyboard; | ||
| 320 | struct { | ||
| 321 | Vector2 offset; // Mouse offset | ||
| 322 | Vector2 scale; // Mouse scaling | ||
| 323 | Vector2 currentPosition; // Mouse position on screen | ||
| 324 | Vector2 previousPosition; // Previous mouse position | ||
| 325 | |||
| 326 | int cursor; // Tracks current mouse cursor | ||
| 327 | bool cursorHidden; // Track if cursor is hidden | ||
| 328 | bool cursorOnScreen; // Tracks if cursor is inside client area | ||
| 329 | |||
| 330 | char currentButtonState[MAX_MOUSE_BUTTONS]; // Registers current mouse button state | ||
| 331 | char previousButtonState[MAX_MOUSE_BUTTONS]; // Registers previous mouse button state | ||
| 332 | Vector2 currentWheelMove; // Registers current mouse wheel variation | ||
| 333 | Vector2 previousWheelMove; // Registers previous mouse wheel variation | ||
| 334 | |||
| 335 | } Mouse; | ||
| 336 | struct { | ||
| 337 | int pointCount; // Number of touch points active | ||
| 338 | int pointId[MAX_TOUCH_POINTS]; // Point identifiers | ||
| 339 | Vector2 position[MAX_TOUCH_POINTS]; // Touch position on screen | ||
| 340 | char currentTouchState[MAX_TOUCH_POINTS]; // Registers current touch state | ||
| 341 | char previousTouchState[MAX_TOUCH_POINTS]; // Registers previous touch state | ||
| 342 | |||
| 343 | } Touch; | ||
| 344 | struct { | ||
| 345 | int lastButtonPressed; // Register last gamepad button pressed | ||
| 346 | int axisCount[MAX_GAMEPADS]; // Register number of available gamepad axis | ||
| 347 | bool ready[MAX_GAMEPADS]; // Flag to know if gamepad is ready | ||
| 348 | char name[MAX_GAMEPADS][64]; // Gamepad name holder | ||
| 349 | char currentButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Current gamepad buttons state | ||
| 350 | char previousButtonState[MAX_GAMEPADS][MAX_GAMEPAD_BUTTONS]; // Previous gamepad buttons state | ||
| 351 | float axisState[MAX_GAMEPADS][MAX_GAMEPAD_AXIS]; // Gamepad axis state | ||
| 352 | |||
| 353 | } Gamepad; | ||
| 354 | } Input; | ||
| 355 | struct { | ||
| 356 | double current; // Current time measure | ||
| 357 | double previous; // Previous time measure | ||
| 358 | double update; // Time measure for frame update | ||
| 359 | double draw; // Time measure for frame draw | ||
| 360 | double frame; // Time measure for one frame | ||
| 361 | double target; // Desired time for one frame, if 0 not applied | ||
| 362 | unsigned long long int base; // Base time measure for hi-res timer (PLATFORM_ANDROID, PLATFORM_DRM) | ||
| 363 | unsigned int frameCounter; // Frame counter | ||
| 364 | |||
| 365 | } Time; | ||
| 366 | } CoreData; | ||
| 367 | |||
| 368 | //---------------------------------------------------------------------------------- | ||
| 369 | // Global Variables Definition | ||
| 370 | //---------------------------------------------------------------------------------- | ||
| 371 | RLAPI const char *raylib_version = RAYLIB_VERSION; // raylib version exported symbol, required for some bindings | ||
| 372 | |||
| 373 | CoreData CORE = { 0 }; // Global CORE state context | ||
| 374 | |||
| 375 | // Flag to note GPU acceleration is available, | ||
| 376 | // referenced from other modules to support GPU data loading | ||
| 377 | // NOTE: Useful to allow Texture, RenderTexture, Font.texture, Mesh.vaoId/vboId, Shader loading | ||
| 378 | bool isGpuReady = false; | ||
| 379 | |||
| 380 | #if defined(SUPPORT_SCREEN_CAPTURE) | ||
| 381 | static int screenshotCounter = 0; // Screenshots counter | ||
| 382 | #endif | ||
| 383 | |||
| 384 | #if defined(SUPPORT_GIF_RECORDING) | ||
| 385 | static unsigned int gifFrameCounter = 0; // GIF frames counter | ||
| 386 | static bool gifRecording = false; // GIF recording state | ||
| 387 | static MsfGifState gifState = { 0 }; // MSGIF context state | ||
| 388 | #endif | ||
| 389 | |||
| 390 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 391 | // Automation events type | ||
| 392 | typedef enum AutomationEventType { | ||
| 393 | EVENT_NONE = 0, | ||
| 394 | // Input events | ||
| 395 | INPUT_KEY_UP, // param[0]: key | ||
| 396 | INPUT_KEY_DOWN, // param[0]: key | ||
| 397 | INPUT_KEY_PRESSED, // param[0]: key | ||
| 398 | INPUT_KEY_RELEASED, // param[0]: key | ||
| 399 | INPUT_MOUSE_BUTTON_UP, // param[0]: button | ||
| 400 | INPUT_MOUSE_BUTTON_DOWN, // param[0]: button | ||
| 401 | INPUT_MOUSE_POSITION, // param[0]: x, param[1]: y | ||
| 402 | INPUT_MOUSE_WHEEL_MOTION, // param[0]: x delta, param[1]: y delta | ||
| 403 | INPUT_GAMEPAD_CONNECT, // param[0]: gamepad | ||
| 404 | INPUT_GAMEPAD_DISCONNECT, // param[0]: gamepad | ||
| 405 | INPUT_GAMEPAD_BUTTON_UP, // param[0]: button | ||
| 406 | INPUT_GAMEPAD_BUTTON_DOWN, // param[0]: button | ||
| 407 | INPUT_GAMEPAD_AXIS_MOTION, // param[0]: axis, param[1]: delta | ||
| 408 | INPUT_TOUCH_UP, // param[0]: id | ||
| 409 | INPUT_TOUCH_DOWN, // param[0]: id | ||
| 410 | INPUT_TOUCH_POSITION, // param[0]: x, param[1]: y | ||
| 411 | INPUT_GESTURE, // param[0]: gesture | ||
| 412 | // Window events | ||
| 413 | WINDOW_CLOSE, // no params | ||
| 414 | WINDOW_MAXIMIZE, // no params | ||
| 415 | WINDOW_MINIMIZE, // no params | ||
| 416 | WINDOW_RESIZE, // param[0]: width, param[1]: height | ||
| 417 | // Custom events | ||
| 418 | ACTION_TAKE_SCREENSHOT, // no params | ||
| 419 | ACTION_SETTARGETFPS // param[0]: fps | ||
| 420 | } AutomationEventType; | ||
| 421 | |||
| 422 | // Event type to config events flags | ||
| 423 | // TODO: Not used at the moment | ||
| 424 | typedef enum { | ||
| 425 | EVENT_INPUT_KEYBOARD = 0, | ||
| 426 | EVENT_INPUT_MOUSE = 1, | ||
| 427 | EVENT_INPUT_GAMEPAD = 2, | ||
| 428 | EVENT_INPUT_TOUCH = 4, | ||
| 429 | EVENT_INPUT_GESTURE = 8, | ||
| 430 | EVENT_WINDOW = 16, | ||
| 431 | EVENT_CUSTOM = 32 | ||
| 432 | } EventType; | ||
| 433 | |||
| 434 | // Event type name strings, required for export | ||
| 435 | static const char *autoEventTypeName[] = { | ||
| 436 | "EVENT_NONE", | ||
| 437 | "INPUT_KEY_UP", | ||
| 438 | "INPUT_KEY_DOWN", | ||
| 439 | "INPUT_KEY_PRESSED", | ||
| 440 | "INPUT_KEY_RELEASED", | ||
| 441 | "INPUT_MOUSE_BUTTON_UP", | ||
| 442 | "INPUT_MOUSE_BUTTON_DOWN", | ||
| 443 | "INPUT_MOUSE_POSITION", | ||
| 444 | "INPUT_MOUSE_WHEEL_MOTION", | ||
| 445 | "INPUT_GAMEPAD_CONNECT", | ||
| 446 | "INPUT_GAMEPAD_DISCONNECT", | ||
| 447 | "INPUT_GAMEPAD_BUTTON_UP", | ||
| 448 | "INPUT_GAMEPAD_BUTTON_DOWN", | ||
| 449 | "INPUT_GAMEPAD_AXIS_MOTION", | ||
| 450 | "INPUT_TOUCH_UP", | ||
| 451 | "INPUT_TOUCH_DOWN", | ||
| 452 | "INPUT_TOUCH_POSITION", | ||
| 453 | "INPUT_GESTURE", | ||
| 454 | "WINDOW_CLOSE", | ||
| 455 | "WINDOW_MAXIMIZE", | ||
| 456 | "WINDOW_MINIMIZE", | ||
| 457 | "WINDOW_RESIZE", | ||
| 458 | "ACTION_TAKE_SCREENSHOT", | ||
| 459 | "ACTION_SETTARGETFPS" | ||
| 460 | }; | ||
| 461 | |||
| 462 | /* | ||
| 463 | // Automation event (24 bytes) | ||
| 464 | // NOTE: Opaque struct, internal to raylib | ||
| 465 | struct AutomationEvent { | ||
| 466 | unsigned int frame; // Event frame | ||
| 467 | unsigned int type; // Event type (AutomationEventType) | ||
| 468 | int params[4]; // Event parameters (if required) | ||
| 469 | }; | ||
| 470 | */ | ||
| 471 | |||
| 472 | static AutomationEventList *currentEventList = NULL; // Current automation events list, set by user, keep internal pointer | ||
| 473 | static bool automationEventRecording = false; // Recording automation events flag | ||
| 474 | //static short automationEventEnabled = 0b0000001111111111; // TODO: Automation events enabled for recording/playing | ||
| 475 | #endif | ||
| 476 | //----------------------------------------------------------------------------------- | ||
| 477 | |||
| 478 | //---------------------------------------------------------------------------------- | ||
| 479 | // Module Functions Declaration | ||
| 480 | // NOTE: Those functions are common for all platforms! | ||
| 481 | //---------------------------------------------------------------------------------- | ||
| 482 | |||
| 483 | #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) | ||
| 484 | extern void LoadFontDefault(void); // [Module: text] Loads default font on InitWindow() | ||
| 485 | extern void UnloadFontDefault(void); // [Module: text] Unloads default font from GPU memory | ||
| 486 | #endif | ||
| 487 | |||
| 488 | extern int InitPlatform(void); // Initialize platform (graphics, inputs and more) | ||
| 489 | extern void ClosePlatform(void); // Close platform | ||
| 490 | |||
| 491 | static void InitTimer(void); // Initialize timer, hi-resolution if available (required by InitPlatform()) | ||
| 492 | static void SetupFramebuffer(int width, int height); // Setup main framebuffer (required by InitPlatform()) | ||
| 493 | static void SetupViewport(int width, int height); // Set viewport for a provided width and height | ||
| 494 | |||
| 495 | static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories in a base path | ||
| 496 | static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter); // Scan all files and directories recursively from a base path | ||
| 497 | |||
| 498 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 499 | static void RecordAutomationEvent(void); // Record frame events (to internal events array) | ||
| 500 | #endif | ||
| 501 | |||
| 502 | #if defined(_WIN32) && !defined(PLATFORM_DESKTOP_RGFW) | ||
| 503 | // NOTE: We declare Sleep() function symbol to avoid including windows.h (kernel32.lib linkage required) | ||
| 504 | void __stdcall Sleep(unsigned long msTimeout); // Required for: WaitTime() | ||
| 505 | #endif | ||
| 506 | |||
| 507 | #if !defined(SUPPORT_MODULE_RTEXT) | ||
| 508 | const char *TextFormat(const char *text, ...); // Formatting of text with variables to 'embed' | ||
| 509 | #endif // !SUPPORT_MODULE_RTEXT | ||
| 510 | |||
| 511 | #if defined(PLATFORM_DESKTOP) | ||
| 512 | #define PLATFORM_DESKTOP_GLFW | ||
| 513 | #endif | ||
| 514 | |||
| 515 | // We're using `#pragma message` because `#warning` is not adopted by MSVC. | ||
| 516 | #if defined(SUPPORT_CLIPBOARD_IMAGE) | ||
| 517 | #if !defined(SUPPORT_MODULE_RTEXTURES) | ||
| 518 | #pragma message ("Warning: Enabling SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_MODULE_RTEXTURES to work properly") | ||
| 519 | #endif | ||
| 520 | |||
| 521 | // It's nice to have support Bitmap on Linux as well, but not as necessary as Windows | ||
| 522 | #if !defined(SUPPORT_FILEFORMAT_BMP) && defined(_WIN32) | ||
| 523 | #pragma message ("Warning: Enabling SUPPORT_CLIPBOARD_IMAGE requires SUPPORT_FILEFORMAT_BMP, specially on Windows") | ||
| 524 | #endif | ||
| 525 | |||
| 526 | // From what I've tested applications on Wayland saves images on clipboard as PNG. | ||
| 527 | #if (!defined(SUPPORT_FILEFORMAT_PNG) || !defined(SUPPORT_FILEFORMAT_JPG)) && !defined(_WIN32) | ||
| 528 | #pragma message ("Warning: Getting image from the clipboard might not work without SUPPORT_FILEFORMAT_PNG or SUPPORT_FILEFORMAT_JPG") | ||
| 529 | #endif | ||
| 530 | |||
| 531 | // Not needed because `rtexture.c` will automatically defined STBI_REQUIRED when any SUPPORT_FILEFORMAT_* is defined. | ||
| 532 | // #if !defined(STBI_REQUIRED) | ||
| 533 | // #pragma message ("Warning: "STBI_REQUIRED is not defined, that means we can't load images from clipbard" | ||
| 534 | // #endif | ||
| 535 | |||
| 536 | #endif // SUPPORT_CLIPBOARD_IMAGE | ||
| 537 | |||
| 538 | // Include platform-specific submodules | ||
| 539 | #if defined(PLATFORM_DESKTOP_GLFW) | ||
| 540 | #include "platforms/rcore_desktop_glfw.c" | ||
| 541 | #elif defined(PLATFORM_DESKTOP_SDL) | ||
| 542 | #include "platforms/rcore_desktop_sdl.c" | ||
| 543 | #elif defined(PLATFORM_DESKTOP_RGFW) | ||
| 544 | #include "platforms/rcore_desktop_rgfw.c" | ||
| 545 | #elif defined(PLATFORM_WEB) | ||
| 546 | #include "platforms/rcore_web.c" | ||
| 547 | #elif defined(PLATFORM_DRM) | ||
| 548 | #include "platforms/rcore_drm.c" | ||
| 549 | #elif defined(PLATFORM_ANDROID) | ||
| 550 | #include "platforms/rcore_android.c" | ||
| 551 | #else | ||
| 552 | // TODO: Include your custom platform backend! | ||
| 553 | // i.e software rendering backend or console backend! | ||
| 554 | #endif | ||
| 555 | |||
| 556 | //---------------------------------------------------------------------------------- | ||
| 557 | // Module Functions Definition: Window and Graphics Device | ||
| 558 | //---------------------------------------------------------------------------------- | ||
| 559 | |||
| 560 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 561 | //bool WindowShouldClose(void) | ||
| 562 | //void ToggleFullscreen(void) | ||
| 563 | //void ToggleBorderlessWindowed(void) | ||
| 564 | //void MaximizeWindow(void) | ||
| 565 | //void MinimizeWindow(void) | ||
| 566 | //void RestoreWindow(void) | ||
| 567 | |||
| 568 | //void SetWindowState(unsigned int flags) | ||
| 569 | //void ClearWindowState(unsigned int flags) | ||
| 570 | |||
| 571 | //void SetWindowIcon(Image image) | ||
| 572 | //void SetWindowIcons(Image *images, int count) | ||
| 573 | //void SetWindowTitle(const char *title) | ||
| 574 | //void SetWindowPosition(int x, int y) | ||
| 575 | //void SetWindowMonitor(int monitor) | ||
| 576 | //void SetWindowMinSize(int width, int height) | ||
| 577 | //void SetWindowMaxSize(int width, int height) | ||
| 578 | //void SetWindowSize(int width, int height) | ||
| 579 | //void SetWindowOpacity(float opacity) | ||
| 580 | //void SetWindowFocused(void) | ||
| 581 | //void *GetWindowHandle(void) | ||
| 582 | //Vector2 GetWindowPosition(void) | ||
| 583 | //Vector2 GetWindowScaleDPI(void) | ||
| 584 | |||
| 585 | //int GetMonitorCount(void) | ||
| 586 | //int GetCurrentMonitor(void) | ||
| 587 | //int GetMonitorWidth(int monitor) | ||
| 588 | //int GetMonitorHeight(int monitor) | ||
| 589 | //int GetMonitorPhysicalWidth(int monitor) | ||
| 590 | //int GetMonitorPhysicalHeight(int monitor) | ||
| 591 | //int GetMonitorRefreshRate(int monitor) | ||
| 592 | //Vector2 GetMonitorPosition(int monitor) | ||
| 593 | //const char *GetMonitorName(int monitor) | ||
| 594 | |||
| 595 | //void SetClipboardText(const char *text) | ||
| 596 | //const char *GetClipboardText(void) | ||
| 597 | |||
| 598 | //void ShowCursor(void) | ||
| 599 | //void HideCursor(void) | ||
| 600 | //void EnableCursor(void) | ||
| 601 | //void DisableCursor(void) | ||
| 602 | |||
| 603 | // Initialize window and OpenGL context | ||
| 604 | void InitWindow(int width, int height, const char *title) | ||
| 605 | { | ||
| 606 | TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); | ||
| 607 | |||
| 608 | #if defined(PLATFORM_DESKTOP_GLFW) | ||
| 609 | TRACELOG(LOG_INFO, "Platform backend: DESKTOP (GLFW)"); | ||
| 610 | #elif defined(PLATFORM_DESKTOP_SDL) | ||
| 611 | TRACELOG(LOG_INFO, "Platform backend: DESKTOP (SDL)"); | ||
| 612 | #elif defined(PLATFORM_DESKTOP_RGFW) | ||
| 613 | TRACELOG(LOG_INFO, "Platform backend: DESKTOP (RGFW)"); | ||
| 614 | #elif defined(PLATFORM_WEB) | ||
| 615 | TRACELOG(LOG_INFO, "Platform backend: WEB (HTML5)"); | ||
| 616 | #elif defined(PLATFORM_DRM) | ||
| 617 | TRACELOG(LOG_INFO, "Platform backend: NATIVE DRM"); | ||
| 618 | #elif defined(PLATFORM_ANDROID) | ||
| 619 | TRACELOG(LOG_INFO, "Platform backend: ANDROID"); | ||
| 620 | #else | ||
| 621 | // TODO: Include your custom platform backend! | ||
| 622 | // i.e software rendering backend or console backend! | ||
| 623 | TRACELOG(LOG_INFO, "Platform backend: CUSTOM"); | ||
| 624 | #endif | ||
| 625 | |||
| 626 | TRACELOG(LOG_INFO, "Supported raylib modules:"); | ||
| 627 | TRACELOG(LOG_INFO, " > rcore:..... loaded (mandatory)"); | ||
| 628 | TRACELOG(LOG_INFO, " > rlgl:...... loaded (mandatory)"); | ||
| 629 | #if defined(SUPPORT_MODULE_RSHAPES) | ||
| 630 | TRACELOG(LOG_INFO, " > rshapes:... loaded (optional)"); | ||
| 631 | #else | ||
| 632 | TRACELOG(LOG_INFO, " > rshapes:... not loaded (optional)"); | ||
| 633 | #endif | ||
| 634 | #if defined(SUPPORT_MODULE_RTEXTURES) | ||
| 635 | TRACELOG(LOG_INFO, " > rtextures:. loaded (optional)"); | ||
| 636 | #else | ||
| 637 | TRACELOG(LOG_INFO, " > rtextures:. not loaded (optional)"); | ||
| 638 | #endif | ||
| 639 | #if defined(SUPPORT_MODULE_RTEXT) | ||
| 640 | TRACELOG(LOG_INFO, " > rtext:..... loaded (optional)"); | ||
| 641 | #else | ||
| 642 | TRACELOG(LOG_INFO, " > rtext:..... not loaded (optional)"); | ||
| 643 | #endif | ||
| 644 | #if defined(SUPPORT_MODULE_RMODELS) | ||
| 645 | TRACELOG(LOG_INFO, " > rmodels:... loaded (optional)"); | ||
| 646 | #else | ||
| 647 | TRACELOG(LOG_INFO, " > rmodels:... not loaded (optional)"); | ||
| 648 | #endif | ||
| 649 | #if defined(SUPPORT_MODULE_RAUDIO) | ||
| 650 | TRACELOG(LOG_INFO, " > raudio:.... loaded (optional)"); | ||
| 651 | #else | ||
| 652 | TRACELOG(LOG_INFO, " > raudio:.... not loaded (optional)"); | ||
| 653 | #endif | ||
| 654 | |||
| 655 | // Initialize window data | ||
| 656 | CORE.Window.screen.width = width; | ||
| 657 | CORE.Window.screen.height = height; | ||
| 658 | CORE.Window.eventWaiting = false; | ||
| 659 | CORE.Window.screenScale = MatrixIdentity(); // No draw scaling required by default | ||
| 660 | if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; | ||
| 661 | |||
| 662 | // Initialize global input state | ||
| 663 | memset(&CORE.Input, 0, sizeof(CORE.Input)); // Reset CORE.Input structure to 0 | ||
| 664 | CORE.Input.Keyboard.exitKey = KEY_ESCAPE; | ||
| 665 | CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f }; | ||
| 666 | CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW; | ||
| 667 | CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_UNKNOWN; | ||
| 668 | |||
| 669 | // Initialize platform | ||
| 670 | //-------------------------------------------------------------- | ||
| 671 | InitPlatform(); | ||
| 672 | //-------------------------------------------------------------- | ||
| 673 | |||
| 674 | // Initialize rlgl default data (buffers and shaders) | ||
| 675 | // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl | ||
| 676 | rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); | ||
| 677 | isGpuReady = true; // Flag to note GPU has been initialized successfully | ||
| 678 | |||
| 679 | // Setup default viewport | ||
| 680 | SetupViewport(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height); | ||
| 681 | |||
| 682 | #if defined(SUPPORT_MODULE_RTEXT) | ||
| 683 | #if defined(SUPPORT_DEFAULT_FONT) | ||
| 684 | // Load default font | ||
| 685 | // WARNING: External function: Module required: rtext | ||
| 686 | LoadFontDefault(); | ||
| 687 | #if defined(SUPPORT_MODULE_RSHAPES) | ||
| 688 | // Set font white rectangle for shapes drawing, so shapes and text can be batched together | ||
| 689 | // WARNING: rshapes module is required, if not available, default internal white rectangle is used | ||
| 690 | Rectangle rec = GetFontDefault().recs[95]; | ||
| 691 | if (CORE.Window.flags & FLAG_MSAA_4X_HINT) | ||
| 692 | { | ||
| 693 | // NOTE: We try to maxime rec padding to avoid pixel bleeding on MSAA filtering | ||
| 694 | SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 2, rec.y + 2, 1, 1 }); | ||
| 695 | } | ||
| 696 | else | ||
| 697 | { | ||
| 698 | // NOTE: We set up a 1px padding on char rectangle to avoid pixel bleeding | ||
| 699 | SetShapesTexture(GetFontDefault().texture, (Rectangle){ rec.x + 1, rec.y + 1, rec.width - 2, rec.height - 2 }); | ||
| 700 | } | ||
| 701 | #endif | ||
| 702 | #endif | ||
| 703 | #else | ||
| 704 | #if defined(SUPPORT_MODULE_RSHAPES) | ||
| 705 | // Set default texture and rectangle to be used for shapes drawing | ||
| 706 | // NOTE: rlgl default texture is a 1x1 pixel UNCOMPRESSED_R8G8B8A8 | ||
| 707 | Texture2D texture = { rlGetTextureIdDefault(), 1, 1, 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; | ||
| 708 | SetShapesTexture(texture, (Rectangle){ 0.0f, 0.0f, 1.0f, 1.0f }); // WARNING: Module required: rshapes | ||
| 709 | #endif | ||
| 710 | #endif | ||
| 711 | |||
| 712 | CORE.Time.frameCounter = 0; | ||
| 713 | CORE.Window.shouldClose = false; | ||
| 714 | |||
| 715 | // Initialize random seed | ||
| 716 | SetRandomSeed((unsigned int)time(NULL)); | ||
| 717 | |||
| 718 | TRACELOG(LOG_INFO, "SYSTEM: Working Directory: %s", GetWorkingDirectory()); | ||
| 719 | } | ||
| 720 | |||
| 721 | // Close window and unload OpenGL context | ||
| 722 | void CloseWindow(void) | ||
| 723 | { | ||
| 724 | #if defined(SUPPORT_GIF_RECORDING) | ||
| 725 | if (gifRecording) | ||
| 726 | { | ||
| 727 | MsfGifResult result = msf_gif_end(&gifState); | ||
| 728 | msf_gif_free(result); | ||
| 729 | gifRecording = false; | ||
| 730 | } | ||
| 731 | #endif | ||
| 732 | |||
| 733 | #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_DEFAULT_FONT) | ||
| 734 | UnloadFontDefault(); // WARNING: Module required: rtext | ||
| 735 | #endif | ||
| 736 | |||
| 737 | rlglClose(); // De-init rlgl | ||
| 738 | |||
| 739 | // De-initialize platform | ||
| 740 | //-------------------------------------------------------------- | ||
| 741 | ClosePlatform(); | ||
| 742 | //-------------------------------------------------------------- | ||
| 743 | |||
| 744 | CORE.Window.ready = false; | ||
| 745 | TRACELOG(LOG_INFO, "Window closed successfully"); | ||
| 746 | } | ||
| 747 | |||
| 748 | // Check if window has been initialized successfully | ||
| 749 | bool IsWindowReady(void) | ||
| 750 | { | ||
| 751 | return CORE.Window.ready; | ||
| 752 | } | ||
| 753 | |||
| 754 | // Check if window is currently fullscreen | ||
| 755 | bool IsWindowFullscreen(void) | ||
| 756 | { | ||
| 757 | return CORE.Window.fullscreen; | ||
| 758 | } | ||
| 759 | |||
| 760 | // Check if window is currently hidden | ||
| 761 | bool IsWindowHidden(void) | ||
| 762 | { | ||
| 763 | return ((CORE.Window.flags & FLAG_WINDOW_HIDDEN) > 0); | ||
| 764 | } | ||
| 765 | |||
| 766 | // Check if window has been minimized | ||
| 767 | bool IsWindowMinimized(void) | ||
| 768 | { | ||
| 769 | return ((CORE.Window.flags & FLAG_WINDOW_MINIMIZED) > 0); | ||
| 770 | } | ||
| 771 | |||
| 772 | // Check if window has been maximized | ||
| 773 | bool IsWindowMaximized(void) | ||
| 774 | { | ||
| 775 | return ((CORE.Window.flags & FLAG_WINDOW_MAXIMIZED) > 0); | ||
| 776 | } | ||
| 777 | |||
| 778 | // Check if window has the focus | ||
| 779 | bool IsWindowFocused(void) | ||
| 780 | { | ||
| 781 | return ((CORE.Window.flags & FLAG_WINDOW_UNFOCUSED) == 0); | ||
| 782 | } | ||
| 783 | |||
| 784 | // Check if window has been resizedLastFrame | ||
| 785 | bool IsWindowResized(void) | ||
| 786 | { | ||
| 787 | return CORE.Window.resizedLastFrame; | ||
| 788 | } | ||
| 789 | |||
| 790 | // Check if one specific window flag is enabled | ||
| 791 | bool IsWindowState(unsigned int flag) | ||
| 792 | { | ||
| 793 | return ((CORE.Window.flags & flag) > 0); | ||
| 794 | } | ||
| 795 | |||
| 796 | // Get current screen width | ||
| 797 | int GetScreenWidth(void) | ||
| 798 | { | ||
| 799 | return CORE.Window.screen.width; | ||
| 800 | } | ||
| 801 | |||
| 802 | // Get current screen height | ||
| 803 | int GetScreenHeight(void) | ||
| 804 | { | ||
| 805 | return CORE.Window.screen.height; | ||
| 806 | } | ||
| 807 | |||
| 808 | // Get current render width which is equal to screen width*dpi scale | ||
| 809 | int GetRenderWidth(void) | ||
| 810 | { | ||
| 811 | int width = 0; | ||
| 812 | #if defined(__APPLE__) | ||
| 813 | Vector2 scale = GetWindowScaleDPI(); | ||
| 814 | width = (int)((float)CORE.Window.render.width*scale.x); | ||
| 815 | #else | ||
| 816 | width = CORE.Window.render.width; | ||
| 817 | #endif | ||
| 818 | return width; | ||
| 819 | } | ||
| 820 | |||
| 821 | // Get current screen height which is equal to screen height*dpi scale | ||
| 822 | int GetRenderHeight(void) | ||
| 823 | { | ||
| 824 | int height = 0; | ||
| 825 | #if defined(__APPLE__) | ||
| 826 | Vector2 scale = GetWindowScaleDPI(); | ||
| 827 | height = (int)((float)CORE.Window.render.height*scale.y); | ||
| 828 | #else | ||
| 829 | height = CORE.Window.render.height; | ||
| 830 | #endif | ||
| 831 | return height; | ||
| 832 | } | ||
| 833 | |||
| 834 | // Enable waiting for events on EndDrawing(), no automatic event polling | ||
| 835 | void EnableEventWaiting(void) | ||
| 836 | { | ||
| 837 | CORE.Window.eventWaiting = true; | ||
| 838 | } | ||
| 839 | |||
| 840 | // Disable waiting for events on EndDrawing(), automatic events polling | ||
| 841 | void DisableEventWaiting(void) | ||
| 842 | { | ||
| 843 | CORE.Window.eventWaiting = false; | ||
| 844 | } | ||
| 845 | |||
| 846 | // Check if cursor is not visible | ||
| 847 | bool IsCursorHidden(void) | ||
| 848 | { | ||
| 849 | return CORE.Input.Mouse.cursorHidden; | ||
| 850 | } | ||
| 851 | |||
| 852 | // Check if cursor is on the current screen | ||
| 853 | bool IsCursorOnScreen(void) | ||
| 854 | { | ||
| 855 | return CORE.Input.Mouse.cursorOnScreen; | ||
| 856 | } | ||
| 857 | |||
| 858 | //---------------------------------------------------------------------------------- | ||
| 859 | // Module Functions Definition: Screen Drawing | ||
| 860 | //---------------------------------------------------------------------------------- | ||
| 861 | |||
| 862 | // Set background color (framebuffer clear color) | ||
| 863 | void ClearBackground(Color color) | ||
| 864 | { | ||
| 865 | rlClearColor(color.r, color.g, color.b, color.a); // Set clear color | ||
| 866 | rlClearScreenBuffers(); // Clear current framebuffers | ||
| 867 | } | ||
| 868 | |||
| 869 | // Setup canvas (framebuffer) to start drawing | ||
| 870 | void BeginDrawing(void) | ||
| 871 | { | ||
| 872 | // WARNING: Previously to BeginDrawing() other render textures drawing could happen, | ||
| 873 | // consequently the measure for update vs draw is not accurate (only the total frame time is accurate) | ||
| 874 | |||
| 875 | CORE.Time.current = GetTime(); // Number of elapsed seconds since InitTimer() | ||
| 876 | CORE.Time.update = CORE.Time.current - CORE.Time.previous; | ||
| 877 | CORE.Time.previous = CORE.Time.current; | ||
| 878 | |||
| 879 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 880 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling | ||
| 881 | |||
| 882 | //rlTranslatef(0.375, 0.375, 0); // HACK to have 2D pixel-perfect drawing on OpenGL 1.1 | ||
| 883 | // NOTE: Not required with OpenGL 3.3+ | ||
| 884 | } | ||
| 885 | |||
| 886 | // End canvas drawing and swap buffers (double buffering) | ||
| 887 | void EndDrawing(void) | ||
| 888 | { | ||
| 889 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 890 | |||
| 891 | #if defined(SUPPORT_GIF_RECORDING) | ||
| 892 | // Draw record indicator | ||
| 893 | if (gifRecording) | ||
| 894 | { | ||
| 895 | #ifndef GIF_RECORD_FRAMERATE | ||
| 896 | #define GIF_RECORD_FRAMERATE 10 | ||
| 897 | #endif | ||
| 898 | gifFrameCounter += (unsigned int)(GetFrameTime()*1000); | ||
| 899 | |||
| 900 | // NOTE: We record one gif frame depending on the desired gif framerate | ||
| 901 | if (gifFrameCounter > 1000/GIF_RECORD_FRAMERATE) | ||
| 902 | { | ||
| 903 | // Get image data for the current frame (from backbuffer) | ||
| 904 | // NOTE: This process is quite slow... :( | ||
| 905 | Vector2 scale = GetWindowScaleDPI(); | ||
| 906 | unsigned char *screenData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); | ||
| 907 | |||
| 908 | #ifndef GIF_RECORD_BITRATE | ||
| 909 | #define GIF_RECORD_BITRATE 16 | ||
| 910 | #endif | ||
| 911 | |||
| 912 | // Add the frame to the gif recording, given how many frames have passed in centiseconds | ||
| 913 | msf_gif_frame(&gifState, screenData, gifFrameCounter/10, GIF_RECORD_BITRATE, (int)((float)CORE.Window.render.width*scale.x)*4); | ||
| 914 | gifFrameCounter -= 1000/GIF_RECORD_FRAMERATE; | ||
| 915 | |||
| 916 | RL_FREE(screenData); // Free image data | ||
| 917 | } | ||
| 918 | |||
| 919 | #if defined(SUPPORT_MODULE_RSHAPES) && defined(SUPPORT_MODULE_RTEXT) | ||
| 920 | // Display the recording indicator every half-second | ||
| 921 | if ((int)(GetTime()/0.5)%2 == 1) | ||
| 922 | { | ||
| 923 | DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON); // WARNING: Module required: rshapes | ||
| 924 | DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED); // WARNING: Module required: rtext | ||
| 925 | } | ||
| 926 | #endif | ||
| 927 | |||
| 928 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 929 | } | ||
| 930 | #endif | ||
| 931 | |||
| 932 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 933 | if (automationEventRecording) RecordAutomationEvent(); // Event recording | ||
| 934 | #endif | ||
| 935 | |||
| 936 | #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) | ||
| 937 | SwapScreenBuffer(); // Copy back buffer to front buffer (screen) | ||
| 938 | |||
| 939 | // Frame time control system | ||
| 940 | CORE.Time.current = GetTime(); | ||
| 941 | CORE.Time.draw = CORE.Time.current - CORE.Time.previous; | ||
| 942 | CORE.Time.previous = CORE.Time.current; | ||
| 943 | |||
| 944 | CORE.Time.frame = CORE.Time.update + CORE.Time.draw; | ||
| 945 | |||
| 946 | // Wait for some milliseconds... | ||
| 947 | if (CORE.Time.frame < CORE.Time.target) | ||
| 948 | { | ||
| 949 | WaitTime(CORE.Time.target - CORE.Time.frame); | ||
| 950 | |||
| 951 | CORE.Time.current = GetTime(); | ||
| 952 | double waitTime = CORE.Time.current - CORE.Time.previous; | ||
| 953 | CORE.Time.previous = CORE.Time.current; | ||
| 954 | |||
| 955 | CORE.Time.frame += waitTime; // Total frame time: update + draw + wait | ||
| 956 | } | ||
| 957 | |||
| 958 | PollInputEvents(); // Poll user events (before next frame update) | ||
| 959 | #endif | ||
| 960 | |||
| 961 | #if defined(SUPPORT_SCREEN_CAPTURE) | ||
| 962 | if (IsKeyPressed(KEY_F12)) | ||
| 963 | { | ||
| 964 | #if defined(SUPPORT_GIF_RECORDING) | ||
| 965 | if (IsKeyDown(KEY_LEFT_CONTROL)) | ||
| 966 | { | ||
| 967 | if (gifRecording) | ||
| 968 | { | ||
| 969 | gifRecording = false; | ||
| 970 | |||
| 971 | MsfGifResult result = msf_gif_end(&gifState); | ||
| 972 | |||
| 973 | SaveFileData(TextFormat("%s/screenrec%03i.gif", CORE.Storage.basePath, screenshotCounter), result.data, (unsigned int)result.dataSize); | ||
| 974 | msf_gif_free(result); | ||
| 975 | |||
| 976 | TRACELOG(LOG_INFO, "SYSTEM: Finish animated GIF recording"); | ||
| 977 | } | ||
| 978 | else | ||
| 979 | { | ||
| 980 | gifRecording = true; | ||
| 981 | gifFrameCounter = 0; | ||
| 982 | |||
| 983 | Vector2 scale = GetWindowScaleDPI(); | ||
| 984 | msf_gif_begin(&gifState, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); | ||
| 985 | screenshotCounter++; | ||
| 986 | |||
| 987 | TRACELOG(LOG_INFO, "SYSTEM: Start animated GIF recording: %s", TextFormat("screenrec%03i.gif", screenshotCounter)); | ||
| 988 | } | ||
| 989 | } | ||
| 990 | else | ||
| 991 | #endif // SUPPORT_GIF_RECORDING | ||
| 992 | { | ||
| 993 | TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); | ||
| 994 | screenshotCounter++; | ||
| 995 | } | ||
| 996 | } | ||
| 997 | #endif // SUPPORT_SCREEN_CAPTURE | ||
| 998 | |||
| 999 | CORE.Time.frameCounter++; | ||
| 1000 | } | ||
| 1001 | |||
| 1002 | // Initialize 2D mode with custom camera (2D) | ||
| 1003 | void BeginMode2D(Camera2D camera) | ||
| 1004 | { | ||
| 1005 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1006 | |||
| 1007 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1008 | |||
| 1009 | // Apply 2d camera transformation to modelview | ||
| 1010 | rlMultMatrixf(MatrixToFloat(GetCameraMatrix2D(camera))); | ||
| 1011 | } | ||
| 1012 | |||
| 1013 | // Ends 2D mode with custom camera | ||
| 1014 | void EndMode2D(void) | ||
| 1015 | { | ||
| 1016 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1017 | |||
| 1018 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1019 | |||
| 1020 | if (rlGetActiveFramebuffer() == 0) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required | ||
| 1021 | } | ||
| 1022 | |||
| 1023 | // Initializes 3D mode with custom camera (3D) | ||
| 1024 | void BeginMode3D(Camera camera) | ||
| 1025 | { | ||
| 1026 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1027 | |||
| 1028 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix | ||
| 1029 | rlPushMatrix(); // Save previous matrix, which contains the settings for the 2d ortho projection | ||
| 1030 | rlLoadIdentity(); // Reset current matrix (projection) | ||
| 1031 | |||
| 1032 | float aspect = (float)CORE.Window.currentFbo.width/(float)CORE.Window.currentFbo.height; | ||
| 1033 | |||
| 1034 | // NOTE: zNear and zFar values are important when computing depth buffer values | ||
| 1035 | if (camera.projection == CAMERA_PERSPECTIVE) | ||
| 1036 | { | ||
| 1037 | // Setup perspective projection | ||
| 1038 | double top = rlGetCullDistanceNear()*tan(camera.fovy*0.5*DEG2RAD); | ||
| 1039 | double right = top*aspect; | ||
| 1040 | |||
| 1041 | rlFrustum(-right, right, -top, top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1042 | } | ||
| 1043 | else if (camera.projection == CAMERA_ORTHOGRAPHIC) | ||
| 1044 | { | ||
| 1045 | // Setup orthographic projection | ||
| 1046 | double top = camera.fovy/2.0; | ||
| 1047 | double right = top*aspect; | ||
| 1048 | |||
| 1049 | rlOrtho(-right, right, -top,top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1050 | } | ||
| 1051 | |||
| 1052 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | ||
| 1053 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1054 | |||
| 1055 | // Setup Camera view | ||
| 1056 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); | ||
| 1057 | rlMultMatrixf(MatrixToFloat(matView)); // Multiply modelview matrix by view matrix (camera) | ||
| 1058 | |||
| 1059 | rlEnableDepthTest(); // Enable DEPTH_TEST for 3D | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | // Ends 3D mode and returns to default 2D orthographic mode | ||
| 1063 | void EndMode3D(void) | ||
| 1064 | { | ||
| 1065 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1066 | |||
| 1067 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix | ||
| 1068 | rlPopMatrix(); // Restore previous matrix (projection) from matrix stack | ||
| 1069 | |||
| 1070 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | ||
| 1071 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1072 | |||
| 1073 | if (rlGetActiveFramebuffer() == 0) rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required | ||
| 1074 | |||
| 1075 | rlDisableDepthTest(); // Disable DEPTH_TEST for 2D | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | // Initializes render texture for drawing | ||
| 1079 | void BeginTextureMode(RenderTexture2D target) | ||
| 1080 | { | ||
| 1081 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1082 | |||
| 1083 | rlEnableFramebuffer(target.id); // Enable render target | ||
| 1084 | |||
| 1085 | // Set viewport and RLGL internal framebuffer size | ||
| 1086 | rlViewport(0, 0, target.texture.width, target.texture.height); | ||
| 1087 | rlSetFramebufferWidth(target.texture.width); | ||
| 1088 | rlSetFramebufferHeight(target.texture.height); | ||
| 1089 | |||
| 1090 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix | ||
| 1091 | rlLoadIdentity(); // Reset current matrix (projection) | ||
| 1092 | |||
| 1093 | // Set orthographic projection to current framebuffer size | ||
| 1094 | // NOTE: Configured top-left corner as (0, 0) | ||
| 1095 | rlOrtho(0, target.texture.width, target.texture.height, 0, 0.0f, 1.0f); | ||
| 1096 | |||
| 1097 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | ||
| 1098 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1099 | |||
| 1100 | //rlScalef(0.0f, -1.0f, 0.0f); // Flip Y-drawing (?) | ||
| 1101 | |||
| 1102 | // Setup current width/height for proper aspect ratio | ||
| 1103 | // calculation when using BeginMode3D() | ||
| 1104 | CORE.Window.currentFbo.width = target.texture.width; | ||
| 1105 | CORE.Window.currentFbo.height = target.texture.height; | ||
| 1106 | CORE.Window.usingFbo = true; | ||
| 1107 | } | ||
| 1108 | |||
| 1109 | // Ends drawing to render texture | ||
| 1110 | void EndTextureMode(void) | ||
| 1111 | { | ||
| 1112 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1113 | |||
| 1114 | rlDisableFramebuffer(); // Disable render target (fbo) | ||
| 1115 | |||
| 1116 | // Set viewport to default framebuffer size | ||
| 1117 | SetupViewport(CORE.Window.render.width, CORE.Window.render.height); | ||
| 1118 | |||
| 1119 | // Go back to the modelview state from BeginDrawing since we are back to the default FBO | ||
| 1120 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | ||
| 1121 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 1122 | rlMultMatrixf(MatrixToFloat(CORE.Window.screenScale)); // Apply screen scaling if required | ||
| 1123 | |||
| 1124 | // Reset current fbo to screen size | ||
| 1125 | CORE.Window.currentFbo.width = CORE.Window.render.width; | ||
| 1126 | CORE.Window.currentFbo.height = CORE.Window.render.height; | ||
| 1127 | CORE.Window.usingFbo = false; | ||
| 1128 | } | ||
| 1129 | |||
| 1130 | // Begin custom shader mode | ||
| 1131 | void BeginShaderMode(Shader shader) | ||
| 1132 | { | ||
| 1133 | rlSetShader(shader.id, shader.locs); | ||
| 1134 | } | ||
| 1135 | |||
| 1136 | // End custom shader mode (returns to default shader) | ||
| 1137 | void EndShaderMode(void) | ||
| 1138 | { | ||
| 1139 | rlSetShader(rlGetShaderIdDefault(), rlGetShaderLocsDefault()); | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | // Begin blending mode (alpha, additive, multiplied, subtract, custom) | ||
| 1143 | // NOTE: Blend modes supported are enumerated in BlendMode enum | ||
| 1144 | void BeginBlendMode(int mode) | ||
| 1145 | { | ||
| 1146 | rlSetBlendMode(mode); | ||
| 1147 | } | ||
| 1148 | |||
| 1149 | // End blending mode (reset to default: alpha blending) | ||
| 1150 | void EndBlendMode(void) | ||
| 1151 | { | ||
| 1152 | rlSetBlendMode(BLEND_ALPHA); | ||
| 1153 | } | ||
| 1154 | |||
| 1155 | // Begin scissor mode (define screen area for following drawing) | ||
| 1156 | // NOTE: Scissor rec refers to bottom-left corner, we change it to upper-left | ||
| 1157 | void BeginScissorMode(int x, int y, int width, int height) | ||
| 1158 | { | ||
| 1159 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1160 | |||
| 1161 | rlEnableScissorTest(); | ||
| 1162 | |||
| 1163 | #if defined(__APPLE__) | ||
| 1164 | if (!CORE.Window.usingFbo) | ||
| 1165 | { | ||
| 1166 | Vector2 scale = GetWindowScaleDPI(); | ||
| 1167 | rlScissor((int)(x*scale.x), (int)(GetScreenHeight()*scale.y - (((y + height)*scale.y))), (int)(width*scale.x), (int)(height*scale.y)); | ||
| 1168 | } | ||
| 1169 | #else | ||
| 1170 | if (!CORE.Window.usingFbo && ((CORE.Window.flags & FLAG_WINDOW_HIGHDPI) > 0)) | ||
| 1171 | { | ||
| 1172 | Vector2 scale = GetWindowScaleDPI(); | ||
| 1173 | rlScissor((int)(x*scale.x), (int)(CORE.Window.currentFbo.height - (y + height)*scale.y), (int)(width*scale.x), (int)(height*scale.y)); | ||
| 1174 | } | ||
| 1175 | #endif | ||
| 1176 | else | ||
| 1177 | { | ||
| 1178 | rlScissor(x, CORE.Window.currentFbo.height - (y + height), width, height); | ||
| 1179 | } | ||
| 1180 | } | ||
| 1181 | |||
| 1182 | // End scissor mode | ||
| 1183 | void EndScissorMode(void) | ||
| 1184 | { | ||
| 1185 | rlDrawRenderBatchActive(); // Update and draw internal render batch | ||
| 1186 | rlDisableScissorTest(); | ||
| 1187 | } | ||
| 1188 | |||
| 1189 | //---------------------------------------------------------------------------------- | ||
| 1190 | // Module Functions Definition: VR Stereo Rendering | ||
| 1191 | //---------------------------------------------------------------------------------- | ||
| 1192 | |||
| 1193 | // Begin VR drawing configuration | ||
| 1194 | void BeginVrStereoMode(VrStereoConfig config) | ||
| 1195 | { | ||
| 1196 | rlEnableStereoRender(); | ||
| 1197 | |||
| 1198 | // Set stereo render matrices | ||
| 1199 | rlSetMatrixProjectionStereo(config.projection[0], config.projection[1]); | ||
| 1200 | rlSetMatrixViewOffsetStereo(config.viewOffset[0], config.viewOffset[1]); | ||
| 1201 | } | ||
| 1202 | |||
| 1203 | // End VR drawing process (and desktop mirror) | ||
| 1204 | void EndVrStereoMode(void) | ||
| 1205 | { | ||
| 1206 | rlDisableStereoRender(); | ||
| 1207 | } | ||
| 1208 | |||
| 1209 | // Load VR stereo config for VR simulator device parameters | ||
| 1210 | VrStereoConfig LoadVrStereoConfig(VrDeviceInfo device) | ||
| 1211 | { | ||
| 1212 | VrStereoConfig config = { 0 }; | ||
| 1213 | |||
| 1214 | if (rlGetVersion() != RL_OPENGL_11) | ||
| 1215 | { | ||
| 1216 | // Compute aspect ratio | ||
| 1217 | float aspect = ((float)device.hResolution*0.5f)/(float)device.vResolution; | ||
| 1218 | |||
| 1219 | // Compute lens parameters | ||
| 1220 | float lensShift = (device.hScreenSize*0.25f - device.lensSeparationDistance*0.5f)/device.hScreenSize; | ||
| 1221 | config.leftLensCenter[0] = 0.25f + lensShift; | ||
| 1222 | config.leftLensCenter[1] = 0.5f; | ||
| 1223 | config.rightLensCenter[0] = 0.75f - lensShift; | ||
| 1224 | config.rightLensCenter[1] = 0.5f; | ||
| 1225 | config.leftScreenCenter[0] = 0.25f; | ||
| 1226 | config.leftScreenCenter[1] = 0.5f; | ||
| 1227 | config.rightScreenCenter[0] = 0.75f; | ||
| 1228 | config.rightScreenCenter[1] = 0.5f; | ||
| 1229 | |||
| 1230 | // Compute distortion scale parameters | ||
| 1231 | // NOTE: To get lens max radius, lensShift must be normalized to [-1..1] | ||
| 1232 | float lensRadius = fabsf(-1.0f - 4.0f*lensShift); | ||
| 1233 | float lensRadiusSq = lensRadius*lensRadius; | ||
| 1234 | float distortionScale = device.lensDistortionValues[0] + | ||
| 1235 | device.lensDistortionValues[1]*lensRadiusSq + | ||
| 1236 | device.lensDistortionValues[2]*lensRadiusSq*lensRadiusSq + | ||
| 1237 | device.lensDistortionValues[3]*lensRadiusSq*lensRadiusSq*lensRadiusSq; | ||
| 1238 | |||
| 1239 | float normScreenWidth = 0.5f; | ||
| 1240 | float normScreenHeight = 1.0f; | ||
| 1241 | config.scaleIn[0] = 2.0f/normScreenWidth; | ||
| 1242 | config.scaleIn[1] = 2.0f/normScreenHeight/aspect; | ||
| 1243 | config.scale[0] = normScreenWidth*0.5f/distortionScale; | ||
| 1244 | config.scale[1] = normScreenHeight*0.5f*aspect/distortionScale; | ||
| 1245 | |||
| 1246 | // Fovy is normally computed with: 2*atan2f(device.vScreenSize, 2*device.eyeToScreenDistance) | ||
| 1247 | // ...but with lens distortion it is increased (see Oculus SDK Documentation) | ||
| 1248 | float fovy = 2.0f*atan2f(device.vScreenSize*0.5f*distortionScale, device.eyeToScreenDistance); // Really need distortionScale? | ||
| 1249 | // float fovy = 2.0f*(float)atan2f(device.vScreenSize*0.5f, device.eyeToScreenDistance); | ||
| 1250 | |||
| 1251 | // Compute camera projection matrices | ||
| 1252 | float projOffset = 4.0f*lensShift; // Scaled to projection space coordinates [-1..1] | ||
| 1253 | Matrix proj = MatrixPerspective(fovy, aspect, rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1254 | |||
| 1255 | config.projection[0] = MatrixMultiply(proj, MatrixTranslate(projOffset, 0.0f, 0.0f)); | ||
| 1256 | config.projection[1] = MatrixMultiply(proj, MatrixTranslate(-projOffset, 0.0f, 0.0f)); | ||
| 1257 | |||
| 1258 | // Compute camera transformation matrices | ||
| 1259 | // NOTE: Camera movement might seem more natural if we model the head | ||
| 1260 | // Our axis of rotation is the base of our head, so we might want to add | ||
| 1261 | // some y (base of head to eye level) and -z (center of head to eye protrusion) to the camera positions | ||
| 1262 | config.viewOffset[0] = MatrixTranslate(device.interpupillaryDistance*0.5f, 0.075f, 0.045f); | ||
| 1263 | config.viewOffset[1] = MatrixTranslate(-device.interpupillaryDistance*0.5f, 0.075f, 0.045f); | ||
| 1264 | |||
| 1265 | // Compute eyes Viewports | ||
| 1266 | /* | ||
| 1267 | config.eyeViewportRight[0] = 0; | ||
| 1268 | config.eyeViewportRight[1] = 0; | ||
| 1269 | config.eyeViewportRight[2] = device.hResolution/2; | ||
| 1270 | config.eyeViewportRight[3] = device.vResolution; | ||
| 1271 | |||
| 1272 | config.eyeViewportLeft[0] = device.hResolution/2; | ||
| 1273 | config.eyeViewportLeft[1] = 0; | ||
| 1274 | config.eyeViewportLeft[2] = device.hResolution/2; | ||
| 1275 | config.eyeViewportLeft[3] = device.vResolution; | ||
| 1276 | */ | ||
| 1277 | } | ||
| 1278 | else TRACELOG(LOG_WARNING, "RLGL: VR Simulator not supported on OpenGL 1.1"); | ||
| 1279 | |||
| 1280 | return config; | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | // Unload VR stereo config properties | ||
| 1284 | void UnloadVrStereoConfig(VrStereoConfig config) | ||
| 1285 | { | ||
| 1286 | TRACELOG(LOG_INFO, "UnloadVrStereoConfig not implemented in rcore.c"); | ||
| 1287 | } | ||
| 1288 | |||
| 1289 | //---------------------------------------------------------------------------------- | ||
| 1290 | // Module Functions Definition: Shaders Management | ||
| 1291 | //---------------------------------------------------------------------------------- | ||
| 1292 | |||
| 1293 | // Load shader from files and bind default locations | ||
| 1294 | // NOTE: If shader string is NULL, using default vertex/fragment shaders | ||
| 1295 | Shader LoadShader(const char *vsFileName, const char *fsFileName) | ||
| 1296 | { | ||
| 1297 | Shader shader = { 0 }; | ||
| 1298 | |||
| 1299 | char *vShaderStr = NULL; | ||
| 1300 | char *fShaderStr = NULL; | ||
| 1301 | |||
| 1302 | if (vsFileName != NULL) vShaderStr = LoadFileText(vsFileName); | ||
| 1303 | if (fsFileName != NULL) fShaderStr = LoadFileText(fsFileName); | ||
| 1304 | |||
| 1305 | shader = LoadShaderFromMemory(vShaderStr, fShaderStr); | ||
| 1306 | |||
| 1307 | UnloadFileText(vShaderStr); | ||
| 1308 | UnloadFileText(fShaderStr); | ||
| 1309 | |||
| 1310 | return shader; | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | // Load shader from code strings and bind default locations | ||
| 1314 | Shader LoadShaderFromMemory(const char *vsCode, const char *fsCode) | ||
| 1315 | { | ||
| 1316 | Shader shader = { 0 }; | ||
| 1317 | |||
| 1318 | shader.id = rlLoadShaderCode(vsCode, fsCode); | ||
| 1319 | |||
| 1320 | // After shader loading, we TRY to set default location names | ||
| 1321 | if (shader.id > 0) | ||
| 1322 | { | ||
| 1323 | // Default shader attribute locations have been binded before linking: | ||
| 1324 | // vertex position location = 0 | ||
| 1325 | // vertex texcoord location = 1 | ||
| 1326 | // vertex normal location = 2 | ||
| 1327 | // vertex color location = 3 | ||
| 1328 | // vertex tangent location = 4 | ||
| 1329 | // vertex texcoord2 location = 5 | ||
| 1330 | // vertex boneIds location = 6 | ||
| 1331 | // vertex boneWeights location = 7 | ||
| 1332 | |||
| 1333 | // NOTE: If any location is not found, loc point becomes -1 | ||
| 1334 | |||
| 1335 | shader.locs = (int *)RL_CALLOC(RL_MAX_SHADER_LOCATIONS, sizeof(int)); | ||
| 1336 | |||
| 1337 | // All locations reset to -1 (no location) | ||
| 1338 | for (int i = 0; i < RL_MAX_SHADER_LOCATIONS; i++) shader.locs[i] = -1; | ||
| 1339 | |||
| 1340 | // Get handles to GLSL input attribute locations | ||
| 1341 | shader.locs[SHADER_LOC_VERTEX_POSITION] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_POSITION); | ||
| 1342 | shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD); | ||
| 1343 | shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TEXCOORD2); | ||
| 1344 | shader.locs[SHADER_LOC_VERTEX_NORMAL] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_NORMAL); | ||
| 1345 | shader.locs[SHADER_LOC_VERTEX_TANGENT] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_TANGENT); | ||
| 1346 | shader.locs[SHADER_LOC_VERTEX_COLOR] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_COLOR); | ||
| 1347 | shader.locs[SHADER_LOC_VERTEX_BONEIDS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEIDS); | ||
| 1348 | shader.locs[SHADER_LOC_VERTEX_BONEWEIGHTS] = rlGetLocationAttrib(shader.id, RL_DEFAULT_SHADER_ATTRIB_NAME_BONEWEIGHTS); | ||
| 1349 | |||
| 1350 | // Get handles to GLSL uniform locations (vertex shader) | ||
| 1351 | shader.locs[SHADER_LOC_MATRIX_MVP] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MVP); | ||
| 1352 | shader.locs[SHADER_LOC_MATRIX_VIEW] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_VIEW); | ||
| 1353 | shader.locs[SHADER_LOC_MATRIX_PROJECTION] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_PROJECTION); | ||
| 1354 | shader.locs[SHADER_LOC_MATRIX_MODEL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_MODEL); | ||
| 1355 | shader.locs[SHADER_LOC_MATRIX_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_NORMAL); | ||
| 1356 | shader.locs[SHADER_LOC_BONE_MATRICES] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_BONE_MATRICES); | ||
| 1357 | |||
| 1358 | // Get handles to GLSL uniform locations (fragment shader) | ||
| 1359 | shader.locs[SHADER_LOC_COLOR_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_UNIFORM_NAME_COLOR); | ||
| 1360 | shader.locs[SHADER_LOC_MAP_DIFFUSE] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE0); // SHADER_LOC_MAP_ALBEDO | ||
| 1361 | shader.locs[SHADER_LOC_MAP_SPECULAR] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE1); // SHADER_LOC_MAP_METALNESS | ||
| 1362 | shader.locs[SHADER_LOC_MAP_NORMAL] = rlGetLocationUniform(shader.id, RL_DEFAULT_SHADER_SAMPLER2D_NAME_TEXTURE2); | ||
| 1363 | } | ||
| 1364 | |||
| 1365 | return shader; | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | // Check if a shader is valid (loaded on GPU) | ||
| 1369 | bool IsShaderValid(Shader shader) | ||
| 1370 | { | ||
| 1371 | return ((shader.id > 0) && // Validate shader id (GPU loaded successfully) | ||
| 1372 | (shader.locs != NULL)); // Validate memory has been allocated for default shader locations | ||
| 1373 | |||
| 1374 | // The following locations are tried to be set automatically (locs[i] >= 0), | ||
| 1375 | // any of them can be checked for validation but the only mandatory one is, afaik, SHADER_LOC_VERTEX_POSITION | ||
| 1376 | // NOTE: Users can also setup manually their own attributes/uniforms and do not used the default raylib ones | ||
| 1377 | |||
| 1378 | // Vertex shader attribute locations (default) | ||
| 1379 | // shader.locs[SHADER_LOC_VERTEX_POSITION] // Set by default internal shader | ||
| 1380 | // shader.locs[SHADER_LOC_VERTEX_TEXCOORD01] // Set by default internal shader | ||
| 1381 | // shader.locs[SHADER_LOC_VERTEX_TEXCOORD02] | ||
| 1382 | // shader.locs[SHADER_LOC_VERTEX_NORMAL] | ||
| 1383 | // shader.locs[SHADER_LOC_VERTEX_TANGENT] | ||
| 1384 | // shader.locs[SHADER_LOC_VERTEX_COLOR] // Set by default internal shader | ||
| 1385 | |||
| 1386 | // Vertex shader uniform locations (default) | ||
| 1387 | // shader.locs[SHADER_LOC_MATRIX_MVP] // Set by default internal shader | ||
| 1388 | // shader.locs[SHADER_LOC_MATRIX_VIEW] | ||
| 1389 | // shader.locs[SHADER_LOC_MATRIX_PROJECTION] | ||
| 1390 | // shader.locs[SHADER_LOC_MATRIX_MODEL] | ||
| 1391 | // shader.locs[SHADER_LOC_MATRIX_NORMAL] | ||
| 1392 | |||
| 1393 | // Fragment shader uniform locations (default) | ||
| 1394 | // shader.locs[SHADER_LOC_COLOR_DIFFUSE] // Set by default internal shader | ||
| 1395 | // shader.locs[SHADER_LOC_MAP_DIFFUSE] // Set by default internal shader | ||
| 1396 | // shader.locs[SHADER_LOC_MAP_SPECULAR] | ||
| 1397 | // shader.locs[SHADER_LOC_MAP_NORMAL] | ||
| 1398 | } | ||
| 1399 | |||
| 1400 | // Unload shader from GPU memory (VRAM) | ||
| 1401 | void UnloadShader(Shader shader) | ||
| 1402 | { | ||
| 1403 | if (shader.id != rlGetShaderIdDefault()) | ||
| 1404 | { | ||
| 1405 | rlUnloadShaderProgram(shader.id); | ||
| 1406 | |||
| 1407 | // NOTE: If shader loading failed, it should be 0 | ||
| 1408 | RL_FREE(shader.locs); | ||
| 1409 | } | ||
| 1410 | } | ||
| 1411 | |||
| 1412 | // Get shader uniform location | ||
| 1413 | int GetShaderLocation(Shader shader, const char *uniformName) | ||
| 1414 | { | ||
| 1415 | return rlGetLocationUniform(shader.id, uniformName); | ||
| 1416 | } | ||
| 1417 | |||
| 1418 | // Get shader attribute location | ||
| 1419 | int GetShaderLocationAttrib(Shader shader, const char *attribName) | ||
| 1420 | { | ||
| 1421 | return rlGetLocationAttrib(shader.id, attribName); | ||
| 1422 | } | ||
| 1423 | |||
| 1424 | // Set shader uniform value | ||
| 1425 | void SetShaderValue(Shader shader, int locIndex, const void *value, int uniformType) | ||
| 1426 | { | ||
| 1427 | SetShaderValueV(shader, locIndex, value, uniformType, 1); | ||
| 1428 | } | ||
| 1429 | |||
| 1430 | // Set shader uniform value vector | ||
| 1431 | void SetShaderValueV(Shader shader, int locIndex, const void *value, int uniformType, int count) | ||
| 1432 | { | ||
| 1433 | if (locIndex > -1) | ||
| 1434 | { | ||
| 1435 | rlEnableShader(shader.id); | ||
| 1436 | rlSetUniform(locIndex, value, uniformType, count); | ||
| 1437 | //rlDisableShader(); // Avoid resetting current shader program, in case other uniforms are set | ||
| 1438 | } | ||
| 1439 | } | ||
| 1440 | |||
| 1441 | // Set shader uniform value (matrix 4x4) | ||
| 1442 | void SetShaderValueMatrix(Shader shader, int locIndex, Matrix mat) | ||
| 1443 | { | ||
| 1444 | if (locIndex > -1) | ||
| 1445 | { | ||
| 1446 | rlEnableShader(shader.id); | ||
| 1447 | rlSetUniformMatrix(locIndex, mat); | ||
| 1448 | //rlDisableShader(); | ||
| 1449 | } | ||
| 1450 | } | ||
| 1451 | |||
| 1452 | // Set shader uniform value for texture | ||
| 1453 | void SetShaderValueTexture(Shader shader, int locIndex, Texture2D texture) | ||
| 1454 | { | ||
| 1455 | if (locIndex > -1) | ||
| 1456 | { | ||
| 1457 | rlEnableShader(shader.id); | ||
| 1458 | rlSetUniformSampler(locIndex, texture.id); | ||
| 1459 | //rlDisableShader(); | ||
| 1460 | } | ||
| 1461 | } | ||
| 1462 | |||
| 1463 | //---------------------------------------------------------------------------------- | ||
| 1464 | // Module Functions Definition: Screen-space Queries | ||
| 1465 | //---------------------------------------------------------------------------------- | ||
| 1466 | |||
| 1467 | // Get a ray trace from screen position (i.e mouse) | ||
| 1468 | Ray GetScreenToWorldRay(Vector2 position, Camera camera) | ||
| 1469 | { | ||
| 1470 | Ray ray = GetScreenToWorldRayEx(position, camera, GetScreenWidth(), GetScreenHeight()); | ||
| 1471 | |||
| 1472 | return ray; | ||
| 1473 | } | ||
| 1474 | |||
| 1475 | // Get a ray trace from the screen position (i.e mouse) within a specific section of the screen | ||
| 1476 | Ray GetScreenToWorldRayEx(Vector2 position, Camera camera, int width, int height) | ||
| 1477 | { | ||
| 1478 | Ray ray = { 0 }; | ||
| 1479 | |||
| 1480 | // Calculate normalized device coordinates | ||
| 1481 | // NOTE: y value is negative | ||
| 1482 | float x = (2.0f*position.x)/(float)width - 1.0f; | ||
| 1483 | float y = 1.0f - (2.0f*position.y)/(float)height; | ||
| 1484 | float z = 1.0f; | ||
| 1485 | |||
| 1486 | // Store values in a vector | ||
| 1487 | Vector3 deviceCoords = { x, y, z }; | ||
| 1488 | |||
| 1489 | // Calculate view matrix from camera look at | ||
| 1490 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); | ||
| 1491 | |||
| 1492 | Matrix matProj = MatrixIdentity(); | ||
| 1493 | |||
| 1494 | if (camera.projection == CAMERA_PERSPECTIVE) | ||
| 1495 | { | ||
| 1496 | // Calculate projection matrix from perspective | ||
| 1497 | matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1498 | } | ||
| 1499 | else if (camera.projection == CAMERA_ORTHOGRAPHIC) | ||
| 1500 | { | ||
| 1501 | double aspect = (double)width/(double)height; | ||
| 1502 | double top = camera.fovy/2.0; | ||
| 1503 | double right = top*aspect; | ||
| 1504 | |||
| 1505 | // Calculate projection matrix from orthographic | ||
| 1506 | matProj = MatrixOrtho(-right, right, -top, top, 0.01, 1000.0); | ||
| 1507 | } | ||
| 1508 | |||
| 1509 | // Unproject far/near points | ||
| 1510 | Vector3 nearPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 0.0f }, matProj, matView); | ||
| 1511 | Vector3 farPoint = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, 1.0f }, matProj, matView); | ||
| 1512 | |||
| 1513 | // Unproject the mouse cursor in the near plane | ||
| 1514 | // We need this as the source position because orthographic projects, | ||
| 1515 | // compared to perspective doesn't have a convergence point, | ||
| 1516 | // meaning that the "eye" of the camera is more like a plane than a point | ||
| 1517 | Vector3 cameraPlanePointerPos = Vector3Unproject((Vector3){ deviceCoords.x, deviceCoords.y, -1.0f }, matProj, matView); | ||
| 1518 | |||
| 1519 | // Calculate normalized direction vector | ||
| 1520 | Vector3 direction = Vector3Normalize(Vector3Subtract(farPoint, nearPoint)); | ||
| 1521 | |||
| 1522 | if (camera.projection == CAMERA_PERSPECTIVE) ray.position = camera.position; | ||
| 1523 | else if (camera.projection == CAMERA_ORTHOGRAPHIC) ray.position = cameraPlanePointerPos; | ||
| 1524 | |||
| 1525 | // Apply calculated vectors to ray | ||
| 1526 | ray.direction = direction; | ||
| 1527 | |||
| 1528 | return ray; | ||
| 1529 | } | ||
| 1530 | |||
| 1531 | // Get transform matrix for camera | ||
| 1532 | Matrix GetCameraMatrix(Camera camera) | ||
| 1533 | { | ||
| 1534 | Matrix mat = MatrixLookAt(camera.position, camera.target, camera.up); | ||
| 1535 | |||
| 1536 | return mat; | ||
| 1537 | } | ||
| 1538 | |||
| 1539 | // Get camera 2d transform matrix | ||
| 1540 | Matrix GetCameraMatrix2D(Camera2D camera) | ||
| 1541 | { | ||
| 1542 | Matrix matTransform = { 0 }; | ||
| 1543 | // The camera in world-space is set by | ||
| 1544 | // 1. Move it to target | ||
| 1545 | // 2. Rotate by -rotation and scale by (1/zoom) | ||
| 1546 | // When setting higher scale, it's more intuitive for the world to become bigger (= camera become smaller), | ||
| 1547 | // not for the camera getting bigger, hence the invert. Same deal with rotation | ||
| 1548 | // 3. Move it by (-offset); | ||
| 1549 | // Offset defines target transform relative to screen, but since we're effectively "moving" screen (camera) | ||
| 1550 | // we need to do it into opposite direction (inverse transform) | ||
| 1551 | |||
| 1552 | // Having camera transform in world-space, inverse of it gives the modelview transform | ||
| 1553 | // Since (A*B*C)' = C'*B'*A', the modelview is | ||
| 1554 | // 1. Move to offset | ||
| 1555 | // 2. Rotate and Scale | ||
| 1556 | // 3. Move by -target | ||
| 1557 | Matrix matOrigin = MatrixTranslate(-camera.target.x, -camera.target.y, 0.0f); | ||
| 1558 | Matrix matRotation = MatrixRotate((Vector3){ 0.0f, 0.0f, 1.0f }, camera.rotation*DEG2RAD); | ||
| 1559 | Matrix matScale = MatrixScale(camera.zoom, camera.zoom, 1.0f); | ||
| 1560 | Matrix matTranslation = MatrixTranslate(camera.offset.x, camera.offset.y, 0.0f); | ||
| 1561 | |||
| 1562 | matTransform = MatrixMultiply(MatrixMultiply(matOrigin, MatrixMultiply(matScale, matRotation)), matTranslation); | ||
| 1563 | |||
| 1564 | return matTransform; | ||
| 1565 | } | ||
| 1566 | |||
| 1567 | // Get the screen space position from a 3d world space position | ||
| 1568 | Vector2 GetWorldToScreen(Vector3 position, Camera camera) | ||
| 1569 | { | ||
| 1570 | Vector2 screenPosition = GetWorldToScreenEx(position, camera, GetScreenWidth(), GetScreenHeight()); | ||
| 1571 | |||
| 1572 | return screenPosition; | ||
| 1573 | } | ||
| 1574 | |||
| 1575 | // Get size position for a 3d world space position (useful for texture drawing) | ||
| 1576 | Vector2 GetWorldToScreenEx(Vector3 position, Camera camera, int width, int height) | ||
| 1577 | { | ||
| 1578 | // Calculate projection matrix (from perspective instead of frustum | ||
| 1579 | Matrix matProj = MatrixIdentity(); | ||
| 1580 | |||
| 1581 | if (camera.projection == CAMERA_PERSPECTIVE) | ||
| 1582 | { | ||
| 1583 | // Calculate projection matrix from perspective | ||
| 1584 | matProj = MatrixPerspective(camera.fovy*DEG2RAD, ((double)width/(double)height), rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1585 | } | ||
| 1586 | else if (camera.projection == CAMERA_ORTHOGRAPHIC) | ||
| 1587 | { | ||
| 1588 | double aspect = (double)width/(double)height; | ||
| 1589 | double top = camera.fovy/2.0; | ||
| 1590 | double right = top*aspect; | ||
| 1591 | |||
| 1592 | // Calculate projection matrix from orthographic | ||
| 1593 | matProj = MatrixOrtho(-right, right, -top, top, rlGetCullDistanceNear(), rlGetCullDistanceFar()); | ||
| 1594 | } | ||
| 1595 | |||
| 1596 | // Calculate view matrix from camera look at (and transpose it) | ||
| 1597 | Matrix matView = MatrixLookAt(camera.position, camera.target, camera.up); | ||
| 1598 | |||
| 1599 | // TODO: Why not use Vector3Transform(Vector3 v, Matrix mat)? | ||
| 1600 | |||
| 1601 | // Convert world position vector to quaternion | ||
| 1602 | Quaternion worldPos = { position.x, position.y, position.z, 1.0f }; | ||
| 1603 | |||
| 1604 | // Transform world position to view | ||
| 1605 | worldPos = QuaternionTransform(worldPos, matView); | ||
| 1606 | |||
| 1607 | // Transform result to projection (clip space position) | ||
| 1608 | worldPos = QuaternionTransform(worldPos, matProj); | ||
| 1609 | |||
| 1610 | // Calculate normalized device coordinates (inverted y) | ||
| 1611 | Vector3 ndcPos = { worldPos.x/worldPos.w, -worldPos.y/worldPos.w, worldPos.z/worldPos.w }; | ||
| 1612 | |||
| 1613 | // Calculate 2d screen position vector | ||
| 1614 | Vector2 screenPosition = { (ndcPos.x + 1.0f)/2.0f*(float)width, (ndcPos.y + 1.0f)/2.0f*(float)height }; | ||
| 1615 | |||
| 1616 | return screenPosition; | ||
| 1617 | } | ||
| 1618 | |||
| 1619 | // Get the screen space position for a 2d camera world space position | ||
| 1620 | Vector2 GetWorldToScreen2D(Vector2 position, Camera2D camera) | ||
| 1621 | { | ||
| 1622 | Matrix matCamera = GetCameraMatrix2D(camera); | ||
| 1623 | Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, matCamera); | ||
| 1624 | |||
| 1625 | return (Vector2){ transform.x, transform.y }; | ||
| 1626 | } | ||
| 1627 | |||
| 1628 | // Get the world space position for a 2d camera screen space position | ||
| 1629 | Vector2 GetScreenToWorld2D(Vector2 position, Camera2D camera) | ||
| 1630 | { | ||
| 1631 | Matrix invMatCamera = MatrixInvert(GetCameraMatrix2D(camera)); | ||
| 1632 | Vector3 transform = Vector3Transform((Vector3){ position.x, position.y, 0 }, invMatCamera); | ||
| 1633 | |||
| 1634 | return (Vector2){ transform.x, transform.y }; | ||
| 1635 | } | ||
| 1636 | |||
| 1637 | //---------------------------------------------------------------------------------- | ||
| 1638 | // Module Functions Definition: Timming | ||
| 1639 | //---------------------------------------------------------------------------------- | ||
| 1640 | |||
| 1641 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 1642 | //double GetTime(void) | ||
| 1643 | |||
| 1644 | // Set target FPS (maximum) | ||
| 1645 | void SetTargetFPS(int fps) | ||
| 1646 | { | ||
| 1647 | if (fps < 1) CORE.Time.target = 0.0; | ||
| 1648 | else CORE.Time.target = 1.0/(double)fps; | ||
| 1649 | |||
| 1650 | TRACELOG(LOG_INFO, "TIMER: Target time per frame: %02.03f milliseconds", (float)CORE.Time.target*1000.0f); | ||
| 1651 | } | ||
| 1652 | |||
| 1653 | // Get current FPS | ||
| 1654 | // NOTE: We calculate an average framerate | ||
| 1655 | int GetFPS(void) | ||
| 1656 | { | ||
| 1657 | int fps = 0; | ||
| 1658 | |||
| 1659 | #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL) | ||
| 1660 | #define FPS_CAPTURE_FRAMES_COUNT 30 // 30 captures | ||
| 1661 | #define FPS_AVERAGE_TIME_SECONDS 0.5f // 500 milliseconds | ||
| 1662 | #define FPS_STEP (FPS_AVERAGE_TIME_SECONDS/FPS_CAPTURE_FRAMES_COUNT) | ||
| 1663 | |||
| 1664 | static int index = 0; | ||
| 1665 | static float history[FPS_CAPTURE_FRAMES_COUNT] = { 0 }; | ||
| 1666 | static float average = 0, last = 0; | ||
| 1667 | float fpsFrame = GetFrameTime(); | ||
| 1668 | |||
| 1669 | // if we reset the window, reset the FPS info | ||
| 1670 | if (CORE.Time.frameCounter == 0) | ||
| 1671 | { | ||
| 1672 | average = 0; | ||
| 1673 | last = 0; | ||
| 1674 | index = 0; | ||
| 1675 | |||
| 1676 | for (int i = 0; i < FPS_CAPTURE_FRAMES_COUNT; i++) history[i] = 0; | ||
| 1677 | } | ||
| 1678 | |||
| 1679 | if (fpsFrame == 0) return 0; | ||
| 1680 | |||
| 1681 | if ((GetTime() - last) > FPS_STEP) | ||
| 1682 | { | ||
| 1683 | last = (float)GetTime(); | ||
| 1684 | index = (index + 1)%FPS_CAPTURE_FRAMES_COUNT; | ||
| 1685 | average -= history[index]; | ||
| 1686 | history[index] = fpsFrame/FPS_CAPTURE_FRAMES_COUNT; | ||
| 1687 | average += history[index]; | ||
| 1688 | } | ||
| 1689 | |||
| 1690 | fps = (int)roundf(1.0f/average); | ||
| 1691 | #endif | ||
| 1692 | |||
| 1693 | return fps; | ||
| 1694 | } | ||
| 1695 | |||
| 1696 | // Get time in seconds for last frame drawn (delta time) | ||
| 1697 | float GetFrameTime(void) | ||
| 1698 | { | ||
| 1699 | return (float)CORE.Time.frame; | ||
| 1700 | } | ||
| 1701 | |||
| 1702 | //---------------------------------------------------------------------------------- | ||
| 1703 | // Module Functions Definition: Custom frame control | ||
| 1704 | //---------------------------------------------------------------------------------- | ||
| 1705 | |||
| 1706 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 1707 | //void SwapScreenBuffer(void); | ||
| 1708 | //void PollInputEvents(void); | ||
| 1709 | |||
| 1710 | // Wait for some time (stop program execution) | ||
| 1711 | // NOTE: Sleep() granularity could be around 10 ms, it means, Sleep() could | ||
| 1712 | // take longer than expected... for that reason we use the busy wait loop | ||
| 1713 | // Ref: http://stackoverflow.com/questions/43057578/c-programming-win32-games-sleep-taking-longer-than-expected | ||
| 1714 | // Ref: http://www.geisswerks.com/ryan/FAQS/timing.html --> All about timing on Win32! | ||
| 1715 | void WaitTime(double seconds) | ||
| 1716 | { | ||
| 1717 | if (seconds < 0) return; // Security check | ||
| 1718 | |||
| 1719 | #if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) | ||
| 1720 | double destinationTime = GetTime() + seconds; | ||
| 1721 | #endif | ||
| 1722 | |||
| 1723 | #if defined(SUPPORT_BUSY_WAIT_LOOP) | ||
| 1724 | while (GetTime() < destinationTime) { } | ||
| 1725 | #else | ||
| 1726 | #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) | ||
| 1727 | double sleepSeconds = seconds - seconds*0.05; // NOTE: We reserve a percentage of the time for busy waiting | ||
| 1728 | #else | ||
| 1729 | double sleepSeconds = seconds; | ||
| 1730 | #endif | ||
| 1731 | |||
| 1732 | // System halt functions | ||
| 1733 | #if defined(_WIN32) | ||
| 1734 | Sleep((unsigned long)(sleepSeconds*1000.0)); | ||
| 1735 | #endif | ||
| 1736 | #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) | ||
| 1737 | struct timespec req = { 0 }; | ||
| 1738 | time_t sec = sleepSeconds; | ||
| 1739 | long nsec = (sleepSeconds - sec)*1000000000L; | ||
| 1740 | req.tv_sec = sec; | ||
| 1741 | req.tv_nsec = nsec; | ||
| 1742 | |||
| 1743 | // NOTE: Use nanosleep() on Unix platforms... usleep() it's deprecated | ||
| 1744 | while (nanosleep(&req, &req) == -1) continue; | ||
| 1745 | #endif | ||
| 1746 | #if defined(__APPLE__) | ||
| 1747 | usleep(sleepSeconds*1000000.0); | ||
| 1748 | #endif | ||
| 1749 | |||
| 1750 | #if defined(SUPPORT_PARTIALBUSY_WAIT_LOOP) | ||
| 1751 | while (GetTime() < destinationTime) { } | ||
| 1752 | #endif | ||
| 1753 | #endif | ||
| 1754 | } | ||
| 1755 | |||
| 1756 | //---------------------------------------------------------------------------------- | ||
| 1757 | // Module Functions Definition: Misc | ||
| 1758 | //---------------------------------------------------------------------------------- | ||
| 1759 | |||
| 1760 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 1761 | //void OpenURL(const char *url) | ||
| 1762 | |||
| 1763 | // Set the seed for the random number generator | ||
| 1764 | void SetRandomSeed(unsigned int seed) | ||
| 1765 | { | ||
| 1766 | #if defined(SUPPORT_RPRAND_GENERATOR) | ||
| 1767 | rprand_set_seed(seed); | ||
| 1768 | #else | ||
| 1769 | srand(seed); | ||
| 1770 | #endif | ||
| 1771 | } | ||
| 1772 | |||
| 1773 | // Get a random value between min and max included | ||
| 1774 | int GetRandomValue(int min, int max) | ||
| 1775 | { | ||
| 1776 | int value = 0; | ||
| 1777 | |||
| 1778 | if (min > max) | ||
| 1779 | { | ||
| 1780 | int tmp = max; | ||
| 1781 | max = min; | ||
| 1782 | min = tmp; | ||
| 1783 | } | ||
| 1784 | |||
| 1785 | #if defined(SUPPORT_RPRAND_GENERATOR) | ||
| 1786 | value = rprand_get_value(min, max); | ||
| 1787 | #else | ||
| 1788 | // WARNING: Ranges higher than RAND_MAX will return invalid results | ||
| 1789 | // More specifically, if (max - min) > INT_MAX there will be an overflow, | ||
| 1790 | // and otherwise if (max - min) > RAND_MAX the random value will incorrectly never exceed a certain threshold | ||
| 1791 | // NOTE: Depending on the library it can be as low as 32767 | ||
| 1792 | if ((unsigned int)(max - min) > (unsigned int)RAND_MAX) | ||
| 1793 | { | ||
| 1794 | TRACELOG(LOG_WARNING, "Invalid GetRandomValue() arguments, range should not be higher than %i", RAND_MAX); | ||
| 1795 | } | ||
| 1796 | |||
| 1797 | value = (rand()%(abs(max - min) + 1) + min); | ||
| 1798 | #endif | ||
| 1799 | return value; | ||
| 1800 | } | ||
| 1801 | |||
| 1802 | // Load random values sequence, no values repeated, min and max included | ||
| 1803 | int *LoadRandomSequence(unsigned int count, int min, int max) | ||
| 1804 | { | ||
| 1805 | int *values = NULL; | ||
| 1806 | |||
| 1807 | #if defined(SUPPORT_RPRAND_GENERATOR) | ||
| 1808 | values = rprand_load_sequence(count, min, max); | ||
| 1809 | #else | ||
| 1810 | if (count > ((unsigned int)abs(max - min) + 1)) return values; // Security check | ||
| 1811 | |||
| 1812 | values = (int *)RL_CALLOC(count, sizeof(int)); | ||
| 1813 | |||
| 1814 | int value = 0; | ||
| 1815 | bool dupValue = false; | ||
| 1816 | |||
| 1817 | for (int i = 0; i < (int)count;) | ||
| 1818 | { | ||
| 1819 | value = (rand()%(abs(max - min) + 1) + min); | ||
| 1820 | dupValue = false; | ||
| 1821 | |||
| 1822 | for (int j = 0; j < i; j++) | ||
| 1823 | { | ||
| 1824 | if (values[j] == value) | ||
| 1825 | { | ||
| 1826 | dupValue = true; | ||
| 1827 | break; | ||
| 1828 | } | ||
| 1829 | } | ||
| 1830 | |||
| 1831 | if (!dupValue) | ||
| 1832 | { | ||
| 1833 | values[i] = value; | ||
| 1834 | i++; | ||
| 1835 | } | ||
| 1836 | } | ||
| 1837 | #endif | ||
| 1838 | return values; | ||
| 1839 | } | ||
| 1840 | |||
| 1841 | // Unload random values sequence | ||
| 1842 | void UnloadRandomSequence(int *sequence) | ||
| 1843 | { | ||
| 1844 | #if defined(SUPPORT_RPRAND_GENERATOR) | ||
| 1845 | rprand_unload_sequence(sequence); | ||
| 1846 | #else | ||
| 1847 | RL_FREE(sequence); | ||
| 1848 | #endif | ||
| 1849 | } | ||
| 1850 | |||
| 1851 | // Takes a screenshot of current screen | ||
| 1852 | // NOTE: Provided fileName should not contain paths, saving to working directory | ||
| 1853 | void TakeScreenshot(const char *fileName) | ||
| 1854 | { | ||
| 1855 | #if defined(SUPPORT_MODULE_RTEXTURES) | ||
| 1856 | // Security check to (partially) avoid malicious code | ||
| 1857 | if (strchr(fileName, '\'') != NULL) { TRACELOG(LOG_WARNING, "SYSTEM: Provided fileName could be potentially malicious, avoid [\'] character"); return; } | ||
| 1858 | |||
| 1859 | Vector2 scale = GetWindowScaleDPI(); | ||
| 1860 | unsigned char *imgData = rlReadScreenPixels((int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y)); | ||
| 1861 | Image image = { imgData, (int)((float)CORE.Window.render.width*scale.x), (int)((float)CORE.Window.render.height*scale.y), 1, PIXELFORMAT_UNCOMPRESSED_R8G8B8A8 }; | ||
| 1862 | |||
| 1863 | char path[512] = { 0 }; | ||
| 1864 | strcpy(path, TextFormat("%s/%s", CORE.Storage.basePath, GetFileName(fileName))); | ||
| 1865 | |||
| 1866 | ExportImage(image, path); // WARNING: Module required: rtextures | ||
| 1867 | RL_FREE(imgData); | ||
| 1868 | |||
| 1869 | if (FileExists(path)) TRACELOG(LOG_INFO, "SYSTEM: [%s] Screenshot taken successfully", path); | ||
| 1870 | else TRACELOG(LOG_WARNING, "SYSTEM: [%s] Screenshot could not be saved", path); | ||
| 1871 | #else | ||
| 1872 | TRACELOG(LOG_WARNING,"IMAGE: ExportImage() requires module: rtextures"); | ||
| 1873 | #endif | ||
| 1874 | } | ||
| 1875 | |||
| 1876 | // Setup window configuration flags (view FLAGS) | ||
| 1877 | // NOTE: This function is expected to be called before window creation, | ||
| 1878 | // because it sets up some flags for the window creation process | ||
| 1879 | // To configure window states after creation, just use SetWindowState() | ||
| 1880 | void SetConfigFlags(unsigned int flags) | ||
| 1881 | { | ||
| 1882 | // Selected flags are set but not evaluated at this point, | ||
| 1883 | // flag evaluation happens at InitWindow() or SetWindowState() | ||
| 1884 | CORE.Window.flags |= flags; | ||
| 1885 | } | ||
| 1886 | |||
| 1887 | //---------------------------------------------------------------------------------- | ||
| 1888 | // Module Functions Definition: File system | ||
| 1889 | //---------------------------------------------------------------------------------- | ||
| 1890 | |||
| 1891 | // Check if the file exists | ||
| 1892 | bool FileExists(const char *fileName) | ||
| 1893 | { | ||
| 1894 | bool result = false; | ||
| 1895 | |||
| 1896 | #if defined(_WIN32) | ||
| 1897 | if (_access(fileName, 0) != -1) result = true; | ||
| 1898 | #else | ||
| 1899 | if (access(fileName, F_OK) != -1) result = true; | ||
| 1900 | #endif | ||
| 1901 | |||
| 1902 | // NOTE: Alternatively, stat() can be used instead of access() | ||
| 1903 | //#include <sys/stat.h> | ||
| 1904 | //struct stat statbuf; | ||
| 1905 | //if (stat(filename, &statbuf) == 0) result = true; | ||
| 1906 | |||
| 1907 | return result; | ||
| 1908 | } | ||
| 1909 | |||
| 1910 | // Check file extension | ||
| 1911 | // NOTE: Extensions checking is not case-sensitive | ||
| 1912 | bool IsFileExtension(const char *fileName, const char *ext) | ||
| 1913 | { | ||
| 1914 | #define MAX_FILE_EXTENSION_LENGTH 16 | ||
| 1915 | |||
| 1916 | bool result = false; | ||
| 1917 | const char *fileExt = GetFileExtension(fileName); | ||
| 1918 | |||
| 1919 | if (fileExt != NULL) | ||
| 1920 | { | ||
| 1921 | #if defined(SUPPORT_MODULE_RTEXT) && defined(SUPPORT_TEXT_MANIPULATION) | ||
| 1922 | int extCount = 0; | ||
| 1923 | const char **checkExts = TextSplit(ext, ';', &extCount); // WARNING: Module required: rtext | ||
| 1924 | |||
| 1925 | char fileExtLower[MAX_FILE_EXTENSION_LENGTH + 1] = { 0 }; | ||
| 1926 | strncpy(fileExtLower, TextToLower(fileExt), MAX_FILE_EXTENSION_LENGTH); // WARNING: Module required: rtext | ||
| 1927 | |||
| 1928 | for (int i = 0; i < extCount; i++) | ||
| 1929 | { | ||
| 1930 | if (strcmp(fileExtLower, TextToLower(checkExts[i])) == 0) | ||
| 1931 | { | ||
| 1932 | result = true; | ||
| 1933 | break; | ||
| 1934 | } | ||
| 1935 | } | ||
| 1936 | #else | ||
| 1937 | if (strcmp(fileExt, ext) == 0) result = true; | ||
| 1938 | #endif | ||
| 1939 | } | ||
| 1940 | |||
| 1941 | return result; | ||
| 1942 | } | ||
| 1943 | |||
| 1944 | // Check if a directory path exists | ||
| 1945 | bool DirectoryExists(const char *dirPath) | ||
| 1946 | { | ||
| 1947 | bool result = false; | ||
| 1948 | DIR *dir = opendir(dirPath); | ||
| 1949 | |||
| 1950 | if (dir != NULL) | ||
| 1951 | { | ||
| 1952 | result = true; | ||
| 1953 | closedir(dir); | ||
| 1954 | } | ||
| 1955 | |||
| 1956 | return result; | ||
| 1957 | } | ||
| 1958 | |||
| 1959 | // Get file length in bytes | ||
| 1960 | // NOTE: GetFileSize() conflicts with windows.h | ||
| 1961 | int GetFileLength(const char *fileName) | ||
| 1962 | { | ||
| 1963 | int size = 0; | ||
| 1964 | |||
| 1965 | // NOTE: On Unix-like systems, it can by used the POSIX system call: stat(), | ||
| 1966 | // but depending on the platform that call could not be available | ||
| 1967 | //struct stat result = { 0 }; | ||
| 1968 | //stat(fileName, &result); | ||
| 1969 | //return result.st_size; | ||
| 1970 | |||
| 1971 | FILE *file = fopen(fileName, "rb"); | ||
| 1972 | |||
| 1973 | if (file != NULL) | ||
| 1974 | { | ||
| 1975 | fseek(file, 0L, SEEK_END); | ||
| 1976 | long int fileSize = ftell(file); | ||
| 1977 | |||
| 1978 | // Check for size overflow (INT_MAX) | ||
| 1979 | if (fileSize > 2147483647) TRACELOG(LOG_WARNING, "[%s] File size overflows expected limit, do not use GetFileLength()", fileName); | ||
| 1980 | else size = (int)fileSize; | ||
| 1981 | |||
| 1982 | fclose(file); | ||
| 1983 | } | ||
| 1984 | |||
| 1985 | return size; | ||
| 1986 | } | ||
| 1987 | |||
| 1988 | // Get pointer to extension for a filename string (includes the dot: .png) | ||
| 1989 | const char *GetFileExtension(const char *fileName) | ||
| 1990 | { | ||
| 1991 | const char *dot = strrchr(fileName, '.'); | ||
| 1992 | |||
| 1993 | if (!dot || dot == fileName) return NULL; | ||
| 1994 | |||
| 1995 | return dot; | ||
| 1996 | } | ||
| 1997 | |||
| 1998 | // String pointer reverse break: returns right-most occurrence of charset in s | ||
| 1999 | static const char *strprbrk(const char *s, const char *charset) | ||
| 2000 | { | ||
| 2001 | const char *latestMatch = NULL; | ||
| 2002 | |||
| 2003 | for (; s = strpbrk(s, charset), s != NULL; latestMatch = s++) { } | ||
| 2004 | |||
| 2005 | return latestMatch; | ||
| 2006 | } | ||
| 2007 | |||
| 2008 | // Get pointer to filename for a path string | ||
| 2009 | const char *GetFileName(const char *filePath) | ||
| 2010 | { | ||
| 2011 | const char *fileName = NULL; | ||
| 2012 | |||
| 2013 | if (filePath != NULL) fileName = strprbrk(filePath, "\\/"); | ||
| 2014 | |||
| 2015 | if (fileName == NULL) return filePath; | ||
| 2016 | |||
| 2017 | return fileName + 1; | ||
| 2018 | } | ||
| 2019 | |||
| 2020 | // Get filename string without extension (uses static string) | ||
| 2021 | const char *GetFileNameWithoutExt(const char *filePath) | ||
| 2022 | { | ||
| 2023 | #define MAX_FILENAME_LENGTH 256 | ||
| 2024 | |||
| 2025 | static char fileName[MAX_FILENAME_LENGTH] = { 0 }; | ||
| 2026 | memset(fileName, 0, MAX_FILENAME_LENGTH); | ||
| 2027 | |||
| 2028 | if (filePath != NULL) | ||
| 2029 | { | ||
| 2030 | strcpy(fileName, GetFileName(filePath)); // Get filename.ext without path | ||
| 2031 | int size = (int)strlen(fileName); // Get size in bytes | ||
| 2032 | |||
| 2033 | for (int i = size; i > 0; i--) // Reverse search '.' | ||
| 2034 | { | ||
| 2035 | if (fileName[i] == '.') | ||
| 2036 | { | ||
| 2037 | // NOTE: We break on first '.' found | ||
| 2038 | fileName[i] = '\0'; | ||
| 2039 | break; | ||
| 2040 | } | ||
| 2041 | } | ||
| 2042 | } | ||
| 2043 | |||
| 2044 | return fileName; | ||
| 2045 | } | ||
| 2046 | |||
| 2047 | // Get directory for a given filePath | ||
| 2048 | const char *GetDirectoryPath(const char *filePath) | ||
| 2049 | { | ||
| 2050 | /* | ||
| 2051 | // NOTE: Directory separator is different in Windows and other platforms, | ||
| 2052 | // fortunately, Windows also support the '/' separator, that's the one should be used | ||
| 2053 | #if defined(_WIN32) | ||
| 2054 | char separator = '\\'; | ||
| 2055 | #else | ||
| 2056 | char separator = '/'; | ||
| 2057 | #endif | ||
| 2058 | */ | ||
| 2059 | const char *lastSlash = NULL; | ||
| 2060 | static char dirPath[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 2061 | memset(dirPath, 0, MAX_FILEPATH_LENGTH); | ||
| 2062 | |||
| 2063 | // In case provided path does not contain a root drive letter (C:\, D:\) nor leading path separator (\, /), | ||
| 2064 | // we add the current directory path to dirPath | ||
| 2065 | if (filePath[1] != ':' && filePath[0] != '\\' && filePath[0] != '/') | ||
| 2066 | { | ||
| 2067 | // For security, we set starting path to current directory, | ||
| 2068 | // obtained path will be concatenated to this | ||
| 2069 | dirPath[0] = '.'; | ||
| 2070 | dirPath[1] = '/'; | ||
| 2071 | } | ||
| 2072 | |||
| 2073 | lastSlash = strprbrk(filePath, "\\/"); | ||
| 2074 | if (lastSlash) | ||
| 2075 | { | ||
| 2076 | if (lastSlash == filePath) | ||
| 2077 | { | ||
| 2078 | // The last and only slash is the leading one: path is in a root directory | ||
| 2079 | dirPath[0] = filePath[0]; | ||
| 2080 | dirPath[1] = '\0'; | ||
| 2081 | } | ||
| 2082 | else | ||
| 2083 | { | ||
| 2084 | // NOTE: Be careful, strncpy() is not safe, it does not care about '\0' | ||
| 2085 | char *dirPathPtr = dirPath; | ||
| 2086 | if ((filePath[1] != ':') && (filePath[0] != '\\') && (filePath[0] != '/')) dirPathPtr += 2; // Skip drive letter, "C:" | ||
| 2087 | memcpy(dirPathPtr, filePath, strlen(filePath) - (strlen(lastSlash) - 1)); | ||
| 2088 | dirPath[strlen(filePath) - strlen(lastSlash) + (((filePath[1] != ':') && (filePath[0] != '\\') && (filePath[0] != '/'))? 2 : 0)] = '\0'; // Add '\0' manually | ||
| 2089 | } | ||
| 2090 | } | ||
| 2091 | |||
| 2092 | return dirPath; | ||
| 2093 | } | ||
| 2094 | |||
| 2095 | // Get previous directory path for a given path | ||
| 2096 | const char *GetPrevDirectoryPath(const char *dirPath) | ||
| 2097 | { | ||
| 2098 | static char prevDirPath[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 2099 | memset(prevDirPath, 0, MAX_FILEPATH_LENGTH); | ||
| 2100 | int pathLen = (int)strlen(dirPath); | ||
| 2101 | |||
| 2102 | if (pathLen <= 3) strcpy(prevDirPath, dirPath); | ||
| 2103 | |||
| 2104 | for (int i = (pathLen - 1); (i >= 0) && (pathLen > 3); i--) | ||
| 2105 | { | ||
| 2106 | if ((dirPath[i] == '\\') || (dirPath[i] == '/')) | ||
| 2107 | { | ||
| 2108 | // Check for root: "C:\" or "/" | ||
| 2109 | if (((i == 2) && (dirPath[1] ==':')) || (i == 0)) i++; | ||
| 2110 | |||
| 2111 | strncpy(prevDirPath, dirPath, i); | ||
| 2112 | break; | ||
| 2113 | } | ||
| 2114 | } | ||
| 2115 | |||
| 2116 | return prevDirPath; | ||
| 2117 | } | ||
| 2118 | |||
| 2119 | // Get current working directory | ||
| 2120 | const char *GetWorkingDirectory(void) | ||
| 2121 | { | ||
| 2122 | static char currentDir[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 2123 | memset(currentDir, 0, MAX_FILEPATH_LENGTH); | ||
| 2124 | |||
| 2125 | char *path = GETCWD(currentDir, MAX_FILEPATH_LENGTH - 1); | ||
| 2126 | |||
| 2127 | return path; | ||
| 2128 | } | ||
| 2129 | |||
| 2130 | const char *GetApplicationDirectory(void) | ||
| 2131 | { | ||
| 2132 | static char appDir[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 2133 | memset(appDir, 0, MAX_FILEPATH_LENGTH); | ||
| 2134 | |||
| 2135 | #if defined(_WIN32) | ||
| 2136 | int len = 0; | ||
| 2137 | #if defined(UNICODE) | ||
| 2138 | unsigned short widePath[MAX_PATH]; | ||
| 2139 | len = GetModuleFileNameW(NULL, widePath, MAX_PATH); | ||
| 2140 | len = WideCharToMultiByte(0, 0, widePath, len, appDir, MAX_PATH, NULL, NULL); | ||
| 2141 | #else | ||
| 2142 | len = GetModuleFileNameA(NULL, appDir, MAX_PATH); | ||
| 2143 | #endif | ||
| 2144 | if (len > 0) | ||
| 2145 | { | ||
| 2146 | for (int i = len; i >= 0; --i) | ||
| 2147 | { | ||
| 2148 | if (appDir[i] == '\\') | ||
| 2149 | { | ||
| 2150 | appDir[i + 1] = '\0'; | ||
| 2151 | break; | ||
| 2152 | } | ||
| 2153 | } | ||
| 2154 | } | ||
| 2155 | else | ||
| 2156 | { | ||
| 2157 | appDir[0] = '.'; | ||
| 2158 | appDir[1] = '\\'; | ||
| 2159 | } | ||
| 2160 | |||
| 2161 | #elif defined(__linux__) | ||
| 2162 | unsigned int size = sizeof(appDir); | ||
| 2163 | ssize_t len = readlink("/proc/self/exe", appDir, size); | ||
| 2164 | |||
| 2165 | if (len > 0) | ||
| 2166 | { | ||
| 2167 | for (int i = len; i >= 0; --i) | ||
| 2168 | { | ||
| 2169 | if (appDir[i] == '/') | ||
| 2170 | { | ||
| 2171 | appDir[i + 1] = '\0'; | ||
| 2172 | break; | ||
| 2173 | } | ||
| 2174 | } | ||
| 2175 | } | ||
| 2176 | else | ||
| 2177 | { | ||
| 2178 | appDir[0] = '.'; | ||
| 2179 | appDir[1] = '/'; | ||
| 2180 | } | ||
| 2181 | #elif defined(__APPLE__) | ||
| 2182 | uint32_t size = sizeof(appDir); | ||
| 2183 | |||
| 2184 | if (_NSGetExecutablePath(appDir, &size) == 0) | ||
| 2185 | { | ||
| 2186 | int len = strlen(appDir); | ||
| 2187 | for (int i = len; i >= 0; --i) | ||
| 2188 | { | ||
| 2189 | if (appDir[i] == '/') | ||
| 2190 | { | ||
| 2191 | appDir[i + 1] = '\0'; | ||
| 2192 | break; | ||
| 2193 | } | ||
| 2194 | } | ||
| 2195 | } | ||
| 2196 | else | ||
| 2197 | { | ||
| 2198 | appDir[0] = '.'; | ||
| 2199 | appDir[1] = '/'; | ||
| 2200 | } | ||
| 2201 | #elif defined(__FreeBSD__) | ||
| 2202 | size_t size = sizeof(appDir); | ||
| 2203 | int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; | ||
| 2204 | |||
| 2205 | if (sysctl(mib, 4, appDir, &size, NULL, 0) == 0) | ||
| 2206 | { | ||
| 2207 | int len = strlen(appDir); | ||
| 2208 | for (int i = len; i >= 0; --i) | ||
| 2209 | { | ||
| 2210 | if (appDir[i] == '/') | ||
| 2211 | { | ||
| 2212 | appDir[i + 1] = '\0'; | ||
| 2213 | break; | ||
| 2214 | } | ||
| 2215 | } | ||
| 2216 | } | ||
| 2217 | else | ||
| 2218 | { | ||
| 2219 | appDir[0] = '.'; | ||
| 2220 | appDir[1] = '/'; | ||
| 2221 | } | ||
| 2222 | |||
| 2223 | #endif | ||
| 2224 | |||
| 2225 | return appDir; | ||
| 2226 | } | ||
| 2227 | |||
| 2228 | // Load directory filepaths | ||
| 2229 | // NOTE: Base path is prepended to the scanned filepaths | ||
| 2230 | // WARNING: Directory is scanned twice, first time to get files count | ||
| 2231 | // No recursive scanning is done! | ||
| 2232 | FilePathList LoadDirectoryFiles(const char *dirPath) | ||
| 2233 | { | ||
| 2234 | FilePathList files = { 0 }; | ||
| 2235 | unsigned int fileCounter = 0; | ||
| 2236 | |||
| 2237 | struct dirent *entity; | ||
| 2238 | DIR *dir = opendir(dirPath); | ||
| 2239 | |||
| 2240 | if (dir != NULL) // It's a directory | ||
| 2241 | { | ||
| 2242 | // SCAN 1: Count files | ||
| 2243 | while ((entity = readdir(dir)) != NULL) | ||
| 2244 | { | ||
| 2245 | // NOTE: We skip '.' (current dir) and '..' (parent dir) filepaths | ||
| 2246 | if ((strcmp(entity->d_name, ".") != 0) && (strcmp(entity->d_name, "..") != 0)) fileCounter++; | ||
| 2247 | } | ||
| 2248 | |||
| 2249 | // Memory allocation for dirFileCount | ||
| 2250 | files.capacity = fileCounter; | ||
| 2251 | files.paths = (char **)RL_MALLOC(files.capacity*sizeof(char *)); | ||
| 2252 | for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_MALLOC(MAX_FILEPATH_LENGTH*sizeof(char)); | ||
| 2253 | |||
| 2254 | closedir(dir); | ||
| 2255 | |||
| 2256 | // SCAN 2: Read filepaths | ||
| 2257 | // NOTE: Directory paths are also registered | ||
| 2258 | ScanDirectoryFiles(dirPath, &files, NULL); | ||
| 2259 | |||
| 2260 | // Security check: read files.count should match fileCounter | ||
| 2261 | if (files.count != files.capacity) TRACELOG(LOG_WARNING, "FILEIO: Read files count do not match capacity allocated"); | ||
| 2262 | } | ||
| 2263 | else TRACELOG(LOG_WARNING, "FILEIO: Failed to open requested directory"); // Maybe it's a file... | ||
| 2264 | |||
| 2265 | return files; | ||
| 2266 | } | ||
| 2267 | |||
| 2268 | // Load directory filepaths with extension filtering and recursive directory scan | ||
| 2269 | // NOTE: On recursive loading we do not pre-scan for file count, we use MAX_FILEPATH_CAPACITY | ||
| 2270 | FilePathList LoadDirectoryFilesEx(const char *basePath, const char *filter, bool scanSubdirs) | ||
| 2271 | { | ||
| 2272 | FilePathList files = { 0 }; | ||
| 2273 | |||
| 2274 | files.capacity = MAX_FILEPATH_CAPACITY; | ||
| 2275 | files.paths = (char **)RL_CALLOC(files.capacity, sizeof(char *)); | ||
| 2276 | for (unsigned int i = 0; i < files.capacity; i++) files.paths[i] = (char *)RL_CALLOC(MAX_FILEPATH_LENGTH, sizeof(char)); | ||
| 2277 | |||
| 2278 | // WARNING: basePath is always prepended to scanned paths | ||
| 2279 | if (scanSubdirs) ScanDirectoryFilesRecursively(basePath, &files, filter); | ||
| 2280 | else ScanDirectoryFiles(basePath, &files, filter); | ||
| 2281 | |||
| 2282 | return files; | ||
| 2283 | } | ||
| 2284 | |||
| 2285 | // Unload directory filepaths | ||
| 2286 | // WARNING: files.count is not reseted to 0 after unloading | ||
| 2287 | void UnloadDirectoryFiles(FilePathList files) | ||
| 2288 | { | ||
| 2289 | for (unsigned int i = 0; i < files.capacity; i++) RL_FREE(files.paths[i]); | ||
| 2290 | |||
| 2291 | RL_FREE(files.paths); | ||
| 2292 | } | ||
| 2293 | |||
| 2294 | // Create directories (including full path requested), returns 0 on success | ||
| 2295 | int MakeDirectory(const char *dirPath) | ||
| 2296 | { | ||
| 2297 | if ((dirPath == NULL) || (dirPath[0] == '\0')) return 1; // Path is not valid | ||
| 2298 | if (DirectoryExists(dirPath)) return 0; // Path already exists (is valid) | ||
| 2299 | |||
| 2300 | // Copy path string to avoid modifying original | ||
| 2301 | int len = (int)strlen(dirPath) + 1; | ||
| 2302 | char *pathcpy = (char *)RL_CALLOC(len, 1); | ||
| 2303 | memcpy(pathcpy, dirPath, len); | ||
| 2304 | |||
| 2305 | // Iterate over pathcpy, create each subdirectory as needed | ||
| 2306 | for (int i = 0; (i < len) && (pathcpy[i] != '\0'); i++) | ||
| 2307 | { | ||
| 2308 | if (pathcpy[i] == ':') i++; | ||
| 2309 | else | ||
| 2310 | { | ||
| 2311 | if ((pathcpy[i] == '\\') || (pathcpy[i] == '/')) | ||
| 2312 | { | ||
| 2313 | pathcpy[i] = '\0'; | ||
| 2314 | if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); | ||
| 2315 | pathcpy[i] = '/'; | ||
| 2316 | } | ||
| 2317 | } | ||
| 2318 | } | ||
| 2319 | |||
| 2320 | // Create final directory | ||
| 2321 | if (!DirectoryExists(pathcpy)) MKDIR(pathcpy); | ||
| 2322 | |||
| 2323 | RL_FREE(pathcpy); | ||
| 2324 | |||
| 2325 | return 0; | ||
| 2326 | } | ||
| 2327 | |||
| 2328 | // Change working directory, returns true on success | ||
| 2329 | bool ChangeDirectory(const char *dir) | ||
| 2330 | { | ||
| 2331 | bool result = CHDIR(dir); | ||
| 2332 | |||
| 2333 | if (result != 0) TRACELOG(LOG_WARNING, "SYSTEM: Failed to change to directory: %s", dir); | ||
| 2334 | |||
| 2335 | return (result == 0); | ||
| 2336 | } | ||
| 2337 | |||
| 2338 | // Check if a given path point to a file | ||
| 2339 | bool IsPathFile(const char *path) | ||
| 2340 | { | ||
| 2341 | struct stat result = { 0 }; | ||
| 2342 | stat(path, &result); | ||
| 2343 | |||
| 2344 | return S_ISREG(result.st_mode); | ||
| 2345 | } | ||
| 2346 | |||
| 2347 | // Check if fileName is valid for the platform/OS | ||
| 2348 | bool IsFileNameValid(const char *fileName) | ||
| 2349 | { | ||
| 2350 | bool valid = true; | ||
| 2351 | |||
| 2352 | if ((fileName != NULL) && (fileName[0] != '\0')) | ||
| 2353 | { | ||
| 2354 | int length = (int)strlen(fileName); | ||
| 2355 | bool allPeriods = true; | ||
| 2356 | |||
| 2357 | for (int i = 0; i < length; i++) | ||
| 2358 | { | ||
| 2359 | // Check invalid characters | ||
| 2360 | if ((fileName[i] == '<') || | ||
| 2361 | (fileName[i] == '>') || | ||
| 2362 | (fileName[i] == ':') || | ||
| 2363 | (fileName[i] == '\"') || | ||
| 2364 | (fileName[i] == '/') || | ||
| 2365 | (fileName[i] == '\\') || | ||
| 2366 | (fileName[i] == '|') || | ||
| 2367 | (fileName[i] == '?') || | ||
| 2368 | (fileName[i] == '*')) { valid = false; break; } | ||
| 2369 | |||
| 2370 | // Check non-glyph characters | ||
| 2371 | if ((unsigned char)fileName[i] < 32) { valid = false; break; } | ||
| 2372 | |||
| 2373 | // TODO: Check trailing periods/spaces? | ||
| 2374 | |||
| 2375 | // Check if filename is not all periods | ||
| 2376 | if (fileName[i] != '.') allPeriods = false; | ||
| 2377 | } | ||
| 2378 | |||
| 2379 | if (allPeriods) valid = false; | ||
| 2380 | |||
| 2381 | /* | ||
| 2382 | if (valid) | ||
| 2383 | { | ||
| 2384 | // Check invalid DOS names | ||
| 2385 | if (length >= 3) | ||
| 2386 | { | ||
| 2387 | if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'N')) || // CON | ||
| 2388 | ((fileName[0] == 'P') && (fileName[1] == 'R') && (fileName[2] == 'N')) || // PRN | ||
| 2389 | ((fileName[0] == 'A') && (fileName[1] == 'U') && (fileName[2] == 'X')) || // AUX | ||
| 2390 | ((fileName[0] == 'N') && (fileName[1] == 'U') && (fileName[2] == 'L'))) valid = false; // NUL | ||
| 2391 | } | ||
| 2392 | |||
| 2393 | if (length >= 4) | ||
| 2394 | { | ||
| 2395 | if (((fileName[0] == 'C') && (fileName[1] == 'O') && (fileName[2] == 'M') && ((fileName[3] >= '0') && (fileName[3] <= '9'))) || // COM0-9 | ||
| 2396 | ((fileName[0] == 'L') && (fileName[1] == 'P') && (fileName[2] == 'T') && ((fileName[3] >= '0') && (fileName[3] <= '9')))) valid = false; // LPT0-9 | ||
| 2397 | } | ||
| 2398 | } | ||
| 2399 | */ | ||
| 2400 | } | ||
| 2401 | |||
| 2402 | return valid; | ||
| 2403 | } | ||
| 2404 | |||
| 2405 | // Check if a file has been dropped into window | ||
| 2406 | bool IsFileDropped(void) | ||
| 2407 | { | ||
| 2408 | bool result = false; | ||
| 2409 | |||
| 2410 | if (CORE.Window.dropFileCount > 0) result = true; | ||
| 2411 | |||
| 2412 | return result; | ||
| 2413 | } | ||
| 2414 | |||
| 2415 | // Load dropped filepaths | ||
| 2416 | FilePathList LoadDroppedFiles(void) | ||
| 2417 | { | ||
| 2418 | FilePathList files = { 0 }; | ||
| 2419 | |||
| 2420 | files.count = CORE.Window.dropFileCount; | ||
| 2421 | files.paths = CORE.Window.dropFilepaths; | ||
| 2422 | |||
| 2423 | return files; | ||
| 2424 | } | ||
| 2425 | |||
| 2426 | // Unload dropped filepaths | ||
| 2427 | void UnloadDroppedFiles(FilePathList files) | ||
| 2428 | { | ||
| 2429 | // WARNING: files pointers are the same as internal ones | ||
| 2430 | |||
| 2431 | if (files.count > 0) | ||
| 2432 | { | ||
| 2433 | for (unsigned int i = 0; i < files.count; i++) RL_FREE(files.paths[i]); | ||
| 2434 | |||
| 2435 | RL_FREE(files.paths); | ||
| 2436 | |||
| 2437 | CORE.Window.dropFileCount = 0; | ||
| 2438 | CORE.Window.dropFilepaths = NULL; | ||
| 2439 | } | ||
| 2440 | } | ||
| 2441 | |||
| 2442 | // Get file modification time (last write time) | ||
| 2443 | long GetFileModTime(const char *fileName) | ||
| 2444 | { | ||
| 2445 | struct stat result = { 0 }; | ||
| 2446 | long modTime = 0; | ||
| 2447 | |||
| 2448 | if (stat(fileName, &result) == 0) | ||
| 2449 | { | ||
| 2450 | time_t mod = result.st_mtime; | ||
| 2451 | |||
| 2452 | modTime = (long)mod; | ||
| 2453 | } | ||
| 2454 | |||
| 2455 | return modTime; | ||
| 2456 | } | ||
| 2457 | |||
| 2458 | //---------------------------------------------------------------------------------- | ||
| 2459 | // Module Functions Definition: Compression and Encoding | ||
| 2460 | //---------------------------------------------------------------------------------- | ||
| 2461 | |||
| 2462 | // Compress data (DEFLATE algorithm) | ||
| 2463 | unsigned char *CompressData(const unsigned char *data, int dataSize, int *compDataSize) | ||
| 2464 | { | ||
| 2465 | #define COMPRESSION_QUALITY_DEFLATE 8 | ||
| 2466 | |||
| 2467 | unsigned char *compData = NULL; | ||
| 2468 | |||
| 2469 | #if defined(SUPPORT_COMPRESSION_API) | ||
| 2470 | // Compress data and generate a valid DEFLATE stream | ||
| 2471 | struct sdefl *sdefl = RL_CALLOC(1, sizeof(struct sdefl)); // WARNING: Possible stack overflow, struct sdefl is almost 1MB | ||
| 2472 | int bounds = sdefl_bound(dataSize); | ||
| 2473 | compData = (unsigned char *)RL_CALLOC(bounds, 1); | ||
| 2474 | |||
| 2475 | *compDataSize = sdeflate(sdefl, compData, data, dataSize, COMPRESSION_QUALITY_DEFLATE); // Compression level 8, same as stbiw | ||
| 2476 | RL_FREE(sdefl); | ||
| 2477 | |||
| 2478 | TRACELOG(LOG_INFO, "SYSTEM: Compress data: Original size: %i -> Comp. size: %i", dataSize, *compDataSize); | ||
| 2479 | #endif | ||
| 2480 | |||
| 2481 | return compData; | ||
| 2482 | } | ||
| 2483 | |||
| 2484 | // Decompress data (DEFLATE algorithm) | ||
| 2485 | unsigned char *DecompressData(const unsigned char *compData, int compDataSize, int *dataSize) | ||
| 2486 | { | ||
| 2487 | unsigned char *data = NULL; | ||
| 2488 | |||
| 2489 | #if defined(SUPPORT_COMPRESSION_API) | ||
| 2490 | // Decompress data from a valid DEFLATE stream | ||
| 2491 | data = (unsigned char *)RL_CALLOC(MAX_DECOMPRESSION_SIZE*1024*1024, 1); | ||
| 2492 | int length = sinflate(data, MAX_DECOMPRESSION_SIZE*1024*1024, compData, compDataSize); | ||
| 2493 | |||
| 2494 | // WARNING: RL_REALLOC can make (and leave) data copies in memory, be careful with sensitive compressed data! | ||
| 2495 | // TODO: Use a different approach, create another buffer, copy data manually to it and wipe original buffer memory | ||
| 2496 | unsigned char *temp = (unsigned char *)RL_REALLOC(data, length); | ||
| 2497 | |||
| 2498 | if (temp != NULL) data = temp; | ||
| 2499 | else TRACELOG(LOG_WARNING, "SYSTEM: Failed to re-allocate required decompression memory"); | ||
| 2500 | |||
| 2501 | *dataSize = length; | ||
| 2502 | |||
| 2503 | TRACELOG(LOG_INFO, "SYSTEM: Decompress data: Comp. size: %i -> Original size: %i", compDataSize, *dataSize); | ||
| 2504 | #endif | ||
| 2505 | |||
| 2506 | return data; | ||
| 2507 | } | ||
| 2508 | |||
| 2509 | // Encode data to Base64 string | ||
| 2510 | char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize) | ||
| 2511 | { | ||
| 2512 | static const unsigned char base64encodeTable[] = { | ||
| 2513 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', | ||
| 2514 | 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', | ||
| 2515 | 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' | ||
| 2516 | }; | ||
| 2517 | |||
| 2518 | static const int modTable[] = { 0, 2, 1 }; | ||
| 2519 | |||
| 2520 | *outputSize = 4*((dataSize + 2)/3); | ||
| 2521 | |||
| 2522 | char *encodedData = (char *)RL_MALLOC(*outputSize); | ||
| 2523 | |||
| 2524 | if (encodedData == NULL) return NULL; // Security check | ||
| 2525 | |||
| 2526 | for (int i = 0, j = 0; i < dataSize;) | ||
| 2527 | { | ||
| 2528 | unsigned int octetA = (i < dataSize)? (unsigned char)data[i++] : 0; | ||
| 2529 | unsigned int octetB = (i < dataSize)? (unsigned char)data[i++] : 0; | ||
| 2530 | unsigned int octetC = (i < dataSize)? (unsigned char)data[i++] : 0; | ||
| 2531 | |||
| 2532 | unsigned int triple = (octetA << 0x10) + (octetB << 0x08) + octetC; | ||
| 2533 | |||
| 2534 | encodedData[j++] = base64encodeTable[(triple >> 3*6) & 0x3F]; | ||
| 2535 | encodedData[j++] = base64encodeTable[(triple >> 2*6) & 0x3F]; | ||
| 2536 | encodedData[j++] = base64encodeTable[(triple >> 1*6) & 0x3F]; | ||
| 2537 | encodedData[j++] = base64encodeTable[(triple >> 0*6) & 0x3F]; | ||
| 2538 | } | ||
| 2539 | |||
| 2540 | for (int i = 0; i < modTable[dataSize%3]; i++) encodedData[*outputSize - 1 - i] = '='; // Padding character | ||
| 2541 | |||
| 2542 | return encodedData; | ||
| 2543 | } | ||
| 2544 | |||
| 2545 | // Decode Base64 string data | ||
| 2546 | unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize) | ||
| 2547 | { | ||
| 2548 | static const unsigned char base64decodeTable[] = { | ||
| 2549 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
| 2550 | 0, 0, 0, 62, 0, 0, 0, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, | ||
| 2551 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 0, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, | ||
| 2552 | 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 | ||
| 2553 | }; | ||
| 2554 | |||
| 2555 | // Get output size of Base64 input data | ||
| 2556 | int outSize = 0; | ||
| 2557 | for (int i = 0; data[4*i] != 0; i++) | ||
| 2558 | { | ||
| 2559 | if (data[4*i + 3] == '=') | ||
| 2560 | { | ||
| 2561 | if (data[4*i + 2] == '=') outSize += 1; | ||
| 2562 | else outSize += 2; | ||
| 2563 | } | ||
| 2564 | else outSize += 3; | ||
| 2565 | } | ||
| 2566 | |||
| 2567 | // Allocate memory to store decoded Base64 data | ||
| 2568 | unsigned char *decodedData = (unsigned char *)RL_MALLOC(outSize); | ||
| 2569 | |||
| 2570 | for (int i = 0; i < outSize/3; i++) | ||
| 2571 | { | ||
| 2572 | unsigned char a = base64decodeTable[(int)data[4*i]]; | ||
| 2573 | unsigned char b = base64decodeTable[(int)data[4*i + 1]]; | ||
| 2574 | unsigned char c = base64decodeTable[(int)data[4*i + 2]]; | ||
| 2575 | unsigned char d = base64decodeTable[(int)data[4*i + 3]]; | ||
| 2576 | |||
| 2577 | decodedData[3*i] = (a << 2) | (b >> 4); | ||
| 2578 | decodedData[3*i + 1] = (b << 4) | (c >> 2); | ||
| 2579 | decodedData[3*i + 2] = (c << 6) | d; | ||
| 2580 | } | ||
| 2581 | |||
| 2582 | if (outSize%3 == 1) | ||
| 2583 | { | ||
| 2584 | int n = outSize/3; | ||
| 2585 | unsigned char a = base64decodeTable[(int)data[4*n]]; | ||
| 2586 | unsigned char b = base64decodeTable[(int)data[4*n + 1]]; | ||
| 2587 | decodedData[outSize - 1] = (a << 2) | (b >> 4); | ||
| 2588 | } | ||
| 2589 | else if (outSize%3 == 2) | ||
| 2590 | { | ||
| 2591 | int n = outSize/3; | ||
| 2592 | unsigned char a = base64decodeTable[(int)data[4*n]]; | ||
| 2593 | unsigned char b = base64decodeTable[(int)data[4*n + 1]]; | ||
| 2594 | unsigned char c = base64decodeTable[(int)data[4*n + 2]]; | ||
| 2595 | decodedData[outSize - 2] = (a << 2) | (b >> 4); | ||
| 2596 | decodedData[outSize - 1] = (b << 4) | (c >> 2); | ||
| 2597 | } | ||
| 2598 | |||
| 2599 | *outputSize = outSize; | ||
| 2600 | return decodedData; | ||
| 2601 | } | ||
| 2602 | |||
| 2603 | // Compute CRC32 hash code | ||
| 2604 | unsigned int ComputeCRC32(unsigned char *data, int dataSize) | ||
| 2605 | { | ||
| 2606 | static unsigned int crcTable[256] = { | ||
| 2607 | 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, | ||
| 2608 | 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, | ||
| 2609 | 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, | ||
| 2610 | 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, | ||
| 2611 | 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, | ||
| 2612 | 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, | ||
| 2613 | 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, | ||
| 2614 | 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, | ||
| 2615 | 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, | ||
| 2616 | 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, | ||
| 2617 | 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, | ||
| 2618 | 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, | ||
| 2619 | 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, | ||
| 2620 | 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, | ||
| 2621 | 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, | ||
| 2622 | 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, | ||
| 2623 | 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, | ||
| 2624 | 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, | ||
| 2625 | 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, | ||
| 2626 | 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, | ||
| 2627 | 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, | ||
| 2628 | 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, | ||
| 2629 | 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, | ||
| 2630 | 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, | ||
| 2631 | 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, | ||
| 2632 | 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, | ||
| 2633 | 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, | ||
| 2634 | 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, | ||
| 2635 | 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, | ||
| 2636 | 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, | ||
| 2637 | 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, | ||
| 2638 | 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D | ||
| 2639 | }; | ||
| 2640 | |||
| 2641 | unsigned int crc = ~0u; | ||
| 2642 | |||
| 2643 | for (int i = 0; i < dataSize; i++) crc = (crc >> 8) ^ crcTable[data[i] ^ (crc & 0xff)]; | ||
| 2644 | |||
| 2645 | return ~crc; | ||
| 2646 | } | ||
| 2647 | |||
| 2648 | // Compute MD5 hash code | ||
| 2649 | // NOTE: Returns a static int[4] array (16 bytes) | ||
| 2650 | unsigned int *ComputeMD5(unsigned char *data, int dataSize) | ||
| 2651 | { | ||
| 2652 | #define ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) | ||
| 2653 | |||
| 2654 | static unsigned int hash[4] = { 0 }; // Hash to be returned | ||
| 2655 | |||
| 2656 | // WARNING: All variables are unsigned 32 bit and wrap modulo 2^32 when calculating | ||
| 2657 | |||
| 2658 | // NOTE: r specifies the per-round shift amounts | ||
| 2659 | unsigned int r[] = { | ||
| 2660 | 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, | ||
| 2661 | 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, | ||
| 2662 | 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, | ||
| 2663 | 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 | ||
| 2664 | }; | ||
| 2665 | |||
| 2666 | // Using binary integer part of the sines of integers (in radians) as constants | ||
| 2667 | unsigned int k[] = { | ||
| 2668 | 0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee, | ||
| 2669 | 0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501, | ||
| 2670 | 0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be, | ||
| 2671 | 0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821, | ||
| 2672 | 0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa, | ||
| 2673 | 0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8, | ||
| 2674 | 0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed, | ||
| 2675 | 0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a, | ||
| 2676 | 0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c, | ||
| 2677 | 0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70, | ||
| 2678 | 0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05, | ||
| 2679 | 0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665, | ||
| 2680 | 0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039, | ||
| 2681 | 0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1, | ||
| 2682 | 0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1, | ||
| 2683 | 0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 | ||
| 2684 | }; | ||
| 2685 | |||
| 2686 | hash[0] = 0x67452301; | ||
| 2687 | hash[1] = 0xefcdab89; | ||
| 2688 | hash[2] = 0x98badcfe; | ||
| 2689 | hash[3] = 0x10325476; | ||
| 2690 | |||
| 2691 | // Pre-processing: adding a single 1 bit | ||
| 2692 | // Append '1' bit to message | ||
| 2693 | // NOTE: The input bytes are considered as bits strings, | ||
| 2694 | // where the first bit is the most significant bit of the byte | ||
| 2695 | |||
| 2696 | // Pre-processing: padding with zeros | ||
| 2697 | // Append '0' bit until message length in bit 448 (mod 512) | ||
| 2698 | // Append length mod (2 pow 64) to message | ||
| 2699 | |||
| 2700 | int newDataSize = ((((dataSize + 8)/64) + 1)*64) - 8; | ||
| 2701 | |||
| 2702 | unsigned char *msg = RL_CALLOC(newDataSize + 64, 1); // Initialize with '0' bits, allocating 64 extra bytes | ||
| 2703 | memcpy(msg, data, dataSize); | ||
| 2704 | msg[dataSize] = 128; // Write the '1' bit | ||
| 2705 | |||
| 2706 | unsigned int bitsLen = 8*dataSize; | ||
| 2707 | memcpy(msg + newDataSize, &bitsLen, 4); // Append the len in bits at the end of the buffer | ||
| 2708 | |||
| 2709 | // Process the message in successive 512-bit chunks for each 512-bit chunk of message | ||
| 2710 | for (int offset = 0; offset < newDataSize; offset += (512/8)) | ||
| 2711 | { | ||
| 2712 | // Break chunk into sixteen 32-bit words w[j], 0 <= j <= 15 | ||
| 2713 | unsigned int *w = (unsigned int *)(msg + offset); | ||
| 2714 | |||
| 2715 | // Initialize hash value for this chunk | ||
| 2716 | unsigned int a = hash[0]; | ||
| 2717 | unsigned int b = hash[1]; | ||
| 2718 | unsigned int c = hash[2]; | ||
| 2719 | unsigned int d = hash[3]; | ||
| 2720 | |||
| 2721 | for (int i = 0; i < 64; i++) | ||
| 2722 | { | ||
| 2723 | unsigned int f = 0; | ||
| 2724 | unsigned int g = 0; | ||
| 2725 | |||
| 2726 | if (i < 16) | ||
| 2727 | { | ||
| 2728 | f = (b & c) | ((~b) & d); | ||
| 2729 | g = i; | ||
| 2730 | } | ||
| 2731 | else if (i < 32) | ||
| 2732 | { | ||
| 2733 | f = (d & b) | ((~d) & c); | ||
| 2734 | g = (5*i + 1)%16; | ||
| 2735 | } | ||
| 2736 | else if (i < 48) | ||
| 2737 | { | ||
| 2738 | f = b ^ c ^ d; | ||
| 2739 | g = (3*i + 5)%16; | ||
| 2740 | } | ||
| 2741 | else | ||
| 2742 | { | ||
| 2743 | f = c ^ (b | (~d)); | ||
| 2744 | g = (7*i)%16; | ||
| 2745 | } | ||
| 2746 | |||
| 2747 | unsigned int temp = d; | ||
| 2748 | d = c; | ||
| 2749 | c = b; | ||
| 2750 | b = b + ROTATE_LEFT((a + f + k[i] + w[g]), r[i]); | ||
| 2751 | a = temp; | ||
| 2752 | } | ||
| 2753 | |||
| 2754 | // Add chunk's hash to result so far | ||
| 2755 | hash[0] += a; | ||
| 2756 | hash[1] += b; | ||
| 2757 | hash[2] += c; | ||
| 2758 | hash[3] += d; | ||
| 2759 | } | ||
| 2760 | |||
| 2761 | RL_FREE(msg); | ||
| 2762 | |||
| 2763 | return hash; | ||
| 2764 | } | ||
| 2765 | |||
| 2766 | // Compute SHA-1 hash code | ||
| 2767 | // NOTE: Returns a static int[5] array (20 bytes) | ||
| 2768 | unsigned int *ComputeSHA1(unsigned char *data, int dataSize) { | ||
| 2769 | #define ROTATE_LEFT(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) | ||
| 2770 | |||
| 2771 | static unsigned int hash[5] = { 0 }; // Hash to be returned | ||
| 2772 | |||
| 2773 | // Initialize hash values | ||
| 2774 | hash[0] = 0x67452301; | ||
| 2775 | hash[1] = 0xEFCDAB89; | ||
| 2776 | hash[2] = 0x98BADCFE; | ||
| 2777 | hash[3] = 0x10325476; | ||
| 2778 | hash[4] = 0xC3D2E1F0; | ||
| 2779 | |||
| 2780 | // Pre-processing: adding a single 1 bit | ||
| 2781 | // Append '1' bit to message | ||
| 2782 | // NOTE: The input bytes are considered as bits strings, | ||
| 2783 | // where the first bit is the most significant bit of the byte | ||
| 2784 | |||
| 2785 | // Pre-processing: padding with zeros | ||
| 2786 | // Append '0' bit until message length in bit 448 (mod 512) | ||
| 2787 | // Append length mod (2 pow 64) to message | ||
| 2788 | |||
| 2789 | int newDataSize = ((((dataSize + 8)/64) + 1)*64); | ||
| 2790 | |||
| 2791 | unsigned char *msg = RL_CALLOC(newDataSize, 1); // Initialize with '0' bits | ||
| 2792 | memcpy(msg, data, dataSize); | ||
| 2793 | msg[dataSize] = 128; // Write the '1' bit | ||
| 2794 | |||
| 2795 | unsigned int bitsLen = 8*dataSize; | ||
| 2796 | msg[newDataSize-1] = bitsLen; | ||
| 2797 | |||
| 2798 | // Process the message in successive 512-bit chunks | ||
| 2799 | for (int offset = 0; offset < newDataSize; offset += (512/8)) | ||
| 2800 | { | ||
| 2801 | // Break chunk into sixteen 32-bit words w[j], 0 <= j <= 15 | ||
| 2802 | unsigned int w[80] = {0}; | ||
| 2803 | for (int i = 0; i < 16; i++) { | ||
| 2804 | w[i] = (msg[offset + (i * 4) + 0] << 24) | | ||
| 2805 | (msg[offset + (i * 4) + 1] << 16) | | ||
| 2806 | (msg[offset + (i * 4) + 2] << 8) | | ||
| 2807 | (msg[offset + (i * 4) + 3]); | ||
| 2808 | } | ||
| 2809 | |||
| 2810 | // Message schedule: extend the sixteen 32-bit words into eighty 32-bit words: | ||
| 2811 | for (int i = 16; i < 80; ++i) { | ||
| 2812 | w[i] = ROTATE_LEFT(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1); | ||
| 2813 | } | ||
| 2814 | |||
| 2815 | // Initialize hash value for this chunk | ||
| 2816 | unsigned int a = hash[0]; | ||
| 2817 | unsigned int b = hash[1]; | ||
| 2818 | unsigned int c = hash[2]; | ||
| 2819 | unsigned int d = hash[3]; | ||
| 2820 | unsigned int e = hash[4]; | ||
| 2821 | |||
| 2822 | for (int i = 0; i < 80; i++) | ||
| 2823 | { | ||
| 2824 | unsigned int f = 0; | ||
| 2825 | unsigned int k = 0; | ||
| 2826 | |||
| 2827 | if (i < 20) { | ||
| 2828 | f = (b & c) | ((~b) & d); | ||
| 2829 | k = 0x5A827999; | ||
| 2830 | } else if (i < 40) { | ||
| 2831 | f = b ^ c ^ d; | ||
| 2832 | k = 0x6ED9EBA1; | ||
| 2833 | } else if (i < 60) { | ||
| 2834 | f = (b & c) | (b & d) | (c & d); | ||
| 2835 | k = 0x8F1BBCDC; | ||
| 2836 | } else { | ||
| 2837 | f = b ^ c ^ d; | ||
| 2838 | k = 0xCA62C1D6; | ||
| 2839 | } | ||
| 2840 | |||
| 2841 | unsigned int temp = ROTATE_LEFT(a, 5) + f + e + k + w[i]; | ||
| 2842 | e = d; | ||
| 2843 | d = c; | ||
| 2844 | c = ROTATE_LEFT(b, 30); | ||
| 2845 | b = a; | ||
| 2846 | a = temp; | ||
| 2847 | } | ||
| 2848 | |||
| 2849 | // Add this chunk's hash to result so far | ||
| 2850 | hash[0] += a; | ||
| 2851 | hash[1] += b; | ||
| 2852 | hash[2] += c; | ||
| 2853 | hash[3] += d; | ||
| 2854 | hash[4] += e; | ||
| 2855 | } | ||
| 2856 | |||
| 2857 | free(msg); | ||
| 2858 | |||
| 2859 | return hash; | ||
| 2860 | } | ||
| 2861 | |||
| 2862 | //---------------------------------------------------------------------------------- | ||
| 2863 | // Module Functions Definition: Automation Events Recording and Playing | ||
| 2864 | //---------------------------------------------------------------------------------- | ||
| 2865 | |||
| 2866 | // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS | ||
| 2867 | AutomationEventList LoadAutomationEventList(const char *fileName) | ||
| 2868 | { | ||
| 2869 | AutomationEventList list = { 0 }; | ||
| 2870 | |||
| 2871 | // Allocate and empty automation event list, ready to record new events | ||
| 2872 | list.events = (AutomationEvent *)RL_CALLOC(MAX_AUTOMATION_EVENTS, sizeof(AutomationEvent)); | ||
| 2873 | list.capacity = MAX_AUTOMATION_EVENTS; | ||
| 2874 | |||
| 2875 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 2876 | if (fileName == NULL) TRACELOG(LOG_INFO, "AUTOMATION: New empty events list loaded successfully"); | ||
| 2877 | else | ||
| 2878 | { | ||
| 2879 | // Load automation events file (binary) | ||
| 2880 | /* | ||
| 2881 | //int dataSize = 0; | ||
| 2882 | //unsigned char *data = LoadFileData(fileName, &dataSize); | ||
| 2883 | |||
| 2884 | FILE *raeFile = fopen(fileName, "rb"); | ||
| 2885 | unsigned char fileId[4] = { 0 }; | ||
| 2886 | |||
| 2887 | fread(fileId, 1, 4, raeFile); | ||
| 2888 | |||
| 2889 | if ((fileId[0] == 'r') && (fileId[1] == 'A') && (fileId[2] == 'E') && (fileId[1] == ' ')) | ||
| 2890 | { | ||
| 2891 | fread(&eventCount, sizeof(int), 1, raeFile); | ||
| 2892 | TRACELOG(LOG_WARNING, "Events loaded: %i\n", eventCount); | ||
| 2893 | fread(events, sizeof(AutomationEvent), eventCount, raeFile); | ||
| 2894 | } | ||
| 2895 | |||
| 2896 | fclose(raeFile); | ||
| 2897 | */ | ||
| 2898 | |||
| 2899 | // Load events file (text) | ||
| 2900 | //unsigned char *buffer = LoadFileText(fileName); | ||
| 2901 | FILE *raeFile = fopen(fileName, "rt"); | ||
| 2902 | |||
| 2903 | if (raeFile != NULL) | ||
| 2904 | { | ||
| 2905 | unsigned int counter = 0; | ||
| 2906 | char buffer[256] = { 0 }; | ||
| 2907 | char eventDesc[64] = { 0 }; | ||
| 2908 | |||
| 2909 | fgets(buffer, 256, raeFile); | ||
| 2910 | |||
| 2911 | while (!feof(raeFile)) | ||
| 2912 | { | ||
| 2913 | switch (buffer[0]) | ||
| 2914 | { | ||
| 2915 | case 'c': sscanf(buffer, "c %i", &list.count); break; | ||
| 2916 | case 'e': | ||
| 2917 | { | ||
| 2918 | sscanf(buffer, "e %d %d %d %d %d %d %[^\n]s", &list.events[counter].frame, &list.events[counter].type, | ||
| 2919 | &list.events[counter].params[0], &list.events[counter].params[1], &list.events[counter].params[2], &list.events[counter].params[3], eventDesc); | ||
| 2920 | |||
| 2921 | counter++; | ||
| 2922 | } break; | ||
| 2923 | default: break; | ||
| 2924 | } | ||
| 2925 | |||
| 2926 | fgets(buffer, 256, raeFile); | ||
| 2927 | } | ||
| 2928 | |||
| 2929 | if (counter != list.count) | ||
| 2930 | { | ||
| 2931 | TRACELOG(LOG_WARNING, "AUTOMATION: Events read from file [%i] do not mach event count specified [%i]", counter, list.count); | ||
| 2932 | list.count = counter; | ||
| 2933 | } | ||
| 2934 | |||
| 2935 | fclose(raeFile); | ||
| 2936 | |||
| 2937 | TRACELOG(LOG_INFO, "AUTOMATION: Events file loaded successfully"); | ||
| 2938 | } | ||
| 2939 | |||
| 2940 | TRACELOG(LOG_INFO, "AUTOMATION: Events loaded from file: %i", list.count); | ||
| 2941 | } | ||
| 2942 | #endif | ||
| 2943 | return list; | ||
| 2944 | } | ||
| 2945 | |||
| 2946 | // Unload automation events list from file | ||
| 2947 | void UnloadAutomationEventList(AutomationEventList list) | ||
| 2948 | { | ||
| 2949 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 2950 | RL_FREE(list.events); | ||
| 2951 | #endif | ||
| 2952 | } | ||
| 2953 | |||
| 2954 | // Export automation events list as text file | ||
| 2955 | bool ExportAutomationEventList(AutomationEventList list, const char *fileName) | ||
| 2956 | { | ||
| 2957 | bool success = false; | ||
| 2958 | |||
| 2959 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 2960 | // Export events as binary file | ||
| 2961 | // TODO: Save to memory buffer and SaveFileData() | ||
| 2962 | /* | ||
| 2963 | unsigned char fileId[4] = "rAE "; | ||
| 2964 | FILE *raeFile = fopen(fileName, "wb"); | ||
| 2965 | fwrite(fileId, sizeof(unsigned char), 4, raeFile); | ||
| 2966 | fwrite(&eventCount, sizeof(int), 1, raeFile); | ||
| 2967 | fwrite(events, sizeof(AutomationEvent), eventCount, raeFile); | ||
| 2968 | fclose(raeFile); | ||
| 2969 | */ | ||
| 2970 | |||
| 2971 | // Export events as text | ||
| 2972 | // TODO: Save to memory buffer and SaveFileText() | ||
| 2973 | char *txtData = (char *)RL_CALLOC(256*list.count + 2048, sizeof(char)); // 256 characters per line plus some header | ||
| 2974 | |||
| 2975 | int byteCount = 0; | ||
| 2976 | byteCount += sprintf(txtData + byteCount, "#\n"); | ||
| 2977 | byteCount += sprintf(txtData + byteCount, "# Automation events exporter v1.0 - raylib automation events list\n"); | ||
| 2978 | byteCount += sprintf(txtData + byteCount, "#\n"); | ||
| 2979 | byteCount += sprintf(txtData + byteCount, "# c <events_count>\n"); | ||
| 2980 | byteCount += sprintf(txtData + byteCount, "# e <frame> <event_type> <param0> <param1> <param2> <param3> // <event_type_name>\n"); | ||
| 2981 | byteCount += sprintf(txtData + byteCount, "#\n"); | ||
| 2982 | byteCount += sprintf(txtData + byteCount, "# more info and bugs-report: github.com/raysan5/raylib\n"); | ||
| 2983 | byteCount += sprintf(txtData + byteCount, "# feedback and support: ray[at]raylib.com\n"); | ||
| 2984 | byteCount += sprintf(txtData + byteCount, "#\n"); | ||
| 2985 | byteCount += sprintf(txtData + byteCount, "# Copyright (c) 2023-2024 Ramon Santamaria (@raysan5)\n"); | ||
| 2986 | byteCount += sprintf(txtData + byteCount, "#\n\n"); | ||
| 2987 | |||
| 2988 | // Add events data | ||
| 2989 | byteCount += sprintf(txtData + byteCount, "c %i\n", list.count); | ||
| 2990 | for (unsigned int i = 0; i < list.count; i++) | ||
| 2991 | { | ||
| 2992 | byteCount += snprintf(txtData + byteCount, 256, "e %i %i %i %i %i %i // Event: %s\n", list.events[i].frame, list.events[i].type, | ||
| 2993 | list.events[i].params[0], list.events[i].params[1], list.events[i].params[2], list.events[i].params[3], autoEventTypeName[list.events[i].type]); | ||
| 2994 | } | ||
| 2995 | |||
| 2996 | // NOTE: Text data size exported is determined by '\0' (NULL) character | ||
| 2997 | success = SaveFileText(fileName, txtData); | ||
| 2998 | |||
| 2999 | RL_FREE(txtData); | ||
| 3000 | #endif | ||
| 3001 | |||
| 3002 | return success; | ||
| 3003 | } | ||
| 3004 | |||
| 3005 | // Setup automation event list to record to | ||
| 3006 | void SetAutomationEventList(AutomationEventList *list) | ||
| 3007 | { | ||
| 3008 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 3009 | currentEventList = list; | ||
| 3010 | #endif | ||
| 3011 | } | ||
| 3012 | |||
| 3013 | // Set automation event internal base frame to start recording | ||
| 3014 | void SetAutomationEventBaseFrame(int frame) | ||
| 3015 | { | ||
| 3016 | CORE.Time.frameCounter = frame; | ||
| 3017 | } | ||
| 3018 | |||
| 3019 | // Start recording automation events (AutomationEventList must be set) | ||
| 3020 | void StartAutomationEventRecording(void) | ||
| 3021 | { | ||
| 3022 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 3023 | automationEventRecording = true; | ||
| 3024 | #endif | ||
| 3025 | } | ||
| 3026 | |||
| 3027 | // Stop recording automation events | ||
| 3028 | void StopAutomationEventRecording(void) | ||
| 3029 | { | ||
| 3030 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 3031 | automationEventRecording = false; | ||
| 3032 | #endif | ||
| 3033 | } | ||
| 3034 | |||
| 3035 | // Play a recorded automation event | ||
| 3036 | void PlayAutomationEvent(AutomationEvent event) | ||
| 3037 | { | ||
| 3038 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 3039 | // WARNING: When should event be played? After/before/replace PollInputEvents()? -> Up to the user! | ||
| 3040 | |||
| 3041 | if (!automationEventRecording) // TODO: Allow recording events while playing? | ||
| 3042 | { | ||
| 3043 | switch (event.type) | ||
| 3044 | { | ||
| 3045 | // Input event | ||
| 3046 | case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[event.params[0]] = false; break; // param[0]: key | ||
| 3047 | case INPUT_KEY_DOWN: { // param[0]: key | ||
| 3048 | CORE.Input.Keyboard.currentKeyState[event.params[0]] = true; | ||
| 3049 | |||
| 3050 | if (CORE.Input.Keyboard.previousKeyState[event.params[0]] == false) | ||
| 3051 | { | ||
| 3052 | if (CORE.Input.Keyboard.keyPressedQueueCount < MAX_KEY_PRESSED_QUEUE) | ||
| 3053 | { | ||
| 3054 | // Add character to the queue | ||
| 3055 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount] = event.params[0]; | ||
| 3056 | CORE.Input.Keyboard.keyPressedQueueCount++; | ||
| 3057 | } | ||
| 3058 | } | ||
| 3059 | } break; | ||
| 3060 | case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[event.params[0]] = false; break; // param[0]: key | ||
| 3061 | case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[event.params[0]] = true; break; // param[0]: key | ||
| 3062 | case INPUT_MOUSE_POSITION: // param[0]: x, param[1]: y | ||
| 3063 | { | ||
| 3064 | CORE.Input.Mouse.currentPosition.x = (float)event.params[0]; | ||
| 3065 | CORE.Input.Mouse.currentPosition.y = (float)event.params[1]; | ||
| 3066 | } break; | ||
| 3067 | case INPUT_MOUSE_WHEEL_MOTION: // param[0]: x delta, param[1]: y delta | ||
| 3068 | { | ||
| 3069 | CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; | ||
| 3070 | CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; | ||
| 3071 | } break; | ||
| 3072 | case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break; // param[0]: id | ||
| 3073 | case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break; // param[0]: id | ||
| 3074 | case INPUT_TOUCH_POSITION: // param[0]: id, param[1]: x, param[2]: y | ||
| 3075 | { | ||
| 3076 | CORE.Input.Touch.position[event.params[0]].x = (float)event.params[1]; | ||
| 3077 | CORE.Input.Touch.position[event.params[0]].y = (float)event.params[2]; | ||
| 3078 | } break; | ||
| 3079 | case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[event.params[0]] = true; break; // param[0]: gamepad | ||
| 3080 | case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[event.params[0]] = false; break; // param[0]: gamepad | ||
| 3081 | case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.params[1]] = false; break; // param[0]: gamepad, param[1]: button | ||
| 3082 | case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.params[1]] = true; break; // param[0]: gamepad, param[1]: button | ||
| 3083 | case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta | ||
| 3084 | { | ||
| 3085 | CORE.Input.Gamepad.axisState[event.params[0]][event.params[1]] = ((float)event.params[2]/32768.0f); | ||
| 3086 | } break; | ||
| 3087 | #if defined(SUPPORT_GESTURES_SYSTEM) | ||
| 3088 | case INPUT_GESTURE: GESTURES.current = event.params[0]; break; // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current | ||
| 3089 | #endif | ||
| 3090 | // Window event | ||
| 3091 | case WINDOW_CLOSE: CORE.Window.shouldClose = true; break; | ||
| 3092 | case WINDOW_MAXIMIZE: MaximizeWindow(); break; | ||
| 3093 | case WINDOW_MINIMIZE: MinimizeWindow(); break; | ||
| 3094 | case WINDOW_RESIZE: SetWindowSize(event.params[0], event.params[1]); break; | ||
| 3095 | |||
| 3096 | // Custom event | ||
| 3097 | #if defined(SUPPORT_SCREEN_CAPTURE) | ||
| 3098 | case ACTION_TAKE_SCREENSHOT: | ||
| 3099 | { | ||
| 3100 | TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); | ||
| 3101 | screenshotCounter++; | ||
| 3102 | } break; | ||
| 3103 | #endif | ||
| 3104 | case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break; | ||
| 3105 | default: break; | ||
| 3106 | } | ||
| 3107 | |||
| 3108 | TRACELOG(LOG_INFO, "AUTOMATION PLAY: Frame: %i | Event type: %i | Event parameters: %i, %i, %i", event.frame, event.type, event.params[0], event.params[1], event.params[2]); | ||
| 3109 | } | ||
| 3110 | #endif | ||
| 3111 | } | ||
| 3112 | |||
| 3113 | //---------------------------------------------------------------------------------- | ||
| 3114 | // Module Functions Definition: Input Handling: Keyboard | ||
| 3115 | //---------------------------------------------------------------------------------- | ||
| 3116 | |||
| 3117 | // Check if a key has been pressed once | ||
| 3118 | bool IsKeyPressed(int key) | ||
| 3119 | { | ||
| 3120 | |||
| 3121 | bool pressed = false; | ||
| 3122 | |||
| 3123 | if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) | ||
| 3124 | { | ||
| 3125 | if ((CORE.Input.Keyboard.previousKeyState[key] == 0) && (CORE.Input.Keyboard.currentKeyState[key] == 1)) pressed = true; | ||
| 3126 | } | ||
| 3127 | |||
| 3128 | return pressed; | ||
| 3129 | } | ||
| 3130 | |||
| 3131 | // Check if a key has been pressed again | ||
| 3132 | bool IsKeyPressedRepeat(int key) | ||
| 3133 | { | ||
| 3134 | bool repeat = false; | ||
| 3135 | |||
| 3136 | if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) | ||
| 3137 | { | ||
| 3138 | if (CORE.Input.Keyboard.keyRepeatInFrame[key] == 1) repeat = true; | ||
| 3139 | } | ||
| 3140 | |||
| 3141 | return repeat; | ||
| 3142 | } | ||
| 3143 | |||
| 3144 | // Check if a key is being pressed (key held down) | ||
| 3145 | bool IsKeyDown(int key) | ||
| 3146 | { | ||
| 3147 | bool down = false; | ||
| 3148 | |||
| 3149 | if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) | ||
| 3150 | { | ||
| 3151 | if (CORE.Input.Keyboard.currentKeyState[key] == 1) down = true; | ||
| 3152 | } | ||
| 3153 | |||
| 3154 | return down; | ||
| 3155 | } | ||
| 3156 | |||
| 3157 | // Check if a key has been released once | ||
| 3158 | bool IsKeyReleased(int key) | ||
| 3159 | { | ||
| 3160 | bool released = false; | ||
| 3161 | |||
| 3162 | if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) | ||
| 3163 | { | ||
| 3164 | if ((CORE.Input.Keyboard.previousKeyState[key] == 1) && (CORE.Input.Keyboard.currentKeyState[key] == 0)) released = true; | ||
| 3165 | } | ||
| 3166 | |||
| 3167 | return released; | ||
| 3168 | } | ||
| 3169 | |||
| 3170 | // Check if a key is NOT being pressed (key not held down) | ||
| 3171 | bool IsKeyUp(int key) | ||
| 3172 | { | ||
| 3173 | bool up = false; | ||
| 3174 | |||
| 3175 | if ((key > 0) && (key < MAX_KEYBOARD_KEYS)) | ||
| 3176 | { | ||
| 3177 | if (CORE.Input.Keyboard.currentKeyState[key] == 0) up = true; | ||
| 3178 | } | ||
| 3179 | |||
| 3180 | return up; | ||
| 3181 | } | ||
| 3182 | |||
| 3183 | // Get the last key pressed | ||
| 3184 | int GetKeyPressed(void) | ||
| 3185 | { | ||
| 3186 | int value = 0; | ||
| 3187 | |||
| 3188 | if (CORE.Input.Keyboard.keyPressedQueueCount > 0) | ||
| 3189 | { | ||
| 3190 | // Get character from the queue head | ||
| 3191 | value = CORE.Input.Keyboard.keyPressedQueue[0]; | ||
| 3192 | |||
| 3193 | // Shift elements 1 step toward the head | ||
| 3194 | for (int i = 0; i < (CORE.Input.Keyboard.keyPressedQueueCount - 1); i++) | ||
| 3195 | CORE.Input.Keyboard.keyPressedQueue[i] = CORE.Input.Keyboard.keyPressedQueue[i + 1]; | ||
| 3196 | |||
| 3197 | // Reset last character in the queue | ||
| 3198 | CORE.Input.Keyboard.keyPressedQueue[CORE.Input.Keyboard.keyPressedQueueCount - 1] = 0; | ||
| 3199 | CORE.Input.Keyboard.keyPressedQueueCount--; | ||
| 3200 | } | ||
| 3201 | |||
| 3202 | return value; | ||
| 3203 | } | ||
| 3204 | |||
| 3205 | // Get the last char pressed | ||
| 3206 | int GetCharPressed(void) | ||
| 3207 | { | ||
| 3208 | int value = 0; | ||
| 3209 | |||
| 3210 | if (CORE.Input.Keyboard.charPressedQueueCount > 0) | ||
| 3211 | { | ||
| 3212 | // Get character from the queue head | ||
| 3213 | value = CORE.Input.Keyboard.charPressedQueue[0]; | ||
| 3214 | |||
| 3215 | // Shift elements 1 step toward the head | ||
| 3216 | for (int i = 0; i < (CORE.Input.Keyboard.charPressedQueueCount - 1); i++) | ||
| 3217 | CORE.Input.Keyboard.charPressedQueue[i] = CORE.Input.Keyboard.charPressedQueue[i + 1]; | ||
| 3218 | |||
| 3219 | // Reset last character in the queue | ||
| 3220 | CORE.Input.Keyboard.charPressedQueue[CORE.Input.Keyboard.charPressedQueueCount - 1] = 0; | ||
| 3221 | CORE.Input.Keyboard.charPressedQueueCount--; | ||
| 3222 | } | ||
| 3223 | |||
| 3224 | return value; | ||
| 3225 | } | ||
| 3226 | |||
| 3227 | // Set a custom key to exit program | ||
| 3228 | // NOTE: default exitKey is set to ESCAPE | ||
| 3229 | void SetExitKey(int key) | ||
| 3230 | { | ||
| 3231 | CORE.Input.Keyboard.exitKey = key; | ||
| 3232 | } | ||
| 3233 | |||
| 3234 | //---------------------------------------------------------------------------------- | ||
| 3235 | // Module Functions Definition: Input Handling: Gamepad | ||
| 3236 | //---------------------------------------------------------------------------------- | ||
| 3237 | |||
| 3238 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 3239 | //int SetGamepadMappings(const char *mappings) | ||
| 3240 | |||
| 3241 | // Check if a gamepad is available | ||
| 3242 | bool IsGamepadAvailable(int gamepad) | ||
| 3243 | { | ||
| 3244 | bool result = false; | ||
| 3245 | |||
| 3246 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad]) result = true; | ||
| 3247 | |||
| 3248 | return result; | ||
| 3249 | } | ||
| 3250 | |||
| 3251 | // Get gamepad internal name id | ||
| 3252 | const char *GetGamepadName(int gamepad) | ||
| 3253 | { | ||
| 3254 | return CORE.Input.Gamepad.name[gamepad]; | ||
| 3255 | } | ||
| 3256 | |||
| 3257 | // Check if a gamepad button has been pressed once | ||
| 3258 | bool IsGamepadButtonPressed(int gamepad, int button) | ||
| 3259 | { | ||
| 3260 | bool pressed = false; | ||
| 3261 | |||
| 3262 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && | ||
| 3263 | (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 0) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) pressed = true; | ||
| 3264 | |||
| 3265 | return pressed; | ||
| 3266 | } | ||
| 3267 | |||
| 3268 | // Check if a gamepad button is being pressed | ||
| 3269 | bool IsGamepadButtonDown(int gamepad, int button) | ||
| 3270 | { | ||
| 3271 | bool down = false; | ||
| 3272 | |||
| 3273 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && | ||
| 3274 | (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 1)) down = true; | ||
| 3275 | |||
| 3276 | return down; | ||
| 3277 | } | ||
| 3278 | |||
| 3279 | // Check if a gamepad button has NOT been pressed once | ||
| 3280 | bool IsGamepadButtonReleased(int gamepad, int button) | ||
| 3281 | { | ||
| 3282 | bool released = false; | ||
| 3283 | |||
| 3284 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && | ||
| 3285 | (CORE.Input.Gamepad.previousButtonState[gamepad][button] == 1) && (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) released = true; | ||
| 3286 | |||
| 3287 | return released; | ||
| 3288 | } | ||
| 3289 | |||
| 3290 | // Check if a gamepad button is NOT being pressed | ||
| 3291 | bool IsGamepadButtonUp(int gamepad, int button) | ||
| 3292 | { | ||
| 3293 | bool up = false; | ||
| 3294 | |||
| 3295 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (button < MAX_GAMEPAD_BUTTONS) && | ||
| 3296 | (CORE.Input.Gamepad.currentButtonState[gamepad][button] == 0)) up = true; | ||
| 3297 | |||
| 3298 | return up; | ||
| 3299 | } | ||
| 3300 | |||
| 3301 | // Get the last gamepad button pressed | ||
| 3302 | int GetGamepadButtonPressed(void) | ||
| 3303 | { | ||
| 3304 | return CORE.Input.Gamepad.lastButtonPressed; | ||
| 3305 | } | ||
| 3306 | |||
| 3307 | // Get gamepad axis count | ||
| 3308 | int GetGamepadAxisCount(int gamepad) | ||
| 3309 | { | ||
| 3310 | return CORE.Input.Gamepad.axisCount[gamepad]; | ||
| 3311 | } | ||
| 3312 | |||
| 3313 | // Get axis movement vector for a gamepad | ||
| 3314 | float GetGamepadAxisMovement(int gamepad, int axis) | ||
| 3315 | { | ||
| 3316 | float value = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; | ||
| 3317 | |||
| 3318 | if ((gamepad < MAX_GAMEPADS) && CORE.Input.Gamepad.ready[gamepad] && (axis < MAX_GAMEPAD_AXIS)) { | ||
| 3319 | float movement = value < 0.0f ? CORE.Input.Gamepad.axisState[gamepad][axis] : fabsf(CORE.Input.Gamepad.axisState[gamepad][axis]); | ||
| 3320 | |||
| 3321 | if (movement > value) value = CORE.Input.Gamepad.axisState[gamepad][axis]; | ||
| 3322 | } | ||
| 3323 | |||
| 3324 | return value; | ||
| 3325 | } | ||
| 3326 | |||
| 3327 | //---------------------------------------------------------------------------------- | ||
| 3328 | // Module Functions Definition: Input Handling: Mouse | ||
| 3329 | //---------------------------------------------------------------------------------- | ||
| 3330 | |||
| 3331 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 3332 | //void SetMousePosition(int x, int y) | ||
| 3333 | //void SetMouseCursor(int cursor) | ||
| 3334 | |||
| 3335 | // Check if a mouse button has been pressed once | ||
| 3336 | bool IsMouseButtonPressed(int button) | ||
| 3337 | { | ||
| 3338 | bool pressed = false; | ||
| 3339 | |||
| 3340 | if ((CORE.Input.Mouse.currentButtonState[button] == 1) && (CORE.Input.Mouse.previousButtonState[button] == 0)) pressed = true; | ||
| 3341 | |||
| 3342 | // Map touches to mouse buttons checking | ||
| 3343 | if ((CORE.Input.Touch.currentTouchState[button] == 1) && (CORE.Input.Touch.previousTouchState[button] == 0)) pressed = true; | ||
| 3344 | |||
| 3345 | return pressed; | ||
| 3346 | } | ||
| 3347 | |||
| 3348 | // Check if a mouse button is being pressed | ||
| 3349 | bool IsMouseButtonDown(int button) | ||
| 3350 | { | ||
| 3351 | bool down = false; | ||
| 3352 | |||
| 3353 | if (CORE.Input.Mouse.currentButtonState[button] == 1) down = true; | ||
| 3354 | |||
| 3355 | // NOTE: Touches are considered like mouse buttons | ||
| 3356 | if (CORE.Input.Touch.currentTouchState[button] == 1) down = true; | ||
| 3357 | |||
| 3358 | return down; | ||
| 3359 | } | ||
| 3360 | |||
| 3361 | // Check if a mouse button has been released once | ||
| 3362 | bool IsMouseButtonReleased(int button) | ||
| 3363 | { | ||
| 3364 | bool released = false; | ||
| 3365 | |||
| 3366 | if ((CORE.Input.Mouse.currentButtonState[button] == 0) && (CORE.Input.Mouse.previousButtonState[button] == 1)) released = true; | ||
| 3367 | |||
| 3368 | // Map touches to mouse buttons checking | ||
| 3369 | if ((CORE.Input.Touch.currentTouchState[button] == 0) && (CORE.Input.Touch.previousTouchState[button] == 1)) released = true; | ||
| 3370 | |||
| 3371 | return released; | ||
| 3372 | } | ||
| 3373 | |||
| 3374 | // Check if a mouse button is NOT being pressed | ||
| 3375 | bool IsMouseButtonUp(int button) | ||
| 3376 | { | ||
| 3377 | bool up = false; | ||
| 3378 | |||
| 3379 | if (CORE.Input.Mouse.currentButtonState[button] == 0) up = true; | ||
| 3380 | |||
| 3381 | // NOTE: Touches are considered like mouse buttons | ||
| 3382 | if (CORE.Input.Touch.currentTouchState[button] == 0) up = true; | ||
| 3383 | |||
| 3384 | return up; | ||
| 3385 | } | ||
| 3386 | |||
| 3387 | // Get mouse position X | ||
| 3388 | int GetMouseX(void) | ||
| 3389 | { | ||
| 3390 | int mouseX = (int)((CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x); | ||
| 3391 | return mouseX; | ||
| 3392 | } | ||
| 3393 | |||
| 3394 | // Get mouse position Y | ||
| 3395 | int GetMouseY(void) | ||
| 3396 | { | ||
| 3397 | int mouseY = (int)((CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y); | ||
| 3398 | return mouseY; | ||
| 3399 | } | ||
| 3400 | |||
| 3401 | // Get mouse position XY | ||
| 3402 | Vector2 GetMousePosition(void) | ||
| 3403 | { | ||
| 3404 | Vector2 position = { 0 }; | ||
| 3405 | |||
| 3406 | position.x = (CORE.Input.Mouse.currentPosition.x + CORE.Input.Mouse.offset.x)*CORE.Input.Mouse.scale.x; | ||
| 3407 | position.y = (CORE.Input.Mouse.currentPosition.y + CORE.Input.Mouse.offset.y)*CORE.Input.Mouse.scale.y; | ||
| 3408 | |||
| 3409 | return position; | ||
| 3410 | } | ||
| 3411 | |||
| 3412 | // Get mouse delta between frames | ||
| 3413 | Vector2 GetMouseDelta(void) | ||
| 3414 | { | ||
| 3415 | Vector2 delta = { 0 }; | ||
| 3416 | |||
| 3417 | delta.x = CORE.Input.Mouse.currentPosition.x - CORE.Input.Mouse.previousPosition.x; | ||
| 3418 | delta.y = CORE.Input.Mouse.currentPosition.y - CORE.Input.Mouse.previousPosition.y; | ||
| 3419 | |||
| 3420 | return delta; | ||
| 3421 | } | ||
| 3422 | |||
| 3423 | // Set mouse offset | ||
| 3424 | // NOTE: Useful when rendering to different size targets | ||
| 3425 | void SetMouseOffset(int offsetX, int offsetY) | ||
| 3426 | { | ||
| 3427 | CORE.Input.Mouse.offset = (Vector2){ (float)offsetX, (float)offsetY }; | ||
| 3428 | } | ||
| 3429 | |||
| 3430 | // Set mouse scaling | ||
| 3431 | // NOTE: Useful when rendering to different size targets | ||
| 3432 | void SetMouseScale(float scaleX, float scaleY) | ||
| 3433 | { | ||
| 3434 | CORE.Input.Mouse.scale = (Vector2){ scaleX, scaleY }; | ||
| 3435 | } | ||
| 3436 | |||
| 3437 | // Get mouse wheel movement Y | ||
| 3438 | float GetMouseWheelMove(void) | ||
| 3439 | { | ||
| 3440 | float result = 0.0f; | ||
| 3441 | |||
| 3442 | if (fabsf(CORE.Input.Mouse.currentWheelMove.x) > fabsf(CORE.Input.Mouse.currentWheelMove.y)) result = (float)CORE.Input.Mouse.currentWheelMove.x; | ||
| 3443 | else result = (float)CORE.Input.Mouse.currentWheelMove.y; | ||
| 3444 | |||
| 3445 | return result; | ||
| 3446 | } | ||
| 3447 | |||
| 3448 | // Get mouse wheel movement X/Y as a vector | ||
| 3449 | Vector2 GetMouseWheelMoveV(void) | ||
| 3450 | { | ||
| 3451 | Vector2 result = { 0 }; | ||
| 3452 | |||
| 3453 | result = CORE.Input.Mouse.currentWheelMove; | ||
| 3454 | |||
| 3455 | return result; | ||
| 3456 | } | ||
| 3457 | |||
| 3458 | //---------------------------------------------------------------------------------- | ||
| 3459 | // Module Functions Definition: Input Handling: Touch | ||
| 3460 | //---------------------------------------------------------------------------------- | ||
| 3461 | |||
| 3462 | // Get touch position X for touch point 0 (relative to screen size) | ||
| 3463 | int GetTouchX(void) | ||
| 3464 | { | ||
| 3465 | int touchX = (int)CORE.Input.Touch.position[0].x; | ||
| 3466 | return touchX; | ||
| 3467 | } | ||
| 3468 | |||
| 3469 | // Get touch position Y for touch point 0 (relative to screen size) | ||
| 3470 | int GetTouchY(void) | ||
| 3471 | { | ||
| 3472 | int touchY = (int)CORE.Input.Touch.position[0].y; | ||
| 3473 | return touchY; | ||
| 3474 | } | ||
| 3475 | |||
| 3476 | // Get touch position XY for a touch point index (relative to screen size) | ||
| 3477 | // TODO: Touch position should be scaled depending on display size and render size | ||
| 3478 | Vector2 GetTouchPosition(int index) | ||
| 3479 | { | ||
| 3480 | Vector2 position = { -1.0f, -1.0f }; | ||
| 3481 | |||
| 3482 | if (index < MAX_TOUCH_POINTS) position = CORE.Input.Touch.position[index]; | ||
| 3483 | else TRACELOG(LOG_WARNING, "INPUT: Required touch point out of range (Max touch points: %i)", MAX_TOUCH_POINTS); | ||
| 3484 | |||
| 3485 | return position; | ||
| 3486 | } | ||
| 3487 | |||
| 3488 | // Get touch point identifier for given index | ||
| 3489 | int GetTouchPointId(int index) | ||
| 3490 | { | ||
| 3491 | int id = -1; | ||
| 3492 | |||
| 3493 | if (index < MAX_TOUCH_POINTS) id = CORE.Input.Touch.pointId[index]; | ||
| 3494 | |||
| 3495 | return id; | ||
| 3496 | } | ||
| 3497 | |||
| 3498 | // Get number of touch points | ||
| 3499 | int GetTouchPointCount(void) | ||
| 3500 | { | ||
| 3501 | return CORE.Input.Touch.pointCount; | ||
| 3502 | } | ||
| 3503 | |||
| 3504 | //---------------------------------------------------------------------------------- | ||
| 3505 | // Module Internal Functions Definition | ||
| 3506 | //---------------------------------------------------------------------------------- | ||
| 3507 | |||
| 3508 | // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c | ||
| 3509 | //int InitPlatform(void) | ||
| 3510 | //void ClosePlatform(void) | ||
| 3511 | |||
| 3512 | // Initialize hi-resolution timer | ||
| 3513 | void InitTimer(void) | ||
| 3514 | { | ||
| 3515 | // Setting a higher resolution can improve the accuracy of time-out intervals in wait functions | ||
| 3516 | // However, it can also reduce overall system performance, because the thread scheduler switches tasks more often | ||
| 3517 | // High resolutions can also prevent the CPU power management system from entering power-saving modes | ||
| 3518 | // Setting a higher resolution does not improve the accuracy of the high-resolution performance counter | ||
| 3519 | #if defined(_WIN32) && defined(SUPPORT_WINMM_HIGHRES_TIMER) && !defined(SUPPORT_BUSY_WAIT_LOOP) && !defined(PLATFORM_DESKTOP_SDL) | ||
| 3520 | timeBeginPeriod(1); // Setup high-resolution timer to 1ms (granularity of 1-2 ms) | ||
| 3521 | #endif | ||
| 3522 | |||
| 3523 | #if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) | ||
| 3524 | struct timespec now = { 0 }; | ||
| 3525 | |||
| 3526 | if (clock_gettime(CLOCK_MONOTONIC, &now) == 0) // Success | ||
| 3527 | { | ||
| 3528 | CORE.Time.base = (unsigned long long int)now.tv_sec*1000000000LLU + (unsigned long long int)now.tv_nsec; | ||
| 3529 | } | ||
| 3530 | else TRACELOG(LOG_WARNING, "TIMER: Hi-resolution timer not available"); | ||
| 3531 | #endif | ||
| 3532 | |||
| 3533 | CORE.Time.previous = GetTime(); // Get time as double | ||
| 3534 | } | ||
| 3535 | |||
| 3536 | // Set viewport for a provided width and height | ||
| 3537 | void SetupViewport(int width, int height) | ||
| 3538 | { | ||
| 3539 | CORE.Window.render.width = width; | ||
| 3540 | CORE.Window.render.height = height; | ||
| 3541 | |||
| 3542 | // Set viewport width and height | ||
| 3543 | // NOTE: We consider render size (scaled) and offset in case black bars are required and | ||
| 3544 | // render area does not match full display area (this situation is only applicable on fullscreen mode) | ||
| 3545 | #if defined(__APPLE__) | ||
| 3546 | Vector2 scale = GetWindowScaleDPI(); | ||
| 3547 | rlViewport(CORE.Window.renderOffset.x/2*scale.x, CORE.Window.renderOffset.y/2*scale.y, (CORE.Window.render.width)*scale.x, (CORE.Window.render.height)*scale.y); | ||
| 3548 | #else | ||
| 3549 | rlViewport(CORE.Window.renderOffset.x/2, CORE.Window.renderOffset.y/2, CORE.Window.render.width, CORE.Window.render.height); | ||
| 3550 | #endif | ||
| 3551 | |||
| 3552 | rlMatrixMode(RL_PROJECTION); // Switch to projection matrix | ||
| 3553 | rlLoadIdentity(); // Reset current matrix (projection) | ||
| 3554 | |||
| 3555 | // Set orthographic projection to current framebuffer size | ||
| 3556 | // NOTE: Configured top-left corner as (0, 0) | ||
| 3557 | rlOrtho(0, CORE.Window.render.width, CORE.Window.render.height, 0, 0.0f, 1.0f); | ||
| 3558 | |||
| 3559 | rlMatrixMode(RL_MODELVIEW); // Switch back to modelview matrix | ||
| 3560 | rlLoadIdentity(); // Reset current matrix (modelview) | ||
| 3561 | } | ||
| 3562 | |||
| 3563 | // Compute framebuffer size relative to screen size and display size | ||
| 3564 | // NOTE: Global variables CORE.Window.render.width/CORE.Window.render.height and CORE.Window.renderOffset.x/CORE.Window.renderOffset.y can be modified | ||
| 3565 | void SetupFramebuffer(int width, int height) | ||
| 3566 | { | ||
| 3567 | // Calculate CORE.Window.render.width and CORE.Window.render.height, we have the display size (input params) and the desired screen size (global var) | ||
| 3568 | if ((CORE.Window.screen.width > CORE.Window.display.width) || (CORE.Window.screen.height > CORE.Window.display.height)) | ||
| 3569 | { | ||
| 3570 | TRACELOG(LOG_WARNING, "DISPLAY: Downscaling required: Screen size (%ix%i) is bigger than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); | ||
| 3571 | |||
| 3572 | // Downscaling to fit display with border-bars | ||
| 3573 | float widthRatio = (float)CORE.Window.display.width/(float)CORE.Window.screen.width; | ||
| 3574 | float heightRatio = (float)CORE.Window.display.height/(float)CORE.Window.screen.height; | ||
| 3575 | |||
| 3576 | if (widthRatio <= heightRatio) | ||
| 3577 | { | ||
| 3578 | CORE.Window.render.width = CORE.Window.display.width; | ||
| 3579 | CORE.Window.render.height = (int)round((float)CORE.Window.screen.height*widthRatio); | ||
| 3580 | CORE.Window.renderOffset.x = 0; | ||
| 3581 | CORE.Window.renderOffset.y = (CORE.Window.display.height - CORE.Window.render.height); | ||
| 3582 | } | ||
| 3583 | else | ||
| 3584 | { | ||
| 3585 | CORE.Window.render.width = (int)round((float)CORE.Window.screen.width*heightRatio); | ||
| 3586 | CORE.Window.render.height = CORE.Window.display.height; | ||
| 3587 | CORE.Window.renderOffset.x = (CORE.Window.display.width - CORE.Window.render.width); | ||
| 3588 | CORE.Window.renderOffset.y = 0; | ||
| 3589 | } | ||
| 3590 | |||
| 3591 | // Screen scaling required | ||
| 3592 | float scaleRatio = (float)CORE.Window.render.width/(float)CORE.Window.screen.width; | ||
| 3593 | CORE.Window.screenScale = MatrixScale(scaleRatio, scaleRatio, 1.0f); | ||
| 3594 | |||
| 3595 | // NOTE: We render to full display resolution! | ||
| 3596 | // We just need to calculate above parameters for downscale matrix and offsets | ||
| 3597 | CORE.Window.render.width = CORE.Window.display.width; | ||
| 3598 | CORE.Window.render.height = CORE.Window.display.height; | ||
| 3599 | |||
| 3600 | TRACELOG(LOG_WARNING, "DISPLAY: Downscale matrix generated, content will be rendered at (%ix%i)", CORE.Window.render.width, CORE.Window.render.height); | ||
| 3601 | } | ||
| 3602 | else if ((CORE.Window.screen.width < CORE.Window.display.width) || (CORE.Window.screen.height < CORE.Window.display.height)) | ||
| 3603 | { | ||
| 3604 | // Required screen size is smaller than display size | ||
| 3605 | TRACELOG(LOG_INFO, "DISPLAY: Upscaling required: Screen size (%ix%i) smaller than display size (%ix%i)", CORE.Window.screen.width, CORE.Window.screen.height, CORE.Window.display.width, CORE.Window.display.height); | ||
| 3606 | |||
| 3607 | if ((CORE.Window.screen.width == 0) || (CORE.Window.screen.height == 0)) | ||
| 3608 | { | ||
| 3609 | CORE.Window.screen.width = CORE.Window.display.width; | ||
| 3610 | CORE.Window.screen.height = CORE.Window.display.height; | ||
| 3611 | } | ||
| 3612 | |||
| 3613 | // Upscaling to fit display with border-bars | ||
| 3614 | float displayRatio = (float)CORE.Window.display.width/(float)CORE.Window.display.height; | ||
| 3615 | float screenRatio = (float)CORE.Window.screen.width/(float)CORE.Window.screen.height; | ||
| 3616 | |||
| 3617 | if (displayRatio <= screenRatio) | ||
| 3618 | { | ||
| 3619 | CORE.Window.render.width = CORE.Window.screen.width; | ||
| 3620 | CORE.Window.render.height = (int)round((float)CORE.Window.screen.width/displayRatio); | ||
| 3621 | CORE.Window.renderOffset.x = 0; | ||
| 3622 | CORE.Window.renderOffset.y = (CORE.Window.render.height - CORE.Window.screen.height); | ||
| 3623 | } | ||
| 3624 | else | ||
| 3625 | { | ||
| 3626 | CORE.Window.render.width = (int)round((float)CORE.Window.screen.height*displayRatio); | ||
| 3627 | CORE.Window.render.height = CORE.Window.screen.height; | ||
| 3628 | CORE.Window.renderOffset.x = (CORE.Window.render.width - CORE.Window.screen.width); | ||
| 3629 | CORE.Window.renderOffset.y = 0; | ||
| 3630 | } | ||
| 3631 | } | ||
| 3632 | else | ||
| 3633 | { | ||
| 3634 | CORE.Window.render.width = CORE.Window.screen.width; | ||
| 3635 | CORE.Window.render.height = CORE.Window.screen.height; | ||
| 3636 | CORE.Window.renderOffset.x = 0; | ||
| 3637 | CORE.Window.renderOffset.y = 0; | ||
| 3638 | } | ||
| 3639 | } | ||
| 3640 | |||
| 3641 | // Scan all files and directories in a base path | ||
| 3642 | // WARNING: files.paths[] must be previously allocated and | ||
| 3643 | // contain enough space to store all required paths | ||
| 3644 | static void ScanDirectoryFiles(const char *basePath, FilePathList *files, const char *filter) | ||
| 3645 | { | ||
| 3646 | static char path[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 3647 | memset(path, 0, MAX_FILEPATH_LENGTH); | ||
| 3648 | |||
| 3649 | struct dirent *dp = NULL; | ||
| 3650 | DIR *dir = opendir(basePath); | ||
| 3651 | |||
| 3652 | if (dir != NULL) | ||
| 3653 | { | ||
| 3654 | while ((dp = readdir(dir)) != NULL) | ||
| 3655 | { | ||
| 3656 | if ((strcmp(dp->d_name, ".") != 0) && | ||
| 3657 | (strcmp(dp->d_name, "..") != 0)) | ||
| 3658 | { | ||
| 3659 | #if defined(_WIN32) | ||
| 3660 | sprintf(path, "%s\\%s", basePath, dp->d_name); | ||
| 3661 | #else | ||
| 3662 | sprintf(path, "%s/%s", basePath, dp->d_name); | ||
| 3663 | #endif | ||
| 3664 | |||
| 3665 | if (filter != NULL) | ||
| 3666 | { | ||
| 3667 | if (IsPathFile(path)) | ||
| 3668 | { | ||
| 3669 | if (IsFileExtension(path, filter)) | ||
| 3670 | { | ||
| 3671 | strcpy(files->paths[files->count], path); | ||
| 3672 | files->count++; | ||
| 3673 | } | ||
| 3674 | } | ||
| 3675 | else | ||
| 3676 | { | ||
| 3677 | if (TextFindIndex(filter, DIRECTORY_FILTER_TAG) >= 0) | ||
| 3678 | { | ||
| 3679 | strcpy(files->paths[files->count], path); | ||
| 3680 | files->count++; | ||
| 3681 | } | ||
| 3682 | } | ||
| 3683 | } | ||
| 3684 | else | ||
| 3685 | { | ||
| 3686 | strcpy(files->paths[files->count], path); | ||
| 3687 | files->count++; | ||
| 3688 | } | ||
| 3689 | } | ||
| 3690 | } | ||
| 3691 | |||
| 3692 | closedir(dir); | ||
| 3693 | } | ||
| 3694 | else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); | ||
| 3695 | } | ||
| 3696 | |||
| 3697 | // Scan all files and directories recursively from a base path | ||
| 3698 | static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *files, const char *filter) | ||
| 3699 | { | ||
| 3700 | char path[MAX_FILEPATH_LENGTH] = { 0 }; | ||
| 3701 | memset(path, 0, MAX_FILEPATH_LENGTH); | ||
| 3702 | |||
| 3703 | struct dirent *dp = NULL; | ||
| 3704 | DIR *dir = opendir(basePath); | ||
| 3705 | |||
| 3706 | if (dir != NULL) | ||
| 3707 | { | ||
| 3708 | while (((dp = readdir(dir)) != NULL) && (files->count < files->capacity)) | ||
| 3709 | { | ||
| 3710 | if ((strcmp(dp->d_name, ".") != 0) && (strcmp(dp->d_name, "..") != 0)) | ||
| 3711 | { | ||
| 3712 | // Construct new path from our base path | ||
| 3713 | #if defined(_WIN32) | ||
| 3714 | sprintf(path, "%s\\%s", basePath, dp->d_name); | ||
| 3715 | #else | ||
| 3716 | sprintf(path, "%s/%s", basePath, dp->d_name); | ||
| 3717 | #endif | ||
| 3718 | |||
| 3719 | if (IsPathFile(path)) | ||
| 3720 | { | ||
| 3721 | if (filter != NULL) | ||
| 3722 | { | ||
| 3723 | if (IsFileExtension(path, filter)) | ||
| 3724 | { | ||
| 3725 | strcpy(files->paths[files->count], path); | ||
| 3726 | files->count++; | ||
| 3727 | } | ||
| 3728 | } | ||
| 3729 | else | ||
| 3730 | { | ||
| 3731 | strcpy(files->paths[files->count], path); | ||
| 3732 | files->count++; | ||
| 3733 | } | ||
| 3734 | |||
| 3735 | if (files->count >= files->capacity) | ||
| 3736 | { | ||
| 3737 | TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); | ||
| 3738 | break; | ||
| 3739 | } | ||
| 3740 | } | ||
| 3741 | else | ||
| 3742 | { | ||
| 3743 | if ((filter != NULL) && (TextFindIndex(filter, DIRECTORY_FILTER_TAG) >= 0)) | ||
| 3744 | { | ||
| 3745 | strcpy(files->paths[files->count], path); | ||
| 3746 | files->count++; | ||
| 3747 | } | ||
| 3748 | |||
| 3749 | if (files->count >= files->capacity) | ||
| 3750 | { | ||
| 3751 | TRACELOG(LOG_WARNING, "FILEIO: Maximum filepath scan capacity reached (%i files)", files->capacity); | ||
| 3752 | break; | ||
| 3753 | } | ||
| 3754 | |||
| 3755 | ScanDirectoryFilesRecursively(path, files, filter); | ||
| 3756 | } | ||
| 3757 | } | ||
| 3758 | } | ||
| 3759 | |||
| 3760 | closedir(dir); | ||
| 3761 | } | ||
| 3762 | else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath); | ||
| 3763 | } | ||
| 3764 | |||
| 3765 | #if defined(SUPPORT_AUTOMATION_EVENTS) | ||
| 3766 | // Automation event recording | ||
| 3767 | // NOTE: Recording is by default done at EndDrawing(), before PollInputEvents() | ||
| 3768 | static void RecordAutomationEvent(void) | ||
| 3769 | { | ||
| 3770 | // Checking events in current frame and save them into currentEventList | ||
| 3771 | // TODO: How important is the current frame? Could it be modified? | ||
| 3772 | |||
| 3773 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3774 | |||
| 3775 | // Keyboard input events recording | ||
| 3776 | //------------------------------------------------------------------------------------- | ||
| 3777 | for (int key = 0; key < MAX_KEYBOARD_KEYS; key++) | ||
| 3778 | { | ||
| 3779 | // Event type: INPUT_KEY_UP (only saved once) | ||
| 3780 | if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key]) | ||
| 3781 | { | ||
| 3782 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3783 | currentEventList->events[currentEventList->count].type = INPUT_KEY_UP; | ||
| 3784 | currentEventList->events[currentEventList->count].params[0] = key; | ||
| 3785 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3786 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3787 | |||
| 3788 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_KEY_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3789 | currentEventList->count++; | ||
| 3790 | } | ||
| 3791 | |||
| 3792 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3793 | |||
| 3794 | // Event type: INPUT_KEY_DOWN | ||
| 3795 | if (CORE.Input.Keyboard.currentKeyState[key]) | ||
| 3796 | { | ||
| 3797 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3798 | currentEventList->events[currentEventList->count].type = INPUT_KEY_DOWN; | ||
| 3799 | currentEventList->events[currentEventList->count].params[0] = key; | ||
| 3800 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3801 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3802 | |||
| 3803 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_KEY_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3804 | currentEventList->count++; | ||
| 3805 | } | ||
| 3806 | |||
| 3807 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3808 | } | ||
| 3809 | //------------------------------------------------------------------------------------- | ||
| 3810 | |||
| 3811 | // Mouse input currentEventList->events recording | ||
| 3812 | //------------------------------------------------------------------------------------- | ||
| 3813 | for (int button = 0; button < MAX_MOUSE_BUTTONS; button++) | ||
| 3814 | { | ||
| 3815 | // Event type: INPUT_MOUSE_BUTTON_UP | ||
| 3816 | if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button]) | ||
| 3817 | { | ||
| 3818 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3819 | currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_UP; | ||
| 3820 | currentEventList->events[currentEventList->count].params[0] = button; | ||
| 3821 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3822 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3823 | |||
| 3824 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_BUTTON_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3825 | currentEventList->count++; | ||
| 3826 | } | ||
| 3827 | |||
| 3828 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3829 | |||
| 3830 | // Event type: INPUT_MOUSE_BUTTON_DOWN | ||
| 3831 | if (CORE.Input.Mouse.currentButtonState[button]) | ||
| 3832 | { | ||
| 3833 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3834 | currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_DOWN; | ||
| 3835 | currentEventList->events[currentEventList->count].params[0] = button; | ||
| 3836 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3837 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3838 | |||
| 3839 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_BUTTON_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3840 | currentEventList->count++; | ||
| 3841 | } | ||
| 3842 | |||
| 3843 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3844 | } | ||
| 3845 | |||
| 3846 | // Event type: INPUT_MOUSE_POSITION (only saved if changed) | ||
| 3847 | if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) || | ||
| 3848 | ((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y)) | ||
| 3849 | { | ||
| 3850 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3851 | currentEventList->events[currentEventList->count].type = INPUT_MOUSE_POSITION; | ||
| 3852 | currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentPosition.x; | ||
| 3853 | currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentPosition.y; | ||
| 3854 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3855 | |||
| 3856 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_POSITION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3857 | currentEventList->count++; | ||
| 3858 | |||
| 3859 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3860 | } | ||
| 3861 | |||
| 3862 | // Event type: INPUT_MOUSE_WHEEL_MOTION | ||
| 3863 | if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) || | ||
| 3864 | ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y)) | ||
| 3865 | { | ||
| 3866 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3867 | currentEventList->events[currentEventList->count].type = INPUT_MOUSE_WHEEL_MOTION; | ||
| 3868 | currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x; | ||
| 3869 | currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y; | ||
| 3870 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3871 | |||
| 3872 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_WHEEL_MOTION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3873 | currentEventList->count++; | ||
| 3874 | |||
| 3875 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3876 | } | ||
| 3877 | //------------------------------------------------------------------------------------- | ||
| 3878 | |||
| 3879 | // Touch input currentEventList->events recording | ||
| 3880 | //------------------------------------------------------------------------------------- | ||
| 3881 | for (int id = 0; id < MAX_TOUCH_POINTS; id++) | ||
| 3882 | { | ||
| 3883 | // Event type: INPUT_TOUCH_UP | ||
| 3884 | if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id]) | ||
| 3885 | { | ||
| 3886 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3887 | currentEventList->events[currentEventList->count].type = INPUT_TOUCH_UP; | ||
| 3888 | currentEventList->events[currentEventList->count].params[0] = id; | ||
| 3889 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3890 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3891 | |||
| 3892 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3893 | currentEventList->count++; | ||
| 3894 | } | ||
| 3895 | |||
| 3896 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3897 | |||
| 3898 | // Event type: INPUT_TOUCH_DOWN | ||
| 3899 | if (CORE.Input.Touch.currentTouchState[id]) | ||
| 3900 | { | ||
| 3901 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3902 | currentEventList->events[currentEventList->count].type = INPUT_TOUCH_DOWN; | ||
| 3903 | currentEventList->events[currentEventList->count].params[0] = id; | ||
| 3904 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 3905 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3906 | |||
| 3907 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3908 | currentEventList->count++; | ||
| 3909 | } | ||
| 3910 | |||
| 3911 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3912 | |||
| 3913 | // Event type: INPUT_TOUCH_POSITION | ||
| 3914 | // TODO: It requires the id! | ||
| 3915 | /* | ||
| 3916 | if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) || | ||
| 3917 | ((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y)) | ||
| 3918 | { | ||
| 3919 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3920 | currentEventList->events[currentEventList->count].type = INPUT_TOUCH_POSITION; | ||
| 3921 | currentEventList->events[currentEventList->count].params[0] = id; | ||
| 3922 | currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Touch.currentPosition[id].x; | ||
| 3923 | currentEventList->events[currentEventList->count].params[2] = (int)CORE.Input.Touch.currentPosition[id].y; | ||
| 3924 | |||
| 3925 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_POSITION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3926 | currentEventList->count++; | ||
| 3927 | } | ||
| 3928 | */ | ||
| 3929 | |||
| 3930 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3931 | } | ||
| 3932 | //------------------------------------------------------------------------------------- | ||
| 3933 | |||
| 3934 | // Gamepad input currentEventList->events recording | ||
| 3935 | //------------------------------------------------------------------------------------- | ||
| 3936 | for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++) | ||
| 3937 | { | ||
| 3938 | // Event type: INPUT_GAMEPAD_CONNECT | ||
| 3939 | /* | ||
| 3940 | if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && | ||
| 3941 | (CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to ready | ||
| 3942 | { | ||
| 3943 | // TODO: Save gamepad connect event | ||
| 3944 | } | ||
| 3945 | */ | ||
| 3946 | |||
| 3947 | // Event type: INPUT_GAMEPAD_DISCONNECT | ||
| 3948 | /* | ||
| 3949 | if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) && | ||
| 3950 | (!CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to not-ready | ||
| 3951 | { | ||
| 3952 | // TODO: Save gamepad disconnect event | ||
| 3953 | } | ||
| 3954 | */ | ||
| 3955 | |||
| 3956 | for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++) | ||
| 3957 | { | ||
| 3958 | // Event type: INPUT_GAMEPAD_BUTTON_UP | ||
| 3959 | if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button]) | ||
| 3960 | { | ||
| 3961 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3962 | currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_UP; | ||
| 3963 | currentEventList->events[currentEventList->count].params[0] = gamepad; | ||
| 3964 | currentEventList->events[currentEventList->count].params[1] = button; | ||
| 3965 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3966 | |||
| 3967 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_BUTTON_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3968 | currentEventList->count++; | ||
| 3969 | } | ||
| 3970 | |||
| 3971 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3972 | |||
| 3973 | // Event type: INPUT_GAMEPAD_BUTTON_DOWN | ||
| 3974 | if (CORE.Input.Gamepad.currentButtonState[gamepad][button]) | ||
| 3975 | { | ||
| 3976 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3977 | currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_DOWN; | ||
| 3978 | currentEventList->events[currentEventList->count].params[0] = gamepad; | ||
| 3979 | currentEventList->events[currentEventList->count].params[1] = button; | ||
| 3980 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 3981 | |||
| 3982 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_BUTTON_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 3983 | currentEventList->count++; | ||
| 3984 | } | ||
| 3985 | |||
| 3986 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 3987 | } | ||
| 3988 | |||
| 3989 | for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++) | ||
| 3990 | { | ||
| 3991 | // Event type: INPUT_GAMEPAD_AXIS_MOTION | ||
| 3992 | float defaultMovement = (axis == GAMEPAD_AXIS_LEFT_TRIGGER || axis == GAMEPAD_AXIS_RIGHT_TRIGGER)? -1.0f : 0.0f; | ||
| 3993 | if (GetGamepadAxisMovement(gamepad, axis) != defaultMovement) | ||
| 3994 | { | ||
| 3995 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 3996 | currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_AXIS_MOTION; | ||
| 3997 | currentEventList->events[currentEventList->count].params[0] = gamepad; | ||
| 3998 | currentEventList->events[currentEventList->count].params[1] = axis; | ||
| 3999 | currentEventList->events[currentEventList->count].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f); | ||
| 4000 | |||
| 4001 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_AXIS_MOTION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 4002 | currentEventList->count++; | ||
| 4003 | } | ||
| 4004 | |||
| 4005 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 4006 | } | ||
| 4007 | } | ||
| 4008 | //------------------------------------------------------------------------------------- | ||
| 4009 | |||
| 4010 | #if defined(SUPPORT_GESTURES_SYSTEM) | ||
| 4011 | // Gestures input currentEventList->events recording | ||
| 4012 | //------------------------------------------------------------------------------------- | ||
| 4013 | if (GESTURES.current != GESTURE_NONE) | ||
| 4014 | { | ||
| 4015 | // Event type: INPUT_GESTURE | ||
| 4016 | currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter; | ||
| 4017 | currentEventList->events[currentEventList->count].type = INPUT_GESTURE; | ||
| 4018 | currentEventList->events[currentEventList->count].params[0] = GESTURES.current; | ||
| 4019 | currentEventList->events[currentEventList->count].params[1] = 0; | ||
| 4020 | currentEventList->events[currentEventList->count].params[2] = 0; | ||
| 4021 | |||
| 4022 | TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GESTURE | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]); | ||
| 4023 | currentEventList->count++; | ||
| 4024 | |||
| 4025 | if (currentEventList->count == currentEventList->capacity) return; // Security check | ||
| 4026 | } | ||
| 4027 | //------------------------------------------------------------------------------------- | ||
| 4028 | #endif | ||
| 4029 | } | ||
| 4030 | #endif | ||
| 4031 | |||
| 4032 | #if !defined(SUPPORT_MODULE_RTEXT) | ||
| 4033 | // Formatting of text with variables to 'embed' | ||
| 4034 | // WARNING: String returned will expire after this function is called MAX_TEXTFORMAT_BUFFERS times | ||
| 4035 | const char *TextFormat(const char *text, ...) | ||
| 4036 | { | ||
| 4037 | #ifndef MAX_TEXTFORMAT_BUFFERS | ||
| 4038 | #define MAX_TEXTFORMAT_BUFFERS 4 // Maximum number of static buffers for text formatting | ||
| 4039 | #endif | ||
| 4040 | #ifndef MAX_TEXT_BUFFER_LENGTH | ||
| 4041 | #define MAX_TEXT_BUFFER_LENGTH 1024 // Maximum size of static text buffer | ||
| 4042 | #endif | ||
| 4043 | |||
| 4044 | // We create an array of buffers so strings don't expire until MAX_TEXTFORMAT_BUFFERS invocations | ||
| 4045 | static char buffers[MAX_TEXTFORMAT_BUFFERS][MAX_TEXT_BUFFER_LENGTH] = { 0 }; | ||
| 4046 | static int index = 0; | ||
| 4047 | |||
| 4048 | char *currentBuffer = buffers[index]; | ||
| 4049 | memset(currentBuffer, 0, MAX_TEXT_BUFFER_LENGTH); // Clear buffer before using | ||
| 4050 | |||
| 4051 | va_list args; | ||
| 4052 | va_start(args, text); | ||
| 4053 | int requiredByteCount = vsnprintf(currentBuffer, MAX_TEXT_BUFFER_LENGTH, text, args); | ||
| 4054 | va_end(args); | ||
| 4055 | |||
| 4056 | // If requiredByteCount is larger than the MAX_TEXT_BUFFER_LENGTH, then overflow occured | ||
| 4057 | if (requiredByteCount >= MAX_TEXT_BUFFER_LENGTH) | ||
| 4058 | { | ||
| 4059 | // Inserting "..." at the end of the string to mark as truncated | ||
| 4060 | char *truncBuffer = buffers[index] + MAX_TEXT_BUFFER_LENGTH - 4; // Adding 4 bytes = "...\0" | ||
| 4061 | sprintf(truncBuffer, "..."); | ||
| 4062 | } | ||
| 4063 | |||
| 4064 | index += 1; // Move to next buffer for next function call | ||
| 4065 | if (index >= MAX_TEXTFORMAT_BUFFERS) index = 0; | ||
| 4066 | |||
| 4067 | return currentBuffer; | ||
| 4068 | } | ||
| 4069 | |||
| 4070 | #endif // !SUPPORT_MODULE_RTEXT | ||
